Merge branch 'master' into release

Change-Id: I56ea2217899e92bdd9d6cb56797ac9895e194fff
This commit is contained in:
Alexey Polyudov
2020-06-24 11:01:46 -07:00
147 changed files with 8642 additions and 1214 deletions
+3 -3
View File
@@ -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_;
+39 -1
View File
@@ -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",
+45 -21
View File
@@ -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() {
+7 -6
View File
@@ -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.
+18 -12
View File
@@ -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,
+25 -9
View File
@@ -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
+55 -9
View File
@@ -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
+63 -79
View File
@@ -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);
-2
View File
@@ -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;
+89 -53
View File
@@ -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.
+71 -91
View File
@@ -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_
+48 -15
View File
@@ -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;
}
+2
View File
@@ -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 {}
+4 -2
View File
@@ -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;
};
+41 -21
View File
@@ -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());
}
}
+9 -2
View File
@@ -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));
+18
View File
@@ -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
+81
View File
@@ -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
+12
View File
@@ -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};
+11 -11
View File
@@ -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));
}
+11 -11
View File
@@ -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()};
+4
View File
@@ -26,6 +26,10 @@ BluetoothClassic& Mediums::GetBluetoothClassic() {
return bluetooth_classic_;
}
WifiLan& Mediums::GetWifiLan() {
return wifi_lan_;
}
} // namespace connections
} // namespace nearby
} // namespace location
+6
View File
@@ -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
+1 -1
View File
@@ -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;
+448
View File
@@ -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
+155
View File
@@ -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_
+20 -47
View File
@@ -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_
+121
View File
@@ -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
+230
View File
@@ -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
+118
View File
@@ -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
+6 -6
View File
@@ -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
+282
View File
@@ -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
+15 -2
View File
@@ -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,
+105
View File
@@ -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
+64
View File
@@ -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_
+122
View File
@@ -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
+1 -2
View File
@@ -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};
}
+158
View File
@@ -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
+129
View File
@@ -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_
+77 -56
View File
@@ -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};
+6 -6
View File
@@ -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
View File
@@ -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_)};
};
+16 -8
View File
@@ -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{});
}
+1
View File
@@ -38,6 +38,7 @@ struct Status {
kAlreadyConnectedToEndpoint,
kNotConnectedToEndpoint,
kBluetoothError,
kWifiLanError,
kPayloadUnknown,
};
Value value {kError};
+2 -2
View File
@@ -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_;
+3
View File
@@ -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",
],
+10 -10
View File
@@ -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
+13 -3
View File
@@ -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