mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Roll forward to cl/314747126
Signed-off-by: Alexey Polyudov <apolyudov@google.com> Change-Id: Ie19e006429b138b3768e97dae971a43fdc5ef8bf
This commit is contained in:
@@ -12,6 +12,26 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = [
|
||||
"utils.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"utils.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core/internal/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//platform:types",
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//platform/port:string",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mediums",
|
||||
srcs = [
|
||||
@@ -19,8 +39,6 @@ cc_library(
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"ble_peripheral.cc",
|
||||
"utils.cc",
|
||||
"utils.h",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.cc",
|
||||
@@ -48,9 +66,12 @@ cc_library(
|
||||
"mediums.h",
|
||||
"uuid.cc",
|
||||
"uuid.h",
|
||||
"wifi_lan.cc",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
visibility = ["//core/internal:__pkg__"],
|
||||
deps = [
|
||||
":utils",
|
||||
"//platform:logging",
|
||||
"//platform:types",
|
||||
"//platform:utils",
|
||||
@@ -67,7 +88,8 @@ cc_test(
|
||||
srcs = ["advertisement_read_result_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/impl/default",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/time",
|
||||
],
|
||||
@@ -79,6 +101,8 @@ cc_test(
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
@@ -88,6 +112,8 @@ cc_test(
|
||||
srcs = ["ble_advertisement_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
@@ -97,6 +123,8 @@ cc_test(
|
||||
srcs = ["ble_packet_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
@@ -106,6 +134,8 @@ cc_test(
|
||||
srcs = ["bloom_filter_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
@@ -115,7 +145,8 @@ cc_test(
|
||||
srcs = ["lost_entity_tracker_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/impl/default",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ target_link_libraries(core_internal_mediums_test
|
||||
core_internal_mediums
|
||||
gtest
|
||||
gtest_main
|
||||
platform_impl_default
|
||||
platform_impl_g3
|
||||
platform_utils
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "core/internal/mediums/advertisement_read_result.h"
|
||||
|
||||
#include "platform/impl/default/default_platform.h"
|
||||
#include "platform/api/platform.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
@@ -24,23 +24,7 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class SampleSystemClock : public SystemClock {
|
||||
public:
|
||||
SampleSystemClock() {}
|
||||
~SampleSystemClock() override {}
|
||||
|
||||
std::int64_t elapsedRealtime() override {
|
||||
return absl::ToUnixMillis(absl::Now());
|
||||
}
|
||||
};
|
||||
|
||||
class SamplePlatform {
|
||||
public:
|
||||
static Ptr<Lock> createLock() { return DefaultPlatform::createLock(); }
|
||||
static Ptr<SystemClock> createSystemClock() {
|
||||
return MakePtr(new SampleSystemClock());
|
||||
}
|
||||
};
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C};
|
||||
|
||||
@@ -53,16 +37,16 @@ const absl::Duration kAdvertisementMaxBackoffDuration =
|
||||
|
||||
template <>
|
||||
const std::int64_t AdvertisementReadResult<
|
||||
SamplePlatform>::kAdvertisementMaxBackoffDurationMillis =
|
||||
TestPlatform>::kAdvertisementMaxBackoffDurationMillis =
|
||||
ToInt64Milliseconds(kAdvertisementMaxBackoffDuration);
|
||||
template <>
|
||||
const std::int64_t
|
||||
AdvertisementReadResult<
|
||||
SamplePlatform>::kAdvertisementBaseBackoffDurationMillis =
|
||||
TestPlatform>::kAdvertisementBaseBackoffDurationMillis =
|
||||
ToInt64Milliseconds(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementExists) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
@@ -75,7 +59,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) {
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
@@ -84,23 +68,23 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<
|
||||
SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED);
|
||||
TestPlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep for some time, but not long enough to warrant a retry.
|
||||
@@ -108,22 +92,22 @@ TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) {
|
||||
absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2));
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<SamplePlatform>::RetryStatus::TOO_SOON);
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::TOO_SOON);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep long enough to warrant a retry.
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Record an additional failure so our backoff duration increases.
|
||||
@@ -134,11 +118,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) {
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<SamplePlatform>::RetryStatus::TOO_SOON);
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::TOO_SOON);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Record an absurd amount of failures so we hit the maximum backoff duration.
|
||||
@@ -151,11 +135,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
|
||||
absl::SleepFor(kAdvertisementMaxBackoffDuration);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, GetDurationSinceRead) {
|
||||
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int64_t sleepTime = 420;
|
||||
|
||||
@@ -474,8 +474,8 @@ void BLEV2<Platform>::stopScanning() {
|
||||
|
||||
// TODO(b/112199086) Change to RecurringCancelableAlarm
|
||||
template <typename Platform>
|
||||
Ptr<CancelableAlarm<Platform>> BLEV2<Platform>::createOnLostAlarm() {
|
||||
return Ptr<CancelableAlarm<Platform>>();
|
||||
Ptr<CancelableAlarm> BLEV2<Platform>::createOnLostAlarm() {
|
||||
return Ptr<CancelableAlarm>();
|
||||
}
|
||||
|
||||
// Returns true if the device is currently accepting incoming BLE socket
|
||||
|
||||
@@ -180,7 +180,7 @@ class BLEV2 {
|
||||
struct ScanningInfo {
|
||||
ScanningInfo(const string& service_id,
|
||||
Ptr<ScanCallbackFacade> scan_callback_facade,
|
||||
Ptr<CancelableAlarm<Platform>> on_lost_alarm)
|
||||
Ptr<CancelableAlarm> on_lost_alarm)
|
||||
: service_id(service_id),
|
||||
scan_callback_facade(scan_callback_facade),
|
||||
on_lost_alarm(on_lost_alarm) {}
|
||||
@@ -191,7 +191,7 @@ class BLEV2 {
|
||||
const string service_id;
|
||||
ScopedPtr<Ptr<ScanCallbackFacade>> scan_callback_facade;
|
||||
// TODO(ahlee): Change to recurring cancelable alarm
|
||||
ScopedPtr<Ptr<CancelableAlarm<Platform>>> on_lost_alarm;
|
||||
ScopedPtr<Ptr<CancelableAlarm>> on_lost_alarm;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
@@ -250,7 +250,7 @@ class BLEV2 {
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data);
|
||||
void processOnLostTimeout();
|
||||
Ptr<CancelableAlarm<Platform>> createOnLostAlarm();
|
||||
Ptr<CancelableAlarm> createOnLostAlarm();
|
||||
|
||||
bool isAdvertisementGattServerRunning();
|
||||
bool startAdvertisementGattServer(const string& service_id,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "core/internal/mediums/lost_entity_tracker.h"
|
||||
|
||||
#include "platform/impl/default/default_platform.h"
|
||||
#include "platform/api/platform.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
@@ -23,6 +23,8 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
struct TestEntity {
|
||||
int id;
|
||||
|
||||
@@ -32,7 +34,7 @@ struct TestEntity {
|
||||
};
|
||||
|
||||
TEST(LostEntityTracker, NoEntitiesLost) {
|
||||
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
@@ -55,7 +57,7 @@ TEST(LostEntityTracker, NoEntitiesLost) {
|
||||
}
|
||||
|
||||
TEST(LostEntityTracker, AllEntitiesLost) {
|
||||
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
@@ -69,7 +71,7 @@ TEST(LostEntityTracker, AllEntitiesLost) {
|
||||
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities.
|
||||
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
|
||||
typename LostEntityTracker<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
|
||||
@@ -77,7 +79,7 @@ TEST(LostEntityTracker, AllEntitiesLost) {
|
||||
}
|
||||
|
||||
TEST(LostEntityTracker, SomeEntitiesLost) {
|
||||
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
@@ -94,7 +96,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) {
|
||||
// was lost after the check.
|
||||
lost_entity_tracker.recordFoundEntity(entity_1.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_3.get());
|
||||
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
|
||||
typename LostEntityTracker<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
|
||||
@@ -102,7 +104,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) {
|
||||
}
|
||||
|
||||
TEST(LostEntityTracker, SameEntityMultipleCopies) {
|
||||
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1_copy(
|
||||
MakeConstPtr(new TestEntity(1)));
|
||||
@@ -121,7 +123,7 @@ TEST(LostEntityTracker, SameEntityMultipleCopies) {
|
||||
|
||||
// Go through a round without rediscovering any entities and verify that we
|
||||
// lost an entity equivalent to both copies of it.
|
||||
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
|
||||
typename LostEntityTracker<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_EQ(lost_entities.size(), 1);
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
|
||||
|
||||
@@ -24,7 +24,8 @@ Mediums<Platform>::Mediums()
|
||||
bluetooth_classic_(
|
||||
new BluetoothClassic<Platform>(bluetooth_radio_.get())),
|
||||
ble_(new BLE<Platform>(bluetooth_radio_.get())),
|
||||
ble_v2_(new mediums::BLEV2<Platform>(bluetooth_radio_.get())) {}
|
||||
ble_v2_(new mediums::BLEV2<Platform>(bluetooth_radio_.get())),
|
||||
wifi_lan_(new mediums::WifiLan<Platform>()) {}
|
||||
|
||||
template <typename Platform>
|
||||
Mediums<Platform>::~Mediums() {
|
||||
@@ -51,6 +52,11 @@ Ptr<mediums::BLEV2<Platform> > Mediums<Platform>::bleV2() const {
|
||||
return ble_v2_.get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<mediums::WifiLan<Platform> > Mediums<Platform>::wifi_lan() const {
|
||||
return wifi_lan_.get();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "core/internal/mediums/ble_v2.h"
|
||||
#include "core/internal/mediums/bluetooth_classic.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
@@ -41,6 +42,8 @@ class Mediums {
|
||||
Ptr<BLE<Platform> > ble() const;
|
||||
// Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium.
|
||||
Ptr<mediums::BLEV2<Platform> > bleV2() const;
|
||||
// Returns a handle to the Wifi-Lan medium.
|
||||
Ptr<mediums::WifiLan<Platform> > wifi_lan() const;
|
||||
|
||||
private:
|
||||
// The order of declaration is critical for both construction and
|
||||
@@ -55,6 +58,7 @@ class Mediums {
|
||||
ScopedPtr<Ptr<BluetoothClassic<Platform> > > bluetooth_classic_;
|
||||
ScopedPtr<Ptr<BLE<Platform> > > ble_;
|
||||
ScopedPtr<Ptr<mediums::BLEV2<Platform> > > ble_v2_;
|
||||
ScopedPtr<Ptr<mediums::WifiLan<Platform> > > wifi_lan_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
|
||||
#include "core/internal/mediums/utils.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
|
||||
#include "platform/exception.h"
|
||||
#include "platform/prng.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace location {
|
||||
@@ -62,6 +64,26 @@ ConstPtr<ByteArray> Utils::legacySha256HashOnlyForPrinting(
|
||||
return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length);
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> Utils::generateRandomBytes(size_t length) {
|
||||
Prng rng;
|
||||
std::string data;
|
||||
data.reserve(length);
|
||||
|
||||
// Adds 4 random bytes per iteration.
|
||||
while (length > 0) {
|
||||
std::uint32_t val = rng.nextUInt32();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data += val & 0xFF;
|
||||
val >>= 8;
|
||||
length--;
|
||||
|
||||
if (!length) break;
|
||||
}
|
||||
}
|
||||
|
||||
return MakeConstPtr(new ByteArray(data));
|
||||
}
|
||||
|
||||
std::string Utils::bytesToPrintableHexString(ConstPtr<ByteArray> bytes) {
|
||||
std::string hex_string(
|
||||
absl::BytesToHexString(std::string(bytes->getData(), bytes->size())));
|
||||
|
||||
@@ -35,6 +35,8 @@ class Utils {
|
||||
static ConstPtr<ByteArray> legacySha256HashOnlyForPrinting(
|
||||
Ptr<HashUtils> hash_utils, ConstPtr<ByteArray> source, size_t length);
|
||||
|
||||
static ConstPtr<ByteArray> generateRandomBytes(size_t length);
|
||||
|
||||
private:
|
||||
static std::string bytesToPrintableHexString(ConstPtr<ByteArray> bytes);
|
||||
};
|
||||
|
||||
@@ -31,17 +31,17 @@ namespace connections {
|
||||
template <typename Platform>
|
||||
class UUID {
|
||||
public:
|
||||
explicit UUID(const string& data);
|
||||
explicit UUID(const std::string& data);
|
||||
UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits);
|
||||
~UUID();
|
||||
|
||||
// Returns the canonical textual representation
|
||||
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the
|
||||
// UUID.
|
||||
string str();
|
||||
std::string str();
|
||||
|
||||
private:
|
||||
string data_;
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cc_library(
|
||||
name = "webrtc",
|
||||
hdrs = [
|
||||
"webrtc_socket.cc",
|
||||
"webrtc_socket.h",
|
||||
],
|
||||
deps = [
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "webrtc_test",
|
||||
srcs = ["webrtc_socket_test.cc"],
|
||||
deps = [
|
||||
":webrtc",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "peer_id",
|
||||
srcs = ["peer_id.cc"],
|
||||
hdrs = ["peer_id.h"],
|
||||
deps = [
|
||||
"//core/internal/mediums:utils",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/port:string",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "signaling_frames",
|
||||
srcs = ["signaling_frames.cc"],
|
||||
hdrs = ["signaling_frames.h"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "peer_id_test",
|
||||
srcs = ["peer_id_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "signaling_frames_test",
|
||||
srcs = ["signaling_frames_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
":signaling_frames",
|
||||
"//platform:types",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//net/proto2/public:proto2",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/pc:peerconnection", # buildcleaner: keep
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
constexpr int kPeerIdLength = 64;
|
||||
|
||||
std::string BytesToStringUppercase(ConstPtr<ByteArray> bytes) {
|
||||
std::string hex_string(
|
||||
absl::BytesToHexString(std::string(bytes->getData(), bytes->size())));
|
||||
absl::AsciiStrToUpper(&hex_string);
|
||||
return hex_string;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConstPtr<PeerId> PeerId::FromRandom(Ptr<HashUtils> hash_utils) {
|
||||
return FromSeed(Utils::generateRandomBytes(kPeerIdLength), hash_utils);
|
||||
}
|
||||
|
||||
ConstPtr<PeerId> PeerId::FromSeed(ConstPtr<ByteArray> seed,
|
||||
Ptr<HashUtils> hash_utils) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> full_hash(
|
||||
Utils::sha256Hash(hash_utils, seed, kPeerIdLength));
|
||||
ScopedPtr<ConstPtr<ByteArray>> hashedSeed(
|
||||
MakeConstPtr(new ByteArray(full_hash->getData(), kPeerIdLength / 2)));
|
||||
return MakeConstPtr(new PeerId(BytesToStringUppercase(hashedSeed.get())));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// PeerId is used as an identifier to exchange SDP messages to establish WebRTC
|
||||
// p2p connection.
|
||||
class PeerId {
|
||||
public:
|
||||
explicit PeerId(const string& id) : id_(id) {}
|
||||
~PeerId() = default;
|
||||
|
||||
static ConstPtr<PeerId> FromRandom(Ptr<HashUtils> hash_utils);
|
||||
static ConstPtr<PeerId> FromSeed(ConstPtr<ByteArray> seed,
|
||||
Ptr<HashUtils> hash_utils);
|
||||
|
||||
const string& GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
const string id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
class MockHashUtils : public HashUtils {
|
||||
public:
|
||||
MOCK_METHOD(ConstPtr<ByteArray>, md5, (const std::string& input), (override));
|
||||
MOCK_METHOD(ConstPtr<ByteArray>, sha256, (const std::string& input),
|
||||
(override));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(PeerIdTest, GenerateRandomPeerId) {
|
||||
// These are actual SHA-256 values for |seed| = "seed".
|
||||
std::string hashed_output =
|
||||
"19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b";
|
||||
std::string expected_peer_id =
|
||||
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
|
||||
|
||||
Ptr<testing::NiceMock<MockHashUtils>> mock_hash_utils(
|
||||
MakePtr(new MockHashUtils()));
|
||||
ON_CALL(*mock_hash_utils.get(), sha256(testing::_))
|
||||
.WillByDefault(testing::Return(
|
||||
MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output)))));
|
||||
EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::_));
|
||||
|
||||
ConstPtr<PeerId> peer_id = PeerId::FromRandom(mock_hash_utils);
|
||||
ASSERT_EQ(64, peer_id->GetId().size());
|
||||
ASSERT_EQ(expected_peer_id, peer_id->GetId());
|
||||
}
|
||||
|
||||
TEST(PeerIdTest, GenerateFromSeed) {
|
||||
// Values calculated by running actual SHA-256 hash on |seed|.
|
||||
std::string seed = "sesdfed";
|
||||
std::string hashed_output =
|
||||
"19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b";
|
||||
std::string expected_peer_id =
|
||||
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
|
||||
|
||||
Ptr<testing::NiceMock<MockHashUtils>> mock_hash_utils(
|
||||
MakePtr(new MockHashUtils()));
|
||||
ON_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed)))
|
||||
.WillByDefault(testing::Return(
|
||||
MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output)))));
|
||||
EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed)));
|
||||
|
||||
ConstPtr<PeerId> peer_id =
|
||||
PeerId::FromSeed(MakeConstPtr(new ByteArray(seed)), mock_hash_utils);
|
||||
|
||||
ASSERT_EQ(64, peer_id->GetId().size());
|
||||
ASSERT_EQ(expected_peer_id, peer_id->GetId());
|
||||
}
|
||||
|
||||
TEST(PeerIdTest, GetId) {
|
||||
const std::string id = "this_is_a_test";
|
||||
PeerId peer_id(id);
|
||||
ASSERT_EQ(id, peer_id.GetId());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace webrtc_frames {
|
||||
using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame;
|
||||
|
||||
namespace {
|
||||
|
||||
ConstPtr<ByteArray> FrameToByteArray(
|
||||
const WebRtcSignalingFrame& signaling_frame) {
|
||||
std::string message;
|
||||
signaling_frame.SerializeToString(&message);
|
||||
return MakeConstPtr(new ByteArray(message.c_str(), message.size()));
|
||||
}
|
||||
|
||||
void SetSenderId(ConstPtr<PeerId> sender_id, WebRtcSignalingFrame& frame) {
|
||||
frame.mutable_sender_id()->set_id(sender_id->GetId());
|
||||
}
|
||||
|
||||
ConstPtr<webrtc::IceCandidateInterface> DecodeIceCandidate(
|
||||
const location::nearby::mediums::IceCandidate& ice_candidate_proto) {
|
||||
webrtc::SdpParseError error;
|
||||
return ConstPtr<webrtc::IceCandidateInterface>(webrtc::CreateIceCandidate(
|
||||
ice_candidate_proto.sdp_mid(), ice_candidate_proto.sdp_m_line_index(),
|
||||
ice_candidate_proto.sdp(), &error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConstPtr<ByteArray> EncodeReadyForSignalingPoke(ConstPtr<PeerId> sender_id) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
signaling_frame.mutable_ready_for_signaling_poke();
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeOffer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string offer_str;
|
||||
offer.ToString(&offer_str);
|
||||
signaling_frame.mutable_offer()
|
||||
->mutable_session_description()
|
||||
->set_description(offer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeAnswer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string answer_str;
|
||||
answer.ToString(&answer_str);
|
||||
signaling_frame.mutable_answer()
|
||||
->mutable_session_description()
|
||||
->set_description(answer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeIceCandidates(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>&
|
||||
ice_candidates) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
*signaling_frame.mutable_ice_candidates()->add_ice_candidates() =
|
||||
ice_candidate;
|
||||
}
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return MakePtr(webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kOffer,
|
||||
frame.offer().session_description().description())
|
||||
.release());
|
||||
}
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return MakePtr(webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kAnswer,
|
||||
frame.answer().session_description().description())
|
||||
.release());
|
||||
}
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
for (const auto& candidate : frame.ice_candidates().ice_candidates()) {
|
||||
ice_candidates.push_back(DecodeIceCandidate(candidate));
|
||||
}
|
||||
return ice_candidates;
|
||||
}
|
||||
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate) {
|
||||
std::string sdp;
|
||||
ice_candidate.ToString(&sdp);
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto;
|
||||
ice_candidate_proto.set_sdp(sdp);
|
||||
ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid());
|
||||
ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index());
|
||||
return ice_candidate_proto;
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace webrtc_frames {
|
||||
|
||||
ConstPtr<ByteArray> EncodeReadyForSignalingPoke(ConstPtr<PeerId> sender_id);
|
||||
|
||||
ConstPtr<ByteArray> EncodeOffer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer);
|
||||
ConstPtr<ByteArray> EncodeAnswer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer);
|
||||
|
||||
ConstPtr<ByteArray> EncodeIceCandidates(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>& ice_candidates);
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate);
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
} // namespace webrtc_frames
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "net/proto2/public/text_format.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kSampleSdp[] =
|
||||
"v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 "
|
||||
"0\r\na=msid-semantic: WMS\r\n";
|
||||
|
||||
const char kIceCandidateSdp1[] =
|
||||
"a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host";
|
||||
const char kIceCandidateSdp2[] =
|
||||
"a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr";
|
||||
|
||||
const char kIceSdpMid[] = "data";
|
||||
const int kIceSdpMLineIndex = 0;
|
||||
|
||||
const char kOfferProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: OFFER_TYPE
|
||||
offer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kAnswerProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ANSWER_TYPE
|
||||
answer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kIceCandidatesProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ICE_CANDIDATES_TYPE
|
||||
ice_candidates {
|
||||
ice_candidates {
|
||||
sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
ice_candidates {
|
||||
sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
}
|
||||
)";
|
||||
} // namespace
|
||||
|
||||
TEST(SignalingFramesTest, SignalingPoke) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
ConstPtr<ByteArray> encoded_poke = EncodeReadyForSignalingPoke(sender_id);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_poke->getData(), encoded_poke->size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(R"(
|
||||
sender_id { id: "abc" }
|
||||
type: READY_FOR_SIGNALING_POKE_TYPE
|
||||
ready_for_signaling_poke {}
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidOffer) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp);
|
||||
ConstPtr<ByteArray> encoded_offer = EncodeOffer(sender_id, *offer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_offer->getData(), encoded_offer->size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kOfferProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidOffer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kOfferProto, &frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> decoded_offer = DecodeOffer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType());
|
||||
std::string description;
|
||||
decoded_offer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidAnswer) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp);
|
||||
ConstPtr<ByteArray> encoded_answer = EncodeAnswer(sender_id, *answer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_answer->getData(), encoded_answer->size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidAnswer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kAnswerProto, &frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> decoded_answer = DecodeAnswer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType());
|
||||
std::string description;
|
||||
decoded_answer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidIceCandidates) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
std::vector<location::nearby::mediums::IceCandidate> encoded_candidates_vec;
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate.get()));
|
||||
}
|
||||
ConstPtr<ByteArray> encoded_candidates =
|
||||
EncodeIceCandidates(sender_id, encoded_candidates_vec);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_candidates->getData(), encoded_candidates->size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidIceCandidates) {
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
std::vector<location::nearby::mediums::IceCandidate> encoded_candidates_vec;
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame);
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> decoded_candidates =
|
||||
DecodeIceCandidates(frame);
|
||||
|
||||
ASSERT_EQ(2u, decoded_candidates.size());
|
||||
for (int i = 0; i < static_cast<int>(decoded_candidates.size()); i++) {
|
||||
EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent(
|
||||
decoded_candidates[i]->candidate()));
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid());
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mline_index(),
|
||||
decoded_candidates[i]->sdp_mline_index());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// OutputStreamImpl
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::write(
|
||||
ConstPtr<ByteArray> data) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_data(data);
|
||||
|
||||
if (scoped_data->size() > kMaxDataSize) {
|
||||
NEARBY_LOG(WARNING, "Sending data larger than 1MB");
|
||||
return Exception::IO;
|
||||
}
|
||||
|
||||
socket_->BlockUntilSufficientSpaceInBuffer(scoped_data->size());
|
||||
|
||||
if (socket_->IsClosed()) {
|
||||
NEARBY_LOG(WARNING, "Tried sending message while socket is closed");
|
||||
return Exception::IO;
|
||||
}
|
||||
|
||||
if (!socket_->SendMessage(scoped_data.release())) {
|
||||
return Exception::IO;
|
||||
}
|
||||
return Exception::NONE;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::flush() {
|
||||
// Java implementation is empty.
|
||||
return Exception::NONE;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::close() {
|
||||
socket_->close();
|
||||
return Exception::NONE;
|
||||
}
|
||||
|
||||
// WebRtcSocket
|
||||
template <typename Platform>
|
||||
WebRtcSocket<Platform>::WebRtcSocket(
|
||||
const string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name),
|
||||
data_channel_(std::move(data_channel)),
|
||||
pipe_(MakeRefCountedPtr(new Pipe())),
|
||||
incoming_data_piped_input_stream_(Pipe::createInputStream(pipe_)),
|
||||
incoming_data_piped_output_stream_(Pipe::createOutputStream(pipe_)),
|
||||
output_stream_(MakePtr(new OutputStreamImpl(this))),
|
||||
closed_(Platform::createAtomicBoolean(false)),
|
||||
backpressure_lock_(Platform::createLock()),
|
||||
buffer_variable_(
|
||||
Platform::createConditionVariable(backpressure_lock_.get())) {}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<InputStream> WebRtcSocket<Platform>::getInputStream() {
|
||||
return incoming_data_piped_input_stream_.get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<OutputStream> WebRtcSocket<Platform>::getOutputStream() {
|
||||
return output_stream_.get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::close() {
|
||||
if (IsClosed()) return;
|
||||
|
||||
closed_->set(true);
|
||||
incoming_data_piped_output_stream_->close();
|
||||
incoming_data_piped_input_stream_->close();
|
||||
data_channel_->Close();
|
||||
WakeUpWriter();
|
||||
if (!socket_closed_listener_.isNull()) {
|
||||
socket_closed_listener_->OnSocketClosed();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::NotifyDataChannelMsgReceived(
|
||||
ConstPtr<ByteArray> message) {
|
||||
Exception::Value exception =
|
||||
incoming_data_piped_output_stream_->write(message);
|
||||
if (exception != Exception::NONE) close();
|
||||
|
||||
exception = incoming_data_piped_output_stream_->flush();
|
||||
if (exception != Exception::NONE) close();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::NotifyDataChannelBufferedAmountChanged() {
|
||||
WakeUpWriter();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WebRtcSocket<Platform>::SendMessage(ConstPtr<ByteArray> data) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_data(data);
|
||||
return data_channel_->Send(webrtc::DataBuffer(
|
||||
std::string(scoped_data->getData(), scoped_data->size())));
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WebRtcSocket<Platform>::IsClosed() {
|
||||
return closed_->get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::WakeUpWriter() {
|
||||
Synchronized s(backpressure_lock_.get());
|
||||
buffer_variable_->notify();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::SetOnSocketClosedListener(
|
||||
Ptr<SocketClosedListener> listener) {
|
||||
socket_closed_listener_ = listener;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::BlockUntilSufficientSpaceInBuffer(int length) {
|
||||
Synchronized s(backpressure_lock_.get());
|
||||
while (!IsClosed() &&
|
||||
(data_channel_->buffered_amount() + length > kMaxDataSize)) {
|
||||
// TODO(himanshujaju): Add wait with timeout.
|
||||
buffer_variable_->wait();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
|
||||
#include "platform/api/atomic_boolean.h"
|
||||
#include "platform/api/input_stream.h"
|
||||
#include "platform/api/output_stream.h"
|
||||
#include "platform/api/socket.h"
|
||||
#include "platform/pipe.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Maximum data size: 1 MB
|
||||
constexpr int kMaxDataSize = 1 * 1024 * 1024;
|
||||
|
||||
// Defines the Socket implementation specific to WebRTC, which uses the WebRTC
|
||||
// data channel to send and receive messages.
|
||||
//
|
||||
// Messages are buffered here to prevent the data channel from overflowing,
|
||||
// which could lead to data loss.
|
||||
template <typename Platform>
|
||||
class WebRtcSocket : public Socket {
|
||||
public:
|
||||
WebRtcSocket(const string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
~WebRtcSocket() override = default;
|
||||
|
||||
WebRtcSocket(const WebRtcSocket& other) = delete;
|
||||
WebRtcSocket& operator=(const WebRtcSocket& other) = delete;
|
||||
|
||||
// Overrides for location::nearby::Socket:
|
||||
Ptr<InputStream> getInputStream() override;
|
||||
Ptr<OutputStream> getOutputStream() override;
|
||||
void close() override;
|
||||
|
||||
// Callback from WebRTC data channel when new message has been received from
|
||||
// the remote.
|
||||
void NotifyDataChannelMsgReceived(ConstPtr<ByteArray> message);
|
||||
|
||||
// Callback from WebRTC data channel that the buffered data amount has
|
||||
// changed.
|
||||
void NotifyDataChannelBufferedAmountChanged();
|
||||
|
||||
// Listener class the gets called when the socket is closed.
|
||||
class SocketClosedListener {
|
||||
public:
|
||||
virtual ~SocketClosedListener() = default;
|
||||
virtual void OnSocketClosed() = 0;
|
||||
};
|
||||
void SetOnSocketClosedListener(Ptr<SocketClosedListener> listener);
|
||||
|
||||
private:
|
||||
class OutputStreamImpl : public OutputStream {
|
||||
public:
|
||||
explicit OutputStreamImpl(WebRtcSocket<Platform>* const socket)
|
||||
: socket_(socket) {}
|
||||
~OutputStreamImpl() override = default;
|
||||
|
||||
OutputStreamImpl(const OutputStreamImpl& other) = delete;
|
||||
OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete;
|
||||
|
||||
// OutputStream:
|
||||
Exception::Value write(ConstPtr<ByteArray> data) override;
|
||||
Exception::Value flush() override;
|
||||
Exception::Value close() override;
|
||||
|
||||
private:
|
||||
// |this| OutputStreamImpl is owned by |socket_|.
|
||||
WebRtcSocket<Platform>* const socket_;
|
||||
};
|
||||
|
||||
void WakeUpWriter();
|
||||
bool IsClosed();
|
||||
bool SendMessage(ConstPtr<ByteArray> data);
|
||||
void BlockUntilSufficientSpaceInBuffer(int length);
|
||||
|
||||
string name_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
|
||||
Ptr<Pipe> pipe_;
|
||||
ScopedPtr<Ptr<InputStream>> incoming_data_piped_input_stream_;
|
||||
ScopedPtr<Ptr<OutputStream>> incoming_data_piped_output_stream_;
|
||||
|
||||
ScopedPtr<Ptr<OutputStream>> output_stream_;
|
||||
|
||||
ScopedPtr<Ptr<AtomicBoolean>> closed_;
|
||||
|
||||
Ptr<SocketClosedListener> socket_closed_listener_;
|
||||
|
||||
ScopedPtr<Ptr<Lock>> backpressure_lock_;
|
||||
ScopedPtr<Ptr<ConditionVariable>> buffer_variable_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include "platform/api/platform.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
const char kSocketName[] = "TestSocket";
|
||||
|
||||
class MockDataChannel
|
||||
: public rtc::RefCountedObject<webrtc::DataChannelInterface> {
|
||||
public:
|
||||
MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*));
|
||||
MOCK_METHOD(void, UnregisterObserver, ());
|
||||
|
||||
MOCK_METHOD(std::string, label, (), (const));
|
||||
|
||||
MOCK_METHOD(bool, reliable, (), (const));
|
||||
MOCK_METHOD(int, id, (), (const));
|
||||
MOCK_METHOD(DataState, state, (), (const));
|
||||
MOCK_METHOD(uint32_t, messages_sent, (), (const));
|
||||
MOCK_METHOD(uint64_t, bytes_sent, (), (const));
|
||||
MOCK_METHOD(uint32_t, messages_received, (), (const));
|
||||
MOCK_METHOD(uint64_t, bytes_received, (), (const));
|
||||
|
||||
MOCK_METHOD(uint64_t, buffered_amount, (), (const));
|
||||
|
||||
MOCK_METHOD(void, Close, ());
|
||||
|
||||
MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class MockSocketClosedListener
|
||||
: public WebRtcSocket<TestPlatform>::SocketClosedListener {
|
||||
public:
|
||||
MOCK_METHOD(void, OnSocketClosed, ());
|
||||
};
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromSocket) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(kMessage);
|
||||
ExceptionOr<ConstPtr<ByteArray>> result =
|
||||
webrtc_socket.getInputStream()->read();
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), kMessage);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, ReadMultipleMessages) {
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("Me")));
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(
|
||||
MakeConstPtr(new ByteArray("ssa")));
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("ge")));
|
||||
ExceptionOr<ConstPtr<ByteArray>> result;
|
||||
|
||||
// This behaviour is different from the Java code
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "Me");
|
||||
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "ssa");
|
||||
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "ge");
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToSocket) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillRepeatedly(testing::Return(true));
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::NONE);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, SendDataBiggerThanMax) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray(kMaxDataSize + 1));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToDataChannelFails) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
ON_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillByDefault(testing::Return(false));
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, Close) {
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
ScopedPtr<Ptr<MockSocketClosedListener>> mock_listener(
|
||||
MakePtr(new MockSocketClosedListener()));
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
webrtc_socket.SetOnSocketClosedListener(mock_listener.get());
|
||||
|
||||
EXPECT_CALL(*mock_listener, OnSocketClosed());
|
||||
EXPECT_CALL(*mock_data_channel, Close());
|
||||
webrtc_socket.close();
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteOnClosedChannel) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
webrtc_socket.close();
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromClosedChannel) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
ON_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillByDefault(testing::Return(true));
|
||||
|
||||
webrtc_socket.getOutputStream()->write(kMessage);
|
||||
webrtc_socket.close();
|
||||
|
||||
EXPECT_EQ(webrtc_socket.getInputStream()->read().exception(), Exception::IO);
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,227 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
template <typename Platform>
|
||||
WifiLan<Platform>::WifiLan()
|
||||
: lock_(Platform::createLock()),
|
||||
wifi_lan_medium_(Platform::createWifiLanMedium()) {}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAvailable() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !wifi_lan_medium_.isNull();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartAdvertising(
|
||||
absl::string_view service_id,
|
||||
absl::string_view wifi_lan_service_info_name) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!IsAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StartAdvertising(service_id,
|
||||
// wifi_lan_service_info_name));
|
||||
|
||||
advertising_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopAdvertising(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!IsAdvertising()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StopAdvertising(advertising_info_.service_id);
|
||||
// Reset our bundle of advertising state to mark that we're no longer
|
||||
// advertising.
|
||||
advertising_info_.service_id.clear();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !advertising_info_.service_id.empty();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartDiscovery(
|
||||
absl::string_view service_id,
|
||||
Ptr<DiscoveredServiceCallback> discovered_service_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (discovered_service_callback.isNull() || service_id.empty()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan
|
||||
// discovering because a null parameter was passed in.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscovering(service_id)) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan
|
||||
// discovering because we are already discovering.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Can't start WifiLan discovering
|
||||
// because WifiLan isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<DiscoveredServiceCallbackBridge>>
|
||||
scoped_discovered_service_callback_bridge(
|
||||
new DiscoveredServiceCallbackBridge(discovered_service_callback));
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// wifi_lan_medium_->StartDiscovery(
|
||||
// service_id, Ptr<DiscoveredServiceCallbackBridge>(
|
||||
// discovered_service_callback_bridge.release()));
|
||||
|
||||
discovering_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopDiscovery(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!IsDiscovering(service_id)) {
|
||||
// TODO(b/149806065): logger.atDebug().log("Can't turn off WifiLan
|
||||
// discovering because we never started discovering.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StopDiscovery(discovering_info_.service_id);
|
||||
// Reset our bundle of scanning state to mark that we're no longer scanning.
|
||||
discovering_info_.service_id.clear();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsDiscovering(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !discovering_info_.service_id.empty();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartAcceptingConnections(
|
||||
absl::string_view service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (accepted_connection_callback.isNull() || service_id.empty()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start accepting
|
||||
// WifiLan connections because a null parameter was passed in.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnections(service_id)) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start accepting
|
||||
// WifiLan connections for %s because another WifiLan service socket is
|
||||
// already in-progress.", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Can't start accepting WifiLan
|
||||
// connections for %s because WifiLan isn't available.", serviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedPtr<Ptr<WifiLanAcceptedConnectionCallback>>
|
||||
scoped_wifi_lan_accepted_connection_callback(
|
||||
new WifiLanAcceptedConnectionCallback(
|
||||
accepted_connection_callback));
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// wifi_lan_medium_->StartAcceptingConnections(
|
||||
// service_id, Ptr<WifiLanAcceptedConnectionCallback>(
|
||||
// wifi_lan_accepted_connection_callback.release()));
|
||||
|
||||
accepting_connections_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopAcceptingConnections(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!IsAcceptingConnections(service_id)) {
|
||||
// TODO(b/149806065): logger.atDebug().log("Can't stop accepting WifiLan
|
||||
// connections because it was never started.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.);
|
||||
// A possible implementation is:
|
||||
// wifi_lan_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_.service_id.clear();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAcceptingConnections(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !accepting_connections_info_.service_id.empty();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<WifiLanSocket> WifiLan<Platform>::Connect(
|
||||
Ptr<WifiLanService> wifi_lan_service, absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (wifi_lan_service.isNull() || service_id.empty()) {
|
||||
return Ptr<WifiLanSocket>();
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
return Ptr<WifiLanSocket>();
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// return wifi_lan_medium_->Connect(wifi_lan_service, service_id);
|
||||
return Ptr<WifiLanSocket>();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/api/wifi_lan.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class DiscoveredServiceCallback {
|
||||
public:
|
||||
virtual ~DiscoveredServiceCallback() = default;
|
||||
|
||||
virtual void OnServiceDiscovered(Ptr<WifiLanService> wifi_lan_service) = 0;
|
||||
virtual void OnServiceLost(Ptr<WifiLanService> wifi_lan_service) = 0;
|
||||
};
|
||||
|
||||
template <typename Platform>
|
||||
class WifiLan {
|
||||
public:
|
||||
WifiLan();
|
||||
virtual ~WifiLan() = default;
|
||||
|
||||
bool IsAvailable();
|
||||
|
||||
bool StartAdvertising(absl::string_view service_id,
|
||||
absl::string_view wifi_lan_service_info_name);
|
||||
void StopAdvertising(absl::string_view service_id);
|
||||
bool IsAdvertising();
|
||||
|
||||
bool StartDiscovery(
|
||||
absl::string_view service_id,
|
||||
Ptr<DiscoveredServiceCallback> discovered_service_callback);
|
||||
void StopDiscovery(absl::string_view service_id);
|
||||
bool IsDiscovering(absl::string_view service_id);
|
||||
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() = default;
|
||||
|
||||
virtual void OnConnectionAccepted(Ptr<WifiLanSocket> socket,
|
||||
absl::string_view service_id) = 0;
|
||||
};
|
||||
|
||||
bool StartAcceptingConnections(
|
||||
absl::string_view service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
|
||||
void StopAcceptingConnections(absl::string_view service_id);
|
||||
bool IsAcceptingConnections(absl::string_view service_id);
|
||||
|
||||
Ptr<WifiLanSocket> Connect(Ptr<WifiLanService> wifi_lan_service,
|
||||
absl::string_view service_id);
|
||||
|
||||
private:
|
||||
class DiscoveredServiceCallbackBridge
|
||||
: public WifiLanMedium::DiscoveredServiceCallback {
|
||||
public:
|
||||
explicit DiscoveredServiceCallbackBridge(
|
||||
Ptr<mediums::DiscoveredServiceCallback> discovered_service_callback)
|
||||
: discovered_service_callback_(discovered_service_callback) {}
|
||||
~DiscoveredServiceCallbackBridge() override = default;
|
||||
|
||||
void OnServiceDiscovered(Ptr<WifiLanService> wifi_lan_service) override {
|
||||
discovered_service_callback_->OnServiceDiscovered(wifi_lan_service);
|
||||
}
|
||||
void OnServiceLost(Ptr<WifiLanService> wifi_lan_service) override {
|
||||
discovered_service_callback_->OnServiceLost(wifi_lan_service);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<mediums::DiscoveredServiceCallback>>
|
||||
discovered_service_callback_;
|
||||
};
|
||||
|
||||
class WifiLanAcceptedConnectionCallback
|
||||
: public WifiLanMedium::AcceptedConnectionCallback {
|
||||
public:
|
||||
explicit WifiLanAcceptedConnectionCallback(
|
||||
Ptr<WifiLan::AcceptedConnectionCallback> accepted_connection_callback)
|
||||
: accepted_connection_callback_(accepted_connection_callback) {}
|
||||
~WifiLanAcceptedConnectionCallback() override = default;
|
||||
|
||||
void OnConnectionAccepted(Ptr<WifiLanSocket> wifi_lan_socket,
|
||||
absl::string_view service_id) override {
|
||||
accepted_connection_callback_->OnConnectionAccepted(wifi_lan_socket,
|
||||
service_id);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<WifiLan::AcceptedConnectionCallback>>
|
||||
accepted_connection_callback_;
|
||||
};
|
||||
|
||||
struct DiscoveringInfo {
|
||||
DiscoveringInfo() = default;
|
||||
explicit DiscoveringInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~DiscoveringInfo() = default;
|
||||
|
||||
string service_id;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
AdvertisingInfo() = default;
|
||||
explicit AdvertisingInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~AdvertisingInfo() = default;
|
||||
|
||||
string service_id;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
AcceptingConnectionsInfo() = default;
|
||||
explicit AcceptingConnectionsInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~AcceptingConnectionsInfo() = default;
|
||||
|
||||
string service_id;
|
||||
};
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
|
||||
// ---------- CORE WIFILAN------------
|
||||
|
||||
// The underlying, per-platform implementation.
|
||||
ScopedPtr<Ptr<WifiLanMedium>> wifi_lan_medium_;
|
||||
|
||||
// ------------ DISCOVERY ------------
|
||||
|
||||
// discovering_info_ is not scoped because it's nullable.
|
||||
DiscoveringInfo discovering_info_;
|
||||
|
||||
// ------------ ADVERTISING ------------
|
||||
|
||||
// A bundle of state required to start/stop WifiLan service publishing.
|
||||
AdvertisingInfo advertising_info_;
|
||||
|
||||
// A bundle of state required to start/stop accepting WifiLan service
|
||||
/// connections.
|
||||
AcceptingConnectionsInfo accepting_connections_info_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/wifi_lan.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
Reference in New Issue
Block a user