mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Merge branch 'master' into release
Change-Id: I56ea2217899e92bdd9d6cb56797ac9895e194fff
This commit is contained in:
@@ -29,17 +29,17 @@ namespace mediums {
|
||||
// p2p connection.
|
||||
class PeerId {
|
||||
public:
|
||||
explicit PeerId(const string& id) : id_(id) {}
|
||||
explicit PeerId(const std::string& id) : id_(id) {}
|
||||
~PeerId() = default;
|
||||
|
||||
static ConstPtr<PeerId> FromRandom(Ptr<HashUtils> hash_utils);
|
||||
static ConstPtr<PeerId> FromSeed(ConstPtr<ByteArray> seed,
|
||||
Ptr<HashUtils> hash_utils);
|
||||
|
||||
const string& GetId() const { return id_; }
|
||||
const std::string& GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
const string id_;
|
||||
const std::string id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -60,7 +60,7 @@ Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::close() {
|
||||
// WebRtcSocket
|
||||
template <typename Platform>
|
||||
WebRtcSocket<Platform>::WebRtcSocket(
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name),
|
||||
data_channel_(std::move(data_channel)),
|
||||
|
||||
@@ -38,7 +38,7 @@ constexpr int kMaxDataSize = 1 * 1024 * 1024;
|
||||
template <typename Platform>
|
||||
class WebRtcSocket : public Socket {
|
||||
public:
|
||||
WebRtcSocket(const string& name,
|
||||
WebRtcSocket(const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
~WebRtcSocket() override = default;
|
||||
|
||||
@@ -91,7 +91,7 @@ class WebRtcSocket : public Socket {
|
||||
bool SendMessage(ConstPtr<ByteArray> data);
|
||||
void BlockUntilSufficientSpaceInBuffer(int length);
|
||||
|
||||
string name_;
|
||||
std::string name_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
|
||||
Ptr<Pipe> pipe_;
|
||||
|
||||
@@ -19,12 +19,22 @@ cc_library(
|
||||
"base_pcp_handler.cc",
|
||||
"ble_advertisement.cc",
|
||||
"bluetooth_device_name.cc",
|
||||
"bluetooth_endpoint_channel.cc",
|
||||
"client_proxy.cc",
|
||||
"encryption_runner.cc",
|
||||
"endpoint_channel_manager.cc",
|
||||
"endpoint_manager.cc",
|
||||
"internal_payload.cc",
|
||||
"internal_payload_factory.cc",
|
||||
"offline_frames.cc",
|
||||
"p2p_cluster_pcp_handler.cc",
|
||||
"p2p_point_to_point_pcp_handler.cc",
|
||||
"p2p_star_pcp_handler.cc",
|
||||
"payload_manager.cc",
|
||||
"pcp_manager.cc",
|
||||
"service_controller_router.cc",
|
||||
"webrtc_endpoint_channel.cc",
|
||||
"wifi_lan_endpoint_channel.cc",
|
||||
"wifi_lan_service_info.cc",
|
||||
],
|
||||
hdrs = [
|
||||
@@ -32,16 +42,26 @@ cc_library(
|
||||
"base_pcp_handler.h",
|
||||
"ble_advertisement.h",
|
||||
"bluetooth_device_name.h",
|
||||
"bluetooth_endpoint_channel.h",
|
||||
"client_proxy.h",
|
||||
"encryption_runner.h",
|
||||
"endpoint_channel.h",
|
||||
"endpoint_channel_manager.h",
|
||||
"endpoint_manager.h",
|
||||
"internal_payload.h",
|
||||
"internal_payload_factory.h",
|
||||
"offline_frames.h",
|
||||
"p2p_cluster_pcp_handler.h",
|
||||
"p2p_point_to_point_pcp_handler.h",
|
||||
"p2p_star_pcp_handler.h",
|
||||
"payload_manager.h",
|
||||
"pcp.h",
|
||||
"pcp_handler.h",
|
||||
"pcp_manager.h",
|
||||
"service_controller.h",
|
||||
"service_controller_router.h",
|
||||
"webrtc_endpoint_channel.h",
|
||||
"wifi_lan_endpoint_channel.h",
|
||||
"wifi_lan_service_info.h",
|
||||
],
|
||||
visibility = [
|
||||
@@ -50,8 +70,11 @@ cc_library(
|
||||
deps = [
|
||||
"//core/internal:message_lite",
|
||||
"//core_v2:core_types",
|
||||
"//core_v2/internal/mediums",
|
||||
"//core_v2/internal/mediums/webrtc",
|
||||
"//proto/connections:offline_wire_formats_portable_proto",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/base:util",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
@@ -60,6 +83,7 @@ cc_library(
|
||||
"//absl/base:core_headers",
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/memory",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//absl/types:span",
|
||||
@@ -69,15 +93,23 @@ cc_library(
|
||||
cc_library(
|
||||
name = "internal_test",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"simulation_user.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"mock_service_controller.h",
|
||||
"simulation_user.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core_v2:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":internal",
|
||||
"//core_v2:core_types",
|
||||
"//platform_v2/base:test_util",
|
||||
"//platform_v2/public:types",
|
||||
"//testing/base/public:gunit",
|
||||
"//absl/functional:bind_front",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -93,7 +125,11 @@ cc_test(
|
||||
"encryption_runner_test.cc",
|
||||
"endpoint_channel_manager_test.cc",
|
||||
"endpoint_manager_test.cc",
|
||||
"internal_payload_factory_test.cc",
|
||||
"offline_frames_test.cc",
|
||||
"p2p_cluster_pcp_handler_test.cc",
|
||||
"payload_manager_test.cc",
|
||||
"pcp_manager_test.cc",
|
||||
"service_controller_router_test.cc",
|
||||
"wifi_lan_service_info_test.cc",
|
||||
],
|
||||
@@ -104,8 +140,8 @@ cc_test(
|
||||
"//core_v2:core_types",
|
||||
"//proto/connections:offline_wire_formats_portable_proto",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/base:test_util",
|
||||
"//platform_v2/impl/g3", # build_cleaner: keep
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//proto:connections_enums_portable_proto",
|
||||
@@ -113,6 +149,8 @@ cc_test(
|
||||
"//testing/base/public:gunit",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/functional:bind_front",
|
||||
"//absl/strings",
|
||||
"//absl/synchronization",
|
||||
"//absl/time",
|
||||
"//absl/types:span",
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "core_v2/internal/offline_frames.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/exception.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace location {
|
||||
@@ -113,13 +116,33 @@ ExceptionOr<ByteArray> BaseEndpointChannel::Read() {
|
||||
result = std::move(read_bytes.result());
|
||||
}
|
||||
|
||||
// If encryption is enabled, decode the message.
|
||||
if (IsEncryptionEnabled()) {
|
||||
{
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
result = ByteArray(std::move(
|
||||
*encryption_context_->DecodeMessageFromPeer(std::string(result))));
|
||||
if (result.Empty()) {
|
||||
return ExceptionOr<ByteArray>(Exception::kInvalidProtocolBuffer);
|
||||
if (IsEncryptionEnabledLocked()) {
|
||||
// If encryption is enabled, decode the message.
|
||||
std::string input(std::move(result));
|
||||
std::unique_ptr<std::string> decrypted_data =
|
||||
crypto_context_->DecodeMessageFromPeer(
|
||||
std::string(std::move(result)));
|
||||
if (decrypted_data) {
|
||||
result = ByteArray(std::move(*decrypted_data));
|
||||
} else {
|
||||
// It could be a protocol race, where remote party sends a KEEP_ALIVE
|
||||
// before encryption is setup on their side, and we receive it after
|
||||
// we switched to encryption mode.
|
||||
// In this case, we verify that message is indeed a valid KEEP_ALIVE,
|
||||
// and let it through if it is, otherwise message is erased.
|
||||
// TODO(apolyudov): verify this happens at most once per session.
|
||||
result = {};
|
||||
auto parsed = parser::FromBytes(ByteArray(input));
|
||||
if (parsed.ok() &&
|
||||
parser::GetFrameType(parsed.result()) == V1Frame::KEEP_ALIVE) {
|
||||
result = ByteArray(input);
|
||||
}
|
||||
}
|
||||
if (result.Empty()) {
|
||||
return ExceptionOr<ByteArray>(Exception::kInvalidProtocolBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,10 +165,12 @@ Exception BaseEndpointChannel::Write(const ByteArray& data) {
|
||||
const ByteArray* data_to_write = &data;
|
||||
{
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
// If encryption is enabled, encode the message.
|
||||
if (IsEncryptionEnabled()) {
|
||||
encrypted_data = ByteArray(std::move(
|
||||
*encryption_context_->EncodeMessageToPeer(std::string(data))));
|
||||
if (IsEncryptionEnabledLocked()) {
|
||||
// If encryption is enabled, encode the message.
|
||||
std::unique_ptr<std::string> encrypted =
|
||||
crypto_context_->EncodeMessageToPeer(std::string(data));
|
||||
if (!encrypted) return {Exception::kIo};
|
||||
encrypted_data = ByteArray(std::move(*encrypted));
|
||||
data_to_write = &encrypted_data;
|
||||
}
|
||||
}
|
||||
@@ -154,17 +179,15 @@ Exception BaseEndpointChannel::Write(const ByteArray& data) {
|
||||
MutexLock lock(&writer_mutex_);
|
||||
Exception write_exception =
|
||||
WriteInt(writer_, static_cast<std::int32_t>(data_to_write->size()));
|
||||
if (!write_exception.Ok()) {
|
||||
if (write_exception.Raised()) {
|
||||
return write_exception;
|
||||
}
|
||||
|
||||
write_exception = writer_->Write(*data_to_write);
|
||||
if (write_exception.Ok()) {
|
||||
if (write_exception.Raised()) {
|
||||
return write_exception;
|
||||
}
|
||||
|
||||
Exception flush_exception = writer_->Flush();
|
||||
if (!flush_exception.Ok()) {
|
||||
if (flush_exception.Raised()) {
|
||||
return flush_exception;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +233,8 @@ void BaseEndpointChannel::Close(
|
||||
}
|
||||
|
||||
std::string BaseEndpointChannel::GetType() const {
|
||||
std::string subtype = IsEncryptionEnabled() ? "ENCRYPTED_" : "";
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
std::string subtype = IsEncryptionEnabledLocked() ? "ENCRYPTED_" : "";
|
||||
|
||||
switch (GetMedium()) {
|
||||
case proto::connections::Medium::BLUETOOTH:
|
||||
@@ -231,9 +255,9 @@ std::string BaseEndpointChannel::GetType() const {
|
||||
std::string BaseEndpointChannel::GetName() const { return channel_name_; }
|
||||
|
||||
void BaseEndpointChannel::EnableEncryption(
|
||||
securegcm::D2DConnectionContextV1* encryption_context) {
|
||||
MutexLock lock(&crypto_mutex_);
|
||||
encryption_context_ = encryption_context;
|
||||
std::shared_ptr<EncryptionContext> context) {
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
crypto_context_ = context;
|
||||
}
|
||||
|
||||
bool BaseEndpointChannel::IsPaused() const {
|
||||
@@ -257,8 +281,8 @@ absl::Time BaseEndpointChannel::GetLastReadTimestamp() const {
|
||||
return last_read_timestamp_;
|
||||
}
|
||||
|
||||
bool BaseEndpointChannel::IsEncryptionEnabled() const {
|
||||
return encryption_context_ != nullptr;
|
||||
bool BaseEndpointChannel::IsEncryptionEnabledLocked() const {
|
||||
return crypto_context_ != nullptr;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::BlockUntilUnpaused() {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/endpoint_channel.h"
|
||||
@@ -64,7 +65,7 @@ class BaseEndpointChannel : public EndpointChannel {
|
||||
// Enables encryption on the EndpointChannel.
|
||||
// Should be called after connection is accepted by both parties, and
|
||||
// before entering data phase, where Payloads may be exchanged.
|
||||
void EnableEncryption(securegcm::D2DConnectionContextV1* context) override;
|
||||
void EnableEncryption(std::shared_ptr<EncryptionContext> context) override;
|
||||
|
||||
// True if the EndpointChannel is currently pausing all writes.
|
||||
bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override;
|
||||
@@ -88,7 +89,8 @@ class BaseEndpointChannel : public EndpointChannel {
|
||||
// Used to sanity check that our frame sizes are reasonable.
|
||||
static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB
|
||||
|
||||
bool IsEncryptionEnabled() const;
|
||||
bool IsEncryptionEnabledLocked() const
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(crypto_mutex_);
|
||||
void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_);
|
||||
void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_);
|
||||
void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS;
|
||||
@@ -108,11 +110,10 @@ class BaseEndpointChannel : public EndpointChannel {
|
||||
Mutex writer_mutex_;
|
||||
OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_);
|
||||
|
||||
// Used by both read and write to protect payload encryption/decryption.
|
||||
Mutex crypto_mutex_;
|
||||
// An encryptor/decryptor. May be null.
|
||||
securegcm::D2DConnectionContextV1* encryption_context_
|
||||
ABSL_PT_GUARDED_BY(crypto_mutex_) = nullptr;
|
||||
mutable Mutex crypto_mutex_;
|
||||
std::shared_ptr<EncryptionContext> crypto_context_
|
||||
ABSL_GUARDED_BY(crypto_mutex_) ABSL_PT_GUARDED_BY(crypto_mutex_);
|
||||
|
||||
mutable Mutex is_paused_mutex_;
|
||||
ConditionVariable is_paused_cond_{&is_paused_mutex_};
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::DisconnectionReason;
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
using EncryptionContext = BaseEndpointChannel::EncryptionContext;
|
||||
|
||||
class TestEndpointChannel : public BaseEndpointChannel {
|
||||
public:
|
||||
@@ -90,12 +91,12 @@ std::function<void(const ByteArray&)> MakeDataMonitor(const std::string& label,
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<securegcm::D2DConnectionContextV1>,
|
||||
std::unique_ptr<securegcm::D2DConnectionContextV1>>
|
||||
std::pair<std::shared_ptr<EncryptionContext>,
|
||||
std::shared_ptr<EncryptionContext>>
|
||||
DoDhKeyExchange(BaseEndpointChannel* channel_a,
|
||||
BaseEndpointChannel* channel_b) {
|
||||
std::unique_ptr<securegcm::D2DConnectionContextV1> context_a;
|
||||
std::unique_ptr<securegcm::D2DConnectionContextV1> context_b;
|
||||
std::shared_ptr<EncryptionContext> context_a;
|
||||
std::shared_ptr<EncryptionContext> context_b;
|
||||
EncryptionRunner crypto_a;
|
||||
EncryptionRunner crypto_b;
|
||||
ClientProxy proxy_a;
|
||||
@@ -112,7 +113,7 @@ DoDhKeyExchange(BaseEndpointChannel* channel_a,
|
||||
NEARBY_LOG(INFO, "client-A side key negotiation done");
|
||||
EXPECT_TRUE(ukey2->VerifyHandshake());
|
||||
auto context = ukey2->ToConnectionContext();
|
||||
EXPECT_NE (context, nullptr);
|
||||
EXPECT_NE(context, nullptr);
|
||||
context_a = std::move(context);
|
||||
latch.CountDown();
|
||||
},
|
||||
@@ -133,7 +134,7 @@ DoDhKeyExchange(BaseEndpointChannel* channel_a,
|
||||
NEARBY_LOG(INFO, "client-B side key negotiation done");
|
||||
EXPECT_TRUE(ukey2->VerifyHandshake());
|
||||
auto context = ukey2->ToConnectionContext();
|
||||
EXPECT_NE (context, nullptr);
|
||||
EXPECT_NE(context, nullptr);
|
||||
context_b = std::move(context);
|
||||
latch.CountDown();
|
||||
},
|
||||
@@ -210,7 +211,7 @@ TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) {
|
||||
absl::MutexLock lock(&mutex);
|
||||
std::string message{tx_message};
|
||||
EXPECT_TRUE(capture_a.find(message) != std::string::npos ||
|
||||
capture_b.find(message) != std::string::npos);
|
||||
capture_b.find(message) != std::string::npos);
|
||||
}
|
||||
|
||||
// Shutdown test environment.
|
||||
@@ -253,8 +254,8 @@ TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) {
|
||||
auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b);
|
||||
ASSERT_NE(context_a, nullptr);
|
||||
ASSERT_NE(context_b, nullptr);
|
||||
channel_a.EnableEncryption(context_a.get());
|
||||
channel_b.EnableEncryption(context_b.get());
|
||||
channel_a.EnableEncryption(context_a);
|
||||
channel_b.EnableEncryption(context_b);
|
||||
|
||||
EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH");
|
||||
EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH");
|
||||
@@ -306,26 +307,25 @@ TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) {
|
||||
// Pause and make sure reader blocks.
|
||||
MultiThreadExecutor pause_resume_executor(2);
|
||||
channel_a.Pause();
|
||||
pause_resume_executor.Execute([&channel_a, &more_message](){
|
||||
pause_resume_executor.Execute([&channel_a, &more_message]() {
|
||||
// Write will block until channel is resumed, or closed.
|
||||
EXPECT_TRUE(channel_a.Write(more_message).Ok());
|
||||
});
|
||||
std::atomic_bool done = false;
|
||||
CountDownLatch latch(1);
|
||||
ByteArray read_more;
|
||||
pause_resume_executor.Execute([&channel_b, &read_more, &done](){
|
||||
pause_resume_executor.Execute([&channel_b, &read_more, &latch]() {
|
||||
// Read will block until channel is resumed, or closed.
|
||||
auto response = channel_b.Read();
|
||||
EXPECT_TRUE(response.ok());
|
||||
read_more = std::move(response.result());
|
||||
done = true;
|
||||
latch.CountDown();
|
||||
});
|
||||
absl::SleepFor(absl::Milliseconds(500));
|
||||
EXPECT_TRUE(read_more.Empty());
|
||||
|
||||
// Resume; verify that data transfer comepleted.
|
||||
channel_a.Resume();
|
||||
absl::SleepFor(absl::Milliseconds(500));
|
||||
EXPECT_TRUE(done);
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(read_more, more_message);
|
||||
|
||||
// Shutdown test environment.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/offline_frames.h"
|
||||
#include "core_v2/internal/pcp_handler.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/system_clock.h"
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
@@ -39,17 +40,25 @@ constexpr absl::Duration BasePcpHandler::kConnectionRequestReadTimeout;
|
||||
constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay;
|
||||
|
||||
BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager,
|
||||
EndpointChannelManager* channel_manager)
|
||||
: endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {}
|
||||
EndpointChannelManager* channel_manager, Pcp pcp)
|
||||
: endpoint_manager_(endpoint_manager),
|
||||
channel_manager_(channel_manager),
|
||||
pcp_(pcp) {}
|
||||
|
||||
BasePcpHandler::~BasePcpHandler() {
|
||||
// Unregister ourselves from the FrameProcessors.
|
||||
NEARBY_LOGS(INFO) << "BasePcpHandler: going down; strategy="
|
||||
<< strategy_.GetName();
|
||||
endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE,
|
||||
handle_);
|
||||
|
||||
// Stop all the ongoing Runnables (as gracefully as possible).
|
||||
NEARBY_LOGS(INFO) << "BasePcpHandler: bringing down executors; strategy="
|
||||
<< strategy_.GetName();
|
||||
serial_executor_.Shutdown();
|
||||
alarm_executor_.Shutdown();
|
||||
NEARBY_LOGS(INFO) << "BasePcpHandler: is down; strategy="
|
||||
<< strategy_.GetName();
|
||||
}
|
||||
|
||||
Status BasePcpHandler::StartAdvertising(ClientProxy* client,
|
||||
@@ -563,7 +572,7 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client,
|
||||
// return bandwidth_upgrade_medium_.Get();
|
||||
//}
|
||||
|
||||
void BasePcpHandler::OnIncomingFrame(const OfflineFrame& frame,
|
||||
void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame,
|
||||
const string& endpoint_id,
|
||||
ClientProxy* client,
|
||||
proto::connections::Medium medium) {
|
||||
@@ -620,7 +629,7 @@ ConnectionOptions BasePcpHandler::GetConnectionOptions() const {
|
||||
|
||||
void BasePcpHandler::OnEndpointFound(
|
||||
ClientProxy* client,
|
||||
std::unique_ptr<BasePcpHandler::DiscoveredEndpoint> endpoint) {
|
||||
std::shared_ptr<BasePcpHandler::DiscoveredEndpoint> endpoint) {
|
||||
// Check if we've seen this endpoint ID before.
|
||||
std::string& endpoint_id = endpoint->endpoint_id;
|
||||
BasePcpHandler::DiscoveredEndpoint* previously_discovered_endpoint =
|
||||
@@ -631,8 +640,7 @@ void BasePcpHandler::OnEndpointFound(
|
||||
// If this is the first medium we've discovered this endpoint over, then add
|
||||
// it to the map.
|
||||
const auto& owned_endpoint =
|
||||
discovered_endpoints_
|
||||
.emplace(endpoint_id, std::move(endpoint))
|
||||
discovered_endpoints_.emplace(endpoint_id, std::move(endpoint))
|
||||
.first->second;
|
||||
|
||||
NEARBY_LOG(INFO, "Adding new endpoint: id=%s", endpoint_id.c_str());
|
||||
@@ -655,8 +663,7 @@ void BasePcpHandler::OnEndpointFound(
|
||||
NEARBY_LOG(INFO, "Rediscovered endpoint on new media: id=%s",
|
||||
endpoint_id.c_str());
|
||||
if (IsPreferred(*endpoint, *previously_discovered_endpoint)) {
|
||||
discovered_endpoints_.insert_or_assign(endpoint_id,
|
||||
std::move(endpoint));
|
||||
discovered_endpoints_.insert_or_assign(endpoint_id, std::move(endpoint));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -664,8 +671,7 @@ void BasePcpHandler::OnEndpointFound(
|
||||
void BasePcpHandler::OnEndpointLost(
|
||||
ClientProxy* client, const BasePcpHandler::DiscoveredEndpoint& endpoint) {
|
||||
// Look up the DiscoveredEndpoint we have in our cache.
|
||||
const auto* discovered_endpoint =
|
||||
GetDiscoveredEndpoint(endpoint.endpoint_id);
|
||||
const auto* discovered_endpoint = GetDiscoveredEndpoint(endpoint.endpoint_id);
|
||||
if (discovered_endpoint == nullptr) {
|
||||
NEARBY_LOG(INFO, "No previous endpoint (nothing to lose): id=%s",
|
||||
endpoint.endpoint_id.c_str());
|
||||
@@ -747,7 +753,7 @@ Exception BasePcpHandler::OnIncomingConnection(
|
||||
OfflineFrame& frame = wrapped_frame.result();
|
||||
const ConnectionRequestFrame& connection_request =
|
||||
frame.v1().connection_request();
|
||||
NEARBY_LOG(ERROR,
|
||||
NEARBY_LOG(INFO,
|
||||
"Incoming connection request; client_id=0x%" PRIX64
|
||||
"; device=%s; id=%s",
|
||||
client->GetClientId(), remote_device_name.c_str(),
|
||||
@@ -944,7 +950,7 @@ void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client,
|
||||
bool succeeded = ukey2->VerifyHandshake();
|
||||
CHECK(succeeded); // If this fails, it's a UKEY2 protocol bug.
|
||||
auto context = ukey2->ToConnectionContext();
|
||||
assert(context); // there is no way how this can fail, if Verify succeeded.
|
||||
CHECK(context); // there is no way how this can fail, if Verify succeeded.
|
||||
// If it did, it's a UKEY2 protocol bug.
|
||||
|
||||
channel_manager_->EncryptChannelForEndpoint(endpoint_id,
|
||||
|
||||
@@ -88,9 +88,9 @@ class BasePcpHandler : public PcpHandler,
|
||||
public:
|
||||
using FrameProcessor = EndpointManager::FrameProcessor;
|
||||
|
||||
// TODO(tracyzhou): Add SecureRandom.
|
||||
// TODO(apolyudov): Add SecureRandom.
|
||||
BasePcpHandler(EndpointManager* endpoint_manager,
|
||||
EndpointChannelManager* channel_manager);
|
||||
EndpointChannelManager* channel_manager, Pcp pcp);
|
||||
~BasePcpHandler() override;
|
||||
BasePcpHandler(BasePcpHandler&&) = delete;
|
||||
BasePcpHandler& operator=(BasePcpHandler&&) = delete;
|
||||
@@ -120,7 +120,7 @@ class BasePcpHandler : public PcpHandler,
|
||||
// otherwise does nothing.
|
||||
void StopDiscovery(ClientProxy* client_proxy) override;
|
||||
|
||||
// Requests a newly discoveered remote endpoint it to form a connection.
|
||||
// Requests a newly discovered remote endpoint it to form a connection.
|
||||
// Updates state on ClientProxy.
|
||||
Status RequestConnection(ClientProxy* client_proxy,
|
||||
const std::string& endpoint_id,
|
||||
@@ -140,8 +140,8 @@ class BasePcpHandler : public PcpHandler,
|
||||
const std::string& endpoint_id) override;
|
||||
|
||||
// @EndpointManagerReaderThread
|
||||
void OnIncomingFrame(const OfflineFrame& frame,
|
||||
const std::string& endpoint_id, ClientProxy* client,
|
||||
void OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id,
|
||||
ClientProxy* client,
|
||||
proto::connections::Medium medium) override;
|
||||
|
||||
// Called when an endpoint disconnects while we're waiting for both sides to
|
||||
@@ -151,6 +151,9 @@ class BasePcpHandler : public PcpHandler,
|
||||
const std::string& endpoint_id,
|
||||
CountDownLatch* barrier) override;
|
||||
|
||||
Pcp GetPcp() const override { return pcp_; }
|
||||
Strategy GetStrategy() const override { return strategy_; }
|
||||
|
||||
protected:
|
||||
// The result of a call to startAdvertisingImpl() or startDiscoveryImpl().
|
||||
struct StartOperationResult {
|
||||
@@ -163,6 +166,17 @@ class BasePcpHandler : public PcpHandler,
|
||||
// Represents an endpoint that we've discovered. Typically, the implementation
|
||||
// will know how to connect to this endpoint if asked. (eg. It holds on to a
|
||||
// BluetoothDevice)
|
||||
//
|
||||
// NOTE(DiscoveredEndpoint):
|
||||
// Specific protocol is expected to derive from it, as follows:
|
||||
// struct ProtocolEndpoint : public DiscoveredEndpoint {
|
||||
// ProtocolContext context;
|
||||
// };
|
||||
// Protocol then allocates instance with std::make_shared<ProtocolEndpoint>(),
|
||||
// and passes this instance to OnEndpointFound() method.
|
||||
// When calling OnEndpointLost(), protocol does not need to pass the same
|
||||
// instance (but it can if implementation desires to do so).
|
||||
// BasePcpHandler will hold on to the shared_ptr<DiscoveredEndpoint>.
|
||||
struct DiscoveredEndpoint {
|
||||
std::string endpoint_id;
|
||||
std::string endpoint_name;
|
||||
@@ -183,7 +197,7 @@ class BasePcpHandler : public PcpHandler,
|
||||
|
||||
// @PcpHandlerThread
|
||||
void OnEndpointFound(ClientProxy* client_proxy,
|
||||
std::unique_ptr<DiscoveredEndpoint> endpoint);
|
||||
std::shared_ptr<DiscoveredEndpoint> endpoint);
|
||||
|
||||
// @PcpHandlerThread
|
||||
void OnEndpointLost(ClientProxy* client_proxy,
|
||||
@@ -252,7 +266,7 @@ class BasePcpHandler : public PcpHandler,
|
||||
std::string remote_endpoint_name;
|
||||
std::int32_t nonce = 0;
|
||||
bool is_incoming = false;
|
||||
absl::Time start_time {absl::InfinitePast()};
|
||||
absl::Time start_time{absl::InfinitePast()};
|
||||
// Client callbacks. Always valid.
|
||||
ConnectionListener listener;
|
||||
|
||||
@@ -389,7 +403,7 @@ class BasePcpHandler : public PcpHandler,
|
||||
// removed from this map.
|
||||
absl::flat_hash_map<std::string, PendingConnectionInfo> pending_connections_;
|
||||
// A map of endpoint id -> DiscoveredEndpoint.
|
||||
absl::flat_hash_map<std::string, std::unique_ptr<DiscoveredEndpoint>>
|
||||
absl::flat_hash_map<std::string, std::shared_ptr<DiscoveredEndpoint>>
|
||||
discovered_endpoints_;
|
||||
// A map of endpoint id -> alarm. These alarms delay closing the
|
||||
// EndpointChannel to give the other side enough time to read the rejection
|
||||
@@ -414,9 +428,11 @@ class BasePcpHandler : public PcpHandler,
|
||||
// stops discovering because it might still be useful downstream of
|
||||
// discovery (eg: connection speed, etc.)
|
||||
ConnectionOptions discovery_options_;
|
||||
Pcp pcp_;
|
||||
Strategy strategy_{PcpToStrategy(pcp_)};
|
||||
Prng prng_;
|
||||
EncryptionRunner encryption_runner_;
|
||||
EndpointManager::FrameProcessor::Handle handle_;
|
||||
EndpointManager::FrameProcessor::Handle handle_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "core_v2/internal/base_pcp_handler.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/base_endpoint_channel.h"
|
||||
@@ -71,7 +72,7 @@ class MockEndpointChannel : public BaseEndpointChannel {
|
||||
class MockPcpHandler : public BasePcpHandler {
|
||||
public:
|
||||
MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm)
|
||||
: BasePcpHandler(em, ecm) {}
|
||||
: BasePcpHandler(em, ecm, Pcp::kP2pCluster) {}
|
||||
|
||||
// Expose protected inner types of a base type for mocking.
|
||||
using BasePcpHandler::ConnectImplResult;
|
||||
@@ -112,7 +113,7 @@ class MockPcpHandler : public BasePcpHandler {
|
||||
|
||||
// Mock adapters for protected non-virtual methods of a base class.
|
||||
void OnEndpointFound(ClientProxy* client,
|
||||
std::unique_ptr<DiscoveredEndpoint> endpoint) {
|
||||
std::shared_ptr<DiscoveredEndpoint> endpoint) {
|
||||
BasePcpHandler::OnEndpointFound(client, std::move(endpoint));
|
||||
}
|
||||
void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) {
|
||||
@@ -120,7 +121,25 @@ class MockPcpHandler : public BasePcpHandler {
|
||||
}
|
||||
};
|
||||
|
||||
using MockDiscoveredEndpoint = MockPcpHandler::DiscoveredEndpoint;
|
||||
class MockContext {
|
||||
public:
|
||||
explicit MockContext(std::atomic_bool* destroyed = nullptr) {
|
||||
destroyed_ = destroyed;
|
||||
}
|
||||
MockContext(MockContext&&) = default;
|
||||
MockContext& operator=(MockContext&&) = default;
|
||||
|
||||
~MockContext() {
|
||||
if (destroyed_) *destroyed_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
Swapper<std::atomic_bool> destroyed_{nullptr};
|
||||
};
|
||||
|
||||
struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint {
|
||||
MockContext context;
|
||||
};
|
||||
|
||||
class BasePcpHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
@@ -230,7 +249,8 @@ class BasePcpHandlerTest : public ::testing::Test {
|
||||
void RequestConnection(const std::string& endpoint_id,
|
||||
std::unique_ptr<MockEndpointChannel> channel_a,
|
||||
MockEndpointChannel* channel_b, ClientProxy* client,
|
||||
MockPcpHandler* pcp_handler) {
|
||||
MockPcpHandler* pcp_handler,
|
||||
std::atomic_bool* flag = nullptr) {
|
||||
ConnectionRequestInfo info{
|
||||
.name = "ABCD",
|
||||
.listener = connection_listener_,
|
||||
@@ -254,11 +274,14 @@ class BasePcpHandlerTest : public ::testing::Test {
|
||||
// Simulate successful discovery.
|
||||
auto encryption_runner = std::make_unique<EncryptionRunner>();
|
||||
pcp_handler->OnEndpointFound(
|
||||
client, std::make_unique<MockDiscoveredEndpoint>(MockDiscoveredEndpoint{
|
||||
.endpoint_id = endpoint_id,
|
||||
.endpoint_name = info.name,
|
||||
.service_id = "service",
|
||||
.medium = Medium::BLE,
|
||||
client, std::make_shared<MockDiscoveredEndpoint>(MockDiscoveredEndpoint{
|
||||
{
|
||||
.endpoint_id = endpoint_id,
|
||||
.endpoint_name = info.name,
|
||||
.service_id = "service",
|
||||
.medium = Medium::BLE,
|
||||
},
|
||||
MockContext{flag},
|
||||
}));
|
||||
auto other_client = std::make_unique<ClientProxy>();
|
||||
|
||||
@@ -441,6 +464,29 @@ TEST_F(BasePcpHandlerTest, OnEndpointDisconnectChangesState) {
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result());
|
||||
}
|
||||
|
||||
TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) {
|
||||
std::atomic_bool destroyed_flag = false;
|
||||
{
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
MockPcpHandler pcp_handler(&em, &ecm);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_);
|
||||
auto& channel_b = channel_pair.second;
|
||||
RequestConnection(endpoint_id, std::move(channel_pair.first),
|
||||
channel_b.get(), &client, &pcp_handler, &destroyed_flag);
|
||||
NEARBY_LOG(INFO, "Attempting to accept connection: id=%s",
|
||||
endpoint_id.c_str());
|
||||
EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}),
|
||||
Status{Status::kSuccess});
|
||||
NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str());
|
||||
channel_b->Close();
|
||||
}
|
||||
EXPECT_TRUE(destroyed_flag.load());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform_v2/base/base_input_stream.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
@@ -69,82 +70,70 @@ BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start reading the bytes.
|
||||
auto* ble_advertisement_bytes_read_ptr = ble_advertisement_bytes.data();
|
||||
|
||||
// The first 3 bits are supposed to be the version.
|
||||
version_ = static_cast<Version>(
|
||||
(*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5);
|
||||
ByteArray advertisement_bytes{ble_advertisement_bytes};
|
||||
BaseInputStream base_input_stream{advertisement_bytes};
|
||||
// The first 1 byte is supposed to be the version and pcp.
|
||||
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::kV1) {
|
||||
NEARBY_LOG(ERROR,
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
|
||||
pcp_ = static_cast<Pcp>(*ble_advertisement_bytes_read_ptr & kPcpBitmask);
|
||||
ble_advertisement_bytes_read_ptr++;
|
||||
// The lower 5 bits are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(version_and_pcp_byte & kPcpBitmask);
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint: {
|
||||
// The next 24 bits are supposed to be the service_id_hash.
|
||||
service_id_hash_ =
|
||||
ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength);
|
||||
ble_advertisement_bytes_read_ptr += kServiceIdHashLength;
|
||||
|
||||
// The next 32 bits are supposed to be the endpoint_id.
|
||||
endpoint_id_ =
|
||||
std::string(ble_advertisement_bytes_read_ptr, kEndpointIdLength);
|
||||
ble_advertisement_bytes_read_ptr += kEndpointIdLength;
|
||||
|
||||
// The next 8 bits are the length of the endpoint name.
|
||||
auto expected_endpoint_name_length = static_cast<std::uint32_t>(
|
||||
*ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask);
|
||||
ble_advertisement_bytes_read_ptr++;
|
||||
|
||||
// The next x bits are the endpoint name. (Max length is 131 bytes).
|
||||
// Check that the stated endpoint_name_length is the same as what we
|
||||
// received (based off of the length of ble_advertisement_bytes).
|
||||
auto actual_endpoint_name_length =
|
||||
ComputeEndpointNameLength(ble_advertisement_bytes);
|
||||
if (actual_endpoint_name_length < expected_endpoint_name_length) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"Cannot deserialize BleAdvertisement: expected endpointName to "
|
||||
"be %d bytes, got %d bytes",
|
||||
expected_endpoint_name_length, actual_endpoint_name_length);
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
endpoint_name_ = std::string(ble_advertisement_bytes_read_ptr,
|
||||
expected_endpoint_name_length);
|
||||
ble_advertisement_bytes_read_ptr += expected_endpoint_name_length;
|
||||
|
||||
// The next 48 bits are the bluetooth mac address.
|
||||
auto bluetooth_mac_address_bytes = ByteArray(
|
||||
ble_advertisement_bytes_read_ptr, kBluetoothMacAddressLength);
|
||||
// If the Bluetooth MAC Address bytes are unset or invalid, leave the
|
||||
// string empty. Otherwise, convert it to the proper colon delimited
|
||||
// format.
|
||||
if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) {
|
||||
bluetooth_mac_address_ =
|
||||
HexBytesToColonDelimitedString(bluetooth_mac_address_bytes);
|
||||
}
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// TODO(edwinwu): [ANALYTICIZE] This either represents corruption over
|
||||
// the air, or older versions of GmsCore intermingling with newer
|
||||
// ones.
|
||||
NEARBY_LOG(ERROR,
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d",
|
||||
pcp_);
|
||||
break;
|
||||
}
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The next 4 bytes are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)};
|
||||
|
||||
// The next 1 byte are supposed to be the length of the endpoint_name.
|
||||
std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8();
|
||||
|
||||
// The next x bytes are the endpoint name. (Max length is 131 bytes).
|
||||
// Check that the stated endpoint_name_length is the same as what we
|
||||
// received.
|
||||
auto endpoint_name_bytes =
|
||||
base_input_stream.ReadBytes(expected_endpoint_name_length);
|
||||
if (endpoint_name_bytes.Empty() ||
|
||||
endpoint_name_bytes.size() != expected_endpoint_name_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: expected "
|
||||
"endpointName to be %d bytes, got %" PRIu64,
|
||||
expected_endpoint_name_length, endpoint_name_bytes.size());
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
endpoint_name_ = std::string{endpoint_name_bytes};
|
||||
|
||||
// The next 6 bytes are the bluetooth mac address.
|
||||
auto bluetooth_mac_address_bytes =
|
||||
base_input_stream.ReadBytes(kBluetoothMacAddressLength);
|
||||
// If the Bluetooth MAC Address bytes are unset or invalid, leave the
|
||||
// string empty. Otherwise, convert it to the proper colon delimited
|
||||
// format.
|
||||
if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) {
|
||||
bluetooth_mac_address_ =
|
||||
HexBytesToColonDelimitedString(bluetooth_mac_address_bytes);
|
||||
}
|
||||
base_input_stream.Close();
|
||||
}
|
||||
|
||||
BleAdvertisement::operator ByteArray() const {
|
||||
@@ -152,36 +141,31 @@ BleAdvertisement::operator ByteArray() const {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_pcp_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 5 bits are the Pcp.
|
||||
version_and_pcp_byte |= static_cast<char>(pcp_) & kPcpBitmask;
|
||||
out.reserve(1 + service_id_hash_.size() + kEndpointIdLength + 1 +
|
||||
endpoint_name_.size() + kBluetoothMacAddressLength);
|
||||
out.append(1, version_and_pcp_byte);
|
||||
out.append(std::string(service_id_hash_));
|
||||
out.append(endpoint_id_);
|
||||
out.append(1, endpoint_name_.size());
|
||||
out.append(endpoint_name_);
|
||||
// The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is
|
||||
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
std::string(service_id_hash_),
|
||||
endpoint_id_,
|
||||
std::string(1, endpoint_name_.size()),
|
||||
endpoint_name_);
|
||||
// clang-format on
|
||||
|
||||
// The next 6 bytes are the bluetooth mac address. If bluetooth_mac_address is
|
||||
// invalid or empty, we get back a null byte array.
|
||||
auto bluetooth_mac_address_bytes(
|
||||
BluetoothMacAddressHexStringToBytes(bluetooth_mac_address_));
|
||||
if (!bluetooth_mac_address_bytes.Empty()) {
|
||||
out.append(bluetooth_mac_address_bytes.data(), kBluetoothMacAddressLength);
|
||||
absl::StrAppend(&out, std::string(bluetooth_mac_address_bytes));
|
||||
}
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
std::uint32_t BleAdvertisement::ComputeEndpointNameLength(
|
||||
const ByteArray& ble_advertisement_bytes) const {
|
||||
return ble_advertisement_bytes.size() - kMinAdvertisementLength;
|
||||
}
|
||||
|
||||
ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes(
|
||||
const std::string& bluetooth_mac_address) const {
|
||||
std::string bt_mac_address(bluetooth_mac_address);
|
||||
|
||||
@@ -78,8 +78,6 @@ class BleAdvertisement {
|
||||
std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; }
|
||||
|
||||
private:
|
||||
std::uint32_t ComputeEndpointNameLength(
|
||||
const ByteArray& ble_advertisement_bytes) const;
|
||||
ByteArray BluetoothMacAddressHexStringToBytes(
|
||||
const std::string& bluetooth_mac_address) const;
|
||||
std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const;
|
||||
|
||||
@@ -21,19 +21,22 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1;
|
||||
const Pcp kPcp = Pcp::kP2pCluster;
|
||||
const char kServiceIDHashBytes[] = "\x0a\x0b\x0c";
|
||||
const char kEndPointID[] = "AB12";
|
||||
const char kEndpointName[] =
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?";
|
||||
const char kBluetoothMacAddress[] = "00:00:E6:88:64:13";
|
||||
constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1;
|
||||
constexpr Pcp kPcp = Pcp::kP2pCluster;
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kEndPointID{"AB12"};
|
||||
constexpr absl::string_view kEndpointName{
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?"};
|
||||
constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"};
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
@@ -47,10 +50,13 @@ TEST(BleAdvertisementTest, ConstructionWorks) {
|
||||
TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) {
|
||||
std::string empty_endpoint_name;
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, empty_endpoint_name,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
empty_endpoint_name,
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
@@ -64,10 +70,13 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) {
|
||||
TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) {
|
||||
std::string emoji_endpoint_name{"\u0001F450 \u0001F450"};
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, emoji_endpoint_name,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
emoji_endpoint_name,
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
@@ -82,10 +91,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) {
|
||||
std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1,
|
||||
'x');
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, long_endpoint_name,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
long_endpoint_name,
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
@@ -93,10 +105,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) {
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{bad_version, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{bad_version,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
@@ -104,10 +119,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, bad_pcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
bad_pcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
@@ -115,9 +133,12 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) {
|
||||
TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) {
|
||||
std::string empty_bluetooth_mac_address = "";
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
empty_bluetooth_mac_address};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
@@ -126,9 +147,12 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) {
|
||||
TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) {
|
||||
std::string bad_bluetooth_mac_address = "022:00";
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
bad_bluetooth_mac_address};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
@@ -142,10 +166,13 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) {
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromBytesWorks) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement org_ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
auto ble_advertisement_bytes = ByteArray(org_ble_advertisement);
|
||||
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
@@ -163,10 +190,13 @@ TEST(BleAdvertisementTest, ConstructionFromBytesWorks) {
|
||||
// in the future.
|
||||
TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
auto ble_advertisement_bytes = ByteArray(ble_advertisement);
|
||||
|
||||
// Add bytes to the end of the valid Ble advertisement.
|
||||
@@ -198,10 +228,13 @@ TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) {
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
auto ble_advertisement_bytes = ByteArray(ble_advertisement);
|
||||
|
||||
// Shorten the valid Ble Advertisement.
|
||||
@@ -217,10 +250,13 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) {
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromByesWithWrongEndpointNameLengthFails) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash,
|
||||
kEndPointID, kEndpointName,
|
||||
kBluetoothMacAddress};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndPointID),
|
||||
std::string(kEndpointName),
|
||||
std::string(kBluetoothMacAddress)};
|
||||
auto ble_advertisement_bytes = ByteArray(ble_advertisement);
|
||||
|
||||
// Corrupt the EndpointNameLength bits.
|
||||
|
||||
@@ -20,15 +20,14 @@
|
||||
#include <utility>
|
||||
|
||||
#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 {
|
||||
namespace connections {
|
||||
|
||||
// TODO(edwinwu): Define bitfield struct to replace pointer arithmetic for
|
||||
// those bit parsing.
|
||||
|
||||
BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp,
|
||||
absl::string_view endpoint_id,
|
||||
const ByteArray& service_id_hash,
|
||||
@@ -85,78 +84,60 @@ BluetoothDeviceName::BluetoothDeviceName(
|
||||
return;
|
||||
}
|
||||
|
||||
BaseInputStream base_input_stream{bluetooth_device_name_bytes};
|
||||
// The first 1 byte is supposed to be the version and pcp.
|
||||
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>(
|
||||
(bluetooth_device_name_bytes.data()[0] & kVersionBitmask) >> 5);
|
||||
const char* read_ptr = bluetooth_device_name_bytes.data();
|
||||
switch (version_) {
|
||||
case Version::kV1:
|
||||
// The lower 5 bits of the V1 payload are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(*read_ptr & kPcpBitmask);
|
||||
read_ptr++;
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint: {
|
||||
// The next 32 bits are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string(read_ptr, kEndpointIdLength);
|
||||
read_ptr += kEndpointIdLength;
|
||||
|
||||
// The next 24 bits are supposed to be the service_id_hash.
|
||||
service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength);
|
||||
read_ptr += kServiceIdHashLength;
|
||||
|
||||
// The next 56 bits are supposed to be reserved, and can be left
|
||||
// untouched.
|
||||
read_ptr += kReservedLength;
|
||||
|
||||
// The next 8 bits are supposed to be the length of the endpoint_name.
|
||||
std::uint32_t expected_endpoint_name_length =
|
||||
static_cast<std::uint32_t>(*read_ptr &
|
||||
kEndpointNameLengthBitmask);
|
||||
read_ptr++;
|
||||
|
||||
// Check that the stated endpoint_name_length is the same as what we
|
||||
// received (based off of the length of bluetooth_device_name_bytes).
|
||||
std::uint32_t actual_endpoint_name_length =
|
||||
kMaxBluetoothDeviceNameLength -
|
||||
bluetooth_device_name_bytes.size();
|
||||
if (actual_endpoint_name_length != expected_endpoint_name_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: expected "
|
||||
"endpointName to be %d bytes, got %d bytes",
|
||||
expected_endpoint_name_length,
|
||||
actual_endpoint_name_length);
|
||||
|
||||
endpoint_id_.empty();
|
||||
return;
|
||||
}
|
||||
|
||||
endpoint_name_ = std::string{read_ptr, actual_endpoint_name_length};
|
||||
read_ptr += actual_endpoint_name_length;
|
||||
} break;
|
||||
|
||||
default:
|
||||
// TODO(edwinwu): [ANALYTICIZE] This either represents corruption over
|
||||
// the air, or older versions of GmsCore intermingling with newer
|
||||
// ones.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: unsupported V1 PCP %d",
|
||||
pcp_);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(edwinwu): [ANALYTICIZE] This either represents corruption over
|
||||
// the air, or older versions of GmsCore intermingling with newer ones.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: unsupported Version %d",
|
||||
version_);
|
||||
break;
|
||||
version_ =
|
||||
static_cast<Version>((version_and_pcp_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV1) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: unsupported version=%d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(version_and_pcp_byte & kPcpBitmask);
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
NEARBY_LOG(
|
||||
INFO, "Cannot deserialize BluetoothDeviceName: unsupported V1 PCP %d",
|
||||
pcp_);
|
||||
return;
|
||||
}
|
||||
|
||||
// The next 4 bytes are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)};
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The next 7 bytes are supposed to be reserved, and can be left
|
||||
// untouched.
|
||||
base_input_stream.ReadBytes(kReservedLength);
|
||||
|
||||
// The next 1 byte are supposed to be the length of the endpoint_name.
|
||||
std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8();
|
||||
|
||||
// The rest bytes are supposed to be the endpoint_name
|
||||
auto endpoint_name_bytes =
|
||||
base_input_stream.ReadBytes(expected_endpoint_name_length);
|
||||
if (endpoint_name_bytes.Empty() ||
|
||||
endpoint_name_bytes.size() != expected_endpoint_name_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: expected "
|
||||
"endpointName to be %d bytes, got %" PRIu64,
|
||||
expected_endpoint_name_length, endpoint_name_bytes.size());
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
endpoint_name_ = std::string{endpoint_name_bytes};
|
||||
}
|
||||
|
||||
BluetoothDeviceName::operator std::string() const {
|
||||
@@ -164,6 +145,15 @@ BluetoothDeviceName::operator std::string() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
// The upper 3 bits are the Version.
|
||||
auto version_and_pcp_byte = static_cast<char>(
|
||||
(static_cast<uint32_t>(Version::kV1) << 5) & kVersionBitmask);
|
||||
// The lower 5 bits are the PCP.
|
||||
version_and_pcp_byte |=
|
||||
static_cast<char>(static_cast<uint32_t>(pcp_) & kPcpBitmask);
|
||||
|
||||
ByteArray reserved_bytes{kReservedLength};
|
||||
|
||||
std::string usable_endpoint_name(endpoint_name_);
|
||||
if (endpoint_name_.size() > kMaxEndpointNameLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
@@ -174,24 +164,14 @@ BluetoothDeviceName::operator std::string() const {
|
||||
usable_endpoint_name.erase(kMaxEndpointNameLength);
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// The upper 3 bits are the Version.
|
||||
auto version_and_pcp_byte = static_cast<char>(
|
||||
(static_cast<uint32_t>(Version::kV1) << 5) & kVersionBitmask);
|
||||
// The lower 5 bits are the PCP.
|
||||
version_and_pcp_byte |=
|
||||
static_cast<char>(static_cast<uint32_t>(pcp_) & kPcpBitmask);
|
||||
// TODO(edwinwu): Change to StrCat to gain performance.
|
||||
out.reserve(kMaxBluetoothDeviceNameLength -
|
||||
(kMaxEndpointNameLength - usable_endpoint_name.length()));
|
||||
out.append(1, version_and_pcp_byte);
|
||||
out.append(endpoint_id_);
|
||||
out.append(std::string(service_id_hash_));
|
||||
ByteArray reserverdBytes{kReservedLength};
|
||||
out.append(std::string(reserverdBytes));
|
||||
out.append(1, usable_endpoint_name.size());
|
||||
out.append(usable_endpoint_name);
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
endpoint_id_,
|
||||
std::string(service_id_hash_),
|
||||
std::string(reserved_bytes),
|
||||
std::string(1, usable_endpoint_name.size()),
|
||||
usable_endpoint_name);
|
||||
// clang-format on
|
||||
|
||||
return Base64Utils::Encode(ByteArray{std::move(out)});
|
||||
}
|
||||
|
||||
@@ -25,15 +25,15 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
const BluetoothDeviceName::Version kVersion = BluetoothDeviceName::Version::kV1;
|
||||
const Pcp kPcp = Pcp::kP2pCluster;
|
||||
// TODO(edwinwu): Replace absl::string_view in other medium tests, too.
|
||||
inline constexpr absl::string_view kEndPointID = "AB12";
|
||||
inline constexpr absl::string_view kServiceIDHashBytes = "\x0a\x0b\x0c";
|
||||
inline constexpr absl::string_view kEndPointName = "RAWK + ROWL!";
|
||||
constexpr BluetoothDeviceName::Version kVersion =
|
||||
BluetoothDeviceName::Version::kV1;
|
||||
constexpr Pcp kPcp = Pcp::kP2pCluster;
|
||||
constexpr absl::string_view kEndPointID{"AB12"};
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kEndPointName{"RAWK + ROWL!"};
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -48,7 +48,7 @@ TEST(BluetoothDeviceNameTest, ConstructionWorks) {
|
||||
TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) {
|
||||
std::string empty_endpoint_name;
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_name};
|
||||
|
||||
@@ -63,7 +63,7 @@ TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) {
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BluetoothDeviceName::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{bad_version, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -73,7 +73,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) {
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion, bad_pcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -83,7 +83,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) {
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) {
|
||||
std::string short_endpoint_id("AB1");
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, short_endpoint_id,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -93,7 +93,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) {
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) {
|
||||
std::string long_endpoint_id("AB12X");
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, long_endpoint_id,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -132,7 +132,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortStringLength) {
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) {
|
||||
// Serialize good data into a good Bluetooth Device Name.
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
auto bluetooth_device_name_string = std::string(bluetooth_device_name);
|
||||
@@ -154,7 +154,23 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) {
|
||||
BluetoothDeviceName corrupt_bluetooth_device_name(
|
||||
corrupt_bluetooth_device_name_string);
|
||||
|
||||
EXPECT_TRUE(corrupt_bluetooth_device_name.IsValid());
|
||||
EXPECT_FALSE(corrupt_bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, CanParseGeneratedName) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
// Build name1 from scratch.
|
||||
BluetoothDeviceName name1{kVersion, kPcp, kEndPointID, service_id_hash,
|
||||
kEndPointName};
|
||||
// Build name2 from string composed from name1.
|
||||
BluetoothDeviceName name2{std::string(name1)};
|
||||
EXPECT_TRUE(name1.IsValid());
|
||||
EXPECT_TRUE(name2.IsValid());
|
||||
EXPECT_EQ(name1.GetVersion(), name2.GetVersion());
|
||||
EXPECT_EQ(name1.GetPcp(), name2.GetPcp());
|
||||
EXPECT_EQ(name1.GetEndpointId(), name2.GetEndpointId());
|
||||
EXPECT_EQ(name1.GetServiceIdHash(), name2.GetServiceIdHash());
|
||||
EXPECT_EQ(name1.GetEndpointName(), name2.GetEndpointName());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "core_v2/internal/bluetooth_endpoint_channel.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/public/bluetooth_classic.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
OutputStream* GetOutputStreamOrNull(BluetoothSocket& socket) {
|
||||
if (socket.GetRemoteDevice().IsValid()) return &socket.GetOutputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InputStream* GetInputStreamOrNull(BluetoothSocket& socket) {
|
||||
if (socket.GetRemoteDevice().IsValid()) return &socket.GetInputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BluetoothEndpointChannel::BluetoothEndpointChannel(
|
||||
const std::string& channel_name, BluetoothSocket socket)
|
||||
: BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket),
|
||||
GetOutputStreamOrNull(socket)),
|
||||
bluetooth_socket_(std::move(socket)) {}
|
||||
|
||||
proto::connections::Medium BluetoothEndpointChannel::GetMedium() const {
|
||||
return proto::connections::Medium::BLUETOOTH;
|
||||
}
|
||||
|
||||
void BluetoothEndpointChannel::CloseImpl() {
|
||||
auto status = bluetooth_socket_.Close();
|
||||
if (!status.Ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to close BT socket: exception=%d", status.value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/base_endpoint_channel.h"
|
||||
#include "platform_v2/public/bluetooth_classic.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BluetoothEndpointChannel final : public BaseEndpointChannel {
|
||||
public:
|
||||
// Creates both outgoing and incoming BT channels.
|
||||
BluetoothEndpointChannel(const std::string& channel_name,
|
||||
BluetoothSocket bluetooth_socket);
|
||||
|
||||
proto::connections::Medium GetMedium() const override;
|
||||
|
||||
private:
|
||||
void CloseImpl() override;
|
||||
|
||||
BluetoothSocket bluetooth_socket_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
@@ -39,14 +39,20 @@ ClientProxy::~ClientProxy() { Reset(); }
|
||||
std::int64_t ClientProxy::GetClientId() const { return client_id_; }
|
||||
|
||||
std::string ClientProxy::GenerateLocalEndpointId() {
|
||||
// 1) Concatenate the DeviceID with this ClientID.
|
||||
// 1) Concatenate the Random 64-bit value with "client" string.
|
||||
// 2) Compute a hash of that concatenation.
|
||||
// 3) Base64-encode that hash, to make it human-readable.
|
||||
// 4) Use only the first 4 bytes of that Base64 encoding.
|
||||
ByteArray id_hash(Crypto::Sha256(
|
||||
absl::StrCat(api::ImplementationPlatform::GetDeviceId(), GetClientId())));
|
||||
// 4) Use only the first kEndpointIdLength bytes to make ID.
|
||||
ByteArray id_hash = Crypto::Sha256(
|
||||
absl::StrCat("client", prng_.NextInt64()));
|
||||
|
||||
return Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength);
|
||||
std::string id = Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength);
|
||||
|
||||
NEARBY_LOG(
|
||||
INFO, "ClientProxy [Local Endpoint Generated]: client=%p; endpoint_id=%s",
|
||||
this, id.c_str());
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void ClientProxy::Reset() {
|
||||
@@ -127,9 +133,17 @@ void ClientProxy::OnEndpointFound(const std::string& service_id,
|
||||
proto::connections::Medium medium) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscoveringServiceId(service_id)) return;
|
||||
NEARBY_LOG(INFO,
|
||||
"ClientProxy [Endpoint Found]: [enter] id=%s; service=%s; name=%s",
|
||||
endpoint_id.c_str(), service_id.c_str(), endpoint_name.c_str());
|
||||
if (!IsDiscoveringServiceId(service_id)) {
|
||||
NEARBY_LOG(INFO, "ClientProxy [Endpoint Found]: [no discovery] id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
if (discovered_endpoint_ids_.count(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "ClientProxy [Endpoint Found]: [duplicate] id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
discovered_endpoint_ids_.insert(endpoint_id);
|
||||
@@ -164,7 +178,11 @@ void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id,
|
||||
// Instead of using structured binding which is nice, but banned
|
||||
// (can not use c++17 features, until chromium does) we unpack manually.
|
||||
auto& pair_iter = result.first;
|
||||
bool& inserted = result.second;
|
||||
bool inserted = result.second;
|
||||
NEARBY_LOG(INFO,
|
||||
"ClientProxy [Connection Initiated]: add Connection: client=%p, "
|
||||
"id=%s; inserted=%d",
|
||||
this, endpoint_id.c_str(), inserted);
|
||||
DCHECK(inserted);
|
||||
const Connection& item = pair_iter->second;
|
||||
// Notify the client.
|
||||
@@ -178,7 +196,9 @@ void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!HasPendingConnectionToEndpoint(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO, "ClientProxy [Connection Accepted]: no pending connection; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,8 +215,9 @@ void ClientProxy::OnConnectionRejected(const std::string& endpoint_id,
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!HasPendingConnectionToEndpoint(endpoint_id)) {
|
||||
NEARBY_LOG(INFO, "ClientProxy [Rejected]: no pending connection; id=%s",
|
||||
endpoint_id.c_str());
|
||||
NEARBY_LOG(
|
||||
INFO, "ClientProxy [Connection Rejected]: no pending connection; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,7 +346,10 @@ void ClientProxy::LocalEndpointAcceptedConnection(
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasLocalEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"ClientProxy [Local Accepted]: local endpoint has responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -341,7 +365,10 @@ void ClientProxy::LocalEndpointRejectedConnection(
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasLocalEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"ClientProxy [Local Rejected]: local endpoint has responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -353,7 +380,10 @@ void ClientProxy::RemoteEndpointAcceptedConnection(
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasRemoteEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"ClientProxy [Remote Accepted]: remote endpoint has responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,7 +395,10 @@ void ClientProxy::RemoteEndpointRejectedConnection(
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasRemoteEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"ClientProxy [Remote Rejected]: remote endpoint has responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "core_v2/status.h"
|
||||
#include "core_v2/strategy.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/prng.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
// Prefer using absl:: versions of a set and a map; they tend to be more
|
||||
@@ -201,6 +202,7 @@ class ClientProxy final {
|
||||
|
||||
mutable RecursiveMutex mutex_;
|
||||
std::int64_t client_id_;
|
||||
Prng prng_;
|
||||
|
||||
// If not empty, we are currently advertising and accepting connection
|
||||
// requests for the given service_id.
|
||||
|
||||
@@ -54,8 +54,7 @@ class FakeEndpointChannel : public EndpointChannel {
|
||||
std::string GetType() const override { return "fake-channel-type"; }
|
||||
std::string GetName() const override { return "fake-channel"; }
|
||||
Medium GetMedium() const override { return Medium::BLE; }
|
||||
void EnableEncryption(
|
||||
securegcm::D2DConnectionContextV1* connection_context) override {}
|
||||
void EnableEncryption(std::shared_ptr<EncryptionContext> context) override {}
|
||||
bool IsPaused() const override { return false; }
|
||||
void Pause() override {}
|
||||
void Resume() override {}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/exception.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "absl/time/clock.h"
|
||||
@@ -32,6 +33,8 @@ class EndpointChannel {
|
||||
public:
|
||||
virtual ~EndpointChannel() = default;
|
||||
|
||||
using EncryptionContext = ::securegcm::D2DConnectionContextV1;
|
||||
|
||||
virtual ExceptionOr<ByteArray>
|
||||
Read() = 0; // throws Exception::IO, Exception::INTERRUPTED
|
||||
|
||||
@@ -54,8 +57,7 @@ class EndpointChannel {
|
||||
virtual proto::connections::Medium GetMedium() const = 0;
|
||||
|
||||
// Enables encryption on the EndpointChannel.
|
||||
virtual void EnableEncryption(
|
||||
securegcm::D2DConnectionContextV1* context) = 0;
|
||||
virtual void EnableEncryption(std::shared_ptr<EncryptionContext> context) = 0;
|
||||
|
||||
// True if the EndpointChannel is currently pausing all writes.
|
||||
virtual bool IsPaused() const = 0;
|
||||
|
||||
@@ -80,7 +80,6 @@ std::shared_ptr<EndpointChannel> EndpointChannelManager::GetChannelForEndpoint(
|
||||
void EndpointChannelManager::SetActiveEndpointChannel(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel) {
|
||||
|
||||
// Update the channel first, then encrypt this new channel, if
|
||||
// crypto context is present.
|
||||
channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel));
|
||||
@@ -89,18 +88,19 @@ void EndpointChannelManager::SetActiveEndpointChannel(
|
||||
if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint);
|
||||
}
|
||||
|
||||
///////////////////////////////// ChannelState /////////////////////////////////
|
||||
|
||||
// endpoint - channel endpoint to encrypt
|
||||
bool EndpointChannelManager::ChannelState::EncryptChannel(
|
||||
EndpointChannelManager::ChannelState::EndpointData* endpoint) {
|
||||
if (endpoint != nullptr && endpoint->channel != nullptr &&
|
||||
endpoint->context != nullptr) {
|
||||
endpoint->channel->EnableEncryption(endpoint->context.get());
|
||||
endpoint->channel->EnableEncryption(endpoint->context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///////////////////////////////// ChannelState /////////////////////////////////
|
||||
EndpointChannelManager::ChannelState::EndpointData*
|
||||
EndpointChannelManager::ChannelState::LookupEndpointData(
|
||||
const std::string& endpoint_id) {
|
||||
|
||||
@@ -29,8 +29,6 @@ namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
using EncryptionContext = ::securegcm::D2DConnectionContextV1;
|
||||
|
||||
// NOTE(std::string):
|
||||
// All the strings in internal class public interfaces should be exchanged as
|
||||
// const std::string& if they are immutable, and as std::string
|
||||
@@ -47,6 +45,8 @@ using EncryptionContext = ::securegcm::D2DConnectionContextV1;
|
||||
// are interacting.
|
||||
class EndpointChannelManager final {
|
||||
public:
|
||||
using EncryptionContext = EndpointChannel::EncryptionContext;
|
||||
|
||||
~EndpointChannelManager();
|
||||
|
||||
// Registers the initial EndpointChannel to be associated with an endpoint;
|
||||
@@ -111,10 +111,12 @@ class EndpointChannelManager final {
|
||||
}
|
||||
|
||||
// True if we have a 'context' for the endpoint.
|
||||
bool IsEncrypted() const { return context != nullptr; }
|
||||
bool IsEncrypted() const {
|
||||
return context != nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<EndpointChannel> channel;
|
||||
std::unique_ptr<EncryptionContext> context;
|
||||
std::shared_ptr<EncryptionContext> context;
|
||||
proto::connections::DisconnectionReason disconnect_reason =
|
||||
proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON;
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ void EndpointManager::EndpointChannelLoopRunnable(
|
||||
std::shared_ptr<EndpointChannel> channel =
|
||||
channel_manager_->GetChannelForEndpoint(endpoint_id);
|
||||
if (channel == nullptr) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Endpoint channel is nullptr, bail out.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ void EndpointManager::EndpointChannelLoopRunnable(
|
||||
// EndpointChannel for this endpoint, there's nothing more to do here.
|
||||
if ((last_failed_medium != Medium::UNKNOWN_MEDIUM) &&
|
||||
(channel->GetMedium() == last_failed_medium)) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(
|
||||
INFO, "No new endpoint channel is found after a failure, exit loop.");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -82,7 +83,8 @@ void EndpointManager::EndpointChannelLoopRunnable(
|
||||
Exception exception = keep_using_channel.GetException();
|
||||
if (exception.Raised(Exception::kIo)) {
|
||||
last_failed_medium = channel->GetMedium();
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Endpoint channel IO exception; last_failed_medium=%d",
|
||||
last_failed_medium);
|
||||
continue;
|
||||
}
|
||||
if (exception.Raised(Exception::kInterrupted)) {
|
||||
@@ -91,7 +93,8 @@ void EndpointManager::EndpointChannelLoopRunnable(
|
||||
}
|
||||
|
||||
if (!keep_using_channel.result()) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Dropping current channel: last medium=%d",
|
||||
last_failed_medium);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +130,7 @@ ExceptionOr<bool> EndpointManager::HandleData(
|
||||
if (!wrapped_frame.ok()) {
|
||||
if (wrapped_frame.GetException().Raised(
|
||||
Exception::kInvalidProtocolBuffer)) {
|
||||
NEARBY_LOG(INFO, "failed to decode; endpoint=%s; channel=%s; skip",
|
||||
NEARBY_LOG(INFO, "Failed to decode; endpoint=%s; channel=%s; skip",
|
||||
endpoint_id.c_str(), endpoint_channel->GetType().c_str());
|
||||
continue;
|
||||
} else {
|
||||
@@ -143,7 +146,14 @@ ExceptionOr<bool> EndpointManager::HandleData(
|
||||
EndpointManager::FrameProcessor* frame_processor =
|
||||
GetFrameProcessor(frame_type);
|
||||
if (frame_processor == nullptr) {
|
||||
NEARBY_LOG(ERROR, "Unhandled message: type=%d", frame_type);
|
||||
// report messages without handlers, except KEEP_ALIVE, which has
|
||||
// no explicit handler.
|
||||
if (frame_type == V1Frame::KEEP_ALIVE) {
|
||||
NEARBY_LOG(INFO, "KeepAlive message for: id=%s", endpoint_id.c_str());
|
||||
} else {
|
||||
NEARBY_LOG(ERROR, "Unhandled message: id=%s, type=%d",
|
||||
endpoint_id.c_str(), frame_type);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -156,11 +166,11 @@ ExceptionOr<bool> EndpointManager::HandleKeepAlive(
|
||||
EndpointChannel* endpoint_channel) {
|
||||
// Check if it has been too long since we received a frame from our
|
||||
// endpoint.
|
||||
if ((endpoint_channel->GetLastReadTimestamp() != kInvalidTimestamp) &&
|
||||
((endpoint_channel->GetLastReadTimestamp() +
|
||||
EndpointManager::kKeepAliveReadTimeout) <
|
||||
SystemClock::ElapsedRealtime())) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
auto last_read_time = endpoint_channel->GetLastReadTimestamp();
|
||||
if (last_read_time != kInvalidTimestamp &&
|
||||
SystemClock::ElapsedRealtime() >
|
||||
(last_read_time + EndpointManager::kKeepAliveReadTimeout)) {
|
||||
NEARBY_LOG(INFO, "Receive timeout expired; aborting KeepAlive worker.");
|
||||
return ExceptionOr<bool>(false);
|
||||
}
|
||||
|
||||
@@ -240,7 +250,7 @@ EndpointManager::RegisterFrameProcessor(
|
||||
RunOnEndpointManagerThread([this, frame_type, &latch, processor]() {
|
||||
auto it = frame_processors_.find(frame_type);
|
||||
if (it != frame_processors_.end()) {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Frame processor found, updated; type=%d", frame_type);
|
||||
it->second = processor;
|
||||
} else {
|
||||
frame_processors_.emplace(frame_type, processor);
|
||||
@@ -252,21 +262,27 @@ EndpointManager::RegisterFrameProcessor(
|
||||
}
|
||||
|
||||
void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type,
|
||||
const void* handle) {
|
||||
RunOnEndpointManagerThread([this, frame_type, handle]() {
|
||||
const void* handle, bool sync) {
|
||||
if (handle == nullptr) return;
|
||||
CountDownLatch latch(1);
|
||||
RunOnEndpointManagerThread([this, frame_type, handle, &latch, sync]() {
|
||||
auto it = frame_processors_.find(frame_type);
|
||||
if (it == frame_processors_.end()) return;
|
||||
if (it->second != handle) {
|
||||
if (it->second == handle) {
|
||||
frame_processors_.erase(it);
|
||||
NEARBY_LOG(INFO, "Unregistered: type=%d", frame_type);
|
||||
} else {
|
||||
NEARBY_LOG(INFO,
|
||||
"Failed to unregister: type=%d; handle mismatch: passed=%p, "
|
||||
"expected=%p",
|
||||
frame_type, handle, it->second);
|
||||
return;
|
||||
}
|
||||
|
||||
frame_processors_.erase(it);
|
||||
NEARBY_LOG(INFO, "unregistered: type=%d", frame_type);
|
||||
if (sync) latch.CountDown();
|
||||
});
|
||||
if (sync) {
|
||||
latch.Await();
|
||||
NEARBY_LOG(INFO, "Unregistered: [sync done] type=%d", frame_type);
|
||||
}
|
||||
}
|
||||
|
||||
EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor(
|
||||
@@ -281,6 +297,8 @@ EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor(
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
NEARBY_LOG(INFO, "GetFrameProcessor: type=%d; processor=%p", frame_type,
|
||||
processor);
|
||||
return processor;
|
||||
}
|
||||
|
||||
@@ -359,7 +377,8 @@ void EndpointManager::RegisterEndpoint(ClientProxy* client,
|
||||
return HandleKeepAlive(channel);
|
||||
});
|
||||
});
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Workers started, notifying client; id=%s",
|
||||
endpoint_id.c_str());
|
||||
|
||||
// It's now time to let the client know of this new connection so that
|
||||
// they can accept or reject it.
|
||||
@@ -433,7 +452,8 @@ void EndpointManager::RemoveEndpoint(ClientProxy* client,
|
||||
EnsureWorkersTerminated(endpoint_id);
|
||||
|
||||
client->OnDisconnected(endpoint_id, notify);
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "Removed endpoint; id=%s",
|
||||
endpoint_id.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,14 @@ class EndpointManager {
|
||||
virtual ~FrameProcessor() = default;
|
||||
|
||||
// @EndpointManagerReaderThread
|
||||
virtual void OnIncomingFrame(const OfflineFrame& offline_frame,
|
||||
// Called for every incoming frame of registered type.
|
||||
// NOTE(OfflineFrame& frame):
|
||||
// For large payload in data phase, resources may be saved if data is moved,
|
||||
// rather than copied (if passing data by reference is not an option).
|
||||
// To achieve that, OfflineFrame needs to be either mutabe lvalue reference,
|
||||
// or rvalue reference. Rvalue references are discouraged by go/cstyle,
|
||||
// and that leaves us with mutable lvalue reference.
|
||||
virtual void OnIncomingFrame(OfflineFrame& offline_frame,
|
||||
const std::string& from_endpoint_id,
|
||||
ClientProxy* to_client,
|
||||
proto::connections::Medium current_medium) = 0;
|
||||
@@ -91,7 +98,7 @@ class EndpointManager {
|
||||
const FrameProcessor::Handle RegisterFrameProcessor(
|
||||
V1Frame::FrameType frame_type, FrameProcessor* processor);
|
||||
void UnregisterFrameProcessor(V1Frame::FrameType frame_type,
|
||||
const void* handle);
|
||||
const void* handle, bool sync = false);
|
||||
|
||||
// Invoked from the different PcpHandler implementations (of which there can
|
||||
// be only one at a time).
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::DisconnectionReason;
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
using ::securegcm::D2DConnectionContextV1;
|
||||
using ::testing::_;
|
||||
using ::testing::MockFunction;
|
||||
using ::testing::Return;
|
||||
@@ -55,7 +54,7 @@ class MockEndpointChannel : public EndpointChannel {
|
||||
MOCK_METHOD(std::string, GetName, (), (const override));
|
||||
MOCK_METHOD(Medium, GetMedium, (), (const override));
|
||||
MOCK_METHOD(void, EnableEncryption,
|
||||
(D2DConnectionContextV1 * connection_context),
|
||||
(std::shared_ptr<EncryptionContext> context),
|
||||
(override));
|
||||
MOCK_METHOD(bool, IsPaused, (), (const override));
|
||||
MOCK_METHOD(void, Pause, (), (override));
|
||||
@@ -79,7 +78,7 @@ class MockEndpointChannel : public EndpointChannel {
|
||||
class MockFrameProcessor : public EndpointManager::FrameProcessor {
|
||||
public:
|
||||
MOCK_METHOD(void, OnIncomingFrame,
|
||||
(const OfflineFrame& offline_frame,
|
||||
(OfflineFrame & offline_frame,
|
||||
const std::string& from_endpoint_id, ClientProxy* to_client,
|
||||
Medium current_medium),
|
||||
(override));
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "core_v2/internal/internal_payload.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
InternalPayload::InternalPayload(Payload payload)
|
||||
: payload_(std::move(payload)), payload_id_(payload_.GetId()) {}
|
||||
|
||||
Payload InternalPayload::ReleasePayload() {
|
||||
return std::move(payload_);
|
||||
}
|
||||
|
||||
Payload::Id InternalPayload::GetId() const { return payload_id_; }
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,81 @@
|
||||
#ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
#define CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core_v2/payload.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/exception.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Defines the operations layered atop a Payload, for use inside the
|
||||
// OfflineServiceController.
|
||||
//
|
||||
// <p>There will be an extension of this abstract base class per type of
|
||||
// Payload.
|
||||
class InternalPayload {
|
||||
public:
|
||||
explicit InternalPayload(Payload payload);
|
||||
virtual ~InternalPayload() = default;
|
||||
|
||||
Payload ReleasePayload();
|
||||
|
||||
Payload::Id GetId() const;
|
||||
|
||||
// Returns the PayloadType of the Payload to which this object is bound.
|
||||
//
|
||||
// <p>Note that this is supposed to return the type from the OfflineFrame
|
||||
// proto rather than what is already available via
|
||||
// Payload::getType().
|
||||
//
|
||||
// @return The PayloadType.
|
||||
virtual PayloadTransferFrame::PayloadHeader::PayloadType GetType() const = 0;
|
||||
|
||||
// Deduces the total size of the Payload to which this object is bound.
|
||||
//
|
||||
// @return The total size, or -1 if it cannot be deduced (for example, when
|
||||
// dealing with streaming data).
|
||||
virtual std::int64_t GetTotalSize() const = 0;
|
||||
|
||||
// Breaks off the next chunk from the Payload to which this object is bound.
|
||||
//
|
||||
// <p>Used when we have a complete Payload that we want to break into smaller
|
||||
// byte blobs for sending across a hard boundary (like the other side of
|
||||
// a Binder, or another device altogether).
|
||||
//
|
||||
// @return The next chunk from the Payload, or null if we've reached the end.
|
||||
virtual ByteArray DetachNextChunk() = 0;
|
||||
|
||||
// Adds the next chunk that comprises the Payload to which this object is
|
||||
// bound.
|
||||
//
|
||||
// <p>Used when we are trying to reconstruct a Payload that lives on the
|
||||
// other side of a hard boundary (like the other side of a Binder, or another
|
||||
// device altogether), one byte blob at a time.
|
||||
//
|
||||
// @param chunk The next chunk; this being null signals that this is the last
|
||||
// chunk, which will typically be used as a trigger to perform whatever state
|
||||
// cleanup may be required by the concrete implementation.
|
||||
virtual Exception AttachNextChunk(const ByteArray& chunk) = 0;
|
||||
|
||||
// Cleans up any resources used by this Payload. Called when we're stopping
|
||||
// early, e.g. after being cancelled or having no more recipients left.
|
||||
virtual void Close() {}
|
||||
|
||||
protected:
|
||||
Payload payload_;
|
||||
// We're caching the payload ID here because the backing payload will be
|
||||
// released to another owner during the lifetime of an incoming
|
||||
// InternalPayload.
|
||||
Payload::Id payload_id_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
@@ -0,0 +1,279 @@
|
||||
#include "core_v2/internal/internal_payload_factory.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/payload.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/exception.h"
|
||||
#include "platform_v2/public/condition_variable.h"
|
||||
#include "platform_v2/public/file.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/pipe.h"
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
class BytesInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit BytesInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)),
|
||||
total_size_(payload_.AsBytes().size()),
|
||||
detached_only_chunk_(false) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::BYTES;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
// Relinquishes ownership of the payload_; retrieves and returns the stored
|
||||
// ByteArray.
|
||||
ByteArray DetachNextChunk() override {
|
||||
if (detached_only_chunk_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
detached_only_chunk_ = true;
|
||||
return std::move(payload_).AsBytes();
|
||||
}
|
||||
|
||||
// Does nothing.
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
private:
|
||||
// We're caching the total size here because the backing payload will be
|
||||
// moved to another owner during the lifetime of an incoming
|
||||
// InternalPayload.
|
||||
const std::int64_t total_size_;
|
||||
bool detached_only_chunk_;
|
||||
};
|
||||
|
||||
class OutgoingStreamInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit OutgoingStreamInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::STREAM;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return -1; }
|
||||
|
||||
ByteArray DetachNextChunk() override {
|
||||
InputStream* input_stream = payload_.AsStream();
|
||||
if (!input_stream) return {};
|
||||
|
||||
ExceptionOr<ByteArray> bytes_read = input_stream->Read(kChunkSize);
|
||||
if (!bytes_read.ok()) {
|
||||
input_stream->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
ByteArray scoped_bytes_read = std::move(bytes_read.result());
|
||||
|
||||
if (scoped_bytes_read.Empty()) {
|
||||
// TODO(reznor): logger.atVerbose().log("No more data for outgoing payload
|
||||
// %s, closing InputStream.", this);
|
||||
|
||||
input_stream->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
return scoped_bytes_read;
|
||||
}
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override {
|
||||
// Ignore the potential Exception returned by close(), as a counterpart
|
||||
// to Java's closeQuietly().
|
||||
InputStream* stream = payload_.AsStream();
|
||||
if (stream) stream->Close();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::int64_t kChunkSize = Pipe::kChunkSize;
|
||||
};
|
||||
|
||||
class IncomingStreamInternalPayload : public InternalPayload {
|
||||
public:
|
||||
IncomingStreamInternalPayload(Payload payload, OutputStream& output_stream)
|
||||
: InternalPayload(std::move(payload)), output_stream_(&output_stream) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::STREAM;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return -1; }
|
||||
|
||||
ByteArray DetachNextChunk() override { return {}; }
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
if (chunk.Empty()) {
|
||||
output_stream_->Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
return output_stream_->Write(chunk);
|
||||
}
|
||||
|
||||
void Close() override { output_stream_->Close(); }
|
||||
|
||||
private:
|
||||
OutputStream* output_stream_;
|
||||
};
|
||||
|
||||
class OutgoingFileInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit OutgoingFileInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)),
|
||||
total_size_{payload_.AsFile()->GetTotalSize()} {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::FILE;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
ByteArray DetachNextChunk() override {
|
||||
InputFile* file = payload_.AsFile();
|
||||
if (!file) return {};
|
||||
|
||||
ExceptionOr<ByteArray> bytes_read = file->Read(kChunkSize);
|
||||
if (!bytes_read.ok()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ByteArray bytes = std::move(bytes_read.result());
|
||||
|
||||
if (bytes.Empty()) {
|
||||
// No more data for outgoing payload.
|
||||
|
||||
file->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override {
|
||||
InputFile* file = payload_.AsFile();
|
||||
if (file) file->Close();
|
||||
}
|
||||
|
||||
private:
|
||||
std::int64_t total_size_;
|
||||
static constexpr std::int64_t kChunkSize = 64 * 1024;
|
||||
};
|
||||
|
||||
class IncomingFileInternalPayload : public InternalPayload {
|
||||
public:
|
||||
IncomingFileInternalPayload(Payload payload, OutputFile output_file,
|
||||
std::int64_t total_size)
|
||||
: InternalPayload(std::move(payload)),
|
||||
output_file_(std::move(output_file)),
|
||||
total_size_(total_size) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::FILE;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
ByteArray DetachNextChunk() override { return {}; }
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
if (chunk.Empty()) {
|
||||
// Received null last chunk for incoming payload.
|
||||
output_file_.Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
return output_file_.Write(chunk);
|
||||
}
|
||||
|
||||
void Close() override { output_file_.Close(); }
|
||||
|
||||
private:
|
||||
OutputFile output_file_;
|
||||
const std::int64_t total_size_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<InternalPayload> CreateOutgoingInternalPayload(
|
||||
Payload payload) {
|
||||
switch (payload.GetType()) {
|
||||
case Payload::Type::kBytes:
|
||||
return absl::make_unique<BytesInternalPayload>(std::move(payload));
|
||||
|
||||
case Payload::Type::kFile: {
|
||||
InputFile* file = payload.AsFile();
|
||||
const PayloadId file_payload_id = file ? file->GetPayloadId() : 0;
|
||||
const PayloadId payload_id = payload.GetId();
|
||||
CHECK(payload_id == file_payload_id);
|
||||
return absl::make_unique<OutgoingFileInternalPayload>(std::move(payload));
|
||||
}
|
||||
|
||||
case Payload::Type::kStream:
|
||||
return absl::make_unique<OutgoingStreamInternalPayload>(
|
||||
std::move(payload));
|
||||
|
||||
default:
|
||||
DCHECK(false); // This should never happen.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalPayload> CreateIncomingInternalPayload(
|
||||
const PayloadTransferFrame& frame) {
|
||||
if (frame.packet_type() != PayloadTransferFrame::DATA) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Payload::Id payload_id = frame.payload_header().id();
|
||||
switch (frame.payload_header().type()) {
|
||||
case PayloadTransferFrame::PayloadHeader::BYTES: {
|
||||
return absl::make_unique<BytesInternalPayload>(
|
||||
Payload(payload_id, ByteArray(frame.payload_chunk().body())));
|
||||
}
|
||||
|
||||
case PayloadTransferFrame::PayloadHeader::STREAM: {
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
|
||||
return absl::make_unique<IncomingStreamInternalPayload>(
|
||||
Payload(payload_id,
|
||||
[pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}),
|
||||
pipe->GetOutputStream());
|
||||
}
|
||||
|
||||
case PayloadTransferFrame::PayloadHeader::FILE: {
|
||||
std::int64_t total_size = frame.payload_header().total_size();
|
||||
return absl::make_unique<IncomingFileInternalPayload>(
|
||||
Payload(payload_id, InputFile(payload_id, total_size)),
|
||||
OutputFile(payload_id), total_size);
|
||||
}
|
||||
default:
|
||||
DCHECK(false); // This should never happen.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
#define CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
|
||||
#include "core_v2/internal/internal_payload.h"
|
||||
#include "core_v2/payload.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Creates an InternalPayload representing an outgoing Payload.
|
||||
std::unique_ptr<InternalPayload> CreateOutgoingInternalPayload(Payload payload);
|
||||
|
||||
// Creates an InternalPayload representing an incoming Payload from a remote
|
||||
// endpoint.
|
||||
std::unique_ptr<InternalPayload> CreateIncomingInternalPayload(
|
||||
const PayloadTransferFrame& frame);
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "core_v2/internal/internal_payload_factory.h"
|
||||
|
||||
#include "core_v2/internal/offline_frames.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/pipe.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr char kText[] = "data chunk";
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromBytePayload) {
|
||||
ByteArray data(kText);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(Payload{data});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamPayload) {
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(Payload{[pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_NE(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFilePayload) {
|
||||
Payload::Id payload_id = Payload::GenerateId();
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(
|
||||
Payload{payload_id, InputFile(payload_id, 512)});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_NE(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetId(), payload_id);
|
||||
EXPECT_EQ(payload.AsFile()->GetPayloadId(), payload_id);
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromByteMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
std::int64_t payload_chunk_offset = 0;
|
||||
ByteArray data(kText);
|
||||
PayloadTransferFrame::PayloadChunk payload_chunk;
|
||||
payload_chunk.set_offset(payload_chunk_offset);
|
||||
payload_chunk.set_body(std::string(std::move(data)));
|
||||
payload_chunk.set_flags(0);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::BYTES);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(512);
|
||||
*frame.mutable_payload_chunk() = std::move(payload_chunk);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::STREAM);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(0);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_NE(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kStream);
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFileMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(512);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_NE(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kFile);
|
||||
EXPECT_EQ(payload.GetId(), payload.AsFile()->GetPayloadId());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -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
|
||||
@@ -33,8 +33,8 @@ namespace {
|
||||
using Medium = proto::connections::Medium;
|
||||
using ::testing::EqualsProto;
|
||||
|
||||
constexpr char kEndpointId[] = "ABC";
|
||||
constexpr char kEndpointName[] = "XYZ";
|
||||
constexpr absl::string_view kEndpointId{"ABC"};
|
||||
constexpr absl::string_view kEndpointName{"XYZ"};
|
||||
constexpr int kNonce = 1234;
|
||||
constexpr std::array<Medium, 9> kMediums = {
|
||||
Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT,
|
||||
@@ -92,9 +92,9 @@ TEST(OfflineFramesTest, CanGenerateConnectionRequest) {
|
||||
mediums: WEB_RTC
|
||||
>
|
||||
>)pb";
|
||||
ByteArray bytes =
|
||||
ForConnectionRequest(kEndpointId, kEndpointName, kNonce,
|
||||
std::vector(kMediums.begin(), kMediums.end()));
|
||||
ByteArray bytes = ForConnectionRequest(
|
||||
std::string(kEndpointId), std::string(kEndpointName), kNonce,
|
||||
std::vector(kMediums.begin(), kMediums.end()));
|
||||
auto response = FromBytes(bytes);
|
||||
ASSERT_TRUE(response.ok());
|
||||
OfflineFrame message = FromBytes(bytes).result();
|
||||
@@ -237,7 +237,7 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) {
|
||||
client_introduction: < endpoint_id: "ABC" >
|
||||
>
|
||||
>)pb";
|
||||
ByteArray bytes = ForBandwidthUpgradeIntroduction(kEndpointId);
|
||||
ByteArray bytes = ForBandwidthUpgradeIntroduction(std::string(kEndpointId));
|
||||
auto response = FromBytes(bytes);
|
||||
ASSERT_TRUE(response.ok());
|
||||
OfflineFrame message = FromBytes(bytes).result();
|
||||
|
||||
@@ -0,0 +1,659 @@
|
||||
#include "core_v2/internal/p2p_cluster_pcp_handler.h"
|
||||
|
||||
#include "core_v2/internal/bluetooth_endpoint_channel.h"
|
||||
#include "core_v2/internal/wifi_lan_endpoint_channel.h"
|
||||
#include "platform_v2/public/crypto.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source,
|
||||
size_t size) {
|
||||
ByteArray full_hash = Crypto::Sha256(source);
|
||||
ByteArray result(size);
|
||||
result.CopyAt(0, full_hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
P2pClusterPcpHandler::P2pClusterPcpHandler(
|
||||
Mediums& mediums, EndpointManager* endpoint_manager,
|
||||
EndpointChannelManager* endpoint_channel_manager, Pcp pcp)
|
||||
: BasePcpHandler(endpoint_manager, endpoint_channel_manager, pcp),
|
||||
bluetooth_radio_(mediums.GetBluetoothRadio()),
|
||||
bluetooth_medium_(mediums.GetBluetoothClassic()),
|
||||
wifi_lan_medium_(mediums.GetWifiLan()) {}
|
||||
|
||||
// Returns a vector or mediums sorted in order or decreasing priority for
|
||||
// all the supported mediums.
|
||||
// NOTE: currently we only have BT, but eventually it will be more, and items
|
||||
// will have to be sorted in the order of decreasing traffic bandwidth.
|
||||
// Example: WiFi_LAN, BT, BLE
|
||||
std::vector<proto::connections::Medium>
|
||||
P2pClusterPcpHandler::GetConnectionMediumsByPriority() {
|
||||
std::vector<proto::connections::Medium> mediums;
|
||||
if (bluetooth_medium_.IsAvailable()) {
|
||||
mediums.push_back(proto::connections::BLUETOOTH);
|
||||
}
|
||||
if (wifi_lan_medium_.IsAvailable()) {
|
||||
mediums.push_back(proto::connections::WIFI_LAN);
|
||||
}
|
||||
return mediums;
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pClusterPcpHandler::GetDefaultUpgradeMedium() {
|
||||
return proto::connections::WIFI_LAN;
|
||||
}
|
||||
|
||||
BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name, const ConnectionOptions& options) {
|
||||
std::vector<proto::connections::Medium> mediums_started_successfully;
|
||||
|
||||
const ByteArray bluetooth_hash =
|
||||
GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength);
|
||||
proto::connections::Medium bluetooth_medium =
|
||||
StartBluetoothAdvertising(client, service_id, bluetooth_hash,
|
||||
local_endpoint_id, local_endpoint_name);
|
||||
if (bluetooth_medium != proto::connections::UNKNOWN_MEDIUM) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: BT added");
|
||||
mediums_started_successfully.push_back(bluetooth_medium);
|
||||
}
|
||||
|
||||
const ByteArray wifi_lan_hash =
|
||||
GenerateHash(service_id, WifiLanServiceInfo::kServiceIdHashLength);
|
||||
proto::connections::Medium wifi_lan_medium =
|
||||
StartWifiLanAdvertising(client, service_id, wifi_lan_hash,
|
||||
local_endpoint_id, local_endpoint_name);
|
||||
if (wifi_lan_medium != proto::connections::UNKNOWN_MEDIUM) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartAdvertisingImpl: WifiLan added");
|
||||
mediums_started_successfully.push_back(wifi_lan_medium);
|
||||
}
|
||||
|
||||
if (mediums_started_successfully.empty()) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: not started");
|
||||
return {
|
||||
.status = {Status::kBluetoothError},
|
||||
};
|
||||
}
|
||||
|
||||
// The rest of the operations for startAdvertising() will continue
|
||||
// asynchronously via
|
||||
// IncomingBluetoothConnectionProcessor.onIncomingBluetoothConnection(), so
|
||||
// leave it to that to signal any errors that may occur.
|
||||
return {
|
||||
.status = {Status::kSuccess},
|
||||
.mediums = std::move(mediums_started_successfully),
|
||||
};
|
||||
}
|
||||
|
||||
Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) {
|
||||
wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId());
|
||||
bluetooth_medium_.TurnOffDiscoverability();
|
||||
bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId());
|
||||
return {Status::kSuccess};
|
||||
}
|
||||
|
||||
bool P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint(
|
||||
const std::string& name_string, const std::string& service_id,
|
||||
const BluetoothDeviceName& name) const {
|
||||
if (!name.IsValid()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: name is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.GetPcp() != GetPcp()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: Pcp is "
|
||||
"not matched; name.Pcp=%d, Pcp=%d",
|
||||
name.GetPcp(), GetPcp());
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteArray expected_service_id_hash =
|
||||
GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength);
|
||||
|
||||
if (name.GetServiceIdHash() != expected_service_id_hash) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: service "
|
||||
"id hash is "
|
||||
"not matched; name.service_id_hash=%s, expected=%s",
|
||||
name.GetServiceIdHash().data(), expected_service_id_hash.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::function<void(BluetoothDevice&)>
|
||||
P2pClusterPcpHandler::MakeBluetoothDeviceDiscoveredHandler(
|
||||
ClientProxy* client, const std::string& service_id) {
|
||||
return [this, client, service_id](BluetoothDevice& device) {
|
||||
RunOnPcpHandlerThread([this, client, service_id, &device]() {
|
||||
// Make sure we are still discovering before proceeding.
|
||||
if (!client->IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"BT discovery handler (FOUND) [client=%p, service=%s]: not "
|
||||
"in discovery mode",
|
||||
client, service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the Bluetooth device name.
|
||||
const std::string& device_name_string = device.GetName();
|
||||
BluetoothDeviceName device_name(device_name_string);
|
||||
|
||||
// Make sure the Bluetooth device name points to a valid
|
||||
// endpoint we're discovering.
|
||||
if (!IsRecognizedBluetoothEndpoint(device_name_string, service_id,
|
||||
device_name))
|
||||
return;
|
||||
|
||||
// Report the discovered endpoint to the client.
|
||||
NEARBY_LOG(INFO,
|
||||
"Invoking BasePcpHandler::OnEndpointFound() for BT "
|
||||
"service=%s; id=%s; name=%s",
|
||||
service_id.c_str(), device_name.GetEndpointId().c_str(),
|
||||
device_name.GetEndpointName().c_str());
|
||||
OnEndpointFound(client,
|
||||
std::make_shared<BluetoothEndpoint>(BluetoothEndpoint{
|
||||
{
|
||||
.endpoint_id = device_name.GetEndpointId(),
|
||||
.endpoint_name = device_name.GetEndpointName(),
|
||||
.service_id = service_id,
|
||||
.medium = proto::connections::Medium::BLUETOOTH,
|
||||
},
|
||||
device,
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
std::function<void(BluetoothDevice&)>
|
||||
P2pClusterPcpHandler::MakeBluetoothDeviceLostHandler(
|
||||
ClientProxy* client, const std::string& service_id) {
|
||||
return [this, client, service_id](BluetoothDevice& device) {
|
||||
RunOnPcpHandlerThread([this, client, &service_id, &device]() {
|
||||
// Make sure we are still discovering before proceeding.
|
||||
if (!client->IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"BT discovery handler (LOST) [client=%p, service=%s]: not "
|
||||
"in discovery mode",
|
||||
client, service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the Bluetooth device name.
|
||||
const std::string& device_name_string = device.GetName();
|
||||
BluetoothDeviceName device_name(device_name_string);
|
||||
|
||||
// Make sure the Bluetooth device name points to a valid
|
||||
// endpoint we're discovering.
|
||||
if (!IsRecognizedBluetoothEndpoint(device_name_string, service_id,
|
||||
device_name))
|
||||
return;
|
||||
|
||||
// Report the discovered endpoint to the client.
|
||||
NEARBY_LOG(INFO,
|
||||
"BT discovery handler (LOST) [client=%p, service=%s]: report "
|
||||
"to client",
|
||||
client, service_id.c_str());
|
||||
OnEndpointLost(client,
|
||||
BluetoothEndpoint{
|
||||
{
|
||||
.endpoint_id = device_name.GetEndpointId(),
|
||||
.endpoint_name = device_name.GetEndpointName(),
|
||||
.service_id = service_id,
|
||||
.medium = proto::connections::Medium::BLUETOOTH,
|
||||
},
|
||||
device,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
bool P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint(
|
||||
const std::string& name_string, const std::string& service_id,
|
||||
const WifiLanServiceInfo& name) const {
|
||||
if (!name.IsValid()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: name is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.GetPcp() != GetPcp()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: Pcp is "
|
||||
"not matched; name.Pcp=%d, Pcp=%d",
|
||||
name.GetPcp(), GetPcp());
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteArray expected_service_id_hash =
|
||||
GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength);
|
||||
|
||||
if (name.GetServiceIdHash() != expected_service_id_hash) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: service "
|
||||
"id hash is "
|
||||
"not matched; name.service_id_hash=%s, expected=%s",
|
||||
name.GetServiceIdHash().data(), expected_service_id_hash.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::function<void(WifiLanService&, const std::string&)>
|
||||
P2pClusterPcpHandler::MakeWifiLanServiceDiscoveredHandler(
|
||||
ClientProxy* client, const std::string& service_id) {
|
||||
return [this, client](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
RunOnPcpHandlerThread([this, client, service_id, &service]() {
|
||||
// Make sure we are still discovering before proceeding.
|
||||
if (!client->IsDiscovering()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"WifiLan discovery handler (FOUND) [client=%p, service=%s]: not "
|
||||
"in discovery mode",
|
||||
client, service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the WifiLan service name.
|
||||
const std::string& service_name_string = service.GetName();
|
||||
WifiLanServiceInfo service_name(service_name_string);
|
||||
|
||||
// Make sure the WifiLan service name points to a valid
|
||||
// endpoint we're discovering.
|
||||
if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id,
|
||||
service_name))
|
||||
return;
|
||||
|
||||
// Report the discovered endpoint to the client.
|
||||
NEARBY_LOG(INFO,
|
||||
"Invoking BasePcpHandler::OnEndpointFound() for WifiLan "
|
||||
"service=%s; id=%s; name=%s",
|
||||
service_id.c_str(), service_name.GetEndpointId().c_str(),
|
||||
service_name.GetEndpointName().c_str());
|
||||
OnEndpointFound(client,
|
||||
std::make_shared<WifiLanEndpoint>(WifiLanEndpoint{
|
||||
{
|
||||
.endpoint_id = service_name.GetEndpointId(),
|
||||
.endpoint_name = service_name.GetEndpointName(),
|
||||
.service_id = service_id,
|
||||
.medium = proto::connections::Medium::WIFI_LAN,
|
||||
},
|
||||
service,
|
||||
}));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
std::function<void(WifiLanService&, const std::string&)>
|
||||
P2pClusterPcpHandler::MakeWifiLanServiceLostHandler(
|
||||
ClientProxy* client, const std::string& service_id) {
|
||||
return [this, client](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
RunOnPcpHandlerThread([this, client, &service_id, &service]() {
|
||||
// Make sure we are still discovering before proceeding.
|
||||
if (!client->IsDiscovering()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"WifiLan discovery handler (LOST) [client=%p, service=%s]: not "
|
||||
"in discovery mode",
|
||||
client, service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the WifiLan service name.
|
||||
const std::string& service_name_string = service.GetName();
|
||||
WifiLanServiceInfo service_name(service_name_string);
|
||||
|
||||
// Make sure the WifiLan service name points to a valid
|
||||
// endpoint we're discovering.
|
||||
if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id,
|
||||
service_name))
|
||||
return;
|
||||
|
||||
// Report the discovered endpoint to the client.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"WifiLan discovery handler (LOST) [client=%p, service=%s]: report "
|
||||
"to client",
|
||||
client, service_id.c_str());
|
||||
OnEndpointLost(client,
|
||||
WifiLanEndpoint{
|
||||
{
|
||||
.endpoint_id = service_name.GetEndpointId(),
|
||||
.endpoint_name = service_name.GetEndpointName(),
|
||||
.service_id = service_id,
|
||||
.medium = proto::connections::Medium::WIFI_LAN,
|
||||
},
|
||||
service,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ConnectionOptions& options) {
|
||||
std::vector<proto::connections::Medium> mediums_started_successfully;
|
||||
|
||||
proto::connections::Medium bluetooth_medium = StartBluetoothDiscovery(
|
||||
{
|
||||
.device_discovered_cb =
|
||||
MakeBluetoothDeviceDiscoveredHandler(client, service_id),
|
||||
.device_lost_cb = MakeBluetoothDeviceLostHandler(client, service_id),
|
||||
},
|
||||
client, service_id);
|
||||
if (bluetooth_medium != proto::connections::UNKNOWN_MEDIUM) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: BT added");
|
||||
mediums_started_successfully.push_back(bluetooth_medium);
|
||||
}
|
||||
|
||||
proto::connections::Medium wifi_lan_medium = StartWifiLanDiscovery(
|
||||
{
|
||||
.service_discovered_cb =
|
||||
MakeWifiLanServiceDiscoveredHandler(client, service_id),
|
||||
.service_lost_cb = MakeWifiLanServiceLostHandler(client, service_id),
|
||||
},
|
||||
client, service_id);
|
||||
if (wifi_lan_medium != proto::connections::UNKNOWN_MEDIUM) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: WifiLan added");
|
||||
mediums_started_successfully.push_back(wifi_lan_medium);
|
||||
}
|
||||
|
||||
if (mediums_started_successfully.empty()) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: nothing added");
|
||||
return {
|
||||
.status = {Status::kBluetoothError},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
.status = {Status::kSuccess},
|
||||
.mediums = std::move(mediums_started_successfully),
|
||||
};
|
||||
}
|
||||
|
||||
Status P2pClusterPcpHandler::StopDiscoveryImpl(ClientProxy* client) {
|
||||
wifi_lan_medium_.StopDiscovery(client->GetDiscoveryServiceId());
|
||||
bluetooth_medium_.StopDiscovery();
|
||||
return {Status::kSuccess};
|
||||
}
|
||||
|
||||
BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::ConnectImpl(
|
||||
ClientProxy* client, BasePcpHandler::DiscoveredEndpoint* endpoint) {
|
||||
BluetoothEndpoint* bluetooth_endpoint =
|
||||
static_cast<BluetoothEndpoint*>(endpoint);
|
||||
if (bluetooth_endpoint) {
|
||||
return BluetoothConnectImpl(client, bluetooth_endpoint);
|
||||
}
|
||||
|
||||
WifiLanEndpoint* wifi_lan_endpoint = static_cast<WifiLanEndpoint*>(endpoint);
|
||||
if (wifi_lan_endpoint) {
|
||||
return WifiLanConnectImpl(client, wifi_lan_endpoint);
|
||||
}
|
||||
|
||||
return BasePcpHandler::ConnectImplResult{
|
||||
.status = {Status::kError},
|
||||
};
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ByteArray& service_id_hash, const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name) {
|
||||
// Start listening for connections before advertising in case a connection
|
||||
// request comes in very quickly.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: start",
|
||||
service_id.c_str());
|
||||
if (bluetooth_medium_.IsAcceptingConnections(service_id)) {
|
||||
NEARBY_LOG(ERROR, "BT is already accepting connections for service=%s",
|
||||
service_id.c_str());
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: invoking",
|
||||
service_id.c_str());
|
||||
if (!bluetooth_radio_.Enable() ||
|
||||
!bluetooth_medium_.StartAcceptingConnections(
|
||||
service_id, {.accepted_cb = [this, client, local_endpoint_name](
|
||||
BluetoothSocket socket) {
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s",
|
||||
local_endpoint_name.c_str());
|
||||
return;
|
||||
}
|
||||
RunOnPcpHandlerThread([this, client, local_endpoint_name,
|
||||
socket = std::move(socket)]() mutable {
|
||||
std::string remote_device_name =
|
||||
socket.GetRemoteDevice().GetName();
|
||||
auto channel = absl::make_unique<BluetoothEndpointChannel>(
|
||||
remote_device_name, socket);
|
||||
OnIncomingConnection(client, remote_device_name,
|
||||
std::move(channel),
|
||||
proto::connections::Medium::BLUETOOTH);
|
||||
});
|
||||
}})) {
|
||||
NEARBY_LOG(ERROR, "BT failed to start accepting connections for service=%s",
|
||||
service_id.c_str());
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: "
|
||||
"make name; id=%s, hash=%s, name=%s",
|
||||
service_id.c_str(), local_endpoint_id.c_str(),
|
||||
std::string(service_id_hash).c_str(), local_endpoint_name.c_str());
|
||||
// Generate a BluetoothDeviceName with which to become Bluetooth discoverable.
|
||||
std::string device_name(BluetoothDeviceName(
|
||||
BluetoothDeviceName::Version::kV1, GetPcp(), local_endpoint_id,
|
||||
service_id_hash, local_endpoint_name));
|
||||
if (device_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: generate "
|
||||
"BluetoothDeviceName failed");
|
||||
bluetooth_medium_.StopAcceptingConnections(service_id);
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
} else {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: generate "
|
||||
"BluetoothDeviceName succeeded; device_name=%s",
|
||||
device_name.c_str());
|
||||
}
|
||||
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: come up",
|
||||
service_id.c_str());
|
||||
// Become Bluetooth discoverable.
|
||||
if (!bluetooth_medium_.TurnOnDiscoverability(device_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: failed to "
|
||||
"turn on discoverability, device_name=%s",
|
||||
device_name.c_str());
|
||||
bluetooth_medium_.StopAcceptingConnections(service_id);
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
} else {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartBluetoothAdvertising: succeeded to "
|
||||
"turn on discoverability, device_name=%s",
|
||||
device_name.c_str());
|
||||
}
|
||||
NEARBY_LOG(
|
||||
INFO, "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: done",
|
||||
service_id.c_str());
|
||||
return proto::connections::BLUETOOTH;
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pClusterPcpHandler::StartBluetoothDiscovery(
|
||||
BluetoothDiscoveredDeviceCallback callback, ClientProxy* client,
|
||||
const std::string& service_id) {
|
||||
if (bluetooth_radio_.Enable() &&
|
||||
bluetooth_medium_.StartDiscovery(std::move(callback))) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothDiscovery: ok");
|
||||
return proto::connections::BLUETOOTH;
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothDiscovery: failed");
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BluetoothConnectImpl(
|
||||
ClientProxy* client, BluetoothEndpoint* endpoint) {
|
||||
BluetoothDevice& device = endpoint->bluetooth_device;
|
||||
|
||||
BluetoothSocket bluetooth_socket =
|
||||
bluetooth_medium_.Connect(device, endpoint->service_id);
|
||||
if (!bluetooth_socket.IsValid()) {
|
||||
return BasePcpHandler::ConnectImplResult{
|
||||
.status = {Status::kBluetoothError},
|
||||
};
|
||||
}
|
||||
|
||||
auto channel = absl::make_unique<BluetoothEndpointChannel>(
|
||||
endpoint->endpoint_id, bluetooth_socket);
|
||||
|
||||
return BasePcpHandler::ConnectImplResult{
|
||||
.medium = proto::connections::Medium::BLUETOOTH,
|
||||
.status = {Status::kSuccess},
|
||||
.endpoint_channel = std::move(channel),
|
||||
};
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ByteArray& service_id_hash, const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name) {
|
||||
// Start listening for connections before advertising in case a connection
|
||||
// request comes in very quickly.
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: start",
|
||||
service_id.c_str());
|
||||
if (wifi_lan_medium_.IsAcceptingConnections(service_id)) {
|
||||
NEARBY_LOG(ERROR, "WifiLan is already accepting connections for service=%s",
|
||||
service_id.c_str());
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: invoking",
|
||||
service_id.c_str());
|
||||
if (!wifi_lan_medium_.StartAcceptingConnections(
|
||||
service_id, {.accepted_cb = [this, client, local_endpoint_name](
|
||||
WifiLanSocket& socket,
|
||||
const std::string& service_id) {
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s",
|
||||
local_endpoint_name.c_str());
|
||||
return;
|
||||
}
|
||||
RunOnPcpHandlerThread([this, client, local_endpoint_name,
|
||||
socket = std::move(socket)]() mutable {
|
||||
std::string remote_service_name =
|
||||
socket.GetRemoteWifiLanService().GetName();
|
||||
auto channel = absl::make_unique<WifiLanEndpointChannel>(
|
||||
remote_service_name, socket);
|
||||
OnIncomingConnection(client, remote_service_name,
|
||||
std::move(channel),
|
||||
proto::connections::Medium::WIFI_LAN);
|
||||
});
|
||||
}})) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"WifiLan failed to start accepting connections for service=%s",
|
||||
service_id.c_str());
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: "
|
||||
"make name; id=%s, hash=%s, name=%s",
|
||||
service_id.c_str(), local_endpoint_id.c_str(),
|
||||
std::string(service_id_hash).c_str(), local_endpoint_name.c_str());
|
||||
// Generate a WifiLanServiceInfo with which to become WifiLan discoverable.
|
||||
std::string service_name(WifiLanServiceInfo(
|
||||
WifiLanServiceInfo::Version::kV1, GetPcp(), local_endpoint_id,
|
||||
service_id_hash, local_endpoint_name));
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: generate "
|
||||
"WifiLanServiceInfo failed");
|
||||
wifi_lan_medium_.StopAcceptingConnections(service_id);
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
} else {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: generate "
|
||||
"WifiLanServiceInfo succeeded; service_name=%s",
|
||||
service_name.c_str());
|
||||
}
|
||||
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: come up",
|
||||
service_id.c_str());
|
||||
|
||||
if (!wifi_lan_medium_.StartAdvertising(service_id, service_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: failed to "
|
||||
"start advertising, service_name=%s",
|
||||
service_name.c_str());
|
||||
wifi_lan_medium_.StopAcceptingConnections(service_id);
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
NEARBY_LOG(INFO,
|
||||
"P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: done",
|
||||
service_id.c_str());
|
||||
return proto::connections::WIFI_LAN;
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pClusterPcpHandler::StartWifiLanDiscovery(
|
||||
WifiLanDiscoveredServiceCallback callback, ClientProxy* client,
|
||||
const std::string& service_id) {
|
||||
if (wifi_lan_medium_.StartDiscovery(service_id, std::move(callback))) {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanDiscovery: ok");
|
||||
return proto::connections::WIFI_LAN;
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanDiscovery: failed");
|
||||
return proto::connections::UNKNOWN_MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WifiLanConnectImpl(
|
||||
ClientProxy* client, WifiLanEndpoint* endpoint) {
|
||||
WifiLanService& service = endpoint->wifi_lan_service;
|
||||
|
||||
WifiLanSocket wifi_lan_socket =
|
||||
wifi_lan_medium_.Connect(service, endpoint->service_id);
|
||||
if (!wifi_lan_socket.IsValid()) {
|
||||
return BasePcpHandler::ConnectImplResult{
|
||||
.status = {Status::kWifiLanError},
|
||||
};
|
||||
}
|
||||
|
||||
auto channel = absl::make_unique<WifiLanEndpointChannel>(
|
||||
endpoint->endpoint_id, wifi_lan_socket);
|
||||
|
||||
return BasePcpHandler::ConnectImplResult{
|
||||
.medium = proto::connections::Medium::WIFI_LAN,
|
||||
.status = {Status::kSuccess},
|
||||
.endpoint_channel = std::move(channel),
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,136 @@
|
||||
#ifndef CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
|
||||
#define CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "core_v2/internal/base_pcp_handler.h"
|
||||
#include "core_v2/internal/ble_advertisement.h"
|
||||
#include "core_v2/internal/bluetooth_device_name.h"
|
||||
#include "core_v2/internal/client_proxy.h"
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/mediums/bluetooth_classic.h"
|
||||
#include "core_v2/internal/mediums/mediums.h"
|
||||
#include "core_v2/internal/pcp.h"
|
||||
#include "core_v2/internal/wifi_lan_service_info.h"
|
||||
#include "core_v2/options.h"
|
||||
#include "core_v2/strategy.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/bluetooth_classic.h"
|
||||
#include "platform_v2/public/wifi_lan.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Concrete implementation of the PCPHandler for the P2P_CLUSTER PCP. This PCP
|
||||
// is reserved for mediums that can connect to multiple devices simultaneously
|
||||
// and all devices are considered equal. For asymmetric mediums, where one
|
||||
// device is a server and the others are clients, use P2PStarPCPHandler instead.
|
||||
//
|
||||
// Currently, this implementation advertises/discovers over Bluetooth and
|
||||
// connects over Bluetooth.
|
||||
class P2pClusterPcpHandler : public BasePcpHandler {
|
||||
public:
|
||||
P2pClusterPcpHandler(Mediums& mediums, EndpointManager* endpoint_manager,
|
||||
EndpointChannelManager* channel_manager,
|
||||
Pcp pcp = Pcp::kP2pCluster);
|
||||
~P2pClusterPcpHandler() override = default;
|
||||
|
||||
protected:
|
||||
std::vector<proto::connections::Medium> GetConnectionMediumsByPriority()
|
||||
override;
|
||||
proto::connections::Medium GetDefaultUpgradeMedium() override;
|
||||
|
||||
// @PCPHandlerThread
|
||||
BasePcpHandler::StartOperationResult StartAdvertisingImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name,
|
||||
const ConnectionOptions& options) override;
|
||||
|
||||
// @PCPHandlerThread
|
||||
Status StopAdvertisingImpl(ClientProxy* client) override;
|
||||
|
||||
// @PCPHandlerThread
|
||||
BasePcpHandler::StartOperationResult StartDiscoveryImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ConnectionOptions& options) override;
|
||||
|
||||
// @PCPHandlerThread
|
||||
Status StopDiscoveryImpl(ClientProxy* client) override;
|
||||
|
||||
// @PCPHandlerThread
|
||||
BasePcpHandler::ConnectImplResult ConnectImpl(
|
||||
ClientProxy* client,
|
||||
BasePcpHandler::DiscoveredEndpoint* endpoint) override;
|
||||
|
||||
private:
|
||||
struct BluetoothEndpoint : public BasePcpHandler::DiscoveredEndpoint {
|
||||
BluetoothDevice bluetooth_device;
|
||||
};
|
||||
struct WifiLanEndpoint : public BasePcpHandler::DiscoveredEndpoint {
|
||||
WifiLanService wifi_lan_service;
|
||||
};
|
||||
|
||||
using BluetoothDiscoveredDeviceCallback =
|
||||
BluetoothClassic::DiscoveredDeviceCallback;
|
||||
using WifiLanDiscoveredServiceCallback = WifiLan::DiscoveredServiceCallback;
|
||||
|
||||
static constexpr BluetoothDeviceName::Version kBluetoothDeviceNameVersion =
|
||||
BluetoothDeviceName::Version::kV1;
|
||||
static constexpr WifiLanServiceInfo::Version kWifiLanServiceInfoVersion =
|
||||
WifiLanServiceInfo::Version::kV1;
|
||||
|
||||
static ByteArray GenerateHash(const std::string& source, size_t size);
|
||||
|
||||
// Bluetooth.
|
||||
bool IsRecognizedBluetoothEndpoint(const std::string& name_string,
|
||||
const std::string& service_id,
|
||||
const BluetoothDeviceName& name) const;
|
||||
std::function<void(BluetoothDevice&)> MakeBluetoothDeviceDiscoveredHandler(
|
||||
ClientProxy* client, const std::string& service_id);
|
||||
std::function<void(BluetoothDevice&)> MakeBluetoothDeviceLostHandler(
|
||||
ClientProxy* client, const std::string& service_id);
|
||||
proto::connections::Medium StartBluetoothAdvertising(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ByteArray& service_id_hash, const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name);
|
||||
proto::connections::Medium StartBluetoothDiscovery(
|
||||
BluetoothDiscoveredDeviceCallback callback, ClientProxy* client,
|
||||
const std::string& service_id);
|
||||
BasePcpHandler::ConnectImplResult BluetoothConnectImpl(
|
||||
ClientProxy* client, BluetoothEndpoint* endpoint);
|
||||
|
||||
// WifiLan.
|
||||
bool IsRecognizedWifiLanEndpoint(const std::string& name_string,
|
||||
const std::string& service_id,
|
||||
const WifiLanServiceInfo& name) const;
|
||||
std::function<void(WifiLanService&, const std::string&)>
|
||||
MakeWifiLanServiceDiscoveredHandler(ClientProxy* client,
|
||||
const std::string& service_id);
|
||||
std::function<void(WifiLanService&, const std::string&)>
|
||||
MakeWifiLanServiceLostHandler(ClientProxy* client,
|
||||
const std::string& service_id);
|
||||
proto::connections::Medium StartWifiLanAdvertising(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const ByteArray& service_id_hash, const std::string& local_endpoint_id,
|
||||
const std::string& local_endpoint_name);
|
||||
proto::connections::Medium StartWifiLanDiscovery(
|
||||
WifiLanDiscoveredServiceCallback callback, ClientProxy* client,
|
||||
const std::string& service_id);
|
||||
BasePcpHandler::ConnectImplResult WifiLanConnectImpl(
|
||||
ClientProxy* client, WifiLanEndpoint* endpoint);
|
||||
|
||||
BluetoothRadio& bluetooth_radio_;
|
||||
BluetoothClassic& bluetooth_medium_;
|
||||
WifiLan& wifi_lan_medium_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "core_v2/internal/p2p_cluster_pcp_handler.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/options.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
class P2pClusterPcpHandlerTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
NEARBY_LOG(INFO, "SetUp: begin");
|
||||
env_.Stop();
|
||||
NEARBY_LOG(INFO, "SetUp: end");
|
||||
}
|
||||
|
||||
ClientProxy client_a_;
|
||||
ClientProxy client_b_;
|
||||
std::string service_id_{"service"};
|
||||
ConnectionOptions options_{.strategy = Strategy::kP2pCluster};
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_F(P2pClusterPcpHandlerTest, CanConstructOne) {
|
||||
env_.Start();
|
||||
Mediums mediums;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
P2pClusterPcpHandler handler(mediums, &em, &ecm);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(P2pClusterPcpHandlerTest, CanConstructMultiple) {
|
||||
env_.Start();
|
||||
Mediums mediums_a;
|
||||
Mediums mediums_b;
|
||||
EndpointChannelManager ecm_a;
|
||||
EndpointChannelManager ecm_b;
|
||||
EndpointManager em_a(&ecm_a);
|
||||
EndpointManager em_b(&ecm_b);
|
||||
P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a);
|
||||
P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(P2pClusterPcpHandlerTest, CanAdvertise) {
|
||||
env_.Start();
|
||||
std::string endpoint_name{"endpoint_name"};
|
||||
Mediums mediums_a;
|
||||
EndpointChannelManager ecm_a;
|
||||
EndpointManager em_a(&ecm_a);
|
||||
P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a);
|
||||
EXPECT_EQ(handler_a.StartAdvertising(&client_a_, service_id_, options_,
|
||||
{.name = endpoint_name}),
|
||||
Status{Status::kSuccess});
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(P2pClusterPcpHandlerTest, CanDiscover) {
|
||||
env_.Start();
|
||||
std::string endpoint_name{"endpoint_name"};
|
||||
Mediums mediums_a;
|
||||
Mediums mediums_b;
|
||||
EndpointChannelManager ecm_a;
|
||||
EndpointChannelManager ecm_b;
|
||||
EndpointManager em_a(&ecm_a);
|
||||
EndpointManager em_b(&ecm_b);
|
||||
P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a);
|
||||
P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b);
|
||||
CountDownLatch latch(1);
|
||||
EXPECT_EQ(handler_a.StartAdvertising(&client_a_, service_id_, options_,
|
||||
{.name = endpoint_name}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_EQ(handler_b.StartDiscovery(
|
||||
&client_b_, service_id_, options_,
|
||||
{
|
||||
.endpoint_found_cb =
|
||||
[&latch](const std::string& endpoint_id,
|
||||
const std::string& endpoint_name,
|
||||
const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "Device discovered: id=%s",
|
||||
endpoint_id.c_str());
|
||||
latch.CountDown();
|
||||
},
|
||||
}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(P2pClusterPcpHandlerTest, CanConnect) {
|
||||
env_.Start();
|
||||
std::string endpoint_name_a{"endpoint_name"};
|
||||
Mediums mediums_a;
|
||||
Mediums mediums_b;
|
||||
BluetoothRadio& radio_a = mediums_a.GetBluetoothRadio();
|
||||
BluetoothRadio& radio_b = mediums_b.GetBluetoothRadio();
|
||||
radio_a.GetBluetoothAdapter().SetName("BT Device A");
|
||||
radio_b.GetBluetoothAdapter().SetName("BT Device B");
|
||||
EndpointChannelManager ecm_a;
|
||||
EndpointChannelManager ecm_b;
|
||||
EndpointManager em_a(&ecm_a);
|
||||
EndpointManager em_b(&ecm_b);
|
||||
P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a);
|
||||
P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b);
|
||||
CountDownLatch discover_latch(1);
|
||||
CountDownLatch connect_latch(2);
|
||||
struct DiscoveredInfo {
|
||||
std::string endpoint_id;
|
||||
std::string endpoint_name;
|
||||
std::string service_id;
|
||||
} discovered;
|
||||
EXPECT_EQ(
|
||||
handler_a.StartAdvertising(
|
||||
&client_a_, service_id_, options_,
|
||||
{
|
||||
.name = endpoint_name_a,
|
||||
.listener =
|
||||
{
|
||||
.initiated_cb =
|
||||
[&connect_latch](const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info) {
|
||||
NEARBY_LOG(INFO,
|
||||
"StartAdvertising: initiated_cb called");
|
||||
connect_latch.CountDown();
|
||||
},
|
||||
},
|
||||
}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_EQ(handler_b.StartDiscovery(
|
||||
&client_b_, service_id_, options_,
|
||||
{
|
||||
.endpoint_found_cb =
|
||||
[&discover_latch, &discovered](
|
||||
const std::string& endpoint_id,
|
||||
const std::string& endpoint_name,
|
||||
const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "Device discovered: id=%s",
|
||||
endpoint_id.c_str());
|
||||
discovered = {
|
||||
.endpoint_id = endpoint_id,
|
||||
.endpoint_name = endpoint_name,
|
||||
.service_id = service_id,
|
||||
};
|
||||
discover_latch.CountDown();
|
||||
},
|
||||
}),
|
||||
Status{Status::kSuccess});
|
||||
|
||||
EXPECT_TRUE(discover_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(endpoint_name_a, discovered.endpoint_name);
|
||||
|
||||
handler_b.RequestConnection(
|
||||
&client_b_, discovered.endpoint_id,
|
||||
{
|
||||
.name = discovered.endpoint_name,
|
||||
.listener =
|
||||
{
|
||||
.initiated_cb =
|
||||
[&connect_latch](const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info) {
|
||||
NEARBY_LOG(INFO,
|
||||
"RequestConnection: initiated_cb called");
|
||||
connect_latch.CountDown();
|
||||
},
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "core_v2/internal/p2p_point_to_point_pcp_handler.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
P2pPointToPointPcpHandler::P2pPointToPointPcpHandler(
|
||||
Mediums& mediums, EndpointManager& endpoint_manager,
|
||||
EndpointChannelManager& channel_manager, Pcp pcp)
|
||||
: P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp),
|
||||
mediums_(&mediums) {}
|
||||
|
||||
std::vector<proto::connections::Medium>
|
||||
P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() {
|
||||
std::vector<proto::connections::Medium> mediums;
|
||||
if (mediums_->GetBluetoothClassic().IsAvailable()) {
|
||||
mediums.push_back(proto::connections::BLUETOOTH);
|
||||
}
|
||||
return mediums;
|
||||
}
|
||||
|
||||
bool P2pPointToPointPcpHandler::CanSendOutgoingConnection(
|
||||
ClientProxy* client) const {
|
||||
// For point to point, we can only send an outgoing connection while we have
|
||||
// no other connections.
|
||||
return !this->HasOutgoingConnections(client) &&
|
||||
!this->HasIncomingConnections(client);
|
||||
}
|
||||
|
||||
bool P2pPointToPointPcpHandler::CanReceiveIncomingConnection(
|
||||
ClientProxy* client) const {
|
||||
// For point to point, we can only receive an incoming connection while we
|
||||
// have no other connections.
|
||||
return !this->HasOutgoingConnections(client) &&
|
||||
!this->HasIncomingConnections(client);
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
|
||||
#define CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
|
||||
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/mediums/mediums.h"
|
||||
#include "core_v2/internal/p2p_star_pcp_handler.h"
|
||||
#include "core_v2/internal/pcp.h"
|
||||
#include "core_v2/strategy.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Concrete implementation of the PCPHandler for the P2P_POINT_TO_POINT. This
|
||||
// PCP is for mediums that have limitations on the number of simultaneous
|
||||
// connections; all mediums in P2P_STAR are valid for P2P_POINT_TO_POINT, but
|
||||
// not all mediums in P2P_POINT_TO_POINT and valid for P2P_STAR.
|
||||
//
|
||||
// Currently, this implementation advertises/discovers over Bluetooth
|
||||
// and connects over Bluetooth.
|
||||
class P2pPointToPointPcpHandler : public P2pStarPcpHandler {
|
||||
public:
|
||||
P2pPointToPointPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager,
|
||||
EndpointChannelManager& channel_manager,
|
||||
Pcp pcp = Pcp::kP2pPointToPoint);
|
||||
|
||||
protected:
|
||||
std::vector<proto::connections::Medium> GetConnectionMediumsByPriority()
|
||||
override;
|
||||
|
||||
bool CanSendOutgoingConnection(ClientProxy* client) const override;
|
||||
bool CanReceiveIncomingConnection(ClientProxy* client) const override;
|
||||
|
||||
private:
|
||||
Mediums* mediums_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "core_v2/internal/p2p_star_pcp_handler.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums,
|
||||
EndpointManager& endpoint_manager,
|
||||
EndpointChannelManager& channel_manager,
|
||||
Pcp pcp)
|
||||
: P2pClusterPcpHandler(mediums, &endpoint_manager, &channel_manager, pcp),
|
||||
mediums_(&mediums) {}
|
||||
|
||||
std::vector<proto::connections::Medium>
|
||||
P2pStarPcpHandler::GetConnectionMediumsByPriority() {
|
||||
std::vector<proto::connections::Medium> mediums;
|
||||
if (mediums_->GetBluetoothClassic().IsAvailable()) {
|
||||
mediums.push_back(proto::connections::BLUETOOTH);
|
||||
}
|
||||
return mediums;
|
||||
}
|
||||
|
||||
proto::connections::Medium P2pStarPcpHandler::GetDefaultUpgradeMedium() {
|
||||
return proto::connections::Medium::WIFI_HOTSPOT;
|
||||
}
|
||||
|
||||
bool P2pStarPcpHandler::CanSendOutgoingConnection(ClientProxy* client) const {
|
||||
// For star, we can only send an outgoing connection while we have no other
|
||||
// connections.
|
||||
return !this->HasOutgoingConnections(client) &&
|
||||
!this->HasIncomingConnections(client);
|
||||
}
|
||||
|
||||
bool P2pStarPcpHandler::CanReceiveIncomingConnection(
|
||||
ClientProxy* client) const {
|
||||
// For star, we can only receive an incoming connection if we've sent no
|
||||
// outgoing connections.
|
||||
return !this->HasOutgoingConnections(client);
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_
|
||||
#define CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core_v2/internal/client_proxy.h"
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/mediums/mediums.h"
|
||||
#include "core_v2/internal/p2p_cluster_pcp_handler.h"
|
||||
#include "core_v2/internal/pcp.h"
|
||||
#include "core_v2/strategy.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Concrete implementation of the PcpHandler for the P2P_STAR PCP. This Pcp is
|
||||
// for mediums that have one server with (potentially) many clients; all mediums
|
||||
// in P2P_CLUSTER are valid for P2P_STAR, but not all mediums in P2P_STAR and
|
||||
// valid for P2P_CLUSTER.
|
||||
//
|
||||
// Currently, this implementation advertises/discovers over Bluetooth
|
||||
// and connects over Bluetooth.
|
||||
class P2pStarPcpHandler : public P2pClusterPcpHandler {
|
||||
public:
|
||||
P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager,
|
||||
EndpointChannelManager& channel_manager,
|
||||
Pcp pcp = Pcp::kP2pStar);
|
||||
|
||||
protected:
|
||||
std::vector<proto::connections::Medium> GetConnectionMediumsByPriority()
|
||||
override;
|
||||
proto::connections::Medium GetDefaultUpgradeMedium() override;
|
||||
|
||||
bool CanSendOutgoingConnection(ClientProxy* client) const override;
|
||||
bool CanReceiveIncomingConnection(ClientProxy* client) const override;
|
||||
|
||||
private:
|
||||
Mediums* mediums_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,282 @@
|
||||
#ifndef CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_
|
||||
#define CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core_v2/internal/client_proxy.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/internal_payload.h"
|
||||
#include "core_v2/listeners.h"
|
||||
#include "core_v2/payload.h"
|
||||
#include "core_v2/status.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/atomic_boolean.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class PayloadManager : public EndpointManager::FrameProcessor {
|
||||
public:
|
||||
using EndpointIds = std::vector<std::string>;
|
||||
constexpr static const absl::Duration kWaitCloseTimeout =
|
||||
absl::Milliseconds(5000);
|
||||
|
||||
explicit PayloadManager(EndpointManager& endpoint_manager);
|
||||
~PayloadManager() override;
|
||||
|
||||
void SendPayload(ClientProxy* client, const EndpointIds& endpoint_ids,
|
||||
Payload payload);
|
||||
Status CancelPayload(ClientProxy* client, Payload::Id payload_id);
|
||||
|
||||
// @EndpointManagerReaderThread
|
||||
void OnIncomingFrame(OfflineFrame& offline_frame,
|
||||
const std::string& from_endpoint_id,
|
||||
ClientProxy* to_client,
|
||||
proto::connections::Medium current_medium) override;
|
||||
|
||||
// @EndpointManagerThread
|
||||
void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id,
|
||||
CountDownLatch* barrier) override;
|
||||
|
||||
private:
|
||||
// Information about an endpoint for a particular payload.
|
||||
struct EndpointInfo {
|
||||
// Status set for the endpoint out-of-band via a ControlMessage.
|
||||
enum class Status {
|
||||
kUnknown,
|
||||
kAvailable,
|
||||
kCanceled,
|
||||
kError,
|
||||
};
|
||||
|
||||
void SetStatusFromControlMessage(
|
||||
const PayloadTransferFrame::ControlMessage& control_message);
|
||||
|
||||
static Status ControlMessageEventToEndpointInfoStatus(
|
||||
PayloadTransferFrame::ControlMessage::EventType event);
|
||||
|
||||
std::string id;
|
||||
Status status = Status::kUnknown;
|
||||
std::int64_t offset = 0;
|
||||
};
|
||||
|
||||
// Tracks state for an InternalPayload and the endpoints associated with it.
|
||||
class PendingPayload {
|
||||
public:
|
||||
PendingPayload(std::unique_ptr<InternalPayload> internal_payload,
|
||||
const EndpointIds& endpoint_ids, bool is_incoming);
|
||||
PendingPayload(PendingPayload&&) = default;
|
||||
PendingPayload& operator=(PendingPayload&&) = default;
|
||||
|
||||
~PendingPayload() { Close(); }
|
||||
|
||||
Payload::Id GetId() const;
|
||||
|
||||
InternalPayload* GetInternalPayload();
|
||||
|
||||
bool IsLocallyCanceled() const;
|
||||
void MarkLocallyCanceled();
|
||||
bool IsIncoming() const;
|
||||
|
||||
// Gets the EndpointInfo objects for the endpoints (still) associated with
|
||||
// this payload.
|
||||
std::vector<const EndpointInfo*> GetEndpoints() const
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Returns the EndpointInfo for a given endpoint ID. Returns null if the
|
||||
// endpoint is not associated with this payload.
|
||||
EndpointInfo* GetEndpoint(const std::string& endpoint_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Removes the given endpoints, e.g. on error.
|
||||
void RemoveEndpoints(const EndpointIds& endpoint_ids_to_remove)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets the status for a particular endpoint.
|
||||
void SetEndpointStatusFromControlMessage(
|
||||
const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::ControlMessage& control_message)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets the offset for a particular endpoint.
|
||||
void SetOffsetForEndpoint(const std::string& endpoint_id,
|
||||
std::int64_t offset) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Closes internal_payload_ and triggers close_event_.
|
||||
// Close is called when a pending peyload does not have associated
|
||||
// endpoints.
|
||||
void Close();
|
||||
|
||||
// Waits for close_event_ or for timeout to happen.
|
||||
// Returns true, if event happened, false otherwise.
|
||||
bool WaitForClose();
|
||||
bool IsClosed();
|
||||
|
||||
private:
|
||||
mutable Mutex mutex_;
|
||||
bool is_incoming_;
|
||||
AtomicBoolean is_locally_canceled_{false};
|
||||
CountDownLatch close_event_{1};
|
||||
std::unique_ptr<InternalPayload> internal_payload_;
|
||||
absl::flat_hash_map<std::string, EndpointInfo> endpoints_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
// Tracks and manages PendingPayload objects in a synchronized manner.
|
||||
class PendingPayloads {
|
||||
public:
|
||||
PendingPayloads() = default;
|
||||
~PendingPayloads() = default;
|
||||
|
||||
void StartTrackingPayload(Payload::Id payload_id,
|
||||
std::unique_ptr<PendingPayload> pending_payload)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::unique_ptr<PendingPayload> StopTrackingPayload(Payload::Id payload_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
PendingPayload* GetPayload(Payload::Id payload_id) const
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::vector<Payload::Id> GetAllPayloads() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
mutable Mutex mutex_;
|
||||
absl::flat_hash_map<Payload::Id, std::unique_ptr<PendingPayload>>
|
||||
pending_payloads_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
using Endpoints = std::vector<const EndpointInfo*>;
|
||||
static std::string ToString(const EndpointIds& endpoint_ids);
|
||||
static std::string ToString(const Endpoints& endpoints);
|
||||
|
||||
// Splits the endpoints for this payload by availability.
|
||||
// Returns a pair of lists of EndpointInfo*, with the first being the list of
|
||||
// still-available endpoints, and the second for unavailable endpoints.
|
||||
static std::pair<Endpoints, Endpoints> GetAvailableAndUnavailableEndpoints(
|
||||
const PendingPayload& pending_payload);
|
||||
|
||||
// Converts list of EndpointInfo to list of Endpoint ids.
|
||||
// Returns list of endpoint ids.
|
||||
static EndpointIds EndpointsToEndpointIds(const Endpoints& endpoints);
|
||||
|
||||
bool SendPayloadLoop(ClientProxy* client, PendingPayload& pending_payload,
|
||||
PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t& next_chunk_offset);
|
||||
void SendClientCallbacksForFinishedIncomingPayloadRunnable(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t offset_bytes, proto::connections::PayloadStatus status);
|
||||
|
||||
// Converts the status of an endpoint that's been set out-of-band via a remote
|
||||
// ControlMessage to the PayloadStatus for handling of that endpoint-payload
|
||||
// pair.
|
||||
static proto::connections::PayloadStatus EndpointInfoStatusToPayloadStatus(
|
||||
EndpointInfo::Status status);
|
||||
// Converts a ControlMessage::EventType for a particular payload to a
|
||||
// PayloadStatus. Called when we've received a ControlMessage with this event
|
||||
// from a remote endpoint; thus the PayloadStatuses are REMOTE_*.
|
||||
static proto::connections::PayloadStatus ControlMessageEventToPayloadStatus(
|
||||
PayloadTransferFrame::ControlMessage::EventType event);
|
||||
static PayloadProgressInfo::Status PayloadStatusToTransferUpdateStatus(
|
||||
proto::connections::PayloadStatus status);
|
||||
|
||||
PayloadTransferFrame::PayloadHeader CreatePayloadHeader(
|
||||
const InternalPayload& payload);
|
||||
PayloadTransferFrame::PayloadChunk CreatePayloadChunk(std::int64_t offset,
|
||||
ByteArray body);
|
||||
|
||||
PendingPayload* CreateIncomingPayload(const PayloadTransferFrame& frame,
|
||||
const std::string& endpoint_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Payload::Id CreateOutgoingPayload(Payload payload,
|
||||
const EndpointIds& endpoint_ids)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void SendClientCallbacksForFinishedOutgoingPayload(
|
||||
ClientProxy* client, const EndpointIds& finished_endpoint_ids,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t num_bytes_successfully_transferred,
|
||||
proto::connections::PayloadStatus status);
|
||||
void SendClientCallbacksForFinishedIncomingPayload(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t offset_bytes, proto::connections::PayloadStatus status);
|
||||
|
||||
void SendControlMessage(
|
||||
const EndpointIds& endpoint_ids,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t num_bytes_successfully_transferred,
|
||||
PayloadTransferFrame::ControlMessage::EventType event_type);
|
||||
|
||||
// Handles a finished outgoing payload for the given endpointIds. All statuses
|
||||
// except for SUCCESS are handled here.
|
||||
void HandleFinishedOutgoingPayload(
|
||||
ClientProxy* client, const EndpointIds& finished_endpoint_ids,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t num_bytes_successfully_transferred,
|
||||
proto::connections::PayloadStatus status =
|
||||
proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS);
|
||||
void HandleFinishedIncomingPayload(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int64_t offset_bytes, proto::connections::PayloadStatus status);
|
||||
|
||||
void HandleSuccessfulOutgoingChunk(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset,
|
||||
std::int64_t payload_chunk_body_size);
|
||||
void HandleSuccessfulIncomingChunk(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset,
|
||||
std::int64_t payload_chunk_body_size);
|
||||
|
||||
void ProcessDataPacket(ClientProxy* to_client,
|
||||
const std::string& from_endpoint_id,
|
||||
PayloadTransferFrame& payload_transfer_frame);
|
||||
void ProcessControlPacket(ClientProxy* to_client,
|
||||
const std::string& from_endpoint_id,
|
||||
PayloadTransferFrame& payload_transfer_frame);
|
||||
|
||||
// @PayloadStatusUpdateThread
|
||||
void NotifyClientOfIncomingPayloadProgressInfo(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& payload_transfer_update);
|
||||
|
||||
SingleThreadExecutor* GetOutgoingPayloadExecutor(Payload::Type payload_type);
|
||||
|
||||
void RunOnStatusUpdateThread(std::function<void()> runnable);
|
||||
bool NotifyShutdown() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void DestroyPendingPayload(Payload::Id payload_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
PendingPayload* GetPayload(Payload::Id payload_id) const
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void CancelAllPayloads() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
EndpointManager::FrameProcessor::Handle handle_;
|
||||
AtomicBoolean shutdown_{false};
|
||||
std::unique_ptr<CountDownLatch> shutdown_barrier_;
|
||||
int send_payload_count_ = 0;
|
||||
PendingPayloads pending_payloads_ ABSL_GUARDED_BY(mutex_);
|
||||
SingleThreadExecutor bytes_payload_executor_;
|
||||
SingleThreadExecutor file_payload_executor_;
|
||||
SingleThreadExecutor stream_payload_executor_;
|
||||
SingleThreadExecutor payload_status_update_executor_;
|
||||
|
||||
EndpointManager* endpoint_manager_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_
|
||||
@@ -0,0 +1,278 @@
|
||||
#include "core_v2/internal/payload_manager.h"
|
||||
|
||||
#include "core_v2/internal/simulation_user.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/pipe.h"
|
||||
#include "platform_v2/public/system_clock.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kServiceId = "service-id";
|
||||
constexpr absl::string_view kDeviceA = "device-a";
|
||||
constexpr absl::string_view kDeviceB = "device-b";
|
||||
constexpr absl::string_view kMessage = "message";
|
||||
constexpr absl::Duration kProgressTimeout = absl::Milliseconds(1000);
|
||||
constexpr absl::Duration kDefaultTimeout = absl::Milliseconds(1000);
|
||||
|
||||
class PayloadSimulationUser : public SimulationUser {
|
||||
public:
|
||||
explicit PayloadSimulationUser(absl::string_view name)
|
||||
: SimulationUser(std::string(name)) {}
|
||||
~PayloadSimulationUser() override {
|
||||
// SystemClock::Sleep(kDefaultTimeout);
|
||||
}
|
||||
|
||||
Payload& GetPayload() { return payload_; }
|
||||
void SendPayload(Payload payload) {
|
||||
sender_payload_id_ = payload.GetId();
|
||||
pm_.SendPayload(&client_, {discovered_.endpoint_id}, std::move(payload));
|
||||
}
|
||||
|
||||
Status CancelPayload() {
|
||||
if (sender_payload_id_) {
|
||||
return pm_.CancelPayload(&client_, sender_payload_id_);
|
||||
} else {
|
||||
return pm_.CancelPayload(&client_, payload_.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
bool IsConnected() const {
|
||||
return client_.IsConnectedToEndpoint(discovered_.endpoint_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
Payload::Id sender_payload_id_ = 0;
|
||||
};
|
||||
|
||||
class PayloadManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
PayloadManagerTest() { env_.Stop(); }
|
||||
|
||||
bool SetupConnection(PayloadSimulationUser& user_a,
|
||||
PayloadSimulationUser& user_b) {
|
||||
user_a.StartAdvertising(std::string(kServiceId), &connection_latch_);
|
||||
user_b.StartDiscovery(std::string(kServiceId), &discovery_latch_);
|
||||
EXPECT_TRUE(discovery_latch_.Await(kDefaultTimeout).result());
|
||||
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
|
||||
EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName());
|
||||
EXPECT_FALSE(user_b.GetDiscovered().endpoint_id.empty());
|
||||
NEARBY_LOG(INFO, "EP-B: [discovered] %s",
|
||||
user_b.GetDiscovered().endpoint_id.c_str());
|
||||
user_b.RequestConnection(&connection_latch_);
|
||||
EXPECT_TRUE(connection_latch_.Await(kDefaultTimeout).result());
|
||||
EXPECT_FALSE(user_a.GetDiscovered().endpoint_id.empty());
|
||||
NEARBY_LOG(INFO, "EP-A: [discovered] %s",
|
||||
user_a.GetDiscovered().endpoint_id.c_str());
|
||||
NEARBY_LOG(INFO, "Both users discovered their peers.");
|
||||
user_a.AcceptConnection(&accept_latch_);
|
||||
user_b.AcceptConnection(&accept_latch_);
|
||||
EXPECT_TRUE(accept_latch_.Await(kDefaultTimeout).result());
|
||||
NEARBY_LOG(INFO, "Both users reached connected state.");
|
||||
return user_a.IsConnected() && user_b.IsConnected();
|
||||
}
|
||||
|
||||
CountDownLatch discovery_latch_{1};
|
||||
CountDownLatch connection_latch_{2};
|
||||
CountDownLatch accept_latch_{2};
|
||||
CountDownLatch payload_latch_{1};
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_F(PayloadManagerTest, CanCreateOne) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PayloadManagerTest, CanCreateMultiple) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
PayloadSimulationUser user_b(kDeviceB);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PayloadManagerTest, CanSendBytePayload) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
PayloadSimulationUser user_b(kDeviceB);
|
||||
ASSERT_TRUE(SetupConnection(user_a, user_b));
|
||||
|
||||
user_a.ExpectPayload(payload_latch_);
|
||||
user_b.SendPayload(Payload(ByteArray{std::string(kMessage)}));
|
||||
EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
|
||||
EXPECT_EQ(user_a.GetPayload().AsBytes(), ByteArray(std::string(kMessage)));
|
||||
NEARBY_LOG(INFO, "Test completed.");
|
||||
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PayloadManagerTest, CanSendStreamPayload) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
PayloadSimulationUser user_b(kDeviceB);
|
||||
ASSERT_TRUE(SetupConnection(user_a, user_b));
|
||||
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
OutputStream& tx = pipe->GetOutputStream();
|
||||
|
||||
user_a.ExpectPayload(payload_latch_);
|
||||
const ByteArray message{std::string(kMessage)};
|
||||
// The first write to the output stream will send the first PAYLOAD_TRANSFER
|
||||
// packet with payload info and message data.
|
||||
tx.Write(message);
|
||||
|
||||
user_b.SendPayload(Payload([pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}));
|
||||
ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
|
||||
ASSERT_NE(user_a.GetPayload().AsStream(), nullptr);
|
||||
InputStream& rx = *user_a.GetPayload().AsStream();
|
||||
NEARBY_LOG(INFO, "Stream extracted.");
|
||||
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[&message](const PayloadProgressInfo& info) {
|
||||
return info.bytes_transferred >= message.size();
|
||||
},
|
||||
kProgressTimeout));
|
||||
ByteArray result = rx.Read(Pipe::kChunkSize).result();
|
||||
EXPECT_EQ(result, message);
|
||||
NEARBY_LOG(INFO, "Packet 1 handled.");
|
||||
|
||||
tx.Write(message);
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[&message](const PayloadProgressInfo& info) {
|
||||
return info.bytes_transferred >= 2 * message.size();
|
||||
},
|
||||
kProgressTimeout));
|
||||
ByteArray result2 = rx.Read(Pipe::kChunkSize).result();
|
||||
EXPECT_EQ(result2, message);
|
||||
NEARBY_LOG(INFO, "Packet 2 handled.");
|
||||
|
||||
rx.Close();
|
||||
tx.Close();
|
||||
NEARBY_LOG(INFO, "Test completed.");
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
PayloadSimulationUser user_b(kDeviceB);
|
||||
ASSERT_TRUE(SetupConnection(user_a, user_b));
|
||||
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
OutputStream& tx = pipe->GetOutputStream();
|
||||
|
||||
user_a.ExpectPayload(payload_latch_);
|
||||
const ByteArray message{std::string(kMessage)};
|
||||
tx.Write(message);
|
||||
|
||||
user_b.SendPayload(Payload([pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}));
|
||||
ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
|
||||
ASSERT_NE(user_a.GetPayload().AsStream(), nullptr);
|
||||
InputStream& rx = *user_a.GetPayload().AsStream();
|
||||
NEARBY_LOG(INFO, "Stream extracted.");
|
||||
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[&message](const PayloadProgressInfo& info) {
|
||||
return info.bytes_transferred >= message.size();
|
||||
},
|
||||
kProgressTimeout));
|
||||
ByteArray result = rx.Read(Pipe::kChunkSize).result();
|
||||
EXPECT_EQ(result, message);
|
||||
NEARBY_LOG(INFO, "Packet 1 handled.");
|
||||
|
||||
EXPECT_EQ(user_a.CancelPayload(), Status{Status::kSuccess});
|
||||
NEARBY_LOG(INFO, "Stream canceled on receiver side.");
|
||||
|
||||
// Sender will only handle cancel event if it is sending.
|
||||
// Once cancel is handled, write will fail.
|
||||
int count = 0;
|
||||
while (true) {
|
||||
if (!tx.Write(message).Ok()) break;
|
||||
SystemClock::Sleep(kDefaultTimeout);
|
||||
count++;
|
||||
}
|
||||
ASSERT_LE(count, 10);
|
||||
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[status = PayloadProgressInfo::Status::kCanceled](
|
||||
const PayloadProgressInfo& info) { return info.status == status; },
|
||||
kProgressTimeout));
|
||||
NEARBY_LOG(INFO, "Stream cancelation recevied.");
|
||||
|
||||
tx.Close();
|
||||
rx.Close();
|
||||
|
||||
NEARBY_LOG(INFO, "Test completed.");
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) {
|
||||
env_.Start();
|
||||
PayloadSimulationUser user_a(kDeviceA);
|
||||
PayloadSimulationUser user_b(kDeviceB);
|
||||
ASSERT_TRUE(SetupConnection(user_a, user_b));
|
||||
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
OutputStream& tx = pipe->GetOutputStream();
|
||||
|
||||
user_a.ExpectPayload(payload_latch_);
|
||||
const ByteArray message{std::string(kMessage)};
|
||||
tx.Write(message);
|
||||
|
||||
user_b.SendPayload(Payload([pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}));
|
||||
ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result());
|
||||
ASSERT_NE(user_a.GetPayload().AsStream(), nullptr);
|
||||
InputStream& rx = *user_a.GetPayload().AsStream();
|
||||
NEARBY_LOG(INFO, "Stream extracted.");
|
||||
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[&message](const PayloadProgressInfo& info) {
|
||||
return info.bytes_transferred >= message.size();
|
||||
},
|
||||
kProgressTimeout));
|
||||
ByteArray result = rx.Read(Pipe::kChunkSize).result();
|
||||
EXPECT_EQ(result, message);
|
||||
NEARBY_LOG(INFO, "Packet 1 handled.");
|
||||
|
||||
EXPECT_EQ(user_b.CancelPayload(), Status{Status::kSuccess});
|
||||
NEARBY_LOG(INFO, "Stream canceled on sender side.");
|
||||
|
||||
// Sender will only handle cancel event if it is sending.
|
||||
// Once cancel is handled, write will fail.
|
||||
int count = 0;
|
||||
while (true) {
|
||||
if (!tx.Write(message).Ok()) break;
|
||||
SystemClock::Sleep(kDefaultTimeout);
|
||||
count++;
|
||||
}
|
||||
ASSERT_LE(count, 10);
|
||||
|
||||
EXPECT_TRUE(user_a.WaitForProgress(
|
||||
[status = PayloadProgressInfo::Status::kCanceled](
|
||||
const PayloadProgressInfo& info) { return info.status == status; },
|
||||
kProgressTimeout));
|
||||
NEARBY_LOG(INFO, "Stream cancelation recevied.");
|
||||
|
||||
tx.Close();
|
||||
rx.Close();
|
||||
|
||||
NEARBY_LOG(INFO, "Test completed.");
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -30,6 +30,20 @@ namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
inline Pcp StrategyToPcp(Strategy strategy) {
|
||||
if (strategy == Strategy::kP2pCluster) return Pcp::kP2pCluster;
|
||||
if (strategy == Strategy::kP2pStar) return Pcp::kP2pStar;
|
||||
if (strategy == Strategy::kP2pPointToPoint) return Pcp::kP2pPointToPoint;
|
||||
return Pcp::kUnknown;
|
||||
}
|
||||
|
||||
inline Strategy PcpToStrategy(Pcp pcp) {
|
||||
if (pcp == Pcp::kP2pCluster) return Strategy::kP2pCluster;
|
||||
if (pcp == Pcp::kP2pStar) return Strategy::kP2pStar;
|
||||
if (pcp == Pcp::kP2pPointToPoint) return Strategy::kP2pPointToPoint;
|
||||
return Strategy::kNone;
|
||||
}
|
||||
|
||||
// Defines the set of methods that need to be implemented to handle the
|
||||
// per-PCP-specific operations in the OfflineServiceController.
|
||||
//
|
||||
@@ -52,8 +66,7 @@ class PcpHandler {
|
||||
// We have been asked by the client to start advertising. Once we successfully
|
||||
// start advertising, we'll change the ClientProxy's state.
|
||||
// ConnectionListener (info.listener) will be notified in case of any event.
|
||||
// See
|
||||
// cpp/core_v2/listeners.h;bpv=1;bpt=1;l=71?gsn=ConnectionListener
|
||||
// See cpp/core_v2/listeners.h
|
||||
virtual Status StartAdvertising(ClientProxy* client,
|
||||
const std::string& service_id,
|
||||
const ConnectionOptions& options,
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "core_v2/internal/pcp_manager.h"
|
||||
|
||||
#include "core_v2/internal/p2p_cluster_pcp_handler.h"
|
||||
#include "core_v2/internal/p2p_point_to_point_pcp_handler.h"
|
||||
#include "core_v2/internal/p2p_star_pcp_handler.h"
|
||||
#include "core_v2/internal/pcp_handler.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
PcpManager::PcpManager(Mediums& mediums,
|
||||
EndpointChannelManager& channel_manager,
|
||||
EndpointManager& endpoint_manager) {
|
||||
handlers_[Pcp::kP2pCluster] = std::make_unique<P2pClusterPcpHandler>(
|
||||
mediums, &endpoint_manager, &channel_manager);
|
||||
handlers_[Pcp::kP2pStar] = std::make_unique<P2pStarPcpHandler>(
|
||||
mediums, endpoint_manager, channel_manager);
|
||||
handlers_[Pcp::kP2pPointToPoint] =
|
||||
std::make_unique<P2pPointToPointPcpHandler>(mediums, endpoint_manager,
|
||||
channel_manager);
|
||||
}
|
||||
|
||||
Status PcpManager::StartAdvertising(ClientProxy* client,
|
||||
const string& service_id,
|
||||
const ConnectionOptions& options,
|
||||
const ConnectionRequestInfo& info) {
|
||||
if (!SetCurrentPcpHandler(options.strategy)) {
|
||||
return {Status::kError};
|
||||
}
|
||||
|
||||
return current_->StartAdvertising(client, service_id, options, info);
|
||||
}
|
||||
|
||||
void PcpManager::StopAdvertising(ClientProxy* client) {
|
||||
if (current_) {
|
||||
current_->StopAdvertising(client);
|
||||
}
|
||||
}
|
||||
|
||||
Status PcpManager::StartDiscovery(ClientProxy* client, const string& service_id,
|
||||
const ConnectionOptions& options,
|
||||
DiscoveryListener listener) {
|
||||
if (!SetCurrentPcpHandler(options.strategy)) {
|
||||
return {Status::kError};
|
||||
}
|
||||
|
||||
return current_->StartDiscovery(client, service_id, options,
|
||||
std::move(listener));
|
||||
}
|
||||
|
||||
void PcpManager::StopDiscovery(ClientProxy* client) {
|
||||
if (current_) {
|
||||
current_->StopDiscovery(client);
|
||||
}
|
||||
}
|
||||
|
||||
Status PcpManager::RequestConnection(ClientProxy* client,
|
||||
const string& endpoint_id,
|
||||
const ConnectionRequestInfo& info) {
|
||||
if (!current_) {
|
||||
return {Status::kOutOfOrderApiCall};
|
||||
}
|
||||
|
||||
return current_->RequestConnection(client, endpoint_id, info);
|
||||
}
|
||||
|
||||
Status PcpManager::AcceptConnection(ClientProxy* client,
|
||||
const string& endpoint_id,
|
||||
const PayloadListener& payload_listener) {
|
||||
if (!current_) {
|
||||
return {Status::kOutOfOrderApiCall};
|
||||
}
|
||||
|
||||
return current_->AcceptConnection(client, endpoint_id, payload_listener);
|
||||
}
|
||||
|
||||
Status PcpManager::RejectConnection(ClientProxy* client,
|
||||
const string& endpoint_id) {
|
||||
if (!current_) {
|
||||
return {Status::kOutOfOrderApiCall};
|
||||
}
|
||||
|
||||
return current_->RejectConnection(client, endpoint_id);
|
||||
}
|
||||
|
||||
bool PcpManager::SetCurrentPcpHandler(Strategy strategy) {
|
||||
current_ = GetPcpHandler(StrategyToPcp(strategy));
|
||||
|
||||
if (!current_) {
|
||||
NEARBY_LOG(ERROR, "Failed to set current PCP handler: strategy=%s",
|
||||
strategy.GetName().c_str());
|
||||
}
|
||||
|
||||
return current_;
|
||||
}
|
||||
|
||||
PcpHandler* PcpManager::GetPcpHandler(Pcp pcp) const {
|
||||
auto item = handlers_.find(pcp);
|
||||
return item != handlers_.end() ? item->second.get() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef CORE_V2_INTERNAL_PCP_MANAGER_H_
|
||||
#define CORE_V2_INTERNAL_PCP_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/client_proxy.h"
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/mediums/mediums.h"
|
||||
#include "core_v2/internal/pcp_handler.h"
|
||||
#include "core_v2/listeners.h"
|
||||
#include "core_v2/options.h"
|
||||
#include "core_v2/status.h"
|
||||
#include "core_v2/strategy.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Manages all known PcpHandler implementations, delegating operations to the
|
||||
// appropriate one as per the parameters passed in.
|
||||
//
|
||||
// This will only ever be used by the OfflineServiceController, which has all
|
||||
// of its entrypoints invoked serially, so there's no synchronization needed.
|
||||
// Public method semantics matches definition in the
|
||||
// cpp/core_v2/internal/service_controller.h
|
||||
class PcpManager {
|
||||
public:
|
||||
PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager,
|
||||
EndpointManager& endpoint_manager);
|
||||
~PcpManager() = default;
|
||||
|
||||
Status StartAdvertising(ClientProxy* client_proxy, const string& service_id,
|
||||
const ConnectionOptions& options,
|
||||
const ConnectionRequestInfo& info);
|
||||
void StopAdvertising(ClientProxy* client_proxy);
|
||||
|
||||
Status StartDiscovery(ClientProxy* client_proxy, const string& service_id,
|
||||
const ConnectionOptions& options,
|
||||
DiscoveryListener listener);
|
||||
void StopDiscovery(ClientProxy* client_proxy);
|
||||
|
||||
Status RequestConnection(ClientProxy* client_proxy, const string& endpoint_id,
|
||||
const ConnectionRequestInfo& info);
|
||||
Status AcceptConnection(ClientProxy* client_proxy, const string& endpoint_id,
|
||||
const PayloadListener& payload_listener);
|
||||
Status RejectConnection(ClientProxy* client_proxy, const string& endpoint_id);
|
||||
|
||||
proto::connections::Medium GetBandwidthUpgradeMedium();
|
||||
|
||||
private:
|
||||
bool SetCurrentPcpHandler(Strategy strategy);
|
||||
PcpHandler* GetPcpHandler(Pcp pcp) const;
|
||||
|
||||
absl::flat_hash_map<Pcp, std::unique_ptr<PcpHandler>> handlers_;
|
||||
PcpHandler* current_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_PCP_MANAGER_H_
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "core_v2/internal/pcp_manager.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/simulation_user.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr char kServiceId[] = "service-id";
|
||||
constexpr char kDeviceA[] = "device-A";
|
||||
constexpr char kDeviceB[] = "device-B";
|
||||
|
||||
class PcpManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
PcpManagerTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_F(PcpManagerTest, CanCreateOne) {
|
||||
env_.Start();
|
||||
SimulationUser user(kDeviceA);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanCreateMany) {
|
||||
env_.Start();
|
||||
SimulationUser user_a(kDeviceA);
|
||||
SimulationUser user_b(kDeviceB);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanAdvertise) {
|
||||
env_.Start();
|
||||
SimulationUser user_a(kDeviceA);
|
||||
SimulationUser user_b(kDeviceB);
|
||||
user_a.StartAdvertising(kServiceId, nullptr);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanDiscover) {
|
||||
env_.Start();
|
||||
SimulationUser user_a("device-a");
|
||||
SimulationUser user_b("device-b");
|
||||
user_a.StartAdvertising(kServiceId, nullptr);
|
||||
CountDownLatch latch(1);
|
||||
user_b.StartDiscovery(kServiceId, &latch);
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
|
||||
EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanConnect) {
|
||||
env_.Start();
|
||||
SimulationUser user_a("device-a");
|
||||
SimulationUser user_b("device-b");
|
||||
CountDownLatch discovery_latch(1);
|
||||
CountDownLatch connection_latch(2);
|
||||
user_a.StartAdvertising(kServiceId, &connection_latch);
|
||||
user_b.StartDiscovery(kServiceId, &discovery_latch);
|
||||
EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
|
||||
EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName());
|
||||
user_b.RequestConnection(&connection_latch);
|
||||
EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanAccept) {
|
||||
env_.Start();
|
||||
SimulationUser user_a("device-a");
|
||||
SimulationUser user_b("device-b");
|
||||
CountDownLatch discovery_latch(1);
|
||||
CountDownLatch connection_latch(2);
|
||||
CountDownLatch accept_latch(2);
|
||||
user_a.StartAdvertising(kServiceId, &connection_latch);
|
||||
user_b.StartDiscovery(kServiceId, &discovery_latch);
|
||||
EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
|
||||
EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName());
|
||||
user_b.RequestConnection(&connection_latch);
|
||||
EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result());
|
||||
user_a.AcceptConnection(&accept_latch);
|
||||
user_b.AcceptConnection(&accept_latch);
|
||||
EXPECT_TRUE(accept_latch.Await(absl::Milliseconds(1000)).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(PcpManagerTest, CanReject) {
|
||||
env_.Start();
|
||||
SimulationUser user_a("device-a");
|
||||
SimulationUser user_b("device-b");
|
||||
CountDownLatch discovery_latch(1);
|
||||
CountDownLatch connection_latch(2);
|
||||
CountDownLatch reject_latch(1);
|
||||
user_a.StartAdvertising(kServiceId, &connection_latch);
|
||||
user_b.StartDiscovery(kServiceId, &discovery_latch);
|
||||
EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId);
|
||||
EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName());
|
||||
user_b.RequestConnection(&connection_latch);
|
||||
EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result());
|
||||
user_b.ExpectRejectedConnection(reject_latch);
|
||||
user_a.RejectConnection(nullptr);
|
||||
EXPECT_TRUE(reject_latch.Await(absl::Milliseconds(1000)).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -39,8 +39,7 @@ namespace connections {
|
||||
// The rest of arguments have the same meaning as the corresponding
|
||||
// methods in the definition of location::nearby::Core API.
|
||||
//
|
||||
// See details here:
|
||||
// cpp/core_v2/core.h
|
||||
// See details here: cpp/core_v2/core.h
|
||||
class ServiceController {
|
||||
public:
|
||||
virtual ~ServiceController() = default;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "core_v2/options.h"
|
||||
#include "core_v2/params.h"
|
||||
#include "core_v2/payload.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "absl/time/clock.h"
|
||||
|
||||
namespace location {
|
||||
@@ -30,7 +31,7 @@ namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
ServiceControllerRouter::~ServiceControllerRouter() {
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO, "ServiceControllerRouter going down.");
|
||||
|
||||
// And make sure that cleanup is the last thing we do.
|
||||
serializer_.Shutdown();
|
||||
@@ -142,7 +143,10 @@ void ServiceControllerRouter::AcceptConnection(ClientProxy* client,
|
||||
}
|
||||
|
||||
if (client->HasLocalEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): logging
|
||||
NEARBY_LOG(INFO,
|
||||
"[ServiceControllerRouter:Accept]: Client has local "
|
||||
"endpoint responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
callback.result_cb({Status::kOutOfOrderApiCall});
|
||||
return;
|
||||
}
|
||||
@@ -168,7 +172,10 @@ void ServiceControllerRouter::RejectConnection(ClientProxy* client,
|
||||
}
|
||||
|
||||
if (client->HasLocalEndpointResponded(endpoint_id)) {
|
||||
// TODO(tracyzhou): logging
|
||||
NEARBY_LOG(INFO,
|
||||
"[ServiceControllerRouter:Reject]: Client has local "
|
||||
"endpoint responded; id=%s",
|
||||
endpoint_id.c_str());
|
||||
callback.result_cb({Status::kOutOfOrderApiCall});
|
||||
return;
|
||||
}
|
||||
@@ -278,8 +285,9 @@ void ServiceControllerRouter::ClientDisconnecting(
|
||||
RouteToServiceController([this, client, callback]() {
|
||||
if (ClientHasAcquiredServiceController(client)) {
|
||||
DoneWithStrategySessionForClient(client);
|
||||
// Log the completion of this client's connection.
|
||||
// TODO(tracyzhou): Add logging.
|
||||
NEARBY_LOG(INFO,
|
||||
"[ServiceControllerRouter:Disconnect]: Client has completed "
|
||||
"the client's connection");
|
||||
}
|
||||
callback.result_cb({Status::kSuccess});
|
||||
});
|
||||
@@ -312,14 +320,18 @@ Status ServiceControllerRouter::AcquireServiceControllerForClient(
|
||||
bool is_the_only_client_of_service_controller =
|
||||
clients_.size() == 1 && ClientHasAcquiredServiceController(client);
|
||||
if (!is_the_only_client_of_service_controller) {
|
||||
// TODO(tracyzhou): logging
|
||||
NEARBY_LOG(INFO,
|
||||
"[ServiceControllerRouter:AcquireServiceControllerForClient]: "
|
||||
"Client has already active strategy.");
|
||||
return {Status::kAlreadyHaveActiveStrategy};
|
||||
}
|
||||
|
||||
// If the client still has connected endpoints, they must disconnect before
|
||||
// they can switch.
|
||||
if (!client->GetConnectedEndpoints().empty()) {
|
||||
// TODO(tracyzhou): logging
|
||||
NEARBY_LOG(INFO,
|
||||
"[ServiceControllerRouter:AcquireServiceControllerForClient]: "
|
||||
"Client has connected endpoints.");
|
||||
return {Status::kOutOfOrderApiCall};
|
||||
}
|
||||
|
||||
@@ -383,7 +395,7 @@ bool ServiceControllerRouter::ClientHasConnectionToAtLeastOneEndpoint(
|
||||
Status ServiceControllerRouter::UpdateCurrentServiceControllerAndStrategy(
|
||||
Strategy strategy) {
|
||||
if (!strategy.IsValid()) {
|
||||
// TODO(tracyzhou): logging
|
||||
NEARBY_LOG(INFO, "Strategy is not valid.");
|
||||
return {Status::kError};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "core_v2/internal/simulation_user.h"
|
||||
|
||||
#include "core_v2/listeners.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/system_clock.h"
|
||||
#include "absl/functional/bind_front.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
void SimulationUser::OnConnectionInitiated(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info,
|
||||
bool is_outgoing) {
|
||||
if (is_outgoing) {
|
||||
NEARBY_LOG(INFO, "RequestConnection: initiated_cb called");
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "StartAdvertising: initiated_cb called");
|
||||
discovered_ = DiscoveredInfo{
|
||||
.endpoint_id = endpoint_id,
|
||||
.endpoint_name = name_,
|
||||
.service_id = service_id_,
|
||||
};
|
||||
}
|
||||
if (initiated_latch_) initiated_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnConnectionAccepted(const std::string& endpoint_id) {
|
||||
if (accept_latch_) accept_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnConnectionRejected(const std::string& endpoint_id,
|
||||
Status status) {
|
||||
if (reject_latch_) reject_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnEndpointFound(const std::string& endpoint_id,
|
||||
const std::string& endpoint_name,
|
||||
const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "Device discovered: id=%s", endpoint_id.c_str());
|
||||
discovered_ = DiscoveredInfo{
|
||||
.endpoint_id = endpoint_id,
|
||||
.endpoint_name = endpoint_name,
|
||||
.service_id = service_id,
|
||||
};
|
||||
if (found_latch_) found_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnEndpointLost(const std::string& endpoint_id) {
|
||||
if (lost_latch_) lost_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnPayload(const std::string& endpoint_id,
|
||||
Payload payload) {
|
||||
payload_ = std::move(payload);
|
||||
if (payload_latch_) payload_latch_->CountDown();
|
||||
}
|
||||
|
||||
void SimulationUser::OnPayloadProgress(const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& info) {
|
||||
MutexLock lock(&progress_mutex_);
|
||||
progress_info_ = info;
|
||||
if (future_ && predicate_ && predicate_(info)) future_->Set(true);
|
||||
}
|
||||
|
||||
bool SimulationUser::WaitForProgress(
|
||||
std::function<bool(const PayloadProgressInfo&)> predicate,
|
||||
absl::Duration timeout) {
|
||||
Future<bool> future;
|
||||
{
|
||||
MutexLock lock(&progress_mutex_);
|
||||
if (predicate(progress_info_)) return true;
|
||||
future_ = &future;
|
||||
predicate_ = std::move(predicate);
|
||||
}
|
||||
auto response = future.Get(timeout);
|
||||
{
|
||||
MutexLock lock(&progress_mutex_);
|
||||
future_ = nullptr;
|
||||
predicate_ = nullptr;
|
||||
}
|
||||
return response.ok() && response.result();
|
||||
}
|
||||
|
||||
void SimulationUser::StartAdvertising(const std::string& service_id,
|
||||
CountDownLatch* latch) {
|
||||
initiated_latch_ = latch;
|
||||
service_id_ = service_id;
|
||||
ConnectionListener listener = {
|
||||
.initiated_cb =
|
||||
std::bind(&SimulationUser::OnConnectionInitiated, this,
|
||||
std::placeholders::_1, std::placeholders::_2, false),
|
||||
.accepted_cb =
|
||||
absl::bind_front(&SimulationUser::OnConnectionAccepted, this),
|
||||
.rejected_cb =
|
||||
absl::bind_front(&SimulationUser::OnConnectionRejected, this),
|
||||
};
|
||||
EXPECT_TRUE(mgr_.StartAdvertising(&client_, service_id_, options_,
|
||||
{
|
||||
.name = name_,
|
||||
.listener = std::move(listener),
|
||||
})
|
||||
.Ok());
|
||||
}
|
||||
|
||||
void SimulationUser::StartDiscovery(const std::string& service_id,
|
||||
CountDownLatch* latch) {
|
||||
found_latch_ = latch;
|
||||
EXPECT_TRUE(
|
||||
mgr_.StartDiscovery(&client_, service_id, options_,
|
||||
{
|
||||
.endpoint_found_cb = absl::bind_front(
|
||||
&SimulationUser::OnEndpointFound, this),
|
||||
.endpoint_lost_cb = absl::bind_front(
|
||||
&SimulationUser::OnEndpointLost, this),
|
||||
})
|
||||
.Ok());
|
||||
}
|
||||
|
||||
void SimulationUser::RequestConnection(CountDownLatch* latch) {
|
||||
initiated_latch_ = latch;
|
||||
ConnectionListener listener = {
|
||||
.initiated_cb =
|
||||
std::bind(&SimulationUser::OnConnectionInitiated, this,
|
||||
std::placeholders::_1, std::placeholders::_2, true),
|
||||
.accepted_cb =
|
||||
absl::bind_front(&SimulationUser::OnConnectionAccepted, this),
|
||||
.rejected_cb =
|
||||
absl::bind_front(&SimulationUser::OnConnectionRejected, this),
|
||||
};
|
||||
EXPECT_TRUE(mgr_.RequestConnection(&client_, discovered_.endpoint_id,
|
||||
{
|
||||
.name = discovered_.endpoint_name,
|
||||
.listener = std::move(listener),
|
||||
})
|
||||
.Ok());
|
||||
}
|
||||
|
||||
void SimulationUser::AcceptConnection(CountDownLatch* latch) {
|
||||
accept_latch_ = latch;
|
||||
PayloadListener listener = {
|
||||
.payload_cb = absl::bind_front(&SimulationUser::OnPayload, this),
|
||||
.payload_progress_cb =
|
||||
absl::bind_front(&SimulationUser::OnPayloadProgress, this),
|
||||
};
|
||||
EXPECT_TRUE(mgr_.AcceptConnection(&client_, discovered_.endpoint_id,
|
||||
std::move(listener))
|
||||
.Ok());
|
||||
}
|
||||
|
||||
void SimulationUser::RejectConnection(CountDownLatch* latch) {
|
||||
reject_latch_ = latch;
|
||||
EXPECT_TRUE(mgr_.RejectConnection(&client_, discovered_.endpoint_id).Ok());
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,129 @@
|
||||
#ifndef CORE_V2_INTERNAL_SIMULATION_USER_H_
|
||||
#define CORE_V2_INTERNAL_SIMULATION_USER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/client_proxy.h"
|
||||
#include "core_v2/internal/endpoint_channel_manager.h"
|
||||
#include "core_v2/internal/endpoint_manager.h"
|
||||
#include "core_v2/internal/payload_manager.h"
|
||||
#include "core_v2/internal/pcp_manager.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/condition_variable.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// Test-only class to help run end-to-end simulations for nearby connections
|
||||
// protocol.
|
||||
//
|
||||
// This is a "standalone" version of PcpManager. It can run independently,
|
||||
// provided MediumEnvironment has adequate support for all medium types in use.
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class SimulationUser {
|
||||
public:
|
||||
struct DiscoveredInfo {
|
||||
std::string endpoint_id;
|
||||
std::string endpoint_name;
|
||||
std::string service_id;
|
||||
|
||||
bool Empty() const { return endpoint_id.empty(); }
|
||||
void Clear() { endpoint_id.clear(); }
|
||||
};
|
||||
|
||||
explicit SimulationUser(const std::string& device_name)
|
||||
: name_(device_name) {}
|
||||
virtual ~SimulationUser() = default;
|
||||
|
||||
// Calls PcpManager::StartAdvertising.
|
||||
// If latch is provided, will call latch->CountDown() in the initiated_cb
|
||||
// callback.
|
||||
void StartAdvertising(const std::string& service_id, CountDownLatch* latch);
|
||||
|
||||
// Calls PcpManager::StartDiscovery.
|
||||
// If latch is provided, will call latch->CountDown() in the endpoint_found_cb
|
||||
// callback.
|
||||
void StartDiscovery(const std::string& service_id, CountDownLatch* latch);
|
||||
|
||||
// Calls PcpManager::RequestConnection.
|
||||
// If latch is provided, latch->CountDown() will be called in the initiated_cb
|
||||
// callback.
|
||||
void RequestConnection(CountDownLatch* latch);
|
||||
|
||||
// Calls PcpManager::AcceptConnection.
|
||||
// If latch is provided, latch->CountDown() will be called in the accepted_cb
|
||||
// callback.
|
||||
void AcceptConnection(CountDownLatch* latch);
|
||||
|
||||
// Calls PcpManager::RejectConnection.
|
||||
// If latch is provided, latch->CountDown() will be called in the rejected_cb
|
||||
// callback.
|
||||
void RejectConnection(CountDownLatch* latch);
|
||||
|
||||
// Unlike acceptance, rejection does not have to be mutual, in order to work.
|
||||
// This method will allow to synchronize on the remote rejection, without
|
||||
// performing a local rejection.
|
||||
// latch.CountDown() will be called in the rejected_cb callback.
|
||||
void ExpectRejectedConnection(CountDownLatch& latch) {
|
||||
reject_latch_ = &latch;
|
||||
}
|
||||
|
||||
void ExpectPayload(CountDownLatch& latch) { payload_latch_ = &latch; }
|
||||
|
||||
const DiscoveredInfo& GetDiscovered() const { return discovered_; }
|
||||
std::string GetName() const { return name_; }
|
||||
|
||||
bool WaitForProgress(std::function<bool(const PayloadProgressInfo&)> pred,
|
||||
absl::Duration timeout);
|
||||
|
||||
protected:
|
||||
// ConnectionListener callbacks
|
||||
void OnConnectionInitiated(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info,
|
||||
bool is_outgoing);
|
||||
void OnConnectionAccepted(const std::string& endpoint_id);
|
||||
void OnConnectionRejected(const std::string& endpoint_id, Status status);
|
||||
|
||||
// DiscoveryListener callbacks
|
||||
void OnEndpointFound(const std::string& endpoint_id,
|
||||
const std::string& endpoint_name,
|
||||
const std::string& service_id);
|
||||
void OnEndpointLost(const std::string& endpoint_id);
|
||||
|
||||
// PayloadListener callbacks
|
||||
void OnPayload(const std::string& endpoint_id, Payload payload);
|
||||
void OnPayloadProgress(const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& info);
|
||||
|
||||
std::string service_id_;
|
||||
DiscoveredInfo discovered_;
|
||||
Mutex progress_mutex_;
|
||||
ConditionVariable progress_sync_{&progress_mutex_};
|
||||
PayloadProgressInfo progress_info_;
|
||||
Payload payload_;
|
||||
CountDownLatch* initiated_latch_ = nullptr;
|
||||
CountDownLatch* accept_latch_ = nullptr;
|
||||
CountDownLatch* reject_latch_ = nullptr;
|
||||
CountDownLatch* found_latch_ = nullptr;
|
||||
CountDownLatch* lost_latch_ = nullptr;
|
||||
CountDownLatch* payload_latch_ = nullptr;
|
||||
Future<bool>* future_ = nullptr;
|
||||
std::function<bool(const PayloadProgressInfo&)> predicate_;
|
||||
std::string name_;
|
||||
Mediums mediums_;
|
||||
ConnectionOptions options_{.strategy = Strategy::kP2pCluster};
|
||||
ClientProxy client_;
|
||||
EndpointChannelManager ecm_;
|
||||
EndpointManager em_{&ecm_};
|
||||
PcpManager mgr_{mediums_, ecm_, em_};
|
||||
PayloadManager pm_{em_};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_SIMULATION_USER_H_
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "core_v2/internal/webrtc_endpoint_channel.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
WebRtcEndpointChannel::WebRtcEndpointChannel(
|
||||
const std::string& channel_name, mediums::WebRtcSocketWrapper socket)
|
||||
: BaseEndpointChannel(channel_name, &socket.GetInputStream(),
|
||||
&socket.GetOutputStream()),
|
||||
webrtc_socket_(std::move(socket)) {}
|
||||
|
||||
proto::connections::Medium WebRtcEndpointChannel::GetMedium() const {
|
||||
return proto::connections::Medium::WEB_RTC;
|
||||
}
|
||||
|
||||
void WebRtcEndpointChannel::CloseImpl() {
|
||||
webrtc_socket_.Close();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include "core_v2/internal/base_endpoint_channel.h"
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class WebRtcEndpointChannel final : public BaseEndpointChannel {
|
||||
public:
|
||||
WebRtcEndpointChannel(const std::string& channel_name,
|
||||
mediums::WebRtcSocketWrapper webrtc_socket);
|
||||
|
||||
proto::connections::Medium GetMedium() const override;
|
||||
|
||||
private:
|
||||
void CloseImpl() override;
|
||||
|
||||
mediums::WebRtcSocketWrapper webrtc_socket_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "core_v2/internal/wifi_lan_endpoint_channel.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/wifi_lan.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
OutputStream* GetOutputStreamOrNull(WifiLanSocket& socket) {
|
||||
if (socket.GetRemoteWifiLanService().IsValid())
|
||||
return &socket.GetOutputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InputStream* GetInputStreamOrNull(WifiLanSocket& socket) {
|
||||
if (socket.GetRemoteWifiLanService().IsValid())
|
||||
return &socket.GetInputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WifiLanEndpointChannel::WifiLanEndpointChannel(const std::string& channel_name,
|
||||
WifiLanSocket socket)
|
||||
: BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket),
|
||||
GetOutputStreamOrNull(socket)),
|
||||
wifi_lan_socket_(std::move(socket)) {}
|
||||
|
||||
proto::connections::Medium WifiLanEndpointChannel::GetMedium() const {
|
||||
return proto::connections::Medium::WIFI_LAN;
|
||||
}
|
||||
|
||||
void WifiLanEndpointChannel::CloseImpl() {
|
||||
auto status = wifi_lan_socket_.Close();
|
||||
if (!status.Ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to close WifiLan socket: exception=%d",
|
||||
status.value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include "core_v2/internal/base_endpoint_channel.h"
|
||||
#include "platform_v2/public/wifi_lan.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class WifiLanEndpointChannel final : public BaseEndpointChannel {
|
||||
public:
|
||||
// Creates both outgoing and incoming WifiLan channels.
|
||||
WifiLanEndpointChannel(const std::string& channel_name,
|
||||
WifiLanSocket bluetooth_socket);
|
||||
|
||||
proto::connections::Medium GetMedium() const override;
|
||||
|
||||
private:
|
||||
void CloseImpl() override;
|
||||
|
||||
WifiLanSocket wifi_lan_socket_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_
|
||||
@@ -20,7 +20,9 @@
|
||||
#include <utility>
|
||||
|
||||
#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 {
|
||||
@@ -47,7 +49,8 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp,
|
||||
version_ = version;
|
||||
pcp_ = pcp;
|
||||
service_id_hash_ = service_id_hash;
|
||||
endpoint_id_ = std::string(endpoint_id);
|
||||
endpoint_id_ = endpoint_id;
|
||||
endpoint_name_ = endpoint_name;
|
||||
}
|
||||
|
||||
WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) {
|
||||
@@ -77,54 +80,63 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ = static_cast<Version>(
|
||||
(service_info_bytes.data()[0] & kVersionBitmask) >> kVersionShift);
|
||||
const char* service_info_bytes_read_ptr = service_info_bytes.data();
|
||||
switch (version_) {
|
||||
case Version::kV1:
|
||||
// The lower 5 bits of the V1 payload are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(*service_info_bytes_read_ptr & kPcpBitmask);
|
||||
service_info_bytes_read_ptr++;
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
// The next 32 bits are supposed to be the endpoint_id.
|
||||
endpoint_id_ =
|
||||
std::string(service_info_bytes_read_ptr, kEndpointIdLength);
|
||||
service_info_bytes_read_ptr += kEndpointIdLength;
|
||||
|
||||
// The next 24 bits are supposed to be the service_id_hash.
|
||||
service_id_hash_ =
|
||||
ByteArray(service_info_bytes_read_ptr, kServiceIdHashLength);
|
||||
service_info_bytes_read_ptr += kServiceIdHashLength;
|
||||
|
||||
// The next bits are supposed to be endpoint_name.
|
||||
// TODO(edwinwu): Implements it. Temp to set "found_device".
|
||||
endpoint_name_ = "found_device";
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(edwinwu): [ANALYTICIZE] This either represents corruption over
|
||||
// the air, or older versions of GmsCore intermingling with newer
|
||||
// ones.
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d",
|
||||
pcp_);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// TODO(edwinwu): [ANALYTICIZE] This either represents corruption over
|
||||
// the air, or older versions of GmsCore intermingling with newer ones.
|
||||
NEARBY_LOG(
|
||||
INFO, "Cannot deserialize WifiLanServiceInfo: unsupported Version %d",
|
||||
version_);
|
||||
break;
|
||||
if (service_info_bytes.size() > kMaxEndpointNameLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize WifiLanServiceInfo: expecting max %d raw "
|
||||
"bytes, got %" PRIu64,
|
||||
kMaxEndpointNameLength, service_info_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
BaseInputStream base_input_stream{service_info_bytes};
|
||||
// The first 1 byte is supposed to be the version and pcp.
|
||||
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::kV1) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize WifiLanServiceInfo: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(version_and_pcp_byte & kPcpBitmask);
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d",
|
||||
pcp_);
|
||||
}
|
||||
|
||||
// The next 4 bytes are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)};
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The next 1 byte are supposed to be the length of the endpoint_name.
|
||||
std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8();
|
||||
|
||||
// The rest bytes are supposed to be the endpoint_name
|
||||
auto endpoint_name_bytes =
|
||||
base_input_stream.ReadBytes(expected_endpoint_name_length);
|
||||
if (endpoint_name_bytes.Empty() ||
|
||||
endpoint_name_bytes.size() != expected_endpoint_name_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize WifiLanServiceInfo: expected "
|
||||
"endpointName to be %d bytes, got %" PRIu64,
|
||||
expected_endpoint_name_length, endpoint_name_bytes.size());
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
endpoint_name_ = std::string{endpoint_name_bytes};
|
||||
}
|
||||
|
||||
WifiLanServiceInfo::operator std::string() const {
|
||||
@@ -132,8 +144,6 @@ WifiLanServiceInfo::operator std::string() const {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// The upper 3 bits are the Version.
|
||||
auto version_and_pcp_byte = static_cast<char>(
|
||||
(static_cast<uint32_t>(Version::kV1) << 5) & kVersionBitmask);
|
||||
@@ -141,12 +151,23 @@ WifiLanServiceInfo::operator std::string() const {
|
||||
version_and_pcp_byte |=
|
||||
static_cast<char>(static_cast<uint32_t>(pcp_) & kPcpBitmask);
|
||||
|
||||
out.reserve(kMinLanServiceNameLength);
|
||||
out.append(1, version_and_pcp_byte);
|
||||
out.append(endpoint_id_);
|
||||
out.append(std::string(service_id_hash_));
|
||||
// The last byte is reserved to fit the kMinLanServiceNameLength.
|
||||
out.append(" ");
|
||||
std::string usable_endpoint_name(endpoint_name_);
|
||||
if (endpoint_name_.size() > kMaxEndpointNameLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"While serializing WifiLanServiceInfo, truncating Endpoint Name %s "
|
||||
"(%lu bytes) down to %d bytes",
|
||||
endpoint_name_.c_str(), endpoint_name_.size(), kMaxEndpointNameLength);
|
||||
usable_endpoint_name.erase(kMaxEndpointNameLength);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
endpoint_id_,
|
||||
std::string(service_id_hash_),
|
||||
std::string(1, usable_endpoint_name.size()),
|
||||
usable_endpoint_name);
|
||||
// clang-format on
|
||||
|
||||
return Base64Utils::Encode(ByteArray{std::move(out)});
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ class WifiLanServiceInfo {
|
||||
std::string endpoint_id_;
|
||||
// Connected hash service id.
|
||||
ByteArray service_id_hash_;
|
||||
// TODO(edwinwu): Replaces endpointName as endPointInfo eventually;
|
||||
// it is not in this version yet for endpointName.
|
||||
// Connected endpoint name.
|
||||
std::string endpoint_name_;
|
||||
};
|
||||
|
||||
@@ -25,15 +25,15 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1;
|
||||
const Pcp kPcp = Pcp::kP2pCluster;
|
||||
const char kEndPointID[] = "AB12";
|
||||
const char kServiceIDHashBytes[] = "\x0a\x0b\x0c";
|
||||
// TODO(edwinwu): Temp to set empty string for endpoint_name.
|
||||
const char kEndPointName[] = "";
|
||||
constexpr WifiLanServiceInfo::Version kVersion =
|
||||
WifiLanServiceInfo::Version::kV1;
|
||||
constexpr Pcp kPcp = Pcp::kP2pCluster;
|
||||
constexpr absl::string_view kEndPointID{"AB12"};
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kEndPointName{"RAWK + ROWL!"};
|
||||
|
||||
TEST(WifiLanServiceInfoTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -42,10 +42,11 @@ TEST(WifiLanServiceInfoTest, ConstructionWorks) {
|
||||
EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion());
|
||||
EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId());
|
||||
EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndPointName, wifi_lan_service_info.GetEndpointName());
|
||||
}
|
||||
|
||||
TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo org_wifi_lan_service_info{kVersion, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
std::string wifi_lan_service_info_string{org_wifi_lan_service_info};
|
||||
@@ -57,12 +58,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) {
|
||||
EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion());
|
||||
EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId());
|
||||
EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndPointName, wifi_lan_service_info.GetEndpointName());
|
||||
}
|
||||
|
||||
TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<WifiLanServiceInfo::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo wifi_lan_service_info{bad_version, kPcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -72,7 +74,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) {
|
||||
TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo wifi_lan_service_info{kVersion, bad_pcp, kEndPointID,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -82,7 +84,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) {
|
||||
TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) {
|
||||
std::string short_endpoint_id("AB1");
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, short_endpoint_id,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
@@ -92,7 +94,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) {
|
||||
TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) {
|
||||
std::string long_endpoint_id("AB12X");
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, long_endpoint_id,
|
||||
service_id_hash, kEndPointName};
|
||||
|
||||
|
||||
@@ -53,20 +53,20 @@ struct ConnectionResponseInfo {
|
||||
std::string authentication_token;
|
||||
ByteArray raw_authentication_token;
|
||||
ByteArray endpoint_info;
|
||||
bool is_incoming_connection;
|
||||
bool is_connection_verified;
|
||||
bool is_incoming_connection = false;
|
||||
bool is_connection_verified = false;
|
||||
};
|
||||
|
||||
struct PayloadProgressInfo {
|
||||
std::int64_t payload_id;
|
||||
std::int64_t payload_id = 0;
|
||||
enum class Status {
|
||||
kSuccess,
|
||||
kFailure,
|
||||
kInProgress,
|
||||
kCanceled,
|
||||
} status;
|
||||
std::int64_t total_bytes;
|
||||
std::int64_t bytes_transferred;
|
||||
} status = Status::kSuccess;
|
||||
std::int64_t total_bytes = 0;
|
||||
std::int64_t bytes_transferred = 0;
|
||||
};
|
||||
|
||||
enum class DistanceInfo {
|
||||
|
||||
+29
-19
@@ -16,11 +16,13 @@
|
||||
#define CORE_V2_PAYLOAD_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/base/payload_id.h"
|
||||
#include "platform_v2/base/prng.h"
|
||||
#include "platform_v2/public/file.h"
|
||||
#include "absl/types/variant.h"
|
||||
@@ -34,29 +36,38 @@ namespace connections {
|
||||
// ByteArray, InputStream, or InputFile.
|
||||
class Payload {
|
||||
public:
|
||||
using Id = PayloadId;
|
||||
// Order of types in variant, and values in Type enum is important.
|
||||
// Enum values must match respective variant types.
|
||||
using Content =
|
||||
absl::variant<absl::monostate, ByteArray, std::unique_ptr<InputStream>,
|
||||
std::unique_ptr<InputFile>>;
|
||||
using Content = absl::variant<absl::monostate, ByteArray,
|
||||
std::function<InputStream&()>, InputFile>;
|
||||
enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 };
|
||||
|
||||
Payload(Payload&& other) = default;
|
||||
~Payload() = default;
|
||||
Payload& operator=(Payload&& other) = default;
|
||||
|
||||
// Create Payload from bytes, steam, or file. Payload is immutable.
|
||||
// Default (invalid) payload.
|
||||
Payload() : content_(absl::monostate()) {}
|
||||
|
||||
// Constructors for outgoing payloads.
|
||||
explicit Payload(ByteArray&& bytes) : content_(std::move(bytes)) {}
|
||||
explicit Payload(const ByteArray& bytes) : content_(bytes) {}
|
||||
explicit Payload(std::unique_ptr<InputStream> stream)
|
||||
explicit Payload(std::function<InputStream&()> stream)
|
||||
: content_(std::move(stream)) {}
|
||||
explicit Payload(std::unique_ptr<InputFile> file)
|
||||
: content_(std::move(file)) {}
|
||||
|
||||
// Constructors for incoming payloads.
|
||||
Payload(Id id, ByteArray&& bytes) : content_(std::move(bytes)), id_(id) {}
|
||||
Payload(Id id, const ByteArray& bytes) : content_(bytes), id_(id) {}
|
||||
Payload(Id id, std::function<InputStream&()> stream)
|
||||
: content_(std::move(stream)), id_(id) {}
|
||||
|
||||
// Constructor for incoming and outgoing file payloads.
|
||||
Payload(Id id, InputFile file) : content_(std::move(file)), id_(id) {}
|
||||
|
||||
// Returns ByteArray payload, if it has been defined, or empty ByteArray.
|
||||
const ByteArray& AsBytes() const & {
|
||||
static const ByteArray empty; // NOLINT: function-level static is OK.
|
||||
const ByteArray& AsBytes() const& {
|
||||
static const ByteArray empty; // NOLINT: function-level static is OK.
|
||||
auto* result = absl::get_if<ByteArray>(&content_);
|
||||
return result ? *result : empty;
|
||||
}
|
||||
@@ -65,30 +76,29 @@ class Payload {
|
||||
return result ? std::move(*result) : std::move(ByteArray());
|
||||
}
|
||||
// Returns InputStream* payload, if it has been defined, or nullptr.
|
||||
InputStream* AsStream() const {
|
||||
auto* result = absl::get_if<std::unique_ptr<InputStream>>(&content_);
|
||||
return result ? result->get() : nullptr;
|
||||
InputStream* AsStream() {
|
||||
auto* result = absl::get_if<std::function<InputStream&()>>(&content_);
|
||||
return result ? &(*result)() : nullptr;
|
||||
}
|
||||
// Returns InputFile* payload, if it has been defined, or nullptr.
|
||||
InputFile* AsFile() const {
|
||||
auto* result = absl::get_if<std::unique_ptr<InputFile>>(&content_);
|
||||
return result ? result->get() : nullptr;
|
||||
}
|
||||
InputFile* AsFile() { return absl::get_if<InputFile>(&content_); }
|
||||
|
||||
// Returns Payload unique ID.
|
||||
std::int64_t GetId() const { return id_; }
|
||||
Id GetId() const { return id_; }
|
||||
|
||||
// Returns Payload type.
|
||||
Type GetType() const { return type_; }
|
||||
|
||||
// Generate Payload Id; to be passed to outgoing file constructor.
|
||||
static Id GenerateId() { return Prng().NextInt64(); }
|
||||
|
||||
private:
|
||||
static std::int64_t GenerateId() { return Prng().NextInt64(); }
|
||||
Type FindType(const Content& content) const {
|
||||
return static_cast<Type>(content_.index());
|
||||
}
|
||||
|
||||
Content content_;
|
||||
std::int64_t id_{GenerateId()};
|
||||
Id id_{GenerateId()};
|
||||
Type type_{FindType(content_)};
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/public/file.h"
|
||||
#include "platform_v2/public/pipe.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
@@ -42,21 +43,28 @@ TEST(PayloadTest, SupportsByteArrayType) {
|
||||
}
|
||||
|
||||
TEST(PayloadTest, SupportsFileType) {
|
||||
InputFile* raw_file = new InputFile(/*payload_id=*/23, 0);
|
||||
std::unique_ptr<InputFile> file(raw_file);
|
||||
Payload payload(std::move(file));
|
||||
const auto payload_id = Payload::GenerateId();
|
||||
InputFile file(payload_id, 100);
|
||||
InputStream& stream = file.GetInputStream();
|
||||
Payload payload(payload_id, std::move(file));
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kFile);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsFile(), raw_file);
|
||||
EXPECT_EQ(&payload.AsFile()->GetInputStream(), &stream);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray{});
|
||||
}
|
||||
|
||||
TEST(PayloadTest, SupportsStreamType) {
|
||||
InputFile* raw_file = new InputFile(/*payload_id=*/17, 0);
|
||||
std::unique_ptr<InputStream> stream(raw_file);
|
||||
Payload payload(std::move(stream));
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
Payload payload(
|
||||
[streamable = pipe]() -> InputStream& {
|
||||
// For some reason, linter warns us that we return a dangling reference.
|
||||
// This is not true: we return a reference to internal variable of a
|
||||
// shared_ptr<Pipe> which remains valid while Payload is valid, since
|
||||
// shared_ptr<Pipe> is captured by value.
|
||||
return streamable->GetInputStream(); // NOLINT
|
||||
});
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kStream);
|
||||
EXPECT_EQ(payload.AsStream(), raw_file);
|
||||
EXPECT_EQ(payload.AsStream(), &pipe->GetInputStream());
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray{});
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ struct Status {
|
||||
kAlreadyConnectedToEndpoint,
|
||||
kNotConnectedToEndpoint,
|
||||
kBluetoothError,
|
||||
kWifiLanError,
|
||||
kPayloadUnknown,
|
||||
};
|
||||
Value value {kError};
|
||||
|
||||
@@ -30,7 +30,7 @@ class Strategy {
|
||||
static const Strategy kP2pStar;
|
||||
static const Strategy kP2pPointToPoint;
|
||||
|
||||
Strategy() : Strategy(kNone) {}
|
||||
constexpr Strategy() : Strategy(kNone) {}
|
||||
|
||||
constexpr Strategy(const Strategy& other)
|
||||
: connection_type_(other.connection_type_),
|
||||
@@ -62,7 +62,7 @@ class Strategy {
|
||||
kOneToMany = 2,
|
||||
kManyToMany = 3,
|
||||
};
|
||||
Strategy(ConnectionType connection_type, TopologyType topology_type)
|
||||
constexpr Strategy(ConnectionType connection_type, TopologyType topology_type)
|
||||
: connection_type_(connection_type), topology_type_(topology_type) {}
|
||||
|
||||
ConnectionType connection_type_;
|
||||
|
||||
@@ -25,6 +25,7 @@ cc_library(
|
||||
"future.h",
|
||||
"input_file.h",
|
||||
"listenable_future.h",
|
||||
"log_message.h",
|
||||
"mutex.h",
|
||||
"output_file.h",
|
||||
"scheduled_executor.h",
|
||||
@@ -76,12 +77,14 @@ cc_library(
|
||||
"platform.h",
|
||||
],
|
||||
visibility = [
|
||||
"//platform_v2/base:__pkg__",
|
||||
"//platform_v2/impl:__subpackages__",
|
||||
"//platform_v2/public:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
":comm",
|
||||
":types",
|
||||
"//platform_v2/base",
|
||||
"//absl/strings",
|
||||
"//absl/types:any",
|
||||
],
|
||||
|
||||
@@ -15,22 +15,22 @@
|
||||
#ifndef PLATFORM_V2_API_ATOMIC_REFERENCE_H_
|
||||
#define PLATFORM_V2_API_ATOMIC_REFERENCE_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace api {
|
||||
|
||||
// An object reference that may be updated atomically.
|
||||
//
|
||||
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
|
||||
template <typename T>
|
||||
class AtomicReference {
|
||||
// Type that allows 32-bit atomic reads and writes.
|
||||
class AtomicUint32 {
|
||||
public:
|
||||
virtual ~AtomicReference() = default;
|
||||
virtual ~AtomicUint32() = default;
|
||||
|
||||
virtual T Get() const & = 0;
|
||||
virtual T Get() && = 0;
|
||||
virtual void Set(const T& value) = 0;
|
||||
virtual void Set(T&& value) = 0;
|
||||
// Atomically reads and returns stored value.
|
||||
virtual std::uint32_t Get() const = 0;
|
||||
|
||||
// Atomically stores value.
|
||||
virtual void Set(std::uint32_t value) = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define PLATFORM_V2_API_CONDITION_VARIABLE_H_
|
||||
|
||||
#include "platform_v2/base/exception.h"
|
||||
#include "absl/time/clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -29,10 +30,19 @@ class ConditionVariable {
|
||||
public:
|
||||
virtual ~ConditionVariable() {}
|
||||
|
||||
// https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--
|
||||
// Notifies all the waiters that condition state has changed.
|
||||
virtual void Notify() = 0;
|
||||
// https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--
|
||||
virtual Exception Wait() = 0; // throws Exception::kInterrupted
|
||||
|
||||
// Waits indefinitely for Notify to be called.
|
||||
// May return prematurely in case of interrupt, if supported by platform.
|
||||
// Returns kSuccess, or kInterrupted on interrupt.
|
||||
virtual Exception Wait() = 0;
|
||||
|
||||
// Waits while timeout has not expired for Notify to be called.
|
||||
// May return prematurely in case of interrupt, if supported by platform.
|
||||
// Returns kSuccess, or kInterrupted on interrupt.
|
||||
// If Timeout expired, and Notify was not called, returns kTimeout.
|
||||
virtual Exception Wait(absl::Duration timeout) = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user