nearby: snapshot as of cl/296436629

Signed-off-by: Alexey Polyudov <apolyudov@google.com>
Change-Id: I2cf5bf225b76f4c1541954651f3a7544a14e0cec
This commit is contained in:
Alexey Polyudov
2020-04-04 12:52:31 -07:00
parent 598516303b
commit 204f76077d
195 changed files with 27318 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
cc_library(
name = "internal",
srcs = [
"ble_advertisement.cc",
"bluetooth_device_name.cc",
"internal_payload.cc",
"internal_payload.h",
"loop_runner.cc",
"loop_runner.h",
"offline_frames.cc",
"offline_frames.h",
],
hdrs = [
"bandwidth_upgrade_handler.h",
"bandwidth_upgrade_manager.cc",
"bandwidth_upgrade_manager.h",
"base_bandwidth_upgrade_handler.cc",
"base_bandwidth_upgrade_handler.h",
"base_endpoint_channel.cc",
"base_endpoint_channel.h",
"base_pcp_handler.cc",
"base_pcp_handler.h",
"ble_advertisement.h",
"ble_compat.h",
"ble_endpoint_channel.cc",
"ble_endpoint_channel.h",
"bluetooth_device_name.h",
"bluetooth_endpoint_channel.cc",
"bluetooth_endpoint_channel.h",
"client_proxy.cc",
"client_proxy.h",
"encryption_runner.cc",
"encryption_runner.h",
"endpoint_channel.h",
"endpoint_channel_manager.cc",
"endpoint_channel_manager.h",
"endpoint_manager.cc",
"endpoint_manager.h",
"internal_payload_factory.cc",
"internal_payload_factory.h",
"medium_manager.cc",
"medium_manager.h",
"offline_service_controller.cc",
"offline_service_controller.h",
"p2p_cluster_pcp_handler.cc",
"p2p_cluster_pcp_handler.h",
"p2p_point_to_point_pcp_handler.cc",
"p2p_point_to_point_pcp_handler.h",
"p2p_star_pcp_handler.cc",
"p2p_star_pcp_handler.h",
"payload_manager.cc",
"payload_manager.h",
"pcp.h",
"pcp_handler.h",
"pcp_manager.cc",
"pcp_manager.h",
"service_controller.h",
"service_controller_router.cc",
"service_controller_router.h",
"wifi_lan_upgrade_handler.cc",
"wifi_lan_upgrade_handler.h",
],
visibility = [
"//core:__pkg__",
],
deps = [
"//core:types",
"//core/internal/mediums",
"//proto/connections:offline_wire_formats_portable_proto",
"//platform:logging",
"//platform:types",
"//platform:utils",
"//platform/api",
"//platform/port:down_cast",
"//platform/port:string",
"//proto:connections_enums_portable_proto",
"//net/proto2/compat/public:proto2_lite",
"//securegcm:ukey2",
"//absl/strings",
],
)
cc_test(
name = "bluetooth_device_name_test",
srcs = ["bluetooth_device_name_test.cc"],
deps = [
":internal",
"//platform:utils",
"//platform/port:string",
"//testing/base/public:gunit_main",
],
)
cc_test(
name = "ble_advertisement_test",
srcs = ["ble_advertisement_test.cc"],
deps = [
":internal",
"//platform/port:string",
"//testing/base/public:gunit_main",
],
)
@@ -0,0 +1,50 @@
#ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_
#define CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_
#include "core/internal/client_proxy.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/count_down_latch.h"
#include "platform/port/string.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
// Defines the set of methods that need to be implemented to handle the
// per-Medium-specific operations needed to upgrade an EndpointChannel.
template <typename Platform>
class BandwidthUpgradeHandler {
public:
virtual ~BandwidthUpgradeHandler() {}
// Reverts any changes made to the device in the process of upgrading
// endpoints.
virtual void revert() = 0;
// Cleans up in-progress upgrades after endpoint disconnection.
virtual void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const std::string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) = 0;
// Initiates the upgrade for the endpoint and starts listening for upgraded
// incoming connections on the initiator side of the bandwidth upgrade.
virtual void initiateBandwidthUpgradeForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_id) = 0;
// Processes the BandwidthUpgradeNegotiationFrames that come over the
// EndpointChannel on the non-initiator side of the bandwidth upgrade.
// TODO(ahlee): Rename parameters in the java code.
virtual void processBandwidthUpgradeNegotiationFrame(
ConstPtr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation,
Ptr<ClientProxy<Platform> > to_client_proxy,
const std::string& from_endpoint_id,
proto::connections::Medium current_medium) = 0;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_
@@ -0,0 +1,47 @@
#include "core/internal/bandwidth_upgrade_manager.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
BandwidthUpgradeManager<Platform>::BandwidthUpgradeManager(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<EndpointManager<Platform> > endpoint_manager)
: endpoint_manager_(endpoint_manager),
bandwidth_upgrade_handlers_(),
current_bandwidth_upgrade_handler_() {}
template <typename Platform>
BandwidthUpgradeManager<Platform>::~BandwidthUpgradeManager() {
// TODO(ahlee): Make sure we don't repeat the mistake fixed in cl/201883908.
}
template <typename Platform>
void BandwidthUpgradeManager<Platform>::initiateBandwidthUpgradeForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
proto::connections::Medium medium) {}
template <typename Platform>
void BandwidthUpgradeManager<Platform>::processIncomingOfflineFrame(
ConstPtr<OfflineFrame> offline_frame, const string& from_endpoint_id,
Ptr<ClientProxy<Platform> > to_client_proxy,
proto::connections::Medium current_medium) {}
template <typename Platform>
void BandwidthUpgradeManager<Platform>::processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) {}
template <typename Platform>
bool BandwidthUpgradeManager<Platform>::setCurrentBandwidthUpgradeHandler(
proto::connections::Medium medium) {
return false;
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,66 @@
#ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_
#define CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_
#include <map>
#include "core/internal/bandwidth_upgrade_handler.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
// Manages all known {@link BandwidthUpgradeHandler} implementations, delegating
// operations to the appropriate one as per the parameters passed in.
template <typename Platform>
class BandwidthUpgradeManager
: public EndpointManager<Platform>::IncomingOfflineFrameProcessor {
public:
BandwidthUpgradeManager(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<EndpointManager<Platform> > endpoint_manager);
~BandwidthUpgradeManager() override;
// This is the point on the initiator side where the
// current_bandwidth_upgrade_handler_ is set.
void initiateBandwidthUpgradeForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
proto::connections::Medium medium);
// This is the point on the non-initiator side where the
// current_bandwidth_upgrade_handler_ is set.
// @EndpointManagerReaderThread
void processIncomingOfflineFrame(
ConstPtr<OfflineFrame> offline_frame, const string& from_endpoint_id,
Ptr<ClientProxy<Platform> > to_client_proxy,
proto::connections::Medium current_medium) override;
// @EndpointManagerReaderThread
void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) override;
private:
bool setCurrentBandwidthUpgradeHandler(proto::connections::Medium medium);
Ptr<EndpointManager<Platform> > endpoint_manager_;
typedef std::map<proto::connections::Medium,
Ptr<BandwidthUpgradeHandler<Platform> > >
BandwidthUpgradeHandlersMap;
BandwidthUpgradeHandlersMap bandwidth_upgrade_handlers_;
Ptr<BandwidthUpgradeHandler<Platform> > current_bandwidth_upgrade_handler_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/bandwidth_upgrade_manager.cc"
#endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_
@@ -0,0 +1,145 @@
#include "core/internal/base_bandwidth_upgrade_handler.h"
namespace location {
namespace nearby {
namespace connections {
namespace base_bandwidth_upgrade_handler {
template <typename Platform>
class RevertRunnable : public Runnable {
public:
void run() {}
};
template <typename Platform>
class InitiateBandwidthUpgradeForEndpointRunnable : public Runnable {
public:
void run() {}
};
template <typename Platform>
class ProcessEndpointDisconnectionRunnable : public Runnable {
public:
void run() {}
};
template <typename Platform>
class ProcessBandwidthUpgradeNegotiationFrameRunnable : public Runnable {
public:
void run() {}
};
} // namespace base_bandwidth_upgrade_handler
template <typename Platform>
BaseBandwidthUpgradeHandler<Platform>::BaseBandwidthUpgradeHandler(
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager)
: endpoint_channel_manager_(endpoint_channel_manager),
alarm_executor_(),
serial_executor_(),
previous_endpoint_channels_(),
in_progress_upgrades_(),
safe_to_close_write_timestamps_() {}
template <typename Platform>
BaseBandwidthUpgradeHandler<Platform>::~BaseBandwidthUpgradeHandler() {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::revert() {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::initiateBandwidthUpgradeForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::
processBandwidthUpgradeNegotiationFrame(
ConstPtr<BandwidthUpgradeNegotiationFrame>
bandwidth_upgrade_negotiation,
Ptr<ClientProxy<Platform> > to_client_proxy,
const string& from_endpoint_id,
proto::connections::Medium current_medium) {}
template <typename Platform>
Ptr<EndpointChannelManager<Platform> >
BaseBandwidthUpgradeHandler<Platform>::getEndpointChannelManager() {
return endpoint_channel_manager_;
}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::onIncomingConnection(
Ptr<IncomingSocketConnection> incoming_socket_connection) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::runOnBandwidthUpgradeHandlerThread(
Ptr<Runnable> runnable) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::runUpgradeProtocol(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> new_endpoint_channel) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::
processBandwidthUpgradePathAvailableEvent(
const string& endpoint_id, Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info,
proto::connections::Medium current_medium) {}
template <typename Platform>
Ptr<EndpointChannel> BaseBandwidthUpgradeHandler<Platform>::
processBandwidthUpgradePathAvailableEventInternal(
const string& endpoint_id, Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info) {
return Ptr<EndpointChannel>();
}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::processLastWriteToPriorChannelEvent(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {}
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::processSafeToClosePriorChannelEvent(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {}
template <typename Platform>
std::int64_t BaseBandwidthUpgradeHandler<Platform>::calculateCloseDelay(
const string& endpoint_id) {
return 0;
}
template <typename Platform>
std::int64_t
BaseBandwidthUpgradeHandler<Platform>::getMillisSinceSafeCloseWritten(
const string& endpoint_id) {
return 0;
}
// TODO(ahlee): This will differ from the Java code as we don't have to handle
// analytics in the C++ code.
template <typename Platform>
void BaseBandwidthUpgradeHandler<Platform>::
attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint(
proto::connections::BandwidthUpgradeResult result,
proto::connections::BandwidthUpgradeErrorStage error_stage) {}
// TODO(ahlee): This will differ from the Java code (previously threw an
// UpgradeException).
template <typename Platform>
Ptr<BandwidthUpgradeNegotiationFrame::ClientIntroduction>
BaseBandwidthUpgradeHandler<Platform>::readClientIntroductionFrame(
Ptr<EndpointChannel> endpoint_channel) {
return Ptr<BandwidthUpgradeNegotiationFrame::ClientIntroduction>();
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,189 @@
#ifndef CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_
#define CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_
#include <cstdint>
#include <map>
#include "core/internal/bandwidth_upgrade_handler.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/count_down_latch.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace base_bandwidth_upgrade_handler {
template <typename>
class RevertRunnable;
template <typename>
class InitiateBandwidthUpgradeForEndpointRunnable;
template <typename>
class ProcessEndpointDisconnectionRunnable;
template <typename>
class ProcessBandwidthUpgradeNegotiationFrameRunnable;
} // namespace base_bandwidth_upgrade_handler
// Base class for managing the upgrade of endpoints to a different medium for
// communication (from whatever they were previously using).
//
// <p>The sequencing of the upgrade protocol is as follows:
// <ul>
// <li>Initiator sets up an upgrade path, sends
// BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE to Responder over
// the prior EndpointChannel.
// <li>Responder joins the upgrade path, sends (without encryption)
// BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION over the new
// EndpointChannel, and sends
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the
// prior EndpointChannel.
// <li>Initiator receives BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION
// over the newly-established EndpointChannel, and sends
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the
// prior EndpointChannel.
// <li>Both wait to receive
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL from the
// other, and upon doing so, send
// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL to each other
// <li>Both then wait to receive
// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the
// other, and upon doing so, close the prior EndpointChannel.
// </ul>
template <typename Platform>
class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler<Platform> {
public:
BaseBandwidthUpgradeHandler(
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager);
~BaseBandwidthUpgradeHandler();
void revert();
void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier);
// Initiates the bandwidth upgrade and sends an UPGRADE_PATH_AVAILABLE
// OfflineFrame.
void initiateBandwidthUpgradeForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id);
void processBandwidthUpgradeNegotiationFrame(
ConstPtr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation,
Ptr<ClientProxy<Platform> > to_client_proxy,
const string& from_endpoint_id,
proto::connections::Medium current_medium);
protected:
// Represents the incoming Socket the Initiator has gotten after initializing
// its upgraded bandwidth medium.
class IncomingSocketConnection {
public:
virtual ~IncomingSocketConnection() {}
virtual string socketToString() = 0;
virtual void closeSocket() = 0;
// TODO(ahlee): Make sure to be careful with the ownership story of this.
// Leaning towards releasing to the caller.
virtual Ptr<EndpointChannel> getEndpointChannel() = 0;
};
// Called by the Initiator to setup the upgraded medium for this endpoint (if
// that hasn't already been done), and returns a serialized UpgradePathInfo
// that can be sent to the Responder.
// TODO(ahlee): This will differ from the Java code (previously threw an
// UpgradeException). Leaving the return type simple for the skeleton - I'll
// switch to a pair if the result enum is needed.
// @BandwidthUpgradeHandlerThread
virtual ConstPtr<ByteArray> initializeUpgradedMediumForEndpoint(
const string& endpoint_id) = 0;
// Called to revert any state changed by the Initiator to setup the upgraded
// medium for an endpoint.
// @BandwidthUpgradeHandlerThread
virtual void revertImpl() = 0;
// Called by the Responder to setup the upgraded medium for this endpoint (if
// that hasn't already been done) using the UpgradePathInfo sent by the
// Initiator, and returns a new EndpointChannel for the upgraded medium.
// @BandwidthUpgradeHandlerThread
// TODO(ahlee): This will differ from the Java code (previously threw an
// exception).
virtual Ptr<EndpointChannel> createUpgradedEndpointChannel(
const string& endpoint_id,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info) = 0;
// Returns the upgrade medium of the BandwidthUpgradeHandler.
// @BandwidthUpgradeHandlerThread
virtual proto::connections::Medium getUpgradeMedium() = 0;
Ptr<EndpointChannelManager<Platform> > getEndpointChannelManager();
// Common functionality to take an incoming connection and go through the
// upgrade process.
// @BandwidthUpgradeHandlerThread
void onIncomingConnection(
Ptr<IncomingSocketConnection> incoming_socket_connection);
void runOnBandwidthUpgradeHandlerThread(Ptr<Runnable> runnable);
private:
template <typename>
friend class base_bandwidth_upgrade_handler::RevertRunnable;
template <typename>
friend class base_bandwidth_upgrade_handler::
InitiateBandwidthUpgradeForEndpointRunnable;
template <typename>
friend class base_bandwidth_upgrade_handler::
ProcessEndpointDisconnectionRunnable;
template <typename>
friend class base_bandwidth_upgrade_handler::
ProcessBandwidthUpgradeNegotiationFrameRunnable;
void runUpgradeProtocol(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> new_endpoint_channel);
void processBandwidthUpgradePathAvailableEvent(
const string& endpoint_id, Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info,
proto::connections::Medium current_medium);
Ptr<EndpointChannel> processBandwidthUpgradePathAvailableEventInternal(
const string& endpoint_id, Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info);
void processLastWriteToPriorChannelEvent(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id);
void processSafeToClosePriorChannelEvent(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id);
std::int64_t calculateCloseDelay(const string& endpoint_id);
std::int64_t getMillisSinceSafeCloseWritten(const string& endpoint_id);
void attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint(
proto::connections::BandwidthUpgradeResult result,
proto::connections::BandwidthUpgradeErrorStage error_stage);
Ptr<BandwidthUpgradeNegotiationFrame::ClientIntroduction>
readClientIntroductionFrame(Ptr<EndpointChannel> endpoint_channel);
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager_;
ScopedPtr<Ptr<typename Platform::ScheduledExecutorType> > alarm_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > serial_executor_;
// Stores each upgraded endpoint's previous EndpointChannel (that was
// displaced in favor of a new EndpointChannel) temporarily, until it can
// safely be shut down for good in processLastWriteToPriorChannelEvent().
typedef std::map<string, Ptr<EndpointChannel> > PreviousEndpointChannelsMap;
PreviousEndpointChannelsMap previous_endpoint_channels_;
// Maps endpointId -> ClientProxy for which
// initiateBandwidthUpgradeForEndpoint() has been called but which have not
// yet completed the upgrade via onIncomingConnection().
typedef std::map<string, Ptr<ClientProxy<Platform> > > InProgressUpgradesMap;
InProgressUpgradesMap in_progress_upgrades_;
// Maps endpointId -> timestamp of when the SAFE_TO_CLOSE message was written.
typedef std::map<string, std::int64_t> SafeToCloseWriteTimestampsMap;
SafeToCloseWriteTimestampsMap safe_to_close_write_timestamps_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/base_bandwidth_upgrade_handler.cc"
#endif // CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_
+352
View File
@@ -0,0 +1,352 @@
#include "core/internal/base_endpoint_channel.h"
#include <cassert>
#include "platform/synchronized.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
std::int32_t bytesToInt(ConstPtr<ByteArray> bytes) {
const char* int_bytes = bytes->getData();
std::int32_t result = 0;
result |= (static_cast<std::int32_t>(int_bytes[0]) & 0x0FF) << 24;
result |= (static_cast<std::int32_t>(int_bytes[1]) & 0x0FF) << 16;
result |= (static_cast<std::int32_t>(int_bytes[2]) & 0x0FF) << 8;
result |= (static_cast<std::int32_t>(int_bytes[3]) & 0x0FF);
return result;
}
ConstPtr<ByteArray> intToBytes(std::int32_t value) {
char int_bytes[sizeof(std::int32_t)];
int_bytes[0] = static_cast<char>((value >> 24) & 0x0FF);
int_bytes[1] = static_cast<char>((value >> 16) & 0x0FF);
int_bytes[2] = static_cast<char>((value >> 8) & 0x0FF);
int_bytes[3] = static_cast<char>((value)&0x0FF);
return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes)));
}
ExceptionOr<ConstPtr<ByteArray> > readExactly(Ptr<InputStream> reader,
std::int64_t size) {
string buffer;
std::int64_t remaining_size = size;
while (remaining_size > 0) {
ExceptionOr<ConstPtr<ByteArray> > read_bytes = reader->read(remaining_size);
if (!read_bytes.ok()) {
if (Exception::IO == read_bytes.exception()) {
return ExceptionOr<ConstPtr<ByteArray> >(read_bytes.exception());
}
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_read_bytes(read_bytes.result());
// In Java, EOFException is a sub-variant of IOException.
if (scoped_read_bytes->size() == 0) {
return ExceptionOr<ConstPtr<ByteArray> >(Exception::IO);
}
buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size());
remaining_size -= scoped_read_bytes->size();
}
return ExceptionOr<ConstPtr<ByteArray> >(
MakeConstPtr(new ByteArray(buffer.data(), buffer.size())));
}
ExceptionOr<std::int32_t> readInt(Ptr<InputStream> reader) {
ExceptionOr<ConstPtr<ByteArray> > read_bytes =
readExactly(reader, sizeof(std::int32_t));
if (!read_bytes.ok()) {
if (Exception::IO == read_bytes.exception()) {
return ExceptionOr<std::int32_t>(read_bytes.exception());
}
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_read_bytes(read_bytes.result());
return ExceptionOr<std::int32_t>(bytesToInt(scoped_read_bytes.get()));
}
Exception::Value writeInt(Ptr<OutputStream> writer, std::int32_t value) {
return writer->write(intToBytes(value));
}
} // namespace
template <typename Platform>
BaseEndpointChannel<Platform>::BaseEndpointChannel(const string& channel_name,
Ptr<InputStream> reader,
Ptr<OutputStream> writer)
: last_read_timestamp_(-1),
channel_name_(channel_name),
system_clock_(Platform::createSystemClock()),
reader_lock_(Platform::createLock()),
reader_(reader),
writer_lock_(Platform::createLock()),
writer_(writer),
encryption_context_(Platform::createAtomicReference(
Ptr<securegcm::D2DConnectionContextV1>())),
is_paused_lock_(Platform::createLock()),
is_paused_condition_variable_(
Platform::createConditionVariable(is_paused_lock_.get())),
is_paused_(Platform::createAtomicBoolean(false)) {}
template <typename Platform>
BaseEndpointChannel<Platform>::~BaseEndpointChannel() {
// WARNING: Make sure to never access reader_ and writer_ from here.
//
// They're owned by the specialized *Socket classes that are in turn
// owned by the *EndpointChannel children of this class, so by this point,
// they've been destroyed and now point to invalid memory.
//
// "Ugh!" is right -- this won't be a problem once we have a standardized
// Socket interface we can hold up in this class (instead of holding
// specialized implementations of that hypothetical interface in each child
// of this class).
}
template <typename Platform>
ExceptionOr<ConstPtr<ByteArray> > BaseEndpointChannel<Platform>::read() {
Synchronized s(reader_lock_.get());
ExceptionOr<std::int32_t> read_int = readInt(reader_);
if (!read_int.ok()) {
if (Exception::IO == read_int.exception()) {
return ExceptionOr<ConstPtr<ByteArray> >(read_int.exception());
}
}
if (read_int.result() < 0) {
return ExceptionOr<ConstPtr<ByteArray>>(Exception::IO);
} else if (read_int.result() > kMaxAllowedReadBytes) {
return ExceptionOr<ConstPtr<ByteArray>>(Exception::IO);
}
ExceptionOr<ConstPtr<ByteArray> > read_bytes =
readExactly(reader_, read_int.result());
if (!read_bytes.ok()) {
if (Exception::IO == read_bytes.exception()) {
return ExceptionOr<ConstPtr<ByteArray> >(read_bytes.exception());
}
}
// This should be ScopedPtr usually, but because of the unique requirement of
// reassigning this variable when encryption is enabled, we can't make use of
// the power of ScopedPtr, and instead have to do manual memory management.
ConstPtr<ByteArray> read_bytes_result = read_bytes.result();
// If encryption is enabled, decode the message.
if (isEncryptionEnabled()) {
std::unique_ptr<string> decoded_bytes =
encryption_context_->get()->DecodeMessageFromPeer(
string(read_bytes_result->getData(), read_bytes_result->size()));
// Now that we are done using read_bytes_result, we should unconditionally
// destroy it, because we either reassign to the value of decoded_bytes, or
// short-circuit out of here on error.
read_bytes_result.destroy();
if (decoded_bytes == nullptr) {
return ExceptionOr<ConstPtr<ByteArray> >(
Exception::INVALID_PROTOCOL_BUFFER);
}
read_bytes_result = MakeConstPtr(
new ByteArray(decoded_bytes->data(), decoded_bytes->size()));
}
last_read_timestamp_ = system_clock_->elapsedRealtime();
return ExceptionOr<ConstPtr<ByteArray> >(read_bytes_result);
}
template <typename Platform>
Exception::Value BaseEndpointChannel<Platform>::write(
ConstPtr<ByteArray> data) {
Synchronized s(writer_lock_.get());
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_data(data);
if (isPaused()) {
blockUntilUnpaused();
}
ConstPtr<ByteArray> data_to_write;
// If encryption is enabled, encode the message.
if (isEncryptionEnabled()) {
std::unique_ptr<string> message =
encryption_context_->get()->EncodeMessageToPeer(
string(scoped_data->getData(), scoped_data->size()));
assert(message != nullptr);
data_to_write =
MakeConstPtr(new ByteArray(message->data(), message->size()));
} else {
// Else, just make data_to_write point to the passed-in data.
data_to_write = scoped_data.release();
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_data_to_write(data_to_write);
Exception::Value write_exception = writeInt(
writer_, static_cast<std::int32_t>(scoped_data_to_write->size()));
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
return write_exception;
}
}
write_exception = writer_->write(scoped_data_to_write.release());
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
return write_exception;
}
}
Exception::Value flush_exception = writer_->flush();
if (Exception::NONE != flush_exception) {
if (Exception::IO == flush_exception) {
return flush_exception;
}
}
return Exception::NONE;
}
template <typename Platform>
void BaseEndpointChannel<Platform>::close() {
// WARNING WARNING WARNING
//
// This block deviates from the corresponding Java code.
//
// In the corresponding Java code, close() calls
// close(proto::connections::DisconnectionReason) while here we do the
// opposite. This is because proto::connections::DisconnectionReason can be
// null in Java but not in C++.
Exception::Value reader_close_exception = reader_->close();
if (Exception::NONE != reader_close_exception) {
if (Exception::IO == reader_close_exception) {
// Add logging.
}
}
Exception::Value writer_close_exception = writer_->close();
if (Exception::NONE != writer_close_exception) {
if (Exception::IO == writer_close_exception) {
// Add logging.
}
}
closeImpl();
// TODO(tracyzhou): Add logging.
}
template <typename Platform>
void BaseEndpointChannel<Platform>::close(
proto::connections::DisconnectionReason reason) {
// WARNING WARNING WARNING
//
// This block deviates from the corresponding Java code.
// Look at the corresponding block in the close() method above for details on
// the deviation.
close();
// TODO(tracyzhou): Add logging.
}
template <typename Platform>
string BaseEndpointChannel<Platform>::getType() {
string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : "";
switch (getMedium()) {
case proto::connections::Medium::BLUETOOTH:
return subtype + "BLUETOOTH";
case proto::connections::Medium::BLE:
return subtype + "BLE";
case proto::connections::Medium::MDNS:
return subtype + "MDNS";
case proto::connections::Medium::WIFI_HOTSPOT:
return subtype + "WIFI_HOTSPOT";
case proto::connections::Medium::WIFI_LAN:
return subtype + "WIFI_LAN";
default:
return "UNKNOWN";
}
}
template <typename Platform>
string BaseEndpointChannel<Platform>::getName() {
return channel_name_;
}
template <typename Platform>
void BaseEndpointChannel<Platform>::enableEncryption(
Ptr<securegcm::D2DConnectionContextV1> encryption_context) {
assert(!encryption_context.isNull());
encryption_context_->set(encryption_context);
}
template <typename Platform>
bool BaseEndpointChannel<Platform>::isPaused() {
return is_paused_->get();
}
template <typename Platform>
void BaseEndpointChannel<Platform>::pause() {
is_paused_->set(true);
}
template <typename Platform>
void BaseEndpointChannel<Platform>::resume() {
is_paused_->set(false);
unblockPausedWriter();
}
template <typename Platform>
std::int64_t BaseEndpointChannel<Platform>::getLastReadTimestamp() {
return last_read_timestamp_;
}
template <typename Platform>
bool BaseEndpointChannel<Platform>::isEncryptionEnabled() {
return !encryption_context_->get().isNull();
}
template <typename Platform>
void BaseEndpointChannel<Platform>::unblockPausedWriter() {
Synchronized s(is_paused_lock_.get());
// Notify to tell the thread calling wait() to check again.
// NOTE: There is only ever one thread blocked by wait() at a time, because
// EndpointChannel.write(Ptr<ByteArray>) is synchronized on writer. That means
// the first thread to call write(byte[]) will be blocked via
// blockUntilUnpaused() and all future threads will be blocked via
// synchronized(writer_lock_).
is_paused_condition_variable_->notify();
}
template <typename Platform>
void BaseEndpointChannel<Platform>::blockUntilUnpaused() {
Synchronized s(is_paused_lock_.get());
// For more on how this works, see
// https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
while (is_paused_->get()) {
Exception::Value wait_succeeded = is_paused_condition_variable_->wait();
if (Exception::NONE != wait_succeeded) {
if (Exception::INTERRUPTED == wait_succeeded) {
// If we were interrupted, pass the interrupt up the stack and then exit
// immediately.
// Thread.currentThread().interrupt();
return;
}
}
}
}
} // namespace connections
} // namespace nearby
} // namespace location
+112
View File
@@ -0,0 +1,112 @@
#ifndef CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
#define CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
#include <cstdint>
#include "core/internal/endpoint_channel.h"
#include "platform/api/atomic_boolean.h"
#include "platform/api/atomic_reference.h"
#include "platform/api/condition_variable.h"
#include "platform/api/input_stream.h"
#include "platform/api/lock.h"
#include "platform/api/output_stream.h"
#include "platform/api/system_clock.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
#include "securegcm/d2d_connection_context_v1.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class BaseEndpointChannel : public EndpointChannel {
public:
BaseEndpointChannel(const string& channel_name, Ptr<InputStream> reader,
Ptr<OutputStream> writer);
~BaseEndpointChannel() override;
ExceptionOr<ConstPtr<ByteArray> > read() override;
Exception::Value write(ConstPtr<ByteArray> data) override;
// Closes this EndpointChannel, without tracking the closure in analytics.
void close() override;
// Closes this EndpointChannel and records the closure with the given reason.
void close(proto::connections::DisconnectionReason reason) override;
// Returns a one-word type descriptor for the concrete EndpointChannel
// implementation that can be used in log messages; eg: BLUETOOTH, BLE,
// WIFI.
string getType() override;
// Returns the name of the EndpointChannel.
string getName() override;
// Enables encryption on the EndpointChannel.
void enableEncryption(
Ptr<securegcm::D2DConnectionContextV1> encryption_context) override;
// True if the EndpointChannel is currently pausing all writes.
bool isPaused() override;
// Pauses all writes on this EndpointChannel until resume() is called.
void pause() override;
// Resumes any writes on this EndpointChannel that were suspended when pause()
// was called.
void resume() override;
// Returns the timestamp (in elapsedRealtime) of the last read from this
// endpoint, or -1 if no reads have occurred.
std::int64_t getLastReadTimestamp() override;
protected:
virtual void closeImpl() = 0;
private:
// Used to sanity check that our frame sizes are reasonable.
static const std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB
bool isEncryptionEnabled();
void unblockPausedWriter();
void blockUntilUnpaused();
volatile std::int64_t last_read_timestamp_;
const string channel_name_;
ScopedPtr<Ptr<SystemClock> > system_clock_;
// The reader and writer are synchronized independently since we can't have
// writes waiting on reads that might potentially block forever.
ScopedPtr<Ptr<Lock> > reader_lock_;
// Not owned by this class, see the note in the destructor for a special
// restriction on usage.
Ptr<InputStream> reader_;
ScopedPtr<Ptr<Lock> > writer_lock_;
// Not owned by this class, see the note in the destructor for a special
// restriction on usage.
Ptr<OutputStream> writer_;
// An encryptor/decryptor. May be null.
ScopedPtr<Ptr<AtomicReference<Ptr<securegcm::D2DConnectionContextV1> > > >
encryption_context_;
ScopedPtr<Ptr<Lock> > is_paused_lock_;
ScopedPtr<Ptr<ConditionVariable> > is_paused_condition_variable_;
// If true, writes should block until this has been set to false.
ScopedPtr<Ptr<AtomicBoolean> > is_paused_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/base_endpoint_channel.cc"
#endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
File diff suppressed because it is too large Load Diff
+507
View File
@@ -0,0 +1,507 @@
#ifndef CORE_INTERNAL_BASE_PCP_HANDLER_H_
#define CORE_INTERNAL_BASE_PCP_HANDLER_H_
#include <cstdint>
#include <map>
#include <vector>
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/client_proxy.h"
#include "core/internal/encryption_runner.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/pcp.h"
#include "core/internal/pcp_handler.h"
#include "core/listeners.h"
#include "core/options.h"
#include "core/status.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/atomic_reference.h"
#include "platform/api/count_down_latch.h"
#include "platform/api/settable_future.h"
#include "platform/api/system_clock.h"
#include "platform/cancelable_alarm.h"
#include "platform/port/string.h"
#include "platform/prng.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
#include "securegcm/ukey2_handshake.h"
namespace location {
namespace nearby {
namespace connections {
namespace base_pcp_handler {
template <typename>
class StartAdvertisingCallable;
template <typename>
class StopAdvertisingRunnable;
template <typename>
class StartDiscoveryCallable;
template <typename>
class StopDiscoveryRunnable;
template <typename>
class RequestConnectionRunnable;
template <typename>
class AcceptConnectionCallable;
template <typename>
class RejectConnectionCallable;
template <typename>
class ProcessEndpointDisconnectionRunnable;
template <typename>
class OnConnectionResponseRunnable;
template <typename>
class OnEncryptionSuccessRunnable;
template <typename>
class OnEncryptionFailureRunnable;
} // namespace base_pcp_handler
// A base implementation of the PCPHandler interface that takes care of all
// bookkeeping and handshake protocols that are common across all PCPHandler
// implementations -- thus, every concrete PCPHandler implementation must extend
// this class, so that they can focus exclusively on the medium-specific
// operations.
template <typename Platform>
class BasePCPHandler
: public PCPHandler<Platform>,
public EndpointManager<Platform>::IncomingOfflineFrameProcessor {
public:
// TODO(tracyzhou): Add SecureRandom.
BasePCPHandler(
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager);
~BasePCPHandler() override;
// We have been asked by the client to start advertising. Once we successfully
// start advertising, we'll change the ClientProxy's state.
Status::Value startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const string& local_endpoint_name,
const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) override;
void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy) override;
Status::Value startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) override;
void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy) override;
Status::Value requestConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) override;
Status::Value acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<PayloadListener> payload_listener) override;
Status::Value rejectConnection(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id) override;
proto::connections::Medium getBandwidthUpgradeMedium() override;
// @EndpointManagerReaderThread
void processIncomingOfflineFrame(
ConstPtr<OfflineFrame> offline_frame, const string& from_endpoint_id,
Ptr<ClientProxy<Platform> > to_client_proxy,
proto::connections::Medium current_medium) override;
// Called when an endpoint disconnects while we're waiting for both sides to
// approve/reject the connection.
// @EndpointManagerThread
void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) override;
// Conforms to EncryptionRunner::ResultListener::onEncryptionSuccess().
// @EncryptionRunnerThread
void onEncryptionSuccessImpl(const string& endpoint_id,
Ptr<securegcm::UKey2Handshake> ukey2_handshake,
const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token);
// EncryptionRunner::ResultListener::onEncryptionFailure().
// @EncryptionRunnerThread
void onEncryptionFailureImpl(const string& endpoint_id,
Ptr<EndpointChannel> channel);
protected:
// The result of a call to startAdvertisingImpl() or startDiscoveryImpl().
class StartOperationResult {
public:
static Ptr<StartOperationResult> error(Status::Value status) {
return MakePtr(new StartOperationResult(status));
}
static Ptr<StartOperationResult> success(
const std::vector<proto::connections::Medium>& mediums) {
// Note: check here and not in the constructor, since for errors we have
// null mediums.
return MakePtr(new StartOperationResult(mediums));
}
private:
template <typename>
friend class base_pcp_handler::StartAdvertisingCallable;
template <typename>
friend class base_pcp_handler::StartDiscoveryCallable;
explicit StartOperationResult(Status::Value status)
: status_(status), mediums_() {}
explicit StartOperationResult(
const std::vector<proto::connections::Medium>& mediums)
: status_(Status::SUCCESS), mediums_(mediums) {}
// The status to be returned to the client.
Status::Value status_;
// If success, the mediums on which we are now advertising/discovering, for
// analytics.
std::vector<proto::connections::Medium> mediums_;
};
// 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)
class DiscoveredEndpoint {
public:
virtual ~DiscoveredEndpoint() {}
virtual string getEndpointId() = 0;
virtual string getEndpointName() = 0;
virtual string getServiceId() = 0;
virtual proto::connections::Medium getMedium() = 0;
};
struct ConnectImplResult {
proto::connections::Medium medium;
Status::Value status;
Ptr<EndpointChannel> endpoint_channel;
explicit ConnectImplResult(Ptr<EndpointChannel> endpoint_channel)
: medium(proto::connections::Medium::UNKNOWN_MEDIUM),
status(Status::SUCCESS),
endpoint_channel(endpoint_channel) {}
ConnectImplResult(proto::connections::Medium medium, Status::Value status)
: medium(medium), status(status), endpoint_channel() {}
};
void runOnPCPHandlerThread(Ptr<Runnable> runnable);
Ptr<AdvertisingOptions> getAdvertisingOptions();
// @PCPHandlerThread
void onEndpointFound(Ptr<ClientProxy<Platform> > client_proxy,
Ptr<DiscoveredEndpoint> endpoint);
// @PCPHandlerThread
void onEndpointLost(Ptr<ClientProxy<Platform> > client_proxy,
Ptr<DiscoveredEndpoint> endpoint);
Exception::Value onIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy,
const string& remote_device_name, Ptr<EndpointChannel> endpoint_channel,
proto::connections::Medium medium); // throws Exception::IO
virtual bool hasOutgoingConnections(Ptr<ClientProxy<Platform> > client_proxy);
virtual bool hasIncomingConnections(Ptr<ClientProxy<Platform> > client_proxy);
virtual bool canSendOutgoingConnection(
Ptr<ClientProxy<Platform> > client_proxy);
virtual bool canReceiveIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy);
// @PCPHandlerThread
virtual Ptr<StartOperationResult> startAdvertisingImpl(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const string& local_endpoint_id, const string& local_endpoint_name,
const AdvertisingOptions& options) = 0;
// @PCPHandlerThread
virtual Status::Value stopAdvertisingImpl(
Ptr<ClientProxy<Platform> > client_proxy) = 0;
// @PCPHandlerThread
virtual Ptr<StartOperationResult> startDiscoveryImpl(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& options) = 0;
// @PCPHandlerThread
virtual Status::Value stopDiscoveryImpl(
Ptr<ClientProxy<Platform> > client_proxy) = 0;
// @PCPHandlerThread
virtual ConnectImplResult connectImpl(
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<DiscoveredEndpoint> endpoint) = 0;
virtual std::vector<proto::connections::Medium>
getConnectionMediumsByPriority() = 0;
virtual proto::connections::Medium getDefaultUpgradeMedium() = 0;
Ptr<EndpointManager<Platform> > endpoint_manager_;
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager_;
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager_;
private:
template <typename>
friend class base_pcp_handler::StartAdvertisingCallable;
template <typename>
friend class base_pcp_handler::StopAdvertisingRunnable;
template <typename>
friend class base_pcp_handler::StartDiscoveryCallable;
template <typename>
friend class base_pcp_handler::StopDiscoveryRunnable;
template <typename>
friend class base_pcp_handler::RequestConnectionRunnable;
template <typename>
friend class base_pcp_handler::AcceptConnectionCallable;
template <typename>
friend class base_pcp_handler::RejectConnectionCallable;
template <typename>
friend class base_pcp_handler::OnConnectionResponseRunnable;
template <typename>
friend class base_pcp_handler::ProcessEndpointDisconnectionRunnable;
template <typename>
friend class base_pcp_handler::OnEncryptionSuccessRunnable;
template <typename>
friend class base_pcp_handler::OnEncryptionFailureRunnable;
class ResultListenerFacade
: public EncryptionRunner<Platform>::ResultListener {
public:
explicit ResultListenerFacade(Ptr<BasePCPHandler<Platform> > impl)
: impl_(impl) {}
void onEncryptionSuccess(
const string& endpoint_id,
Ptr<securegcm::UKey2Handshake> ukey2_handshake,
const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token) override {
impl_->onEncryptionSuccessImpl(endpoint_id, ukey2_handshake,
authentication_token,
raw_authentication_token);
}
void onEncryptionFailure(const string& endpoint_id,
Ptr<EndpointChannel> channel) override {
impl_->onEncryptionFailureImpl(endpoint_id, channel);
}
private:
Ptr<BasePCPHandler<Platform> > impl_;
};
class PendingConnectionInfo {
public:
static Ptr<PendingConnectionInfo> newIncomingPendingConnectionInfo(
Ptr<ClientProxy<Platform> > client_proxy,
const string& remote_endpoint_name,
Ptr<EndpointChannel> endpoint_channel, std::int32_t nonce,
std::int64_t start_time_millis,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
const std::vector<proto::connections::Medium>& supported_mediums);
static Ptr<PendingConnectionInfo> newOutgoingPendingConnectionInfo(
Ptr<ClientProxy<Platform> > client_proxy,
const string& remote_endpoint_name,
Ptr<EndpointChannel> endpoint_channel, std::int32_t nonce,
std::int64_t start_time_millis,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
Ptr<SettableFuture<Status::Value> > request_connection_result);
~PendingConnectionInfo();
void setUKey2Handshake(Ptr<securegcm::UKey2Handshake> ukey2_handshake);
void localEndpointAcceptedConnection(const string& endpoint_id,
Ptr<PayloadListener> payload_listener);
void localEndpointRejectedConnection(const string& endpoint_id);
private:
template <typename>
friend class BasePCPHandler;
template <typename>
friend class base_pcp_handler::RequestConnectionRunnable;
template <typename>
friend class base_pcp_handler::AcceptConnectionCallable;
template <typename>
friend class base_pcp_handler::RejectConnectionCallable;
template <typename>
friend class base_pcp_handler::OnEncryptionSuccessRunnable;
template <typename>
friend class base_pcp_handler::OnEncryptionFailureRunnable;
PendingConnectionInfo(
Ptr<ClientProxy<Platform> > client_proxy,
const string& remote_endpoint_name,
Ptr<EndpointChannel> endpoint_channel, std::int32_t nonce,
bool is_incoming, std::int64_t start_time_millis,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
Ptr<SettableFuture<Status::Value> > request_connection_result,
const std::vector<proto::connections::Medium>& supported_mediums);
Ptr<ClientProxy<Platform> > client_proxy_;
const string remote_endpoint_name_;
// Can be released prior to destructor.
ScopedPtr<Ptr<EndpointChannel> > endpoint_channel_;
const std::int32_t nonce_;
const bool is_incoming_;
const std::int64_t start_time_millis_;
// Can be released prior to destructor.
ScopedPtr<Ptr<ConnectionLifecycleListener> > connection_lifecycle_listener_;
// Only set for outgoing connections. Can be released prior to destructor.
// TODO(b/77783039): Consider creating a one-time-use-only wrapper class
// around the Ptr<SettableFuture> that's passed in (that also implements the
// SettableFuture interface) so we can avoid the easy-to-forget calls to
// request_connection_result_.clear() peppered through multiple places in
// the code.
Ptr<SettableFuture<Status::Value> > request_connection_result_;
// Only (possibly) set for incoming connections.
const std::vector<proto::connections::Medium> supported_mediums_;
// If set, this is owned.
Ptr<securegcm::UKey2Handshake> ukey2_handshake_;
};
static Exception::Value writeConnectionRequestFrame(
Ptr<EndpointChannel> endpoint_channel, const string& local_endpoint_id,
const string& local_endpoint_name, std::int32_t nonce,
const std::vector<proto::connections::Medium>& supported_mediums);
static const std::int64_t kConnectionRequestReadTimeoutMillis;
static const std::int64_t kRejectedConnectionCloseDelayMillis;
template <typename T>
Ptr<Future<T> > runOnPCPHandlerThread(Ptr<Callable<T> > callable);
// The interface deviates from the Java code to convey a better ownership
// story. Ownership of 'connection_response_offline_frame' is transferred to
// the callee by calling this method.
void onConnectionResponse(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
ConstPtr<OfflineFrame> connection_response_offline_frame);
// Returns true if the new endpoint is preferred over the old endpoint.
bool isPreferred(Ptr<DiscoveredEndpoint> new_endpoint,
Ptr<DiscoveredEndpoint> old_endpoint);
bool shouldEnforceTopologyConstraints();
bool autoUpgradeBandwidth();
// Returns true if the incoming connection should be killed. This only happens
// when an incoming connection arrives while we have an outgoing connection to
// the same endpoint and we need to stop one connection.
bool breakTie(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id, std::int32_t incoming_nonce,
Ptr<EndpointChannel> endpoint_channel);
// We're not sure how far our outgoing connection has gotten. We may (or may
// not) have called ClientProxy.onConnectionInitiated. Therefore, we'll call
// both preInit and preResult failures.
void processTieBreakLoss(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<PendingConnectionInfo> connection_info);
// Called when an incoming connection has been accepted by both sides.
//
// @param client_proxy The client
// @param endpoint_id The id of the remote device
// @param supported_mediums The mediums supported by the remote device. Empty
// for outgoing connections and older devices that don't report their
// supported mediums.
void initiateBandwidthUpgrade(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
const std::vector<proto::connections::Medium>& supported_mediums);
// Returns the optimal medium supported by both devices.
proto::connections::Medium chooseBestUpgradeMedium(
const std::vector<proto::connections::Medium>& their_supported_mediums);
// This method should assume ownership of endpoint_id.
void processPreConnectionInitiationFailure(
Ptr<ClientProxy<Platform> > client_proxy,
proto::connections::Medium medium, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel, bool is_incoming,
std::int64_t start_time_millis, Status::Value status,
Ptr<SettableFuture<Status::Value> > request_connection_result);
void processPreConnectionResultFailure(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id);
Ptr<DiscoveredEndpoint> getDiscoveredEndpoint(const string& endpoint_id);
// Called when either side accepts/rejects the connection, but only takes
// effect after both have accepted or one side has rejected.
//
// NOTE: We also take in a 'can_close_immediately' variable. This is because
// any writes in transit are dropped when we close. To avoid having a reject
// write being dropped (which causes the other side to report
// onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our close.
// If the other side behaves properly, we shouldn't even see the delay
// (because they will also close the connection).
void evaluateConnectionResult(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
bool can_close_immediately);
ExceptionOr<ConstPtr<OfflineFrame> > readConnectionRequestFrame(
Ptr<EndpointChannel> endpoint_channel);
void waitForLatch(const string& method_name, Ptr<CountDownLatch> latch);
Status::Value waitForResult(const string& method_name, std::int64_t client_id,
Ptr<Future<Status::Value> > result_future);
ScopedPtr<Ptr<AtomicReference<proto::connections::Medium> > >
bandwidth_upgrade_medium_;
ScopedPtr<Ptr<typename Platform::ScheduledExecutorType> > alarm_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > serial_executor_;
ScopedPtr<Ptr<SystemClock> > system_clock_;
Prng prng_;
// A map of endpoint id -> PendingConnectionInfo. Entries in this map imply
// that there is an active connection to the endpoint and we're waiting for
// both sides to accept before allowing payloads through. Once the fate of the
// connection is decided (either accepted or rejected), it should be removed
// from this map.
typedef std::map<string, Ptr<PendingConnectionInfo> > PendingConnectionsMap;
PendingConnectionsMap pending_connections_;
// A map of endpoint id -> DiscoveredEndpoint.
typedef std::map<string, Ptr<DiscoveredEndpoint> > DiscoveredEndpointsMap;
DiscoveredEndpointsMap 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
// message. It's expected that the other side will close the connection after
// reading the message (in which case, this alarm should be cancelled as it's
// no longer needed), but this alarm is the fallback in case that doesn't
// happen.
typedef std::map<string, Ptr<CancelableAlarm<Platform> > >
PendingRejectedConnectionCloseAlarmsMap;
PendingRejectedConnectionCloseAlarmsMap
pending_rejected_connection_close_alarms_;
// The active ClientProxy's advertising constraints. Null if the client hasn't
// started advertising. Note: this is not cleared when the client stops
// advertising because it might still be useful downstream of advertising (eg:
// establishing connections, performing bandwidth upgrades, etc.)
Ptr<AdvertisingOptions> advertising_options_;
// The active ClientProxy's connection lifecycle listener. Non-null while
// advertising.
Ptr<ConnectionLifecycleListener> advertising_connection_lifecycle_listener_;
// The active ClientProxy's discovery constraints. Null if the client
// hasn't started discovering. Note: this is not cleared when the client
// stops discovering because it might still be useful downstream of
// discovery (eg: connection speed, etc.)
Ptr<DiscoveryOptions> discovery_options_;
// This should have been a ScopedPtr, but we are making this a Ptr to manually
// control the order of destruction.
Ptr<EncryptionRunner<Platform> > encryption_runner_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/base_pcp_handler.cc"
#endif // CORE_INTERNAL_BASE_PCP_HANDLER_H_
+277
View File
@@ -0,0 +1,277 @@
#include "core/internal/ble_advertisement.h"
#include <algorithm>
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
namespace location {
namespace nearby {
namespace connections {
const std::uint32_t BLEAdvertisement::kServiceIdHashLength = 3;
const std::uint32_t BLEAdvertisement::kVersionAndPcpLength = 1;
// Should be defined as EndpointManager<Platform>::kEndpointIdLength, but that
// involves making BLEAdvertisement templatized on Platform just for
// that one little thing, so forego it (at least for now).
const std::uint32_t BLEAdvertisement::kEndpointIdLength = 4;
const std::uint32_t BLEAdvertisement::kEndpointNameSizeLength = 1;
const std::uint32_t BLEAdvertisement::kBluetoothMacAddressLength = 6;
const std::uint32_t BLEAdvertisement::kMinAdvertisementLength =
kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength +
kEndpointNameSizeLength + kBluetoothMacAddressLength;
const std::uint32_t BLEAdvertisement::kMaxEndpointNameLength = 131;
const std::uint16_t BLEAdvertisement::kVersionBitmask = 0x0E0;
const std::uint16_t BLEAdvertisement::kPCPBitmask = 0x01F;
const std::uint16_t BLEAdvertisement::kEndpointNameLengthBitmask = 0x0FF;
Ptr<BLEAdvertisement> BLEAdvertisement::fromBytes(
ConstPtr<ByteArray> ble_advertisement_bytes) {
if (ble_advertisement_bytes.isNull()) {
// TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement:
// null bytes passed in.");
return Ptr<BLEAdvertisement>();
}
if (ble_advertisement_bytes->size() < kMinAdvertisementLength) {
// TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement:
// expecting min %d raw bytes, got %d", kMinAdvertisementLength,
// ble_advertisement_bytes->size());
return Ptr<BLEAdvertisement>();
}
// Start reading the bytes.
const char* ble_advertisement_bytes_read_ptr =
ble_advertisement_bytes->getData();
// The first 3 bits are supposed to be the version.
Version::Value version = static_cast<Version::Value>(
(*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5);
if (version != Version::V1) {
// TODO(ahlee): logger.atDebug().log("Cannot deserialize BleAdvertisement:
// unsupported Version %d", version);
return Ptr<BLEAdvertisement>();
}
PCP::Value pcp =
static_cast<PCP::Value>(*ble_advertisement_bytes_read_ptr & kPCPBitmask);
ble_advertisement_bytes_read_ptr++;
if (pcp != PCP::P2P_CLUSTER && pcp != PCP::P2P_STAR &&
pcp != PCP::P2P_POINT_TO_POINT) {
// TODO(ahlee): logger.atDebug().log("Cannot deserialize BleAdvertisement:
// unsupported V1 PCP %d", pcp);
return Ptr<BLEAdvertisement>();
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength)));
ble_advertisement_bytes_read_ptr += kServiceIdHashLength;
std::string endpoint_id(ble_advertisement_bytes_read_ptr, kEndpointIdLength);
ble_advertisement_bytes_read_ptr += kEndpointIdLength;
std::uint32_t expected_endpoint_name_length = static_cast<std::uint32_t>(
*ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask);
ble_advertisement_bytes_read_ptr++;
// Check that the stated endpoint_name_length is the same as what we
// received (based off of the length of ble_advertisement_bytes).
std::uint32_t actual_endpoint_name_length =
computeEndpointNameLength(ble_advertisement_bytes);
if (actual_endpoint_name_length < expected_endpoint_name_length) {
// TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement:
// expected endpointName to be %d bytes, got %d bytes",
// expected_endpoint_name_length, actual_endpoint_name_length);
return Ptr<BLEAdvertisement>();
}
std::string endpoint_name(ble_advertisement_bytes_read_ptr,
expected_endpoint_name_length);
ble_advertisement_bytes_read_ptr += expected_endpoint_name_length;
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_bluetooth_mac_address_bytes(
MakeConstPtr(new ByteArray(ble_advertisement_bytes_read_ptr,
kBluetoothMacAddressLength)));
std::string bluetooth_mac_address;
// 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(scoped_bluetooth_mac_address_bytes.get())) {
bluetooth_mac_address = hexBytesToColonDelimitedString(
scoped_bluetooth_mac_address_bytes.get());
}
return MakePtr(
new BLEAdvertisement(version, pcp, scoped_service_id_hash.release(),
endpoint_id, endpoint_name, bluetooth_mac_address));
}
ConstPtr<ByteArray> BLEAdvertisement::toBytes(
Version::Value version, PCP::Value pcp, ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_id, const std::string& endpoint_name,
const std::string& bluetooth_mac_address) {
if (version != Version::V1) {
// TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement:
// unsupported Version %d", version);
return ConstPtr<ByteArray>();
}
if (pcp != PCP::P2P_CLUSTER && pcp != PCP::P2P_STAR &&
pcp != PCP::P2P_POINT_TO_POINT) {
// TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement:
// unsupported V1 PCP %d", pcp);
return ConstPtr<ByteArray>();
}
if (endpoint_name.size() > kMaxEndpointNameLength) {
// TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement:
// expected an endpointName of at most %d bytes but got %d",
// kMaxEndpoingNameLength, endpoint_name.size());
return ConstPtr<ByteArray>();
}
std::uint32_t ble_advertisement_length =
computeAdvertisementLength(endpoint_name);
Ptr<ByteArray> ble_advertisement_bytes{
new ByteArray{ble_advertisement_length}};
char* ble_advertisement_bytes_write_ptr = ble_advertisement_bytes->getData();
// 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);
*ble_advertisement_bytes_write_ptr = version_and_pcp_byte;
ble_advertisement_bytes_write_ptr++;
// The next 24 bits are the service id hash.
memcpy(ble_advertisement_bytes_write_ptr, service_id_hash->getData(),
kServiceIdHashLength);
ble_advertisement_bytes_write_ptr += kServiceIdHashLength;
// The next 32 bits are the endpoint id.
memcpy(ble_advertisement_bytes_write_ptr, endpoint_id.data(),
kEndpointIdLength);
ble_advertisement_bytes_write_ptr += kEndpointIdLength;
// The next 8 bits are the length of the endpoint name.
*ble_advertisement_bytes_write_ptr =
static_cast<char>(endpoint_name.size() & kEndpointNameLengthBitmask);
ble_advertisement_bytes_write_ptr++;
// The next x bits are the endpoint name. (Max length is 131 bytes).
memcpy(ble_advertisement_bytes_write_ptr, endpoint_name.data(),
endpoint_name.size());
ble_advertisement_bytes_write_ptr += endpoint_name.size();
// The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is
// invalid or empty, we get back a null byte array.
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_bluetooth_mac_address_bytes(
bluetoothMacAddressToHexBytes(bluetooth_mac_address));
if (!scoped_bluetooth_mac_address_bytes.isNull()) {
memcpy(ble_advertisement_bytes_write_ptr,
scoped_bluetooth_mac_address_bytes->getData(),
kBluetoothMacAddressLength);
}
ble_advertisement_bytes_write_ptr += kBluetoothMacAddressLength;
return ConstifyPtr(ble_advertisement_bytes);
}
std::string BLEAdvertisement::hexBytesToColonDelimitedString(
ConstPtr<ByteArray> hex_bytes) {
// Convert the hex bytes to a string.
std::string colon_delimited_string(absl::BytesToHexString(
std::string(hex_bytes->getData(), hex_bytes->size())));
absl::AsciiStrToUpper(&colon_delimited_string);
// Insert the colons.
for (int i = colon_delimited_string.length() - 2; i > 0; i -= 2) {
colon_delimited_string.insert(i, ":");
}
return colon_delimited_string;
}
// TODO(ahlee): Rename to bluetoothMacAddressHexStringToBytes
ConstPtr<ByteArray> BLEAdvertisement::bluetoothMacAddressToHexBytes(
const std::string& bluetooth_mac_address) {
std::string bt_mac_address(bluetooth_mac_address);
// Remove the colon delimiters.
bt_mac_address.erase(
std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'),
bt_mac_address.end());
// If the bluetooth mac address is invalid (wrong size), return a null byte
// array.
if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) {
return ConstPtr<ByteArray>();
}
// Convert to bytes.
std::string bt_mac_address_bytes(absl::HexStringToBytes(bt_mac_address));
return MakeConstPtr(
new ByteArray(bt_mac_address_bytes.data(), bt_mac_address_bytes.size()));
}
bool BLEAdvertisement::isBluetoothMacAddressUnset(
ConstPtr<ByteArray> bluetooth_mac_address_bytes) {
for (int i = 0; i < bluetooth_mac_address_bytes->size(); i++) {
if (bluetooth_mac_address_bytes->getData()[i] != 0) {
return false;
}
}
return true;
}
std::uint32_t BLEAdvertisement::computeEndpointNameLength(
ConstPtr<ByteArray> ble_advertisement_bytes) {
return ble_advertisement_bytes->size() - kMinAdvertisementLength;
}
std::uint32_t BLEAdvertisement::computeAdvertisementLength(
const std::string& endpoint_name) {
return kMinAdvertisementLength + endpoint_name.size();
}
BLEAdvertisement::BLEAdvertisement(Version::Value version, PCP::Value pcp,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_id,
const std::string& endpoint_name,
const std::string& bluetooth_mac_address)
: version_(version),
pcp_(pcp),
service_id_hash_(service_id_hash),
endpoint_id_(endpoint_id),
endpoint_name_(endpoint_name),
bluetooth_mac_address_(bluetooth_mac_address) {}
BLEAdvertisement::~BLEAdvertisement() {
// Nothing to do.
}
BLEAdvertisement::Version::Value BLEAdvertisement::getVersion() const {
return version_;
}
PCP::Value BLEAdvertisement::getPCP() const { return pcp_; }
std::string BLEAdvertisement::getEndpointId() const { return endpoint_id_; }
ConstPtr<ByteArray> BLEAdvertisement::getServiceIdHash() const {
return service_id_hash_.get();
}
std::string BLEAdvertisement::getEndpointName() const { return endpoint_name_; }
std::string BLEAdvertisement::getBluetoothMacAddress() const {
return bluetooth_mac_address_;
}
} // namespace connections
} // namespace nearby
} // namespace location
+95
View File
@@ -0,0 +1,95 @@
#ifndef CORE_INTERNAL_BLE_ADVERTISEMENT_H_
#define CORE_INTERNAL_BLE_ADVERTISEMENT_H_
#include <cstdint>
#include "core/internal/pcp.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Represents the format of the Connections BLE Advertisement used in
// Advertising + Discovery.
//
// <p>[VERSION][PCP][SERVICE_ID_HASH][ENDPOINT_ID][ENDPOINT_NAME_SIZE]
// [ENDPOINT_NAME][BLUETOOTH_MAC]
//
// <p>See go/connections-ble-advertisement for more information.
class BLEAdvertisement {
public:
// Versions of the BLEAdvertisement.
struct Version {
enum Value {
V1 = 1,
// Version is only allocated 3 bits in the BLEAdvertisement, so this
// can never go beyond V7.
};
};
static Ptr<BLEAdvertisement> fromBytes(
ConstPtr<ByteArray> ble_advertisement_bytes);
static ConstPtr<ByteArray> toBytes(Version::Value version, PCP::Value pcp,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_id,
const std::string& endpoint_name,
const std::string& bluetooth_mac_address);
static const std::uint32_t kServiceIdHashLength;
static const std::uint32_t kMinAdvertisementLength;
// TODO(ahlee): Make sure names match for both Java and C++ implementations.
static const std::uint32_t kMaxEndpointNameLength;
~BLEAdvertisement();
Version::Value getVersion() const;
PCP::Value getPCP() const;
ConstPtr<ByteArray> getServiceIdHash() const;
std::string getEndpointId() const;
std::string getEndpointName() const;
std::string getBluetoothMacAddress() const;
private:
static std::string hexBytesToColonDelimitedString(
ConstPtr<ByteArray> hex_bytes);
// TODO(ahlee): Rename to bluetoothMacAddressHexStringToBytes
static ConstPtr<ByteArray> bluetoothMacAddressToHexBytes(
const std::string& bluetooth_mac_address);
static std::uint32_t computeEndpointNameLength(
ConstPtr<ByteArray> ble_advertisement_bytes);
static std::uint32_t computeAdvertisementLength(
const std::string& endpoint_name);
static bool isBluetoothMacAddressUnset(
ConstPtr<ByteArray> bluetooth_mac_address_bytes);
static const std::uint32_t kVersionAndPcpLength;
static const std::uint32_t kEndpointIdLength;
static const std::uint32_t kEndpointNameSizeLength;
static const std::uint32_t kBluetoothMacAddressLength;
static const std::uint16_t kVersionBitmask;
static const std::uint16_t kPCPBitmask;
static const std::uint16_t kEndpointNameLengthBitmask;
BLEAdvertisement(Version::Value version, PCP::Value pcp,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_id,
const std::string& endpoint_name,
const std::string& bluetooth_mac_address);
const Version::Value version_;
const PCP::Value pcp_;
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
const std::string endpoint_id_;
const std::string endpoint_name_;
const std::string bluetooth_mac_address_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_BLE_ADVERTISEMENT_H_
+343
View File
@@ -0,0 +1,343 @@
#include "core/internal/ble_advertisement.h"
#include <cstring>
#include "platform/port/string.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
const BLEAdvertisement::Version::Value version = BLEAdvertisement::Version::V1;
const PCP::Value pcp = PCP::P2P_CLUSTER;
const char endpoint_id[] = "AB12";
const char service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C};
const char endpoint_name[] =
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?";
const char bluetooth_mac_address[] = "00:00:E6:88:64:13";
TEST(BLEAdvertisementTest, SerializationDeserializationWorks) {
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_EQ(bluetooth_mac_address,
scoped_ble_advertisement->getBluetoothMacAddress());
}
TEST(BLEAdvertisementTest, SerializationDeserializationWorksWithGoodPCP) {
PCP::Value good_pcp = PCP::P2P_STAR;
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, good_pcp, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_id, endpoint_name, bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(good_pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_EQ(bluetooth_mac_address,
scoped_ble_advertisement->getBluetoothMacAddress());
}
TEST(BLEAdvertisementTest,
SerializationDeserializationWorksWithEmptyEndpointName) {
std::string empty_endpoint_name;
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
empty_endpoint_name, bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(empty_endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_EQ(bluetooth_mac_address,
scoped_ble_advertisement->getBluetoothMacAddress());
}
TEST(BLEAdvertisementTest,
SerializationDeSerializationFailsWithLongEndpointName) {
std::string long_endpoint_name(BLEAdvertisement::kMaxEndpointNameLength + 1,
'x');
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
long_endpoint_name, bluetooth_mac_address));
ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull());
}
TEST(BLEAdvertisementTest,
SerializationDeserializationWorksWithEmojiEndpointName) {
std::string emoji_endpoint_name("\u0001F450 \u0001F450");
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
emoji_endpoint_name, bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(emoji_endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_EQ(bluetooth_mac_address,
scoped_ble_advertisement->getBluetoothMacAddress());
}
TEST(BLEAdvertisementTest, SerializationFailsWithBadVersion) {
BLEAdvertisement::Version::Value bad_version =
static_cast<BLEAdvertisement::Version::Value>(666);
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
bad_version, pcp, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_id, endpoint_name, bluetooth_mac_address));
ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithBadPCP) {
PCP::Value bad_pcp = static_cast<PCP::Value>(666);
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, bad_pcp, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_id, endpoint_name, bluetooth_mac_address));
ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull());
}
TEST(BLEAdvertisementTest, SerializationSucceedsWithEmptyBluetoothMacAddress) {
std::string empty_bluetooth_mac_address = "";
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, empty_bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_EQ(empty_bluetooth_mac_address,
scoped_ble_advertisement->getBluetoothMacAddress());
}
TEST(BLEAdvertisementTest,
SerializationSucceedsWithInvalidBluetoothMacAddress) {
std::string bad_bluetooth_mac_address = "022:00";
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bad_bluetooth_mac_address));
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP());
ASSERT_EQ(version, scoped_ble_advertisement->getVersion());
ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(service_id_hash_bytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName());
ASSERT_TRUE(scoped_ble_advertisement->getBluetoothMacAddress().empty());
}
TEST(BLEAdvertisementTest, DeserializationFailsWithNullBytes) {
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(ConstPtr<ByteArray>()));
ASSERT_TRUE(scoped_ble_advertisement.get().isNull());
}
TEST(BLEAdvertisementTest, DeserializationFailsWithShortLength) {
// Serialize good data into a good BLE Advertisement.
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bluetooth_mac_address));
// Shorten the valid BLE Advertisement.
ScopedPtr<ConstPtr<ByteArray> > short_ble_advertisement_bytes(MakeConstPtr(
new ByteArray(scoped_ble_advertisement_bytes.get()->getData(),
BLEAdvertisement::kMinAdvertisementLength - 1)));
// Fail to deserialize the short BLE Advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_short_ble_advertisement(
BLEAdvertisement::fromBytes(short_ble_advertisement_bytes.get()));
ASSERT_TRUE(scoped_short_ble_advertisement.get().isNull());
// Make sure deserialization succeeds with the valid BLE Advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_FALSE(scoped_ble_advertisement.get().isNull());
}
TEST(BLEAdvertisementTest, DeserializationFailsWithWrongEndpointNameLength) {
// Serialize good data into a good BLE Advertisement.
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bluetooth_mac_address));
// Corrupt the EndpointNameLength bits.
std::string corrupt_ble_advertisement_bytes(
scoped_ble_advertisement_bytes->getData(),
scoped_ble_advertisement_bytes->size());
corrupt_ble_advertisement_bytes[8] ^= 0x0FF;
ScopedPtr<ConstPtr<ByteArray> > scoped_corrupt_ble_advertisement_bytes(
MakeConstPtr(new ByteArray(corrupt_ble_advertisement_bytes.data(),
corrupt_ble_advertisement_bytes.size())));
// And deserialize the corrupt BLE Advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(
scoped_corrupt_ble_advertisement_bytes.get()));
ASSERT_TRUE(scoped_ble_advertisement.isNull());
}
// Bytes at the end should be ignored so that they can be used as reserve bytes
// in the future.
TEST(BLEAdvertisementTest, DeserializationPassesWithLongLength) {
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bluetooth_mac_address));
// Add bytes to the end of the valid BLE advertisement.
ScopedPtr<ConstPtr<ByteArray> > long_ble_advertisement_bytes(MakeConstPtr(
new ByteArray(scoped_ble_advertisement_bytes.get()->getData(),
BLEAdvertisement::kMinAdvertisementLength + 1000)));
// Deserialize the long BLE advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_long_ble_advertisement(
BLEAdvertisement::fromBytes(long_ble_advertisement_bytes.get()));
ASSERT_FALSE(scoped_long_ble_advertisement.get().isNull());
// Make sure deserialization succeeds with the valid BLE Advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_FALSE(scoped_ble_advertisement.get().isNull());
}
TEST(BLEAdvertisementTest, DeserializationWorksWithLongEndpointName) {
// Serialize good data into a good BLE Advertisement.
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id,
endpoint_name, bluetooth_mac_address));
// Corrupt the EndpointNameLength bits and increase it past the accepted max
// length.
std::string corrupt_ble_advertisement_bytes(
scoped_ble_advertisement_bytes->getData(),
scoped_ble_advertisement_bytes->size());
corrupt_ble_advertisement_bytes[8] ^=
BLEAdvertisement::kMaxEndpointNameLength + 10;
ScopedPtr<ConstPtr<ByteArray> > scoped_corrupt_ble_advertisement_bytes(
MakeConstPtr(new ByteArray(corrupt_ble_advertisement_bytes.data(),
corrupt_ble_advertisement_bytes.size())));
// Increase the size of the advertisement so that there's enough data for the
// now-longer endpoint name.
ScopedPtr<ConstPtr<ByteArray> > long_ble_advertisement_bytes(MakeConstPtr(
new ByteArray(scoped_corrupt_ble_advertisement_bytes.get()->getData(),
BLEAdvertisement::kMinAdvertisementLength + 1000)));
// And deserialize the changed BLE Advertisement.
ScopedPtr<Ptr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(long_ble_advertisement_bytes.get()));
ASSERT_FALSE(scoped_ble_advertisement.isNull());
}
} // namespace
} // namespace connections
} // namespace nearby
} // namespace location
+26
View File
@@ -0,0 +1,26 @@
#ifndef CORE_INTERNAL_BLE_COMPAT_H_
#define CORE_INTERNAL_BLE_COMPAT_H_
#ifndef BLE_V2_IMPLEMENTED
// Flip to true when BLE_V2 is fully implemented and ready to be tested.
#define BLE_V2_IMPLEMENTED 0
#endif
#if BLE_V2_IMPLEMENTED
#include "core/internal/mediums/ble_peripheral.h"
#include "core/internal/mediums/discovered_peripheral_callback.h"
#define BLE_PERIPHERAL location::nearby::connections::mediums::BLEPeripheral
#define DISCOVERED_PERIPHERAL_CALLBACK \
location::nearby::connections::mediums::DiscoveredPeripheralCallback
#else
#include "platform/api/ble.h"
#define BLE_PERIPHERAL location::nearby::BLEPeripheral
#define DISCOVERED_PERIPHERAL_CALLBACK \
BLE<Platform>::DiscoveredPeripheralCallback
#endif // BLE_V2_IMPLEMENTED
#endif // CORE_INTERNAL_BLE_COMPAT_H_
+55
View File
@@ -0,0 +1,55 @@
#include "core/internal/ble_endpoint_channel.h"
#include <string>
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
Ptr<BLEEndpointChannel<Platform> >
BLEEndpointChannel<Platform>::createOutgoing(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BLESocket> ble_socket) {
return MakePtr(
new BLEEndpointChannel<Platform>(channel_name, ble_socket));
}
template <typename Platform>
Ptr<BLEEndpointChannel<Platform> >
BLEEndpointChannel<Platform>::createIncoming(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BLESocket> ble_socket) {
return MakePtr(
new BLEEndpointChannel<Platform>(channel_name, ble_socket));
}
template <typename Platform>
BLEEndpointChannel<Platform>::BLEEndpointChannel(
const string& channel_name, Ptr<BLESocket> ble_socket)
: BaseEndpointChannel<Platform>(channel_name,
ble_socket->getInputStream(),
ble_socket->getOutputStream()),
ble_socket_(ble_socket) {}
template <typename Platform>
BLEEndpointChannel<Platform>::~BLEEndpointChannel() {}
template <typename Platform>
proto::connections::Medium BLEEndpointChannel<Platform>::getMedium() {
return proto::connections::Medium::BLE;
}
template <typename Platform>
void BLEEndpointChannel<Platform>::closeImpl() {
Exception::Value exception = ble_socket_->close();
if (exception != Exception::NONE) {
if (exception == Exception::IO) {
// TODO(ahlee): Add logging.
}
}
}
} // namespace connections
} // namespace nearby
} // namespace location
+44
View File
@@ -0,0 +1,44 @@
#ifndef CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
#define CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
#include "core/internal/base_endpoint_channel.h"
#include "core/internal/medium_manager.h"
#include "platform/api/ble.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class BLEEndpointChannel : public BaseEndpointChannel<Platform> {
public:
static Ptr<BLEEndpointChannel<Platform> > createOutgoing(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BLESocket> ble_socket);
static Ptr<BLEEndpointChannel<Platform> > createIncoming(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BLESocket> ble_socket);
~BLEEndpointChannel() override;
proto::connections::Medium getMedium() override;
protected:
void closeImpl() override;
private:
BLEEndpointChannel(const string& channel_name, Ptr<BLESocket> ble_socket);
ScopedPtr<Ptr<BLESocket> > ble_socket_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/ble_endpoint_channel.cc"
#endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
+292
View File
@@ -0,0 +1,292 @@
#include "core/internal/bluetooth_device_name.h"
#include <cstring>
#include "platform/base64_utils.h"
namespace location {
namespace nearby {
namespace connections {
const std::uint32_t BluetoothDeviceName::kServiceIdHashLength = 3;
const std::uint32_t BluetoothDeviceName::kMaxBluetoothDeviceNameLength = 147;
// Should be defined as ClientProxy<Platform>::kEndpointIdLength, but that
// involves making BluetoothDeviceName templatized on Platform just for
// that one little thing, so forego it (at least for now).
const std::uint32_t BluetoothDeviceName::kEndpointIdLength = 4;
const std::uint32_t BluetoothDeviceName::kReservedLength = 7;
const std::uint32_t BluetoothDeviceName::kMaxEndpointNameLength = 131;
const std::uint32_t BluetoothDeviceName::kMinBluetoothDeviceNameLength =
kMaxBluetoothDeviceNameLength - kMaxEndpointNameLength;
const std::uint16_t BluetoothDeviceName::kVersionBitmask = 0x0E0;
const std::uint16_t BluetoothDeviceName::kPCPBitmask = 0x01F;
const std::uint16_t BluetoothDeviceName::kEndpointNameLengthBitmask = 0x0FF;
Ptr<BluetoothDeviceName> BluetoothDeviceName::fromString(
const std::string& bluetooth_device_name_string) {
ScopedPtr<Ptr<ByteArray> > scoped_bluetooth_device_name_bytes(
Base64Utils::decode(bluetooth_device_name_string));
if (scoped_bluetooth_device_name_bytes.isNull()) {
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: failed Base64 decoding of %s",
// bluetoothDeviceNameString);
return Ptr<BluetoothDeviceName>();
}
if (scoped_bluetooth_device_name_bytes->size() >
kMaxBluetoothDeviceNameLength) {
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: expecting max %d raw bytes, got %d",
// MAX_BLUETOOTH_DEVICE_NAME_LENGTH, bluetoothDeviceNameBytes.length);
return Ptr<BluetoothDeviceName>();
}
if (scoped_bluetooth_device_name_bytes->size() <
kMinBluetoothDeviceNameLength) {
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: expecting min %d raw bytes, got %d",
// MIN_BLUETOOTH_DEVICE_NAME_LENGTH, bluetoothDeviceNameBytes.length);
return Ptr<BluetoothDeviceName>();
}
// The first 3 bits are supposed to be the version.
Version::Value version = static_cast<Version::Value>(
(scoped_bluetooth_device_name_bytes->getData()[0] & kVersionBitmask) >>
5);
switch (version) {
case Version::V1:
return createV1BluetoothDeviceName(
ConstifyPtr(scoped_bluetooth_device_name_bytes.get()));
default:
// TODO(reznor): [ANALYTICIZE] This either represents corruption over the
// air, or older versions of GmsCore intermingling with newer ones.
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: unsupported Version %d", version);
return Ptr<BluetoothDeviceName>();
}
}
std::string BluetoothDeviceName::asString(Version::Value version,
PCP::Value pcp,
const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_name) {
std::string usable_endpoint_name(endpoint_name);
if (endpoint_name.size() > kMaxEndpointNameLength) {
// TODO(reznor): logger.atWarning().log("While serializing Advertisement,
// truncating Endpoint Name %s (%d bytes) down to %d bytes", endpointName,
// endpointNameBytes.length, MAX_ENDPOINT_NAME_LENGTH);
usable_endpoint_name.erase(kMaxEndpointNameLength);
}
ScopedPtr<Ptr<ByteArray> > scoped_endpoint_name_bytes(
new ByteArray(usable_endpoint_name.data(), usable_endpoint_name.size()));
Ptr<ByteArray> bluetooth_device_name_bytes;
switch (version) {
case Version::V1:
bluetooth_device_name_bytes =
createV1Bytes(pcp, endpoint_id, service_id_hash,
ConstifyPtr(scoped_endpoint_name_bytes.get()));
if (bluetooth_device_name_bytes.isNull()) {
return "";
}
break;
default:
// TODO(reznor): logger.atDebug().log("Cannot serialize
// BluetoothDeviceName: unsupported Version %d", version);
return "";
}
ScopedPtr<Ptr<ByteArray> > scoped_bluetooth_device_name_bytes(
bluetooth_device_name_bytes);
// BluetoothDeviceName needs to be binary safe, so apply a Base64 encoding
// over the raw bytes.
return Base64Utils::encode(
ConstifyPtr(scoped_bluetooth_device_name_bytes.get()));
}
Ptr<BluetoothDeviceName> BluetoothDeviceName::createV1BluetoothDeviceName(
ConstPtr<ByteArray> bluetooth_device_name_bytes) {
const char* bluetooth_device_name_bytes_read_ptr =
bluetooth_device_name_bytes->getData();
// The first 5 bits of the V1 payload are supposed to be the PCP.
PCP::Value pcp = static_cast<PCP::Value>(
*bluetooth_device_name_bytes_read_ptr & kPCPBitmask);
bluetooth_device_name_bytes_read_ptr++;
switch (pcp) {
case PCP::P2P_CLUSTER: // Fall through
case PCP::P2P_STAR: // Fall through
case PCP::P2P_POINT_TO_POINT: {
// The next 32 bits are supposed to be the endpoint_id.
std::string endpoint_id(bluetooth_device_name_bytes_read_ptr,
kEndpointIdLength);
bluetooth_device_name_bytes_read_ptr += kEndpointIdLength;
// The next 24 bits are supposed to be the scoped_service_id_hash.
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(bluetooth_device_name_bytes_read_ptr,
kServiceIdHashLength)));
bluetooth_device_name_bytes_read_ptr += kServiceIdHashLength;
// The next 56 bits are supposed to be reserved, and can be left
// untouched.
bluetooth_device_name_bytes_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>(
*bluetooth_device_name_bytes_read_ptr & kEndpointNameLengthBitmask);
bluetooth_device_name_bytes_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 =
computeEndpointNameLength(bluetooth_device_name_bytes);
if (actual_endpoint_name_length != expected_endpoint_name_length) {
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: expected endpointName to be %d bytes, got %d
// bytes", expectedEndpointNameLength, actualEndpointNameLength);
return Ptr<BluetoothDeviceName>();
}
std::string endpoint_name(bluetooth_device_name_bytes_read_ptr,
actual_endpoint_name_length);
bluetooth_device_name_bytes_read_ptr += actual_endpoint_name_length;
return MakePtr(new BluetoothDeviceName(Version::V1, pcp, endpoint_id,
scoped_service_id_hash.release(),
endpoint_name));
}
default:
// TODO(reznor): [ANALYTICIZE] This either represents corruption over the
// air, or older versions of GmsCore intermingling with newer ones.
// TODO(reznor): logger.atDebug().log("Cannot deserialize
// BluetoothDeviceName: unsupported V1 PCP %d", pcp);
return Ptr<BluetoothDeviceName>();
}
}
std::uint32_t BluetoothDeviceName::computeEndpointNameLength(
ConstPtr<ByteArray> bluetooth_device_name_bytes) {
return kMaxEndpointNameLength -
(kMaxBluetoothDeviceNameLength - bluetooth_device_name_bytes->size());
}
std::uint32_t BluetoothDeviceName::computeBluetoothDeviceNameLength(
ConstPtr<ByteArray> endpoint_name_bytes) {
return kMaxBluetoothDeviceNameLength -
(kMaxEndpointNameLength - endpoint_name_bytes->size());
}
Ptr<ByteArray> BluetoothDeviceName::createV1Bytes(
PCP::Value pcp, const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> endpoint_name_bytes) {
std::uint32_t bluetooth_device_name_length =
computeBluetoothDeviceNameLength(endpoint_name_bytes);
Ptr<ByteArray> bluetooth_device_name_bytes{
new ByteArray{bluetooth_device_name_length}};
char* bluetooth_device_name_bytes_write_ptr =
bluetooth_device_name_bytes->getData();
// The first 3 bits are the Version.
char version_and_pcp_byte =
static_cast<char>((Version::V1 << 5) & kVersionBitmask);
// The next 5 bits are the PCP.
version_and_pcp_byte |= static_cast<char>(pcp & kPCPBitmask);
*bluetooth_device_name_bytes_write_ptr = version_and_pcp_byte;
bluetooth_device_name_bytes_write_ptr++;
switch (pcp) {
case PCP::P2P_CLUSTER: // Fall through
case PCP::P2P_STAR: // Fall through
case PCP::P2P_POINT_TO_POINT:
// The next 32 bits are the endpoint_id.
if (endpoint_id.size() != kEndpointIdLength) {
// TODO(reznor): logger.atDebug().log("Cannot serialize
// BluetoothDeviceName: V1 Endpoint ID %s (%d bytes) should be exactly
// %d bytes", endpointId, endpointId.length(), ENDPOINT_ID_LENGTH);
return Ptr<ByteArray>();
}
memcpy(bluetooth_device_name_bytes_write_ptr, endpoint_id.data(),
kEndpointIdLength);
bluetooth_device_name_bytes_write_ptr += kEndpointIdLength;
// The next 24 bits are the service_id_hash.
if (service_id_hash->size() != kServiceIdHashLength) {
// TODO(reznor): logger.atDebug().log("Cannot serialize
// BluetoothDeviceName: V1 ServiceID hash (%d bytes) should be exactly
// %d bytes", serviceIdHash.length, SERVICE_ID_HASH_LENGTH);
return Ptr<ByteArray>();
}
memcpy(bluetooth_device_name_bytes_write_ptr, service_id_hash->getData(),
kServiceIdHashLength);
bluetooth_device_name_bytes_write_ptr += kServiceIdHashLength;
// The next 56 bits are reserved, and should all be zeroed out, so do
// that and then jump over 56 bits to position things for the next write.
memset(bluetooth_device_name_bytes_write_ptr, 0, kReservedLength);
bluetooth_device_name_bytes_write_ptr += kReservedLength;
// The next 8 bits are the length of the endpoint_name.
*bluetooth_device_name_bytes_write_ptr = static_cast<char>(
endpoint_name_bytes->size() & kEndpointNameLengthBitmask);
bluetooth_device_name_bytes_write_ptr++;
// The remaining bits are filled with the endpoint_name.
memcpy(bluetooth_device_name_bytes_write_ptr,
endpoint_name_bytes->getData(), endpoint_name_bytes->size());
bluetooth_device_name_bytes_write_ptr += endpoint_name_bytes->size();
break;
default:
// TODO(reznor): logger.atDebug().log("Cannot serialize
// BluetoothDeviceName: unsupported V1 PCP %d", pcp);
return Ptr<ByteArray>();
}
return bluetooth_device_name_bytes;
}
BluetoothDeviceName::BluetoothDeviceName(Version::Value version, PCP::Value pcp,
const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_name)
: version_(version),
pcp_(pcp),
endpoint_id_(endpoint_id),
service_id_hash_(service_id_hash),
endpoint_name_(endpoint_name) {}
BluetoothDeviceName::~BluetoothDeviceName() {
// Nothing to do.
}
BluetoothDeviceName::Version::Value BluetoothDeviceName::getVersion() const {
return version_;
}
PCP::Value BluetoothDeviceName::getPCP() const { return pcp_; }
std::string BluetoothDeviceName::getEndpointId() const { return endpoint_id_; }
ConstPtr<ByteArray> BluetoothDeviceName::getServiceIdHash() const {
return service_id_hash_.get();
}
std::string BluetoothDeviceName::getEndpointName() const {
return endpoint_name_;
}
} // namespace connections
} // namespace nearby
} // namespace location
+86
View File
@@ -0,0 +1,86 @@
#ifndef CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
#define CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
#include <cstdint>
#include "core/internal/pcp.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Represents the format of the Bluetooth device name used in Advertising +
// Discovery.
//
// <p>See go/nearby-offline-data-interchange-formats for the specification.
class BluetoothDeviceName {
public:
// Versions of the BluetoothDeviceName.
struct Version {
enum Value {
V1 = 1,
// Version is only allocated 3 bits in the BluetoothDeviceName, so this
// can never go beyond V7.
};
};
static Ptr<BluetoothDeviceName> fromString(
const std::string& bluetooth_device_name_string);
static std::string asString(Version::Value version, PCP::Value pcp,
const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_name);
static const std::uint32_t kServiceIdHashLength;
~BluetoothDeviceName();
Version::Value getVersion() const;
PCP::Value getPCP() const;
std::string getEndpointId() const;
ConstPtr<ByteArray> getServiceIdHash() const;
std::string getEndpointName() const;
private:
static Ptr<BluetoothDeviceName> createV1BluetoothDeviceName(
ConstPtr<ByteArray> bluetooth_device_name_bytes);
static std::uint32_t computeEndpointNameLength(
ConstPtr<ByteArray> bluetooth_device_name_bytes);
static std::uint32_t computeBluetoothDeviceNameLength(
ConstPtr<ByteArray> endpoint_name_bytes);
static Ptr<ByteArray> createV1Bytes(PCP::Value pcp,
const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> endpoint_name_bytes);
static const std::uint32_t kMaxBluetoothDeviceNameLength;
static const std::uint32_t kEndpointIdLength;
static const std::uint32_t kReservedLength;
static const std::uint32_t kMaxEndpointNameLength;
static const std::uint32_t kMinBluetoothDeviceNameLength;
static const std::uint16_t kVersionBitmask;
static const std::uint16_t kPCPBitmask;
static const std::uint16_t kEndpointNameLengthBitmask;
BluetoothDeviceName(Version::Value version, PCP::Value pcp,
const std::string& endpoint_id,
ConstPtr<ByteArray> service_id_hash,
const std::string& endpoint_name);
const Version::Value version_;
const PCP::Value pcp_;
const std::string endpoint_id_;
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
const std::string endpoint_name_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
@@ -0,0 +1,198 @@
#include "core/internal/bluetooth_device_name.h"
#include <cstring>
#include "platform/base64_utils.h"
#include "platform/port/string.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
const BluetoothDeviceName::Version::Value version =
BluetoothDeviceName::Version::V1;
const PCP::Value pcp = PCP::P2P_CLUSTER;
const char endpoint_id[] = "AB12";
const char service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C};
const char endpoint_name[] = "RAWK + ROWL!";
TEST(BluetoothDeviceNameTest, SerializationDeserializationWorks) {
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_name);
ScopedPtr<Ptr<BluetoothDeviceName> > scoped_bluetooth_device_name(
BluetoothDeviceName::fromString(bluetooth_device_name_string));
ASSERT_EQ(pcp, scoped_bluetooth_device_name->getPCP());
ASSERT_EQ(version, scoped_bluetooth_device_name->getVersion());
ASSERT_EQ(endpoint_id, scoped_bluetooth_device_name->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_bluetooth_device_name->getServiceIdHash()->size());
ASSERT_EQ(0,
memcmp(service_id_hash_bytes,
scoped_bluetooth_device_name->getServiceIdHash()->getData(),
scoped_bluetooth_device_name->getServiceIdHash()->size()));
ASSERT_EQ(endpoint_name, scoped_bluetooth_device_name->getEndpointName());
}
TEST(BluetoothDeviceNameTest,
SerializationDeserializationWorksWithEmptyEndpointName) {
std::string empty_endpoint_name;
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
empty_endpoint_name);
ScopedPtr<Ptr<BluetoothDeviceName> > scoped_bluetooth_device_name(
BluetoothDeviceName::fromString(bluetooth_device_name_string));
ASSERT_EQ(pcp, scoped_bluetooth_device_name->getPCP());
ASSERT_EQ(version, scoped_bluetooth_device_name->getVersion());
ASSERT_EQ(endpoint_id, scoped_bluetooth_device_name->getEndpointId());
ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char),
scoped_bluetooth_device_name->getServiceIdHash()->size());
ASSERT_EQ(0,
memcmp(service_id_hash_bytes,
scoped_bluetooth_device_name->getServiceIdHash()->getData(),
scoped_bluetooth_device_name->getServiceIdHash()->size()));
ASSERT_EQ(empty_endpoint_name,
scoped_bluetooth_device_name->getEndpointName());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithBadVersion) {
BluetoothDeviceName::Version::Value bad_version =
static_cast<BluetoothDeviceName::Version::Value>(666);
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
bad_version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithBadPCP) {
PCP::Value bad_pcp = static_cast<PCP::Value>(666);
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, bad_pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithShortEndpointId) {
std::string short_endpoint_id("AB1");
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, short_endpoint_id,
ConstifyPtr(scoped_service_id_hash.get()), endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithLongEndpointId) {
std::string long_endpoint_id("AB12X");
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, long_endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithShortServiceIdHash) {
char short_service_id_hash_bytes[] = {0x0A, 0x0B};
ScopedPtr<Ptr<ByteArray> > scoped_short_service_id_hash(
new ByteArray(short_service_id_hash_bytes,
sizeof(short_service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, endpoint_id,
ConstifyPtr(scoped_short_service_id_hash.get()), endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, SerializationFailsWithLongServiceIdHash) {
char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D};
ScopedPtr<Ptr<ByteArray> > scoped_long_service_id_hash(
new ByteArray(long_service_id_hash_bytes,
sizeof(long_service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, endpoint_id, ConstifyPtr(scoped_long_service_id_hash.get()),
endpoint_name);
ASSERT_TRUE(bluetooth_device_name_string.empty());
}
TEST(BluetoothDeviceNameTest, DeserializationFailsWithShortLength) {
char bluetooth_device_name_bytes[] = {'X'};
ScopedPtr<Ptr<ByteArray> > scoped_bluetooth_device_name_bytes(
new ByteArray(bluetooth_device_name_bytes,
sizeof(bluetooth_device_name_bytes) / sizeof(char)));
ScopedPtr<Ptr<BluetoothDeviceName> > scoped_bluetooth_device_name(
BluetoothDeviceName::fromString(Base64Utils::encode(
ConstifyPtr(scoped_bluetooth_device_name_bytes.get()))));
ASSERT_TRUE(scoped_bluetooth_device_name.isNull());
}
TEST(BluetoothDeviceNameTest, DeserializationFailsWithWrongEndpointNameLength) {
// Serialize good data into a good Bluetooth Device Name.
ScopedPtr<Ptr<ByteArray> > scoped_service_id_hash(new ByteArray(
service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char)));
std::string bluetooth_device_name_string = BluetoothDeviceName::asString(
version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()),
endpoint_name);
// Base64-decode the good Bluetooth Device Name.
ScopedPtr<Ptr<ByteArray> > scoped_bluetooth_device_name_bytes(
Base64Utils::decode(bluetooth_device_name_string));
// Corrupt the EndpointNameLength bits (120-127) by reversing all of them.
std::string corrupt_bluetooth_device_name_bytes(
scoped_bluetooth_device_name_bytes->getData(),
scoped_bluetooth_device_name_bytes->size());
corrupt_bluetooth_device_name_bytes[15] ^= 0x0FF;
// Base64-encode the corrupted bytes into a corrupt Bluetooth Device Name.
ScopedPtr<Ptr<ByteArray> > scoped_corrupt_bluetooth_device_name_bytes(
new ByteArray(corrupt_bluetooth_device_name_bytes.data(),
corrupt_bluetooth_device_name_bytes.size()));
std::string corrupt_bluetooth_device_name_string(Base64Utils::encode(
ConstifyPtr(scoped_corrupt_bluetooth_device_name_bytes.get())));
// And deserialize the corrupt Bluetooth Device Name.
ScopedPtr<Ptr<BluetoothDeviceName> > scoped_bluetooth_device_name(
BluetoothDeviceName::fromString(corrupt_bluetooth_device_name_string));
ASSERT_TRUE(scoped_bluetooth_device_name.isNull());
}
} // namespace
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,55 @@
#include "core/internal/bluetooth_endpoint_channel.h"
#include <string>
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
Ptr<BluetoothEndpointChannel<Platform> >
BluetoothEndpointChannel<Platform>::createOutgoing(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BluetoothSocket> bluetooth_socket) {
return MakePtr(
new BluetoothEndpointChannel<Platform>(channel_name, bluetooth_socket));
}
template <typename Platform>
Ptr<BluetoothEndpointChannel<Platform> >
BluetoothEndpointChannel<Platform>::createIncoming(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BluetoothSocket> bluetooth_socket) {
return MakePtr(
new BluetoothEndpointChannel<Platform>(channel_name, bluetooth_socket));
}
template <typename Platform>
BluetoothEndpointChannel<Platform>::BluetoothEndpointChannel(
const string& channel_name, Ptr<BluetoothSocket> bluetooth_socket)
: BaseEndpointChannel<Platform>(channel_name,
bluetooth_socket->getInputStream(),
bluetooth_socket->getOutputStream()),
bluetooth_socket_(bluetooth_socket) {}
template <typename Platform>
BluetoothEndpointChannel<Platform>::~BluetoothEndpointChannel() {}
template <typename Platform>
proto::connections::Medium BluetoothEndpointChannel<Platform>::getMedium() {
return proto::connections::Medium::BLUETOOTH;
}
template <typename Platform>
void BluetoothEndpointChannel<Platform>::closeImpl() {
Exception::Value exception = bluetooth_socket_->close();
if (exception != Exception::NONE) {
if (exception == Exception::IO) {
// TODO(tracyzhou): Add logging.
}
}
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,45 @@
#ifndef CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
#define CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
#include "core/internal/base_endpoint_channel.h"
#include "core/internal/medium_manager.h"
#include "platform/api/bluetooth_classic.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class BluetoothEndpointChannel : public BaseEndpointChannel<Platform> {
public:
static Ptr<BluetoothEndpointChannel<Platform> > createOutgoing(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BluetoothSocket> bluetooth_socket);
static Ptr<BluetoothEndpointChannel<Platform> > createIncoming(
Ptr<MediumManager<Platform> > medium_manager, const string& channel_name,
Ptr<BluetoothSocket> bluetooth_socket);
~BluetoothEndpointChannel() override;
proto::connections::Medium getMedium() override;
protected:
void closeImpl() override;
private:
BluetoothEndpointChannel(const string& channel_name,
Ptr<BluetoothSocket> bluetooth_socket);
ScopedPtr<Ptr<BluetoothSocket> > bluetooth_socket_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/bluetooth_endpoint_channel.cc"
#endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
+590
View File
@@ -0,0 +1,590 @@
#include "core/internal/client_proxy.h"
#include <cstdlib>
#include <limits>
#include <sstream>
#include <utility>
#include "platform/api/hash_utils.h"
#include "platform/base64_utils.h"
#include "platform/prng.h"
#include "platform/synchronized.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace client_proxy {
template <typename K, typename V>
void eraseOwnedPtrFromMap(std::map<K, Ptr<V>>& m, const K& k) {
typename std::map<K, Ptr<V>>::iterator it = m.find(k);
if (it != m.end()) {
it->second.destroy();
m.erase(it);
}
}
} // namespace client_proxy
template <typename Platform>
const std::int32_t ClientProxy<Platform>::kEndpointIdLength = 4;
template <typename Platform>
ClientProxy<Platform>::ClientProxy()
: lock_(Platform::createLock()), client_id_(Prng().nextInt64()) {}
template <typename Platform>
ClientProxy<Platform>::~ClientProxy() {
reset();
}
template <typename Platform>
std::int64_t ClientProxy<Platform>::getClientId() const {
return client_id_;
}
template <typename Platform>
std::string ClientProxy<Platform>::generateLocalEndpointId() {
// 1) Concatenate the DeviceID with this ClientID.
// 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.
std::ostringstream client_id_str;
client_id_str << getClientId();
ScopedPtr<Ptr<HashUtils>> hash_utils(Platform::createHashUtils());
ScopedPtr<ConstPtr<ByteArray>> id_hash(
hash_utils->sha256(Platform::getDeviceId() + client_id_str.str()));
return Base64Utils::encode(id_hash.get()).substr(0, kEndpointIdLength);
}
template <typename Platform>
void ClientProxy<Platform>::reset() {
Synchronized s(lock_.get());
stoppedAdvertising();
stoppedDiscovery();
removeAllEndpoints();
}
template <typename Platform>
void ClientProxy<Platform>::startedAdvertising(
const std::string& service_id, const Strategy& strategy,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
const std::vector<proto::connections::Medium>& mediums) {
Synchronized s(lock_.get());
advertising_info_.destroy();
advertising_info_ =
MakePtr(new AdvertisingInfo(service_id, connection_lifecycle_listener));
}
template <typename Platform>
void ClientProxy<Platform>::stoppedAdvertising() {
Synchronized s(lock_.get());
if (isAdvertising()) {
advertising_info_.destroy();
}
}
template <typename Platform>
bool ClientProxy<Platform>::isAdvertising() {
Synchronized s(lock_.get());
return !advertising_info_.isNull();
}
template <typename Platform>
std::string ClientProxy<Platform>::getAdvertisingServiceId() {
Synchronized s(lock_.get());
if (!isAdvertising()) {
return "";
}
return advertising_info_->service_id;
}
template <typename Platform>
void ClientProxy<Platform>::startedDiscovery(
const std::string& service_id, const Strategy& strategy,
Ptr<DiscoveryListener> discovery_listener,
const std::vector<proto::connections::Medium>& mediums) {
Synchronized s(lock_.get());
discovery_info_.destroy();
discovery_info_ = MakePtr(new DiscoveryInfo(service_id, discovery_listener));
}
template <typename Platform>
void ClientProxy<Platform>::stoppedDiscovery() {
Synchronized s(lock_.get());
if (isDiscovering()) {
discovered_endpoint_ids_.clear();
discovery_info_.destroy();
}
}
template <typename Platform>
bool ClientProxy<Platform>::isDiscoveringServiceId(
const std::string& service_id) {
Synchronized s(lock_.get());
return isDiscovering() && service_id == discovery_info_->service_id;
}
template <typename Platform>
bool ClientProxy<Platform>::isDiscovering() {
Synchronized s(lock_.get());
return !discovery_info_.isNull();
}
template <typename Platform>
std::string ClientProxy<Platform>::getDiscoveryServiceId() {
Synchronized s(lock_.get());
if (!isDiscovering()) {
return "";
}
return discovery_info_->service_id;
}
template <typename Platform>
void ClientProxy<Platform>::onEndpointFound(const std::string& endpoint_id,
const std::string& service_id,
const std::string& endpoint_name,
proto::connections::Medium medium) {
Synchronized s(lock_.get());
if (isDiscoveringServiceId(service_id)) {
if (discovered_endpoint_ids_.find(endpoint_id) !=
discovered_endpoint_ids_.end()) {
// TODO(tracyzhou): Add logging.
return;
}
discovered_endpoint_ids_.insert(endpoint_id);
discovery_info_->discovery_listener->onEndpointFound(MakeConstPtr(
new OnEndpointFoundParams(endpoint_id, service_id, endpoint_name)));
}
}
template <typename Platform>
void ClientProxy<Platform>::onEndpointLost(const std::string& service_id,
const std::string& endpoint_id) {
Synchronized s(lock_.get());
if (isDiscoveringServiceId(service_id)) {
std::set<std::string>::const_iterator it =
discovered_endpoint_ids_.find(endpoint_id);
if (it == discovered_endpoint_ids_.end()) {
return;
}
discovered_endpoint_ids_.erase(it);
discovery_info_->discovery_listener->onEndpointLost(
MakeConstPtr(new OnEndpointLostParams(endpoint_id)));
}
}
template <typename Platform>
void ClientProxy<Platform>::onConnectionInitiated(
const std::string& endpoint_id, const std::string& endpoint_name,
const std::string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token, bool is_incoming_connection,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
Synchronized s(lock_.get());
ScopedPtr<ConstPtr<ByteArray>> scoped_raw_authentication_token(
raw_authentication_token);
// Whether this is incoming or outgoing, the local and remote endpoints both
// still need to accept this connection, so set its establishment status to
// PENDING.
connection_establishment_statuses_.insert(
std::make_pair(endpoint_id, ConnectionMetadata(is_incoming_connection)));
// Remember the ConnectionLifecycleListener for this endpoint.
connection_lifecycle_listeners_.insert(
std::make_pair(endpoint_id, connection_lifecycle_listener));
// Notify the client.
//
// Note: we allow devices to connect to an advertiser even after it stops
// advertising, so no need to check isAdvertising() here.
connection_lifecycle_listeners_.find(endpoint_id)
->second->onConnectionInitiated(
MakeConstPtr(new OnConnectionInitiatedParams(
endpoint_id, endpoint_name, authentication_token,
scoped_raw_authentication_token.release(),
is_incoming_connection)));
}
template <typename Platform>
void ClientProxy<Platform>::onConnectionResult(const std::string& endpoint_id,
Status::Value status) {
Synchronized s(lock_.get());
if (!hasPendingConnectionToEndpoint(endpoint_id)) {
// TODO(tracyzhou): Add logging.
return;
}
// Notify the client.
connection_lifecycle_listeners_.find(endpoint_id)
->second->onConnectionResult(
MakeConstPtr(new OnConnectionResultParams(endpoint_id, status)));
if (Status::SUCCESS == status) {
// Mark ourselves as connected. Payloads should now be allowed.
typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.find(endpoint_id);
if (it != connection_establishment_statuses_.end()) {
it->second.status = ConnectionEstablishmentStatus::CONNECTED;
}
} else {
// Otherwise, clean up.
onDisconnected(endpoint_id, false /* notify */);
}
}
template <typename Platform>
void ClientProxy<Platform>::onBandwidthChanged(const std::string& endpoint_id,
std::int32_t quality) {
Synchronized s(lock_.get());
ConnectionLifecycleListenersMap::iterator it =
connection_lifecycle_listeners_.find(endpoint_id);
if (it != connection_lifecycle_listeners_.end()) {
it->second->onBandwidthChanged(
MakeConstPtr(new OnBandwidthChangedParams(endpoint_id, quality)));
}
}
template <typename Platform>
void ClientProxy<Platform>::onDisconnected(const std::string& endpoint_id,
bool notify) {
Synchronized s(lock_.get());
connection_establishment_statuses_.erase(endpoint_id);
client_proxy::eraseOwnedPtrFromMap(payload_listeners_, endpoint_id);
ConnectionLifecycleListenersMap::iterator it =
connection_lifecycle_listeners_.find(endpoint_id);
if (it != connection_lifecycle_listeners_.end()) {
if (notify) {
it->second->onDisconnected(
MakeConstPtr(new OnDisconnectedParams(endpoint_id)));
}
it->second.destroy();
connection_lifecycle_listeners_.erase(it);
}
}
template <typename Platform>
bool ClientProxy<Platform>::isConnectedToEndpoint(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.find(endpoint_id);
if (it == connection_establishment_statuses_.end()) {
return false;
}
const ConnectionMetadata& metadata = it->second;
return metadata.status == ConnectionEstablishmentStatus::CONNECTED;
}
template <typename Platform>
std::vector<std::string> ClientProxy<Platform>::getConnectedEndpoints() {
Synchronized s(lock_.get());
std::vector<std::string> connected_endpoints;
for (typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.begin();
it != connection_establishment_statuses_.end(); it++) {
const std::string& endpoint_id = it->first;
const ConnectionMetadata& metadata = it->second;
if (ConnectionEstablishmentStatus::CONNECTED == metadata.status) {
connected_endpoints.push_back(endpoint_id);
}
}
return connected_endpoints;
}
template <typename Platform>
std::vector<std::string> ClientProxy<Platform>::getPendingConnectedEndpoints() {
Synchronized s(lock_.get());
std::vector<std::string> pending_connected_endpoints;
for (typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.begin();
it != connection_establishment_statuses_.end(); it++) {
const std::string& endpoint_id = it->first;
const ConnectionMetadata& metadata = it->second;
if (ConnectionEstablishmentStatus::CONNECTED != metadata.status) {
pending_connected_endpoints.push_back(endpoint_id);
}
}
return pending_connected_endpoints;
}
template <typename Platform>
std::int32_t ClientProxy<Platform>::getNumOutgoingConnections() {
Synchronized s(lock_.get());
std::int32_t num_outgoing_connections = 0;
for (typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.begin();
it != connection_establishment_statuses_.end(); it++) {
const ConnectionMetadata& metadata = it->second;
if (ConnectionEstablishmentStatus::CONNECTED == metadata.status &&
!metadata.is_incoming) {
num_outgoing_connections++;
}
}
return num_outgoing_connections;
}
template <typename Platform>
std::int32_t ClientProxy<Platform>::getNumIncomingConnections() {
Synchronized s(lock_.get());
std::int32_t num_incoming_connections = 0;
for (typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.begin();
it != connection_establishment_statuses_.end(); it++) {
const ConnectionMetadata& metadata = it->second;
if (ConnectionEstablishmentStatus::CONNECTED == metadata.status &&
metadata.is_incoming) {
num_incoming_connections++;
}
}
return num_incoming_connections;
}
template <typename Platform>
bool ClientProxy<Platform>::hasPendingConnectionToEndpoint(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.find(endpoint_id);
if (it == connection_establishment_statuses_.end()) {
return false;
}
const ConnectionMetadata& metadata = it->second;
return metadata.status != ConnectionEstablishmentStatus::CONNECTED;
}
template <typename Platform>
bool ClientProxy<Platform>::hasLocalEndpointResponded(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
return connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED) ||
connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED);
}
template <typename Platform>
bool ClientProxy<Platform>::hasRemoteEndpointResponded(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
return connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED) ||
connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED);
}
template <typename Platform>
void ClientProxy<Platform>::localEndpointAcceptedConnection(
const std::string& endpoint_id, Ptr<PayloadListener> payload_listener) {
Synchronized s(lock_.get());
if (hasLocalEndpointResponded(endpoint_id)) {
// TODO(tracyzhou): Add logging.
return;
}
appendConnectionEstablishmentStatus(
endpoint_id, ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED);
payload_listeners_.insert(std::make_pair(endpoint_id, payload_listener));
}
template <typename Platform>
void ClientProxy<Platform>::localEndpointRejectedConnection(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
if (hasLocalEndpointResponded(endpoint_id)) {
// TODO(tracyzhou): Add logging.
return;
}
appendConnectionEstablishmentStatus(
endpoint_id, ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED);
}
template <typename Platform>
void ClientProxy<Platform>::remoteEndpointAcceptedConnection(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
if (hasRemoteEndpointResponded(endpoint_id)) {
// TODO(tracyzhou): Add logging.
return;
}
appendConnectionEstablishmentStatus(
endpoint_id, ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED);
}
template <typename Platform>
void ClientProxy<Platform>::remoteEndpointRejectedConnection(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
if (hasRemoteEndpointResponded(endpoint_id)) {
// TODO(tracyzhou): Add logging.
return;
}
appendConnectionEstablishmentStatus(
endpoint_id, ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED);
}
template <typename Platform>
bool ClientProxy<Platform>::isConnectionAccepted(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
return connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED) &&
connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED);
}
template <typename Platform>
bool ClientProxy<Platform>::isConnectionRejected(
const std::string& endpoint_id) {
Synchronized s(lock_.get());
return connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED) ||
connectionEstablishmentStatusesContains(
endpoint_id,
ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED);
}
template <typename Platform>
void ClientProxy<Platform>::onPayloadReceived(const std::string& endpoint_id,
ConstPtr<Payload> payload) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<ConstPtr<Payload>> scoped_payload(payload);
if (isConnectedToEndpoint(endpoint_id)) {
payload_listeners_.find(endpoint_id)
->second->onPayloadReceived(MakeConstPtr(new OnPayloadReceivedParams(
endpoint_id, scoped_payload.release())));
}
}
template <typename Platform>
void ClientProxy<Platform>::onPayloadTransferUpdate(
const std::string& endpoint_id,
const PayloadTransferUpdate& payload_transfer_update) {
Synchronized s(lock_.get());
if (isConnectedToEndpoint(endpoint_id)) {
payload_listeners_.find(endpoint_id)
->second->onPayloadTransferUpdate(
MakeConstPtr(new OnPayloadTransferUpdateParams(
endpoint_id, payload_transfer_update)));
}
}
template <typename Platform>
bool ClientProxy<Platform>::operator==(const ClientProxy<Platform>& rhs) {
return this->getClientId() == rhs.getClientId();
}
template <typename Platform>
bool ClientProxy<Platform>::operator<(const ClientProxy<Platform>& rhs) {
return this->getClientId() < rhs.getClientId();
}
template <typename Platform>
void ClientProxy<Platform>::removeAllEndpoints() {
Synchronized s(lock_.get());
// Note: we may want to notify the client of onDisconnected() for each
// endpoint, in the case when this is called from stopAllEndpoints(). For now,
// just remove without notifying.
for (ConnectionLifecycleListenersMap::iterator it =
connection_lifecycle_listeners_.begin();
it != connection_lifecycle_listeners_.end(); it++) {
it->second.destroy();
}
connection_lifecycle_listeners_.clear();
for (PayloadListenersMap::iterator it = payload_listeners_.begin();
it != payload_listeners_.end(); it++) {
it->second.destroy();
}
payload_listeners_.clear();
connection_establishment_statuses_.clear();
}
template <typename Platform>
bool ClientProxy<Platform>::connectionEstablishmentStatusesContains(
const std::string& endpoint_id,
typename ConnectionEstablishmentStatus::Value status_to_match) {
typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.find(endpoint_id);
if (it == connection_establishment_statuses_.end()) {
return false;
}
const ConnectionMetadata& metadata = it->second;
return (metadata.status & status_to_match) != 0;
}
template <typename Platform>
void ClientProxy<Platform>::appendConnectionEstablishmentStatus(
const std::string& endpoint_id,
typename ConnectionEstablishmentStatus::Value status_to_append) {
typename ConnectionEstablishmentStatusesMap::iterator it =
connection_establishment_statuses_.find(endpoint_id);
if (it == connection_establishment_statuses_.end()) {
return;
}
ConnectionMetadata& metadata = it->second;
metadata.status = static_cast<typename ConnectionEstablishmentStatus::Value>(
metadata.status | status_to_append);
}
} // namespace connections
} // namespace nearby
} // namespace location
+241
View File
@@ -0,0 +1,241 @@
#ifndef CORE_INTERNAL_CLIENT_PROXY_H_
#define CORE_INTERNAL_CLIENT_PROXY_H_
#include <cstdint>
#include <map>
#include <set>
#include <vector>
#include "core/listeners.h"
#include "core/strategy.h"
#include "platform/api/lock.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class ClientProxy {
public:
static const std::int32_t kEndpointIdLength;
ClientProxy();
~ClientProxy();
std::int64_t getClientId() const;
std::string generateLocalEndpointId();
// Clears all the runtime state of this client.
void reset();
// Marks this client as advertising with the given callbacks.
void startedAdvertising(
const std::string& service_id, const Strategy& strategy,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
const std::vector<proto::connections::Medium>& mediums);
// Marks this client as not advertising.
void stoppedAdvertising();
bool isAdvertising();
std::string getAdvertisingServiceId();
// Marks this client as discovering with the given callback.
void startedDiscovery(const std::string& service_id, const Strategy& strategy,
Ptr<DiscoveryListener> discovery_listener,
const std::vector<proto::connections::Medium>& mediums);
// Marks this client as not discovering at all.
void stoppedDiscovery();
bool isDiscoveringServiceId(const std::string& service_id);
bool isDiscovering();
std::string getDiscoveryServiceId();
// Proxies to the client's DiscoveryListener.onEndpointFound() callback.
void onEndpointFound(const std::string& endpoint_id,
const std::string& service_id,
const std::string& endpoint_name,
proto::connections::Medium medium);
// Proxies to the client's DiscoveryListener.onEndpointLost() callback.
void onEndpointLost(const std::string& service_id,
const std::string& endpoint_id);
// Proxies to the client's ConnectionLifecycleListener.onConnectionInitiated()
// callback.
void onConnectionInitiated(
const std::string& endpoint_id, const std::string& endpoint_name,
const std::string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token, bool is_incoming_connection,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener);
// Proxies to the client's ConnectionLifecycleListener.onConnectionResult()
// callback.
void onConnectionResult(const std::string& endpoint_id, Status::Value status);
void onBandwidthChanged(const std::string& endpoint_id, std::int32_t quality);
// Removes the endpoint from this client's list of connected endpoints. If
// notify is true, also calls the client's
// ConnectionLifecycleListener.onDisconnected() callback.
void onDisconnected(const std::string& endpoint_id, bool notify);
// Returns true if it's safe to send payloads to this endpoint.
bool isConnectedToEndpoint(const std::string& endpoint_id);
// Returns all endpoints that can safely be sent payloads.
std::vector<std::string> getConnectedEndpoints();
// Returns all endpoints that are still awaiting acceptance.
std::vector<std::string> getPendingConnectedEndpoints();
// Returns the number of endpoints that are connected and outgoing.
std::int32_t getNumOutgoingConnections();
// Returns the number of endpoints that are connected and incoming.
std::int32_t getNumIncomingConnections();
// If true, then we're in the process of approving (or rejecting) a
// connection. No payloads should be sent until isConnectedToEndpoint()
// returns true.
bool hasPendingConnectionToEndpoint(const std::string& endpoint_id);
// Returns true if the local endpoint has already marked itself as
// accepted/rejected.
bool hasLocalEndpointResponded(const std::string& endpoint_id);
// Returns true if the remote endpoint has already marked themselves as
// accepted/rejected.
bool hasRemoteEndpointResponded(const std::string& endpoint_id);
// Marks the local endpoint as having accepted the connection.
void localEndpointAcceptedConnection(const std::string& endpoint_id,
Ptr<PayloadListener> payload_listener);
// Marks the local endpoint as having rejected the connection.
void localEndpointRejectedConnection(const std::string& endpoint_id);
// Marks the remote endpoint as having accepted the connection.
void remoteEndpointAcceptedConnection(const std::string& endpoint_id);
// Marks the remote endpoint as having rejected the connection.
void remoteEndpointRejectedConnection(const std::string& endpoint_id);
// Returns true if both the local endpoint and the remote endpoint have
// accepted the connection.
bool isConnectionAccepted(const std::string& endpoint_id);
// Returns true if either the local endpoint or the remote endpoint has
// rejected the connection.
bool isConnectionRejected(const std::string& endpoint_id);
// Proxies to the client's PayloadListener.onPayloadReceived() callback.
void onPayloadReceived(const std::string& endpoint_id,
ConstPtr<Payload> payload);
// Proxies to the client's PayloadListener.onPayloadTransferUpdate() callback.
void onPayloadTransferUpdate(
const std::string& endpoint_id,
const PayloadTransferUpdate& payload_transfer_update);
// Operator overloads when comparing Ptr<ClientProxy>.
bool operator==(const ClientProxy<Platform>& rhs);
bool operator<(const ClientProxy<Platform>& rhs);
private:
struct ConnectionEstablishmentStatus {
enum Value {
PENDING = 0,
LOCAL_ENDPOINT_ACCEPTED = 1 << 0,
LOCAL_ENDPOINT_REJECTED = 1 << 1,
REMOTE_ENDPOINT_ACCEPTED = 1 << 2,
REMOTE_ENDPOINT_REJECTED = 1 << 3,
CONNECTED = 1 << 4,
};
};
struct AdvertisingInfo {
const std::string service_id;
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener;
AdvertisingInfo(
const std::string& service_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener)
: service_id(service_id),
connection_lifecycle_listener(connection_lifecycle_listener) {}
};
struct DiscoveryInfo {
const std::string service_id;
ScopedPtr<Ptr<DiscoveryListener> > discovery_listener;
DiscoveryInfo(const std::string& service_id,
Ptr<DiscoveryListener> discovery_listener)
: service_id(service_id), discovery_listener(discovery_listener) {}
};
struct ConnectionMetadata {
const bool is_incoming;
typename ConnectionEstablishmentStatus::Value status;
explicit ConnectionMetadata(bool is_incoming)
: is_incoming(is_incoming),
status(ConnectionEstablishmentStatus::PENDING) {}
};
void removeAllEndpoints();
bool connectionEstablishmentStatusesContains(
const std::string& endpoint_id,
typename ConnectionEstablishmentStatus::Value status_to_match);
void appendConnectionEstablishmentStatus(
const std::string& endpoint_id,
typename ConnectionEstablishmentStatus::Value status_to_append);
ScopedPtr<Ptr<Lock> > lock_;
const std::int64_t client_id_;
// If set, we are currently advertising and accepting connection requests for
// the given service_id.
Ptr<AdvertisingInfo> advertising_info_;
// If set, we are currently discovering for the given service_id.
Ptr<DiscoveryInfo> discovery_info_;
/**
* Map of endpoint_ids -> ConnectionMetadata. ConnectionMetadata.status may be
* either ConnectionEstablishmentStatus::PENDING, a combination of
* ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED:
* ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED and
* ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED:
* ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED, or
* ConnectionEstablishmentStatus::CONNECTED. Only when this is set to
* CONNECTED should you allow payload transfers.
*/
typedef std::map<std::string, ConnectionMetadata>
ConnectionEstablishmentStatusesMap;
ConnectionEstablishmentStatusesMap connection_establishment_statuses_;
/**
* Map of endpoint_ids -> ConnectionLifecycleListeners. Every endpoint in here
* is guaranteed to at least be in
* ConnectionEstablishmentStatus::PENDING -- the precise status can be found
* from the corresponding entry in connection_establishment_statuses.
*/
typedef std::map<std::string, Ptr<ConnectionLifecycleListener> >
ConnectionLifecycleListenersMap;
ConnectionLifecycleListenersMap connection_lifecycle_listeners_;
/**
* Map of endpoint_ids -> PayloadListeners. Every endpoint in here is
* guaranteed to at least be in
* ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED -- the
* precise status can be found from the corresponding entry in
* connection_establishment_statuses.
*/
typedef std::map<std::string, Ptr<PayloadListener> > PayloadListenersMap;
PayloadListenersMap payload_listeners_;
/**
* A cache of endpoint ids that we've already notified the discoverer of. We
* check this cache before calling onEndpointFound() so that we don't notify
* the client multiple times for the same endpoint. This would otherwise
* happen because some mediums (like Bluetooth) repeatedly give us the same
* endpoints after each scan.
*/
std::set<std::string> discovered_endpoint_ids_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/client_proxy.cc"
#endif // CORE_INTERNAL_CLIENT_PROXY_H_
+437
View File
@@ -0,0 +1,437 @@
#include "core/internal/encryption_runner.h"
#include <cinttypes>
#include <cstdint>
#include "platform/base64_utils.h"
#include "platform/byte_array.h"
#include "platform/cancelable_alarm.h"
#include "platform/exception.h"
#include "platform/logging.h"
#include "absl/strings/ascii.h"
namespace {
std::int64_t kTimeoutMillis = 15 * 1000; // 15 seconds
std::int32_t kMaxUkey2VerificationStringLength = 32;
std::int32_t kTokenLength = 5;
securegcm::UKey2Handshake::HandshakeCipher kCipher =
securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512;
} // namespace
namespace location {
namespace nearby {
namespace connections {
namespace {
// Transforms a raw UKEY2 token (which is a random ByteArray that's
// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only
// uses A-Z0-9 for each character.
string toHumanReadableString(ConstPtr<ByteArray> token) {
string result = Base64Utils::encode(token).substr(0, kTokenLength);
absl::AsciiStrToUpper(&result);
return result;
}
template <typename Platform>
bool handleEncryptionSuccess(
const string& endpoint_id, Ptr<securegcm::UKey2Handshake> ukey2_handshake,
Ptr<typename EncryptionRunner<Platform>::ResultListener> result_listener) {
ScopedPtr<Ptr<securegcm::UKey2Handshake>> scoped_ukey2_handshake(
ukey2_handshake);
std::unique_ptr<string> verification_string =
scoped_ukey2_handshake->GetVerificationString(
kMaxUkey2VerificationStringLength);
if (verification_string == nullptr) {
return false;
}
ScopedPtr<ConstPtr<ByteArray>> raw_authentication_token(MakeConstPtr(
new ByteArray(verification_string->data(), verification_string->size())));
result_listener->onEncryptionSuccess(
endpoint_id, scoped_ukey2_handshake.release(),
toHumanReadableString(raw_authentication_token.get()),
raw_authentication_token.release());
return true;
}
template <typename Platform>
class CancelableAlarmRunnable : public Runnable {
public:
CancelableAlarmRunnable(Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel)
: client_proxy_(client_proxy),
endpoint_id_(endpoint_id),
endpoint_channel_(endpoint_channel) {}
void run() override {
NEARBY_LOG(INFO,
"Timing out encryption for client %" PRId64
" to endpoint %s after %" PRId64 " ms",
client_proxy_->getClientId(), endpoint_id_.c_str(),
kTimeoutMillis);
endpoint_channel_->close();
}
private:
Ptr<ClientProxy<Platform>> client_proxy_;
const string endpoint_id_;
Ptr<EndpointChannel> endpoint_channel_;
};
template <typename Platform>
class ServerRunnable : public Runnable {
public:
ServerRunnable(Ptr<ClientProxy<Platform>> client_proxy,
Ptr<typename Platform::ScheduledExecutorType> alarm_executor,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<typename EncryptionRunner<Platform>::ResultListener>
encryption_result_listener)
: client_proxy_(client_proxy),
alarm_executor_(alarm_executor),
endpoint_id_(endpoint_id),
endpoint_channel_(endpoint_channel),
encryption_result_listener_(encryption_result_listener) {}
void run() override {
CancelableAlarm<Platform> timeout_alarm(
"EncryptionRunner.startServer() timeout",
MakePtr(new CancelableAlarmRunnable<Platform>(
client_proxy_, endpoint_id_, endpoint_channel_)),
kTimeoutMillis, alarm_executor_);
std::unique_ptr<securegcm::UKey2Handshake> server =
securegcm::UKey2Handshake::ForResponder(kCipher);
// Java code throws a HandshakeException.
if (server == nullptr) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
// Message 1 (Client Init)
ExceptionOr<ConstPtr<ByteArray>> client_init = endpoint_channel_->read();
if (!client_init.ok()) {
if (Exception::IO == client_init.exception()) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
ScopedPtr<ConstPtr<ByteArray>> scoped_client_init(client_init.result());
securegcm::UKey2Handshake::ParseResult parse_result =
server->ParseHandshakeMessage(
string(scoped_client_init->getData(), scoped_client_init->size()));
// Java code throws a HandshakeException / AlertException.
if (!parse_result.success) {
logException();
if (parse_result.alert_to_send != nullptr) {
handleAlertException(parse_result);
}
handleHandshakeOrIOException(timeout_alarm);
return;
}
NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 1 from endpoint %s",
endpoint_id_.c_str());
// Message 2 (Server Init)
std::unique_ptr<string> server_init = server->GetNextHandshakeMessage();
// Java code throws a HandshakeException.
if (server_init == nullptr) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
Exception::Value write_exception = endpoint_channel_->write(
MakeConstPtr(new ByteArray(server_init->data(), server_init->size())));
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
NEARBY_LOG(INFO, "In startServer(), wrote UKEY2 Message 2 to endpoint %s",
endpoint_id_.c_str());
// Message 3 (Client Finish)
ExceptionOr<ConstPtr<ByteArray>> client_finish = endpoint_channel_->read();
if (!client_finish.ok()) {
if (Exception::IO == client_finish.exception()) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
ScopedPtr<ConstPtr<ByteArray>> scoped_client_finish(client_finish.result());
parse_result = server->ParseHandshakeMessage(
string(scoped_client_finish->getData(), scoped_client_finish->size()));
// Java code throws an AlertException or a HandshakeException.
if (!parse_result.success) {
logException();
if (parse_result.alert_to_send != nullptr) {
handleAlertException(parse_result);
}
handleHandshakeOrIOException(timeout_alarm);
return;
}
NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 3 from endpoint %s",
endpoint_id_.c_str());
timeout_alarm.cancel();
if (!handleEncryptionSuccess<Platform>(endpoint_id_,
MakePtr(server.release()),
encryption_result_listener_.get())) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
private:
void logException() {
NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s",
endpoint_id_.c_str());
}
void handleHandshakeOrIOException(CancelableAlarm<Platform>& timeout_alarm) {
timeout_alarm.cancel();
encryption_result_listener_->onEncryptionFailure(endpoint_id_,
endpoint_channel_);
}
void handleAlertException(
const securegcm::UKey2Handshake::ParseResult& parse_result) {
Exception::Value write_exception = endpoint_channel_->write(
MakeConstPtr(new ByteArray(parse_result.alert_to_send->data(),
parse_result.alert_to_send->size())));
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
NEARBY_LOG(WARNING,
"In startServer(), client %" PRId64
" failed to pass the alert error message to endpoint %s",
client_proxy_->getClientId(), endpoint_id_.c_str());
}
}
}
Ptr<ClientProxy<Platform>> client_proxy_;
Ptr<typename Platform::ScheduledExecutorType> alarm_executor_;
const string endpoint_id_;
Ptr<EndpointChannel> endpoint_channel_;
ScopedPtr<Ptr<typename EncryptionRunner<Platform>::ResultListener>>
encryption_result_listener_;
};
template <typename Platform>
class ClientRunnable : public Runnable {
public:
ClientRunnable(Ptr<ClientProxy<Platform>> client_proxy,
Ptr<typename Platform::ScheduledExecutorType> alarm_executor,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<typename EncryptionRunner<Platform>::ResultListener>
encryption_result_listener)
: client_proxy_(client_proxy),
alarm_executor_(alarm_executor),
endpoint_id_(endpoint_id),
endpoint_channel_(endpoint_channel),
encryption_result_listener_(encryption_result_listener) {}
void run() override {
CancelableAlarm<Platform> timeout_alarm(
"EncryptionRunner.startClient() timeout",
MakePtr(new CancelableAlarmRunnable<Platform>(
client_proxy_, endpoint_id_, endpoint_channel_)),
kTimeoutMillis, alarm_executor_);
std::unique_ptr<securegcm::UKey2Handshake> client =
securegcm::UKey2Handshake::ForInitiator(kCipher);
// Java code throws a HandshakeException.
if (client == nullptr) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
// Message 1 (Client Init)
std::unique_ptr<string> client_init = client->GetNextHandshakeMessage();
// Java code throws a HandshakeException.
if (client_init == nullptr) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
Exception::Value write_init_exception = endpoint_channel_->write(
MakeConstPtr(new ByteArray(client_init->data(), client_init->size())));
if (Exception::NONE != write_init_exception) {
if (Exception::IO == write_init_exception) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 1 to endpoint %s",
endpoint_id_.c_str());
// Message 2 (Server Init)
ExceptionOr<ConstPtr<ByteArray>> server_init = endpoint_channel_->read();
if (!server_init.ok()) {
if (Exception::IO == server_init.exception()) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
ScopedPtr<ConstPtr<ByteArray>> scoped_server_init(server_init.result());
securegcm::UKey2Handshake::ParseResult parse_result =
client->ParseHandshakeMessage(
string(scoped_server_init->getData(), scoped_server_init->size()));
// Java code throws an AlertException or a HandshakeException.
if (!parse_result.success) {
logException();
if (parse_result.alert_to_send != nullptr) {
handleAlertException(parse_result);
}
handleHandshakeOrIOException(timeout_alarm);
return;
}
NEARBY_LOG(INFO, "In startClient(), read UKEY2 Message 2 from endpoint %s",
endpoint_id_.c_str());
// Message 3 (Client Finish)
std::unique_ptr<string> client_finish = client->GetNextHandshakeMessage();
// Java code throws a HandshakeException.
if (client_finish == nullptr) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
Exception::Value write_finish_exception =
endpoint_channel_->write(MakeConstPtr(
new ByteArray(client_finish->data(), client_finish->size())));
if (Exception::NONE != write_finish_exception) {
if (Exception::IO == write_finish_exception) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 3 to endpoint %s",
endpoint_id_.c_str());
timeout_alarm.cancel();
if (!handleEncryptionSuccess<Platform>(endpoint_id_,
MakePtr(client.release()),
encryption_result_listener_.get())) {
logException();
handleHandshakeOrIOException(timeout_alarm);
return;
}
}
private:
void logException() {
NEARBY_LOG(ERROR, "In startClient(), UKEY2 failed with endpoint %s",
endpoint_id_.c_str());
}
void handleHandshakeOrIOException(CancelableAlarm<Platform>& timeout_alarm) {
timeout_alarm.cancel();
encryption_result_listener_->onEncryptionFailure(endpoint_id_,
endpoint_channel_);
}
void handleAlertException(
const securegcm::UKey2Handshake::ParseResult& parse_result) {
Exception::Value write_exception = endpoint_channel_->write(
MakeConstPtr(new ByteArray(parse_result.alert_to_send->data(),
parse_result.alert_to_send->size())));
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
NEARBY_LOG(WARNING,
"In startClient(), client %" PRId64
" failed to pass the alert error message to endpoint %s",
client_proxy_->getClientId(), endpoint_id_.c_str());
}
}
}
Ptr<ClientProxy<Platform>> client_proxy_;
Ptr<typename Platform::ScheduledExecutorType> alarm_executor_;
const string endpoint_id_;
Ptr<EndpointChannel> endpoint_channel_;
ScopedPtr<Ptr<typename EncryptionRunner<Platform>::ResultListener>>
encryption_result_listener_;
};
} // namespace
template <typename Platform>
EncryptionRunner<Platform>::EncryptionRunner()
: alarm_executor_(Platform::createScheduledExecutor()),
server_executor_(Platform::createSingleThreadExecutor()),
client_executor_(Platform::createSingleThreadExecutor()) {}
template <typename Platform>
EncryptionRunner<Platform>::~EncryptionRunner() {
// Stop all the ongoing Runnables (as gracefully as possible).
client_executor_->shutdown();
server_executor_->shutdown();
alarm_executor_->shutdown();
}
template <typename Platform>
void EncryptionRunner<Platform>::startServer(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ResultListener> result_listener) {
server_executor_->execute(MakePtr(new ServerRunnable<Platform>(
client_proxy, alarm_executor_.get(), endpoint_id, endpoint_channel,
result_listener)));
}
template <typename Platform>
void EncryptionRunner<Platform>::startClient(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ResultListener> result_listener) {
client_executor_->execute(MakePtr(new ClientRunnable<Platform>(
client_proxy, alarm_executor_.get(), endpoint_id, endpoint_channel,
result_listener)));
}
} // namespace connections
} // namespace nearby
} // namespace location
+73
View File
@@ -0,0 +1,73 @@
#ifndef CORE_INTERNAL_ENCRYPTION_RUNNER_H_
#define CORE_INTERNAL_ENCRYPTION_RUNNER_H_
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "securegcm/ukey2_handshake.h"
namespace location {
namespace nearby {
namespace connections {
// Encrypts a connection over UKEY2.
//
// <p>NOTE: Stalled EndpointChannels will be disconnected after {TIMEOUT_MILLIS}
// milliseconds. This is to prevent unverified endpoints from maintaining an
// indefinite connection to us.
template <typename Platform>
class EncryptionRunner {
public:
EncryptionRunner();
~EncryptionRunner();
class ResultListener {
public:
virtual ~ResultListener() {}
// @EncryptionRunnerThread
virtual void onEncryptionSuccess(
const string& endpoint_id,
Ptr<securegcm::UKey2Handshake> ukey2_handshake,
const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token) = 0;
// Encryption has failed. The remote_endpoint_id and channel are given so
// that any pending state can be cleaned up.
//
// <p>We return the EndpointChannel because, at this stage, simultaneous
// connections are a possibility. Use this channel to verify that the state
// you're cleaning up is for this EndpointChannel, and not state for another
// channel to the same endpoint.
//
// @EncryptionRunnerThread
virtual void onEncryptionFailure(const string& endpoint_id,
Ptr<EndpointChannel> channel) = 0;
};
// @AnyThread
void startServer(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ResultListener> result_listener);
// @AnyThread
void startClient(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ResultListener> result_listener);
private:
ScopedPtr<Ptr<typename Platform::ScheduledExecutorType> > alarm_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > server_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > client_executor_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/encryption_runner.cc"
#endif // CORE_INTERNAL_ENCRYPTION_RUNNER_H_
+69
View File
@@ -0,0 +1,69 @@
#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_H_
#define CORE_INTERNAL_ENDPOINT_CHANNEL_H_
#include <cstdint>
#include "platform/byte_array.h"
#include "platform/exception.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
#include "securegcm/d2d_connection_context_v1.h"
namespace location {
namespace nearby {
namespace connections {
class EndpointChannel {
public:
virtual ~EndpointChannel() {}
virtual ExceptionOr<ConstPtr<ByteArray> >
read() = 0; // throws Exception::IO, Exception::INTERRUPTED
virtual Exception::Value write(
ConstPtr<ByteArray> data) = 0; // throws Exception::IO
// Closes this EndpointChannel, without tracking the closure in analytics.
virtual void close() = 0;
// Closes this EndpointChannel and records the closure with the given reason.
virtual void close(proto::connections::DisconnectionReason reason) = 0;
// Returns a one-word type descriptor for the concrete EndpointChannel
// implementation that can be used in log messages; eg: BLUETOOTH, BLE, WIFI.
virtual string getType() = 0;
// Returns the name of the EndpointChannel.
virtual string getName() = 0;
// Returns the analytics enum representing the medium of this EndpointChannel.
virtual proto::connections::Medium getMedium() = 0;
// Enables encryption on the EndpointChannel.
//
// This method takes ownership of the passed-in 'connection_context'.
virtual void enableEncryption(
Ptr<securegcm::D2DConnectionContextV1> connection_context) = 0;
// True if the EndpointChannel is currently pausing all writes.
virtual bool isPaused() = 0;
// Pauses all writes on this EndpointChannel until resume() is called.
virtual void pause() = 0;
// Resumes any writes on this EndpointChannel that were suspended when pause()
// was called.
virtual void resume() = 0;
// Returns the timestamp of the last read from this endpoint, or -1 if no
// reads have occurred.
// TODO(tracyzhou): Clarify units of timestamp.
virtual std::int64_t getLastReadTimestamp() = 0;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_H_
@@ -0,0 +1,299 @@
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/ble_endpoint_channel.h"
#include "core/internal/bluetooth_endpoint_channel.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
EndpointChannelManager<Platform>::EndpointChannelManager(
Ptr<MediumManager<Platform> > medium_manager)
: lock_(Platform::createLock()),
medium_manager_(medium_manager),
channel_state_(new ChannelState()) {}
template <typename Platform>
EndpointChannelManager<Platform>::~EndpointChannelManager() {
Synchronized s(lock_.get());
// TODO(tracyzhou): logger.atDebug().log("Initiating shutdown of
// EndpointChannelManager.")
channel_state_.destroy();
// TODO(tracyzhou): logger.atDebug().log("EndpointChannelManager has shut
// down.");
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::createOutgoingBluetoothEndpointChannel(
const string& channel_name, Ptr<BluetoothSocket> bluetooth_socket) {
return BluetoothEndpointChannel<Platform>::createOutgoing(
medium_manager_, channel_name, bluetooth_socket);
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::createIncomingBluetoothEndpointChannel(
const string& channel_name, Ptr<BluetoothSocket> bluetooth_socket) {
return BluetoothEndpointChannel<Platform>::createIncoming(
medium_manager_, channel_name, bluetooth_socket);
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::createOutgoingBLEEndpointChannel(
const string& channel_name, Ptr<BLESocket> ble_socket) {
return BLEEndpointChannel<Platform>::createOutgoing(medium_manager_,
channel_name, ble_socket);
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::createIncomingBLEEndpointChannel(
const string& channel_name, Ptr<BLESocket> ble_socket) {
return BLEEndpointChannel<Platform>::createIncoming(medium_manager_,
channel_name, ble_socket);
}
template <typename Platform>
void EndpointChannelManager<Platform>::registerChannelForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel) {
Synchronized s(lock_.get());
// Just in case there was a previous channel, unregister (and, thus, close) it
// now.
unregisterChannelForEndpoint(endpoint_id);
setActiveEndpointChannel(client_proxy, endpoint_id, endpoint_channel);
// TODO(tracyzhou): Add logging.
}
#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::replaceChannelForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel) {
Synchronized s(lock_.get());
ScopedPtr<Ptr<EndpointChannel> > scoped_previous_endpoint_channel(
channel_state_->getChannelForEndpoint(endpoint_id));
if (scoped_previous_endpoint_channel.isNull()) {
// TODO(tracyzhou): Add logging.
return Ptr<EndpointChannel>();
}
setActiveEndpointChannel(client_proxy, endpoint_id, endpoint_channel);
// TODO(tracyzhou): Add logging.
return scoped_previous_endpoint_channel.release();
}
#endif
template <typename Platform>
bool EndpointChannelManager<Platform>::encryptChannelForEndpoint(
const string& endpoint_id,
Ptr<securegcm::D2DConnectionContextV1> encryption_context) {
Synchronized s(lock_.get());
ScopedPtr<Ptr<EndpointChannel> > scoped_endpoint_channel(
channel_state_->getChannelForEndpoint(endpoint_id));
if (scoped_endpoint_channel.isNull()) {
// TODO(tracyzhou): Add logging.
return false;
}
// We found the requested EndpointChannel, so encrypt it.
encryptChannel(endpoint_id, scoped_endpoint_channel.get(),
encryption_context);
// Then update 'endpoint_id' to use this new 'encryption_context' here
// onwards.
//
// Remember to manage the memory of the returned
// Ptr<securegcm::D2DConnectionContextV1> responsibly, even though we don't
// need what's returned.
ScopedPtr<Ptr<securegcm::D2DConnectionContextV1> >(
channel_state_->updateEncryptionContextForEndpoint(endpoint_id,
encryption_context));
return true;
}
template <typename Platform>
Ptr<EndpointChannel> EndpointChannelManager<Platform>::getChannelForEndpoint(
const string& endpoint_id) {
Synchronized s(lock_.get());
return channel_state_->getChannelForEndpoint(endpoint_id);
}
template <typename Platform>
void EndpointChannelManager<Platform>::setActiveEndpointChannel(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel) {
#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED
// If the endpoint is currently encrypted, encrypt this new
// 'endpoint_channel'.
if (channel_state_->isEndpointEncrypted(endpoint_id)) {
encryptChannel(
endpoint_id, endpoint_channel,
channel_state_->getEncryptionContextForEndpoint(endpoint_id));
}
#endif
// Then update 'endpoint_id' to use this new 'endpoint_channel' here onwards.
//
// Remember to manage the memory of the returned Ptr<EndpointChannel>
// responsibly, even though we don't need what's returned.
ScopedPtr<Ptr<EndpointChannel> >(
channel_state_->updateChannelForEndpoint(endpoint_id, endpoint_channel));
}
template <typename Platform>
void EndpointChannelManager<Platform>::encryptChannel(
const string& endpoint_id, Ptr<EndpointChannel> endpoint_channel,
Ptr<securegcm::D2DConnectionContextV1> encryption_context) {
// TODO(tracyzhou): Add logging.
endpoint_channel->enableEncryption(encryption_context);
}
///////////////////////////////// ChannelState /////////////////////////////////
template <typename Platform>
EndpointChannelManager<Platform>::ChannelState::~ChannelState() {
while (!endpoint_id_to_metadata_.empty()) {
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.begin();
// TODO(tracyzhou): Add logging.
removeEndpoint(it->first,
proto::connections::DisconnectionReason::SHUTDOWN);
}
}
template <typename Platform>
bool EndpointChannelManager<Platform>::ChannelState::isEndpointEncrypted(
const string& endpoint_id) {
return !getEncryptionContextForEndpoint(endpoint_id).isNull();
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::ChannelState::updateChannelForEndpoint(
const string& endpoint_id, Ptr<EndpointChannel> endpoint_channel) {
Ptr<EndpointChannel> previous_endpoint_channel;
Ptr<EndpointMetaData> endpoint_metadata;
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.find(endpoint_id);
if (it == endpoint_id_to_metadata_.end()) {
endpoint_metadata = MakePtr(new EndpointMetaData());
} else {
endpoint_metadata = it->second;
previous_endpoint_channel = endpoint_metadata->endpoint_channel;
}
// Avoid leaks.
ScopedPtr<Ptr<EndpointChannel> > scoped_previous_endpoint_channel(
previous_endpoint_channel);
// Upgrade endpoint_channel to be reference-counted before starting to track
// it (and make it clear that endpoint_channel no longer owns the raw
// pointer).
endpoint_metadata->endpoint_channel = MakeRefCountedPtr(&(*endpoint_channel));
endpoint_channel.clear();
endpoint_id_to_metadata_[endpoint_id] = endpoint_metadata;
return scoped_previous_endpoint_channel.release();
}
template <typename Platform>
Ptr<securegcm::D2DConnectionContextV1> EndpointChannelManager<Platform>::
ChannelState::updateEncryptionContextForEndpoint(
const string& endpoint_id,
Ptr<securegcm::D2DConnectionContextV1> encryption_context) {
Ptr<securegcm::D2DConnectionContextV1> previous_encryption_context;
Ptr<EndpointMetaData> endpoint_metadata;
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.find(endpoint_id);
if (it == endpoint_id_to_metadata_.end()) {
endpoint_metadata = MakePtr(new EndpointMetaData());
} else {
endpoint_metadata = it->second;
previous_encryption_context = endpoint_metadata->encryption_context;
}
// Avoid leaks.
ScopedPtr<Ptr<securegcm::D2DConnectionContextV1> >
scoped_previous_encryption_context(previous_encryption_context);
endpoint_metadata->encryption_context = encryption_context;
endpoint_id_to_metadata_[endpoint_id] = endpoint_metadata;
return scoped_previous_encryption_context.release();
}
template <typename Platform>
bool EndpointChannelManager<Platform>::ChannelState::removeEndpoint(
const string& endpoint_id, proto::connections::DisconnectionReason reason) {
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.find(endpoint_id);
if (it == endpoint_id_to_metadata_.end()) {
return false;
}
it->second->endpoint_channel->close(reason);
it->second.destroy();
endpoint_id_to_metadata_.erase(it);
return true;
}
template <typename Platform>
Ptr<securegcm::D2DConnectionContextV1>
EndpointChannelManager<Platform>::ChannelState::getEncryptionContextForEndpoint(
const string& endpoint_id) {
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.find(endpoint_id);
if (it == endpoint_id_to_metadata_.end()) {
return Ptr<securegcm::D2DConnectionContextV1>();
}
return it->second->encryption_context;
}
template <typename Platform>
Ptr<EndpointChannel>
EndpointChannelManager<Platform>::ChannelState::getChannelForEndpoint(
const string& endpoint_id) {
typename EndpointIdToMetadataMap::iterator it =
endpoint_id_to_metadata_.find(endpoint_id);
if (it == endpoint_id_to_metadata_.end()) {
return Ptr<EndpointChannel>();
}
return it->second->endpoint_channel;
}
template <typename Platform>
bool EndpointChannelManager<Platform>::unregisterChannelForEndpoint(
const string& endpoint_id) {
Synchronized s(lock_.get());
if (!channel_state_->removeEndpoint(
endpoint_id,
proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) {
return false;
}
// TODO(tracyzhou): Add logging.
return true;
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,143 @@
#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
#define CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
#include <map>
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel.h"
#include "core/internal/medium_manager.h"
#include "platform/api/ble.h"
#include "platform/api/bluetooth_classic.h"
#include "platform/api/lock.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "securegcm/d2d_connection_context_v1.h"
namespace location {
namespace nearby {
namespace connections {
// Manages the communication channels to all the remote endpoints with which we
// are interacting, including serving as a factory for creating said channels.
//
// The factory methods would be static, but for the fact that they need to use
// the MediumManager.
template <typename Platform>
class EndpointChannelManager {
public:
explicit EndpointChannelManager(Ptr<MediumManager<Platform> > medium_manager);
~EndpointChannelManager();
Ptr<EndpointChannel> createOutgoingBluetoothEndpointChannel(
const string& channel_name, Ptr<BluetoothSocket> bluetooth_socket);
Ptr<EndpointChannel> createIncomingBluetoothEndpointChannel(
const string& channel_name, Ptr<BluetoothSocket> bluetooth_socket);
Ptr<EndpointChannel> createOutgoingBLEEndpointChannel(
const string& channel_name, Ptr<BLESocket> ble_socket);
Ptr<EndpointChannel> createIncomingBLEEndpointChannel(
const string& channel_name, Ptr<BLESocket> ble_socket);
// Registers the initial EndpointChannel to be associated with an endpoint;
// if there already exists a previously-associated EndpointChannel, that will
// be closed before continuing the registration.
void registerChannelForEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel);
#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED
// Replaces the EndpointChannel to be associated with an endpoint from here on
// in, transferring the encryption context from the previous EndpointChannel
// to the newly-provided EndpointChannel.
//
// Returns the previous EndpointChannel, or null Ptr object if called out of
// order.
Ptr<EndpointChannel> replaceChannelForEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel);
#endif
bool encryptChannelForEndpoint(
const string& endpoint_id,
Ptr<securegcm::D2DConnectionContextV1> encryption_context);
// The returned Ptr will be owned (and destroyed) by the caller.
Ptr<EndpointChannel> getChannelForEndpoint(const string& endpoint_id);
// Returns true if 'endpoint_id' actually had a registered EndpointChannel.
// IOW, a return of false signifies a no-op.
bool unregisterChannelForEndpoint(const string& endpoint_id);
private:
// Tracks channel state for all endpoints. This includes what EndpointChannel
// the endpoint is currently using and whether or not the EndpointChannel has
// been encrypted yet.
class ChannelState {
public:
~ChannelState();
// True if we have an 'encryption_context' for the endpoint.
bool isEndpointEncrypted(const string& endpoint_id);
// Stores a new EndpointChannel for the endpoint, returning the previous
// one (if it existed).
Ptr<EndpointChannel> updateChannelForEndpoint(
const string& endpoint_id, Ptr<EndpointChannel> endpoint_channel);
// Stores a new D2DConnectionContextV1 for the endpoint, returning the
// previous one (if it existed).
Ptr<securegcm::D2DConnectionContextV1> updateEncryptionContextForEndpoint(
const string& endpoint_id,
Ptr<securegcm::D2DConnectionContextV1> encryption_context);
// Removes all knowledge of this endpoint, cleaning up as necessary.
// Returns false if the endpoint was not found.
bool removeEndpoint(const string& endpoint_id,
proto::connections::DisconnectionReason reason);
// Gets the 'encryption_context' for the endpoint. Null if the endpoint was
// not found, or if there is no 'encryption_context' yet.
Ptr<securegcm::D2DConnectionContextV1> getEncryptionContextForEndpoint(
const string& endpoint_id);
// Gets the 'endpoint_channel' for the endpoint. Null if the endpoint was
// not found.
//
// The returned Ptr will be owned (and destroyed) by the caller.
Ptr<EndpointChannel> getChannelForEndpoint(const string& endpoint_id);
private:
struct EndpointMetaData {
~EndpointMetaData() {
encryption_context.destroy();
endpoint_channel.destroy();
}
Ptr<EndpointChannel> endpoint_channel;
Ptr<securegcm::D2DConnectionContextV1> encryption_context;
};
// Endpoint ID -> EndpointMetadata. Contains everything we know about the
// endpoint.
typedef std::map<string, Ptr<EndpointMetaData> > EndpointIdToMetadataMap;
EndpointIdToMetadataMap endpoint_id_to_metadata_;
};
void setActiveEndpointChannel(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<EndpointChannel> endpoint_channel);
void encryptChannel(
const string& endpoint_id, Ptr<EndpointChannel> endpoint_channel,
Ptr<securegcm::D2DConnectionContextV1> encryption_context);
ScopedPtr<Ptr<Lock> > lock_;
Ptr<MediumManager<Platform> > medium_manager_;
Ptr<ChannelState> channel_state_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/endpoint_channel_manager.cc"
#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
+749
View File
@@ -0,0 +1,749 @@
#include "core/internal/endpoint_manager.h"
#include <utility>
#include "core/internal/offline_frames.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace endpoint_manager {
// A Runnable that continuously grabs the most recent EndpointChannel available
// for an endpoint. Override
// EndpointChannelLoopRunnable.execute(EndpointChannel) to interact with the
// EndpointChannel.
template <typename Platform>
class EndpointChannelLoopRunnable : public Runnable {
public:
EndpointChannelLoopRunnable(Ptr<EndpointManager<Platform>> endpoint_manager,
const string& runnable_name,
Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id)
: endpoint_manager_(endpoint_manager),
runnable_name_(runnable_name),
client_proxy_(client_proxy),
endpoint_id_(endpoint_id) {}
~EndpointChannelLoopRunnable() override {}
void run() override {
// The implication of using the EndpointChannel's medium to identify it is
// that this loop will break if we ever allow creating multiple
// EndpointChannels to the same endpoint over the same medium.
proto::connections::Medium last_failed_endpoint_channel_medium =
proto::connections::UNKNOWN_MEDIUM;
while (true) {
// It's important to keep re-fetching the EndpointChannel for an endpoint
// because it can be changed out from under us (for example, when we
// upgrade from Bluetooth to Wifi).
ScopedPtr<Ptr<EndpointChannel>> scoped_endpoint_channel(
endpoint_manager_->endpoint_channel_manager_->getChannelForEndpoint(
endpoint_id_));
if (scoped_endpoint_channel.isNull()) {
// TODO(tracyzhou): Add logging.
break;
}
// If we're looping back around after a failure, and there's not a new
// EndpointChannel for this endpoint, there's nothing more to do here.
if ((last_failed_endpoint_channel_medium !=
proto::connections::UNKNOWN_MEDIUM) &&
(scoped_endpoint_channel->getMedium() ==
last_failed_endpoint_channel_medium)) {
// TODO(tracyzhou): Add logging.
break;
}
ExceptionOr<bool> keep_using_channel =
useHealthyEndpointChannel(scoped_endpoint_channel.get());
if (!keep_using_channel.ok()) {
Exception::Value exception = keep_using_channel.exception();
if (Exception::IO == exception) {
last_failed_endpoint_channel_medium =
scoped_endpoint_channel->getMedium();
// TODO(tracyzhou): Add logging.
continue;
}
if (Exception::INTERRUPTED == exception) {
// Thread.currentThread().interrupt();
// TODO(tracyzhou): Add logging.
break;
}
}
if (!keep_using_channel.result()) {
// TODO(tracyzhou): Add logging.
break;
}
}
// Always clear out all state related to this endpoint before terminating
// this thread.
endpoint_manager_->discardEndpoint(client_proxy_, endpoint_id_);
}
// Called whenever an EndpointChannel is available for endpointId.
// Implementations are expected to read/write freely to the EndpointChannel
// until an Exception::IO is thrown. Once an Exception::IO occurs, a check
// will be performed to see if another EndpointChannel is available for the
// given endpoint and, if so, useHealthyEndpointChannel(EndpointChannel) will
// be called again.
//
// <p>Return false to exit the loop.
virtual ExceptionOr<bool> useHealthyEndpointChannel(
Ptr<EndpointChannel> endpoint_channel) = 0; // throws Exception::IO,
// Exception::INTERRUPTED
protected:
Ptr<EndpointManager<Platform>> endpoint_manager_;
const string runnable_name_;
Ptr<ClientProxy<Platform>> client_proxy_;
const string endpoint_id_;
};
template <typename Platform>
class ReaderRunnable : public EndpointChannelLoopRunnable<Platform> {
public:
ReaderRunnable(Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id)
: EndpointChannelLoopRunnable<Platform>(endpoint_manager, "Read",
client_proxy, endpoint_id) {}
// @EndpointManagerReaderThread
ExceptionOr<bool> useHealthyEndpointChannel(
Ptr<EndpointChannel> endpoint_channel) override {
// Read as much as we can from the healthy EndpointChannel - when it is no
// longer in good shape (i.e. our read from it throws an Exception), our
// super class will loop back around and try our luck in case there's been
// a replacement for this endpoint since we last checked with the
// EndpointChannelManager.
while (true) {
ExceptionOr<ConstPtr<ByteArray>> read_bytes = endpoint_channel->read();
if (!read_bytes.ok()) {
if (Exception::INVALID_PROTOCOL_BUFFER == read_bytes.exception()) {
// TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager
// failed to decode message from endpoint %s on channel %s,
// discarding.", endpointId, endpointChannel.getType());
continue;
} else if (Exception::IO == read_bytes.exception()) {
return ExceptionOr<bool>(read_bytes.exception());
}
}
ScopedPtr<ConstPtr<ByteArray>> scoped_read_bytes(read_bytes.result());
ExceptionOr<ConstPtr<OfflineFrame>> offline_frame =
OfflineFrames::fromBytes(scoped_read_bytes.get());
if (!offline_frame.ok()) {
if (Exception::INVALID_PROTOCOL_BUFFER == offline_frame.exception()) {
// TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager
// received an invalid OfflineFrame from endpoint %s on channel %s,
// discarding.", endpointId, endpointChannel.getType());
continue;
}
}
ScopedPtr<ConstPtr<OfflineFrame>> scoped_offline_frame(
offline_frame.result());
// Route the incoming offlineFrame to its registered processor.
V1Frame::FrameType frame_type =
OfflineFrames::getFrameType(scoped_offline_frame.get());
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
incoming_offline_frame_processor =
this->endpoint_manager_->getOfflineFrameProcessor(frame_type);
if (incoming_offline_frame_processor.isNull()) {
// TODO(tracyzhou): Add logging.
continue;
}
incoming_offline_frame_processor->processIncomingOfflineFrame(
scoped_offline_frame.release(), this->endpoint_id_,
this->client_proxy_, endpoint_channel->getMedium());
}
}
};
template <typename Platform>
class KeepAliveManagerRunnable : public EndpointChannelLoopRunnable<Platform> {
public:
KeepAliveManagerRunnable(Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id)
: EndpointChannelLoopRunnable<Platform>(
endpoint_manager, "KeepAliveManager", client_proxy, endpoint_id) {}
// @EndpointManagerKeepAliveThread
ExceptionOr<bool> useHealthyEndpointChannel(
Ptr<EndpointChannel> endpoint_channel) override {
// Check if it has been too long since we received a frame from our
// endpoint.
if ((endpoint_channel->getLastReadTimestamp() != -1) &&
((endpoint_channel->getLastReadTimestamp() +
EndpointManager<Platform>::kKeepAliveReadTimeoutMillis) <
this->endpoint_manager_->system_clock_->elapsedRealtime())) {
// TODO(tracyzhou): Add logging.
return ExceptionOr<bool>(false);
}
// Attempt to send the KeepAlive frame over the endpoint channel - if the
// write fails, our super class will loop back around and try our luck again
// in case there's been a replacement for this endpoint.
Exception::Value write_exception =
endpoint_channel->write(OfflineFrames::forKeepAlive());
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
return ExceptionOr<bool>(write_exception);
}
}
// We sleep as the very last step because we want to minimize the caching of
// the EndpointChannel. If we do hold on to the EndpointChannel, and it's
// switched out from under us in BandwidthUpgradeManager, our write will
// trigger an erroneous write to the encryption context that will cascade
// into all our remote endpoint's future reads failing.
Exception::Value sleep_exception =
this->endpoint_manager_->thread_utils_->sleep(
EndpointManager<Platform>::kKeepAliveWriteIntervalMillis);
if (Exception::NONE != sleep_exception) {
if (Exception::INTERRUPTED == sleep_exception) {
return ExceptionOr<bool>(sleep_exception);
}
}
return ExceptionOr<bool>(true);
}
};
template <typename Platform>
class RegisterIncomingOfflineFrameProcessorRunnable : public Runnable {
public:
RegisterIncomingOfflineFrameProcessorRunnable(
Ptr<EndpointManager<Platform>> endpoint_manager,
V1Frame::FrameType frame_type,
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor)
: endpoint_manager_(endpoint_manager),
frame_type_(frame_type),
processor_(processor) {}
void run() override {
typename EndpointManager<
Platform>::IncomingOfflineFrameProcessorsMap::iterator it =
endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_);
if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) {
// TODO(tracyzhou): Add logging.
it->second = processor_;
} else {
endpoint_manager_->incoming_offline_frame_processors_.insert(
std::make_pair(frame_type_, processor_));
}
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
const V1Frame::FrameType frame_type_;
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor_;
};
template <typename Platform>
class UnregisterIncomingOfflineFrameProcessorRunnable : public Runnable {
public:
UnregisterIncomingOfflineFrameProcessorRunnable(
Ptr<EndpointManager<Platform>> endpoint_manager,
V1Frame::FrameType frame_type,
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor)
: endpoint_manager_(endpoint_manager),
frame_type_(frame_type),
processor_(processor) {}
void run() override {
typename EndpointManager<
Platform>::IncomingOfflineFrameProcessorsMap::iterator it =
endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_);
if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) {
if (it->second != processor_) {
// TODO(tracyzhou): Add logging.
return;
}
endpoint_manager_->incoming_offline_frame_processors_.erase(it);
}
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
const V1Frame::FrameType frame_type_;
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor_;
};
template <typename Platform>
class RegisterEndpointRunnable : public Runnable {
public:
RegisterEndpointRunnable(
Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id,
const string& endpoint_name, const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token, bool is_incoming,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener,
Ptr<CountDownLatch> latch)
: endpoint_manager_(endpoint_manager),
client_proxy_(client_proxy),
endpoint_id_(endpoint_id),
endpoint_name_(endpoint_name),
authentication_token_(authentication_token),
raw_authentication_token_(raw_authentication_token),
is_incoming_(is_incoming),
endpoint_channel_(endpoint_channel),
connection_lifecycle_listener_(connection_lifecycle_listener),
latch_(latch) {}
void run() override {
endpoint_manager_->endpoint_channel_manager_->registerChannelForEndpoint(
client_proxy_, endpoint_id_, endpoint_channel_);
// For every endpoint, there's one Reader instance running on the
// EndpointManagerReaderThread. This instance reads from the endpoint and
// delegates incoming frames to various IncomingOfflineFrameProcessors.
// Once the frame has been properly handled, it starts reading again for the
// next frame. If the Reader fails its read and no other EndpointChannels
// are available for this endpoint, a disconnection will be initiated.
endpoint_manager_->startEndpointReader(MakePtr(new ReaderRunnable<Platform>(
endpoint_manager_, client_proxy_, endpoint_id_)));
// For every endpoint, there's one KeepAliveManager instance running on the
// EndpointManagerKeepAliveThread. This instance will periodically
// send out a ping* to the endpoint while listening for an incoming pong**.
// If it fails to send the ping, or if no pong is heard within
// kKeepAliveReadTimeoutMillis milliseconds, it initiates a
// disconnection.
//
// (*) Bluetooth requires a constant outgoing stream of messages. If there's
// silence, Android will break the socket. This is why we ping.
// (**) Wifi Hotspots can fail to notice a connection has been lost, and
// they will happily keep writing to /dev/null. This is why we listen for
// the pong.
endpoint_manager_->startEndpointKeepAliveManager(
MakePtr(new KeepAliveManagerRunnable<Platform>(
endpoint_manager_, client_proxy_, endpoint_id_)));
// TODO(tracyzhou): Add logging.
// It's now time to let the client know of this new connection so that they
// can accept or reject it.
client_proxy_->onConnectionInitiated(
endpoint_id_, endpoint_name_, authentication_token_,
raw_authentication_token_.release(), is_incoming_,
connection_lifecycle_listener_.release());
latch_->countDown();
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
Ptr<ClientProxy<Platform>> client_proxy_;
const string endpoint_id_;
const string endpoint_name_;
const string authentication_token_;
ScopedPtr<ConstPtr<ByteArray>> raw_authentication_token_;
const bool is_incoming_;
Ptr<EndpointChannel> endpoint_channel_;
ScopedPtr<Ptr<ConnectionLifecycleListener>> connection_lifecycle_listener_;
Ptr<CountDownLatch> latch_;
};
template <typename Platform>
class UnregisterEndpointRunnable : public Runnable {
public:
UnregisterEndpointRunnable(Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id,
Ptr<CountDownLatch> latch)
: endpoint_manager_(endpoint_manager),
client_proxy_(client_proxy),
endpoint_id_(endpoint_id),
latch_(latch) {}
void run() override {
endpoint_manager_->removeEndpoint(
client_proxy_, endpoint_id_, /*send_disconnection_notification=*/false);
latch_->countDown();
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
Ptr<ClientProxy<Platform>> client_proxy_;
const string endpoint_id_;
Ptr<CountDownLatch> latch_;
};
template <typename Platform>
class DiscardEndpointRunnable : public Runnable {
public:
DiscardEndpointRunnable(Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<ClientProxy<Platform>> client_proxy,
const string& endpoint_id)
: endpoint_manager_(endpoint_manager),
client_proxy_(client_proxy),
endpoint_id_(endpoint_id) {}
void run() override {
endpoint_manager_->removeEndpoint(
client_proxy_, endpoint_id_,
/*send_disconnection_notification=*/
client_proxy_->isConnectedToEndpoint(endpoint_id_));
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
Ptr<ClientProxy<Platform>> client_proxy_;
const string endpoint_id_;
};
template <typename Platform>
class GetOfflineFrameProcessorCallable
: public Callable<Ptr<
typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>> {
public:
typedef Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
ReturnType;
GetOfflineFrameProcessorCallable(
Ptr<EndpointManager<Platform>> endpoint_manager,
V1Frame::FrameType frame_type)
: endpoint_manager_(endpoint_manager), frame_type_(frame_type) {}
ExceptionOr<ReturnType> call() override {
typename EndpointManager<
Platform>::IncomingOfflineFrameProcessorsMap::iterator it =
endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_);
if (it == endpoint_manager_->incoming_offline_frame_processors_.end()) {
return ExceptionOr<ReturnType>(ReturnType());
}
return ExceptionOr<ReturnType>(it->second);
}
private:
Ptr<EndpointManager<Platform>> endpoint_manager_;
const V1Frame::FrameType frame_type_;
};
} // namespace endpoint_manager
template <typename Platform>
bool EndpointManager<Platform>::IncomingOfflineFrameProcessor::operator==(
const EndpointManager<Platform>::IncomingOfflineFrameProcessor& rhs) {
// We're comparing addresses because these objects are callbacks which need to
// be matched by exact instances.
return this == &rhs;
}
template <typename Platform>
bool EndpointManager<Platform>::IncomingOfflineFrameProcessor::operator<(
const EndpointManager<Platform>::IncomingOfflineFrameProcessor& rhs) {
// We're comparing addresses because these objects are callbacks which need to
// be matched by exact instances.
return this < &rhs;
}
template <typename Platform>
const std::int32_t EndpointManager<Platform>::kKeepAliveWriteIntervalMillis =
5000;
template <typename Platform>
const std::int32_t EndpointManager<Platform>::kKeepAliveReadTimeoutMillis =
30000;
template <typename Platform>
const std::int32_t
EndpointManager<Platform>::kProcessEndpointDisconnectionTimeoutMillis =
2000;
template <typename Platform>
const std::int32_t EndpointManager<Platform>::kMaxConcurrentEndpoints = 50;
template <typename Platform>
EndpointManager<Platform>::EndpointManager(
Ptr<EndpointChannelManager<Platform>> endpoint_channel_manager)
: thread_utils_(Platform::createThreadUtils()),
system_clock_(Platform::createSystemClock()),
endpoint_channel_manager_(endpoint_channel_manager),
incoming_offline_frame_processors_(),
endpoint_keep_alive_manager_thread_pool_(
Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)),
endpoint_readers_thread_pool_(
Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)),
serial_executor_(Platform::createSingleThreadExecutor()) {}
template <typename Platform>
EndpointManager<Platform>::~EndpointManager() {
// TODO(tracyzhou): Add logging.
// Stop all the ongoing Runnables (as gracefully as possible).
serial_executor_->shutdown();
endpoint_readers_thread_pool_->shutdown();
endpoint_keep_alive_manager_thread_pool_->shutdown();
// 'incoming_offline_frame_processors' does not own the processors.
incoming_offline_frame_processors_.clear();
// TODO(tracyzhou): Add logging.
}
template <typename Platform>
void EndpointManager<Platform>::registerIncomingOfflineFrameProcessor(
V1Frame::FrameType frame_type,
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor) {
runOnEndpointManagerThread(MakePtr(
new endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable<
Platform>(MakePtr(this), frame_type, processor)));
}
template <typename Platform>
void EndpointManager<Platform>::unregisterIncomingOfflineFrameProcessor(
V1Frame::FrameType frame_type,
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
processor) {
runOnEndpointManagerThread(MakePtr(
new endpoint_manager::UnregisterIncomingOfflineFrameProcessorRunnable<
Platform>(MakePtr(this), frame_type, processor)));
}
template <typename Platform>
Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
EndpointManager<Platform>::getOfflineFrameProcessor(
V1Frame::FrameType frame_type) {
typedef Ptr<typename EndpointManager<Platform>::IncomingOfflineFrameProcessor>
PtrIncomingOfflineFrameProcessor;
typedef Ptr<Future<PtrIncomingOfflineFrameProcessor>> ResultType;
ScopedPtr<ResultType> future_result(
runOnEndpointManagerThread<PtrIncomingOfflineFrameProcessor>(MakePtr(
new endpoint_manager::GetOfflineFrameProcessorCallable<Platform>(
MakePtr(this), frame_type))));
return waitForResult("getOfflineFrameProcessor", future_result.get());
}
template <typename Platform>
void EndpointManager<Platform>::registerEndpoint(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id,
const string& endpoint_name, const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token, bool is_incoming,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
ScopedPtr<Ptr<CountDownLatch>> latch(Platform::createCountDownLatch(1));
runOnEndpointManagerThread(
MakePtr(new endpoint_manager::RegisterEndpointRunnable<Platform>(
MakePtr(this), client_proxy, endpoint_id, endpoint_name,
authentication_token, raw_authentication_token, is_incoming,
endpoint_channel, connection_lifecycle_listener, latch.get())));
waitForLatch("registerEndpoint", latch.get());
}
template <typename Platform>
void EndpointManager<Platform>::unregisterEndpoint(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id) {
ScopedPtr<Ptr<CountDownLatch>> latch(Platform::createCountDownLatch(1));
runOnEndpointManagerThread(
MakePtr(new endpoint_manager::UnregisterEndpointRunnable<Platform>(
MakePtr(this), client_proxy, endpoint_id, latch.get())));
waitForLatch("unregisterEndpoint", latch.get());
}
template <typename Platform>
void EndpointManager<Platform>::discardEndpoint(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id) {
runOnEndpointManagerThread(
MakePtr(new endpoint_manager::DiscardEndpointRunnable<Platform>(
MakePtr(this), client_proxy, endpoint_id)));
}
template <typename Platform>
std::vector<string> EndpointManager<Platform>::sendPayloadChunk(
const PayloadTransferFrame::PayloadHeader& payload_header,
const PayloadTransferFrame::PayloadChunk& payload_chunk,
const std::vector<string>& endpoint_ids) {
ConstPtr<ByteArray> payload_transfer_frame_bytes =
OfflineFrames::forDataPayloadTransferFrame(payload_header, payload_chunk);
return sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes,
payload_header.id(),
/*offset=*/payload_chunk.offset(),
/*packet_type=*/"DATA");
}
template <typename Platform>
void EndpointManager<Platform>::sendControlMessage(
const PayloadTransferFrame::PayloadHeader& payload_header,
const PayloadTransferFrame::ControlMessage& control_message,
const std::vector<string>& endpoint_ids) {
ConstPtr<ByteArray> payload_transfer_frame_bytes =
OfflineFrames::forControlPayloadTransferFrame(payload_header,
control_message);
sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes,
payload_header.id(),
/*offset=*/control_message.offset(),
/*packet_type=*/"CONTROL");
}
template <typename Platform>
void EndpointManager<Platform>::waitForLatch(const string& method_name,
Ptr<CountDownLatch> latch) {
Exception::Value await_exception = latch->await();
if (Exception::NONE != await_exception) {
if (Exception::INTERRUPTED == await_exception) {
// TODO(tracyzhou): Add logging.
// Thread.currentThread().interrupt();
}
}
}
template <typename Platform>
void EndpointManager<Platform>::waitForLatch(const string& method_name,
Ptr<CountDownLatch> latch,
std::int32_t timeout_millis) {
ExceptionOr<bool> await_succeeded = latch->await(timeout_millis);
if (!await_succeeded.ok()) {
// TODO(tracyzhou): Add logging.
if (Exception::INTERRUPTED == await_succeeded.exception()) {
// TODO(tracyzhou): Add logging.
// Thread.currentThread().interrupt();
return;
}
}
if (!await_succeeded.result()) {
// TODO(tracyzhou): Add logging.
}
}
template <typename Platform>
template <typename T>
T EndpointManager<Platform>::waitForResult(const string& method_name,
Ptr<Future<T>> result_future) {
ExceptionOr<T> result = result_future->get();
if (!result.ok()) {
Exception::Value exception = result.exception();
if (Exception::INTERRUPTED == exception ||
Exception::EXECUTION == exception) {
// TODO(tracyzhou): Add logging.
if (Exception::INTERRUPTED == exception) {
// Thread.currentThread().interrupt();
}
return T();
}
}
return result.result();
}
// @EndpointManagerThread
template <typename Platform>
void EndpointManager<Platform>::removeEndpoint(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id,
bool send_disconnection_notification) {
// Unregistering from endpoint_channel_manager_ will also serve to terminate
// the dedicated reader and KeepAlive threads we started when we registered
// this endpoint.
if (endpoint_channel_manager_->unregisterChannelForEndpoint(endpoint_id)) {
// Notify all frame processors of the disconnection immediately and wait
// for them to clean up state. Only once all processors are done cleaning
// up, we can remove the endpoint from ClientProxy after which there
// should be no further interactions with the endpoint.
// (See b/37352254 for history)
waitForEndpointDisconnectionProcessing(client_proxy, endpoint_id);
client_proxy->onDisconnected(endpoint_id, send_disconnection_notification);
// TODO(tracyzhou): Add logging.
}
}
// @EndpointManagerThread
template <typename Platform>
void EndpointManager<Platform>::waitForEndpointDisconnectionProcessing(
Ptr<ClientProxy<Platform>> client_proxy, const string& endpoint_id) {
ScopedPtr<Ptr<CountDownLatch>> process_disconnection_barrier(
Platform::createCountDownLatch(static_cast<std::int32_t>(
incoming_offline_frame_processors_.size())));
for (typename IncomingOfflineFrameProcessorsMap::iterator it =
incoming_offline_frame_processors_.begin();
it != incoming_offline_frame_processors_.end(); it++) {
it->second->processEndpointDisconnection(
client_proxy, endpoint_id, process_disconnection_barrier.get());
}
waitForLatch("waitForEndpointDisconnectionProcessing",
process_disconnection_barrier.get(),
kProcessEndpointDisconnectionTimeoutMillis);
}
template <typename Platform>
std::vector<string> EndpointManager<Platform>::sendTransferFrameBytes(
const std::vector<string>& endpoint_ids,
ConstPtr<ByteArray> payload_transfer_frame_bytes, std::int64_t payload_id,
std::int64_t offset, const string& packet_type) {
ScopedPtr<ConstPtr<ByteArray>> scoped_payload_transfer_frame_bytes(
payload_transfer_frame_bytes);
std::vector<string> failed_endpoint_ids;
for (std::vector<string>::const_iterator it = endpoint_ids.begin();
it != endpoint_ids.end(); it++) {
const string& endpoint_id = *it;
ScopedPtr<Ptr<EndpointChannel>> scoped_endpoint_channel(
endpoint_channel_manager_->getChannelForEndpoint(endpoint_id));
if (scoped_endpoint_channel.isNull()) {
// We no longer know about this endpoint (it was either explicitly
// unregistered, or a read/write error made us unregister it internally).
// TODO(tracyzhou): Add logging.
failed_endpoint_ids.push_back(endpoint_id);
continue;
}
Exception::Value write_exception = scoped_endpoint_channel->write(
scoped_payload_transfer_frame_bytes.release());
if (Exception::NONE != write_exception) {
if (Exception::IO == write_exception) {
// TODO(tracyzhou): Add logging.
failed_endpoint_ids.push_back(endpoint_id);
continue;
}
}
}
return failed_endpoint_ids;
}
template <typename Platform>
void EndpointManager<Platform>::startEndpointReader(Ptr<Runnable> runnable) {
endpoint_readers_thread_pool_->execute(runnable);
}
template <typename Platform>
void EndpointManager<Platform>::startEndpointKeepAliveManager(
Ptr<Runnable> runnable) {
endpoint_keep_alive_manager_thread_pool_->execute(runnable);
}
template <typename Platform>
void EndpointManager<Platform>::runOnEndpointManagerThread(
Ptr<Runnable> runnable) {
serial_executor_->execute(runnable);
}
template <typename Platform>
template <typename T>
Ptr<Future<T>> EndpointManager<Platform>::runOnEndpointManagerThread(
Ptr<Callable<T>> callable) {
return serial_executor_->submit(callable);
}
} // namespace connections
} // namespace nearby
} // namespace location
+232
View File
@@ -0,0 +1,232 @@
#ifndef CORE_INTERNAL_ENDPOINT_MANAGER_H_
#define CORE_INTERNAL_ENDPOINT_MANAGER_H_
#include <cstdint>
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel.h"
#include "core/internal/endpoint_channel_manager.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/count_down_latch.h"
#include "platform/api/submittable_executor.h"
#include "platform/api/system_clock.h"
#include "platform/api/thread_utils.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "platform/runnable.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace endpoint_manager {
template <typename>
class ReaderRunnable;
template <typename>
class KeepAliveManagerRunnable;
template <typename>
class EndpointChannelLoopRunnable;
template <typename>
class RegisterIncomingOfflineFrameProcessorRunnable;
template <typename>
class UnregisterIncomingOfflineFrameProcessorRunnable;
template <typename>
class RegisterEndpointRunnable;
template <typename>
class UnregisterEndpointRunnable;
template <typename>
class DiscardEndpointRunnable;
template <typename>
class GetOfflineFrameProcessorCallable;
} // namespace endpoint_manager
// Manages all operations related to the remote endpoints with which we are
// interacting.
//
// <p>All processing of incoming and outgoing payloads is spread across this and
// the PayloadManager as described below.
//
// <p>The sending of outgoing payloads originates in
// PayloadManager.sendPayload() before control is transferred over to
// EndpointManager.sendPayloadChunk(). This work happens on one of three
// dedicated writer threads belonging to the PayloadManager. The writer thread
// that is used depends on the PayloadType.
//
// <p>The EndpointManager has one dedicated reader thread for each registered
// endpoint, and the receiving of every incoming payload (and its subsequent
// chunks) originates on one of those threads before control is transferred over
// to PayloadManager.processIncomingOfflineFrame() (still running on that
// same dedicated reader thread).
template <typename Platform>
class EndpointManager {
public:
class IncomingOfflineFrameProcessor {
public:
virtual ~IncomingOfflineFrameProcessor() {}
// This function takes full ownership of offline_frame.
// @EndpointManagerReaderThread
virtual void processIncomingOfflineFrame(
ConstPtr<OfflineFrame> offline_frame, const string& from_endpoint_id,
Ptr<ClientProxy<Platform> > to_client_proxy,
proto::connections::Medium current_medium) = 0;
// Implementations must call process_disconnection_barrier.countDown() once
// they're done. This parallelizes the disconnection event across all frame
// processors.
//
// @EndpointManagerThread
virtual void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) = 0;
// Operator overloads when comparing Ptr<IncomingOfflineFrameProcessor>.
bool operator==(
const typename EndpointManager<Platform>::IncomingOfflineFrameProcessor&
rhs);
bool operator<(
const typename EndpointManager<Platform>::IncomingOfflineFrameProcessor&
rhs);
};
explicit EndpointManager(
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager);
~EndpointManager();
// Invoked from the constructors of the various *Manager components that make
// up the OfflineServiceController implementation.
void registerIncomingOfflineFrameProcessor(
V1Frame::FrameType frame_type,
Ptr<IncomingOfflineFrameProcessor> processor);
void unregisterIncomingOfflineFrameProcessor(
V1Frame::FrameType frame_type,
Ptr<IncomingOfflineFrameProcessor> processor);
// Invoked from the different PCPHandler implementations (of which there can
// be only one at a time).
void registerEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
const string& endpoint_name, const string& authentication_token,
ConstPtr<ByteArray> raw_authentication_token, bool is_incoming,
Ptr<EndpointChannel> endpoint_channel,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener);
// Called when a client explicitly asks to disconnect from this endpoint. In
// this case, we do not notify the client of onDisconnected().
void unregisterEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id);
// Called when we internally want to get rid of the endpoint, without the
// client directly telling us to. For example...
// a) We failed to read from the endpoint in its dedicated reader thread.
// b) We failed to write to the endpoint in PayloadManager.
// c) The connection was rejected in PCPHandler.
// d) The dedicated KeepAlive thread exceeded its period of inactivity.
// Or in the numerous other cases where a failure occurred and we no longer
// believe the endpoint is in a healthy state.
//
// Note: This must not block. Otherwise we can get into a deadlock where we
// ask everyone who's registered an IncomingOfflineFrameProcessor to
// processEndpointDisconnection() while the caller of discardEndpoint() is
// blocked here.
void discardEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id);
Ptr<IncomingOfflineFrameProcessor> getOfflineFrameProcessor(
V1Frame::FrameType frame_type);
// Returns the list of endpoints to which sending this chunk failed.
//
// Invoked from the PayloadManager's sendPayload() method.
std::vector<string> sendPayloadChunk(
const PayloadTransferFrame::PayloadHeader& payload_header,
const PayloadTransferFrame::PayloadChunk& payload_chunk,
const std::vector<string>& endpoint_ids);
void sendControlMessage(
const PayloadTransferFrame::PayloadHeader& payload_header,
const PayloadTransferFrame::ControlMessage& control_message,
const std::vector<string>& endpoint_ids);
private:
template <typename>
friend class endpoint_manager::ReaderRunnable;
template <typename>
friend class endpoint_manager::KeepAliveManagerRunnable;
template <typename>
friend class endpoint_manager::EndpointChannelLoopRunnable;
template <typename>
friend class endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable;
template <typename>
friend class endpoint_manager::
UnregisterIncomingOfflineFrameProcessorRunnable;
template <typename>
friend class endpoint_manager::RegisterEndpointRunnable;
template <typename>
friend class endpoint_manager::UnregisterEndpointRunnable;
template <typename>
friend class endpoint_manager::DiscardEndpointRunnable;
template <typename>
friend class endpoint_manager::GetOfflineFrameProcessorCallable;
static void waitForLatch(const string& method_name,
Ptr<CountDownLatch> latch);
static void waitForLatch(const string& method_name, Ptr<CountDownLatch> latch,
std::int32_t timeout_millis);
template <typename T>
static T waitForResult(const string& method_name,
Ptr<Future<T> > result_future);
static const std::int32_t kKeepAliveWriteIntervalMillis;
static const std::int32_t kKeepAliveReadTimeoutMillis;
static const std::int32_t kProcessEndpointDisconnectionTimeoutMillis;
static const std::int32_t kMaxConcurrentEndpoints;
static const std::int32_t kEndpointIdLength;
// It should be noted that this method may be called multiple times (because
// invoking this method closes the endpoint channel, which causes the
// dedicated reader and KeepAlive threads to terminate, which in turn leads to
// this method being called), but that's alright because the implementation of
// this method is idempotent.
void removeEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
bool send_disconnection_notification);
void waitForEndpointDisconnectionProcessing(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id);
std::vector<string> sendTransferFrameBytes(
const std::vector<string>& endpoint_ids,
ConstPtr<ByteArray> payload_transfer_frame_bytes, std::int64_t payload_id,
std::int64_t offset, const string& packet_type);
void startEndpointReader(Ptr<Runnable> runnable);
void startEndpointKeepAliveManager(Ptr<Runnable> runnable);
void runOnEndpointManagerThread(Ptr<Runnable> runnable);
template <typename T>
Ptr<Future<T> > runOnEndpointManagerThread(Ptr<Callable<T> > callable);
ScopedPtr<Ptr<ThreadUtils> > thread_utils_;
ScopedPtr<Ptr<SystemClock> > system_clock_;
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager_;
typedef std::map<V1Frame::FrameType, Ptr<IncomingOfflineFrameProcessor> >
IncomingOfflineFrameProcessorsMap;
IncomingOfflineFrameProcessorsMap incoming_offline_frame_processors_;
ScopedPtr<Ptr<typename Platform::MultiThreadExecutorType> >
endpoint_keep_alive_manager_thread_pool_;
ScopedPtr<Ptr<typename Platform::MultiThreadExecutorType> >
endpoint_readers_thread_pool_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > serial_executor_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/endpoint_manager.cc"
#endif // CORE_INTERNAL_ENDPOINT_MANAGER_H_
+20
View File
@@ -0,0 +1,20 @@
#include "core/internal/internal_payload.h"
namespace location {
namespace nearby {
namespace connections {
InternalPayload::InternalPayload(ConstPtr<Payload> payload)
: payload_(payload), payload_id_(payload_->getId()) {}
InternalPayload::~InternalPayload() {}
ConstPtr<Payload> InternalPayload::releasePayload() {
return payload_.release();
}
std::int64_t InternalPayload::getId() const { return payload_id_; }
} // namespace connections
} // namespace nearby
} // namespace location
+82
View File
@@ -0,0 +1,82 @@
#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_H_
#define CORE_INTERNAL_INTERNAL_PAYLOAD_H_
#include <cstdint>
#include "core/payload.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/byte_array.h"
#include "platform/exception.h"
#include "platform/ptr.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(ConstPtr<Payload> payload);
virtual ~InternalPayload();
ConstPtr<Payload> releasePayload();
std::int64_t 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 ExceptionOr<ConstPtr<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::Value attachNextChunk(ConstPtr<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:
ScopedPtr<ConstPtr<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.
const std::int64_t payload_id_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_H_
@@ -0,0 +1,312 @@
#include "core/internal/internal_payload_factory.h"
#include <cstdint>
#include "core/payload.h"
#include "platform/api/condition_variable.h"
#include "platform/api/lock.h"
#include "platform/byte_array.h"
#include "platform/exception.h"
#include "platform/file_impl.h"
#include "platform/pipe.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
class BytesInternalPayload : public InternalPayload {
public:
explicit BytesInternalPayload(ConstPtr<Payload> payload)
: InternalPayload(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_; }
ExceptionOr<ConstPtr<ByteArray> > detachNextChunk() override {
if (detached_only_chunk_) {
return ExceptionOr<ConstPtr<ByteArray> >(ConstPtr<ByteArray>());
}
detached_only_chunk_ = true;
return ExceptionOr<ConstPtr<ByteArray> >(payload_->releaseBytes());
}
Exception::Value attachNextChunk(ConstPtr<ByteArray> chunk) override {
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_chunk(chunk);
// Nothing to do - this method makes sense for other, more long-running
// InternalPayload concrete implementations.
return Exception::NONE;
}
private:
// We're caching the total size here because the backing payload will be
// released to another owner during the lifetime of an incoming
// InternalPayload.
const std::int64_t total_size_;
bool detached_only_chunk_;
};
template <typename Platform>
class OutgoingStreamInternalPayload : public InternalPayload {
public:
explicit OutgoingStreamInternalPayload(ConstPtr<Payload> payload)
: InternalPayload(payload) {}
PayloadTransferFrame::PayloadHeader::PayloadType getType() const override {
return PayloadTransferFrame::PayloadHeader::STREAM;
}
std::int64_t getTotalSize() const override { return -1; }
ExceptionOr<ConstPtr<ByteArray> > detachNextChunk() override {
Ptr<InputStream> input_stream(payload_->asStream()->asInputStream());
ExceptionOr<ConstPtr<ByteArray> > bytes_read =
input_stream->read(kChunkSize);
if (!bytes_read.ok()) {
if (Exception::IO == bytes_read.exception()) {
// Ignore the potential Exception returned by close(), as a counterpart
// to Java's closeQuietly().
input_stream->close();
return bytes_read;
}
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray> > scoped_bytes_read(bytes_read.result());
if (scoped_bytes_read.isNull()) {
// TODO(reznor): logger.atVerbose().log("No more data for outgoing payload
// %s, closing InputStream.", this);
// Ignore the potential Exception returned by close(), as a counterpart
// to Java's closeQuietly().
input_stream->close();
return ExceptionOr<ConstPtr<ByteArray> >(ConstPtr<ByteArray>());
}
return ExceptionOr<ConstPtr<ByteArray> >(scoped_bytes_read.release());
}
Exception::Value attachNextChunk(ConstPtr<ByteArray> chunk) override {
return Exception::IO;
}
void close() override {
// Ignore the potential Exception returned by close(), as a counterpart
// to Java's closeQuietly().
payload_->asStream()->asInputStream()->close();
}
private:
static const std::int64_t kChunkSize = 64 * 1024;
};
template <typename Platform>
class IncomingStreamInternalPayload : public InternalPayload {
public:
IncomingStreamInternalPayload(ConstPtr<Payload> payload,
Ptr<OutputStream> output_stream)
: InternalPayload(payload), output_stream_(output_stream) {}
PayloadTransferFrame::PayloadHeader::PayloadType getType() const override {
return PayloadTransferFrame::PayloadHeader::STREAM;
}
std::int64_t getTotalSize() const override { return -1; }
ExceptionOr<ConstPtr<ByteArray> > detachNextChunk() override {
return ExceptionOr<ConstPtr<ByteArray> >(Exception::IO);
}
Exception::Value attachNextChunk(ConstPtr<ByteArray> chunk) override {
ScopedPtr<ConstPtr<ByteArray> > scoped_chunk(chunk);
if (scoped_chunk.isNull()) {
output_stream_->close();
return Exception::NONE;
}
return output_stream_->write(scoped_chunk.release());
}
void close() override {
output_stream_->close();
}
private:
ScopedPtr<Ptr<OutputStream> > output_stream_;
};
class OutgoingFileInternalPayload : public InternalPayload {
public:
explicit OutgoingFileInternalPayload(ConstPtr<Payload> payload)
: InternalPayload(std::move(payload)) {}
PayloadTransferFrame::PayloadHeader::PayloadType getType() const override {
return PayloadTransferFrame::PayloadHeader::FILE;
}
std::int64_t getTotalSize() const override {
return payload_->asFile()->asInputFile()->getTotalSize();
}
ExceptionOr<ConstPtr<ByteArray>> detachNextChunk() override {
Ptr<InputFile> input_file(payload_->asFile()->asInputFile());
ExceptionOr<ConstPtr<ByteArray>> bytes_read = input_file->read(kChunkSize);
if (!bytes_read.ok()) {
if (Exception::IO == bytes_read.exception()) {
input_file->close();
return bytes_read;
}
}
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray>> scoped_bytes_read(bytes_read.result());
if (scoped_bytes_read.isNull()) {
// No more data for outgoing payload.
input_file->close();
return ExceptionOr<ConstPtr<ByteArray>>(ConstPtr<ByteArray>());
}
return ExceptionOr<ConstPtr<ByteArray>>(scoped_bytes_read.release());
}
Exception::Value attachNextChunk(ConstPtr<ByteArray> chunk) override {
return Exception::IO;
}
void close() override { payload_->asFile()->asInputFile()->close(); }
private:
static const std::int64_t kChunkSize = 64 * 1024;
};
class IncomingFileInternalPayload : public InternalPayload {
public:
IncomingFileInternalPayload(ConstPtr<Payload> payload,
const Ptr<OutputFile>& output_file,
std::int64_t total_size)
: InternalPayload(std::move(payload)),
output_file_(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_; }
ExceptionOr<ConstPtr<ByteArray>> detachNextChunk() override {
return ExceptionOr<ConstPtr<ByteArray>>(Exception::IO);
}
Exception::Value attachNextChunk(ConstPtr<ByteArray> chunk) override {
ScopedPtr<ConstPtr<ByteArray>> scoped_chunk(chunk);
if (scoped_chunk.isNull()) {
// Received null last chunk for incoming payload.
output_file_->close();
return Exception::NONE;
}
return output_file_->write(scoped_chunk.release());
}
void close() override { output_file_->close(); }
private:
ScopedPtr<Ptr<OutputFile>> output_file_;
const std::int64_t total_size_;
};
} // namespace
template <typename Platform>
Ptr<InternalPayload> InternalPayloadFactory<Platform>::createOutgoing(
ConstPtr<Payload> payload) {
// Avoid leaks.
ScopedPtr<ConstPtr<Payload> > scoped_payload(payload);
switch (scoped_payload->getType()) {
case Payload::Type::BYTES:
return MakePtr(new BytesInternalPayload(scoped_payload.release()));
case Payload::Type::FILE:
return MakePtr(new OutgoingFileInternalPayload(scoped_payload.release()));
case Payload::Type::STREAM:
return MakePtr(new OutgoingStreamInternalPayload<Platform>(
scoped_payload.release()));
default: {}
// Fall through
}
// This should never be reached since the ServiceControllerRouter has already
// checked whether or not we can work with this Payload type.
return Ptr<InternalPayload>();
}
template <typename Platform>
Ptr<InternalPayload> InternalPayloadFactory<Platform>::createIncoming(
const PayloadTransferFrame& payload_transfer_frame) {
if (PayloadTransferFrame::DATA != payload_transfer_frame.packet_type()) {
return Ptr<InternalPayload>();
}
const int64_t payload_id = payload_transfer_frame.payload_header().id();
switch (payload_transfer_frame.payload_header().type()) {
case PayloadTransferFrame::PayloadHeader::BYTES: {
const string& body = payload_transfer_frame.payload_chunk().body();
return MakePtr(new BytesInternalPayload(MakeConstPtr(new Payload(
payload_id, MakeConstPtr(new ByteArray(body.data(), body.size()))))));
}
case PayloadTransferFrame::PayloadHeader::STREAM: {
// pipe will be auto-destroyed when it is no longer referenced.
auto pipe = MakeRefCountedPtr(new Pipe<Platform>());
return MakePtr(new IncomingStreamInternalPayload<Platform>(
MakeConstPtr(new Payload(
payload_id,
MakeConstPtr(new Payload::Stream(
Pipe<Platform>::createInputStream(pipe))))),
Pipe<Platform>::createOutputStream(pipe)));
}
case PayloadTransferFrame::PayloadHeader::FILE: {
const std::string payload_path = Platform::getPayloadPath(payload_id);
Ptr<InputFile> input_file = MakePtr(new InputFileImpl(
payload_path, payload_transfer_frame.payload_header().total_size()));
Ptr<OutputFile> output_file = MakePtr(new OutputFileImpl(payload_path));
ConstPtr<Payload> payload = MakeConstPtr(
new Payload(payload_id, MakeConstPtr(new Payload::File(input_file))));
return MakePtr(new IncomingFileInternalPayload(
payload, output_file,
payload_transfer_frame.payload_header().total_size()));
}
default: {}
// Fall through.
}
// This should never be reached since the ServiceControllerRouter has
// already checked whether or not we can work with this Payload type.
return Ptr<InternalPayload>();
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,34 @@
#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
#define CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
#include "core/internal/internal_payload.h"
#include "core/payload.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class InternalPayloadFactory {
public:
// Creates an InternalPayload representing an outgoing Payload.
//
// The returned Ptr<InternalPayload> will take ownership of the passed-in
// 'payload'.
Ptr<InternalPayload> createOutgoing(ConstPtr<Payload> payload);
// Creates an InternalPayload representing an incoming Payload from a remote
// endpoint.
Ptr<InternalPayload> createIncoming(
const PayloadTransferFrame& payload_transfer_frame);
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/internal_payload_factory.cc"
#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
+54
View File
@@ -0,0 +1,54 @@
#include "core/internal/loop_runner.h"
#include "platform/exception.h"
namespace location {
namespace nearby {
namespace connections {
LoopRunner::LoopRunner(const std::string& name) : name_(name) {}
bool LoopRunner::loop(Ptr<Callable<bool> > callable) {
ScopedPtr<Ptr<Callable<bool> > > scoped_callable(callable);
onEnterLoop();
while (true) {
onEnterIteration();
ExceptionOr<bool> should_continue = scoped_callable->call();
if (!should_continue.ok()) {
onExceptionExitLoop(should_continue.exception());
break;
}
onExitIteration();
if (!should_continue.result()) {
onExitLoop();
return true;
}
}
return false;
}
void LoopRunner::onEnterLoop() {
// TODO(tracyzhou): Add logging.
}
void LoopRunner::onEnterIteration() {
// TODO(tracyzhou): Add logging.
}
void LoopRunner::onExitIteration() {
// TODO(tracyzhou): Add logging.
}
void LoopRunner::onExitLoop() {
// TODO(tracyzhou): Add logging.
}
void LoopRunner::onExceptionExitLoop(Exception::Value exception) {
// TODO(tracyzhou): Add logging.
}
} // namespace connections
} // namespace nearby
} // namespace location
+42
View File
@@ -0,0 +1,42 @@
#ifndef CORE_INTERNAL_LOOP_RUNNER_H_
#define CORE_INTERNAL_LOOP_RUNNER_H_
#include "platform/callable.h"
#include "platform/exception.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Construct to run a loop repeatedly. This class is useful to increase
// testability for multi-threaded code that runs loops; it shouldn't be used for
// general purpose loops unless tests require fine-grained control over the
// looping procedure.
class LoopRunner {
public:
explicit LoopRunner(const std::string& name);
// Runs the provided callable repeatedly until it returns false.
//
// @return true if the loop completed successfully, false if an exception was
// encountered.
bool loop(Ptr<Callable<bool> > callable);
protected:
void onEnterLoop();
void onEnterIteration();
void onExitIteration();
void onExitLoop();
void onExceptionExitLoop(Exception::Value exception);
private:
const std::string name_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_LOOP_RUNNER_H_
+361
View File
@@ -0,0 +1,361 @@
#include "core/internal/medium_manager.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
MediumManager<Platform>::MediumManager()
: mediums_(new Mediums<Platform>()),
bluetooth_classic_lock_(Platform::createLock()),
ble_lock_(Platform::createLock()) {}
template <typename Platform>
MediumManager<Platform>::~MediumManager() {
// TODO(reznor): log.atDebug().log("Initiating shutdown of MediumManager.");
Synchronized s1(bluetooth_classic_lock_.get());
Synchronized s2(ble_lock_.get());
mediums_.destroy();
// TODO(reznor): log.atDebug().log("MediumManager has shut down.");
}
// ~~~~~~~~~~~~~~~~~~~~~~~~ BLUETOOTH ~~~~~~~~~~~~~~~~~~~~~~~~
template <typename Platform>
bool MediumManager<Platform>::isBluetoothAvailable() {
Synchronized s(bluetooth_classic_lock_.get());
return mediums_->bluetoothClassic()->isAvailable();
}
template <typename Platform>
bool MediumManager<Platform>::turnOnBluetoothDiscoverability(
const string& device_name) {
Synchronized s(bluetooth_classic_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
mediums_->bluetoothClassic()->turnOnDiscoverability(device_name);
}
template <typename Platform>
void MediumManager<Platform>::turnOffBluetoothDiscoverability() {
Synchronized s(bluetooth_classic_lock_.get());
mediums_->bluetoothClassic()->turnOffDiscoverability();
}
template <typename Platform>
class DiscoveredDeviceCallback
: public BluetoothClassic<Platform>::DiscoveredDeviceCallback {
public:
typedef typename MediumManager<Platform>::FoundBluetoothDeviceProcessor
FoundBluetoothDeviceProcessor;
explicit DiscoveredDeviceCallback(
Ptr<FoundBluetoothDeviceProcessor> found_bluetooth_device_processor)
: found_bluetooth_device_processor_(found_bluetooth_device_processor) {}
void onDeviceDiscovered(Ptr<BluetoothDevice> device) override {
found_bluetooth_device_processor_->onFoundBluetoothDevice(device);
}
void onDeviceNameChanged(Ptr<BluetoothDevice> device) override {
found_bluetooth_device_processor_->onFoundBluetoothDevice(device);
}
void onDeviceLost(Ptr<BluetoothDevice> device) override {
found_bluetooth_device_processor_->onLostBluetoothDevice(device);
}
private:
ScopedPtr<Ptr<FoundBluetoothDeviceProcessor> >
found_bluetooth_device_processor_;
};
template <typename Platform>
bool MediumManager<Platform>::startScanningForBluetoothDevices(
Ptr<FoundBluetoothDeviceProcessor> found_bluetooth_device_processor) {
Synchronized s(bluetooth_classic_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
mediums_->bluetoothClassic()->startDiscovery(
MakePtr(new DiscoveredDeviceCallback<Platform>(
found_bluetooth_device_processor)));
}
template <typename Platform>
void MediumManager<Platform>::stopScanningForBluetoothDevices() {
Synchronized s(bluetooth_classic_lock_.get());
mediums_->bluetoothClassic()->stopDiscovery();
}
template <typename Platform>
bool MediumManager<Platform>::isListeningForIncomingBluetoothConnections(
const string& service_name) {
Synchronized s(bluetooth_classic_lock_.get());
return mediums_->bluetoothClassic()->isAcceptingConnections(service_name);
}
template <typename Platform>
class BluetoothAcceptedConnectionCallback
: public BluetoothClassic<Platform>::AcceptedConnectionCallback {
public:
typedef typename MediumManager<Platform>::IncomingBluetoothConnectionProcessor
IncomingBluetoothConnectionProcessor;
explicit BluetoothAcceptedConnectionCallback(
Ptr<IncomingBluetoothConnectionProcessor>
incoming_bluetooth_connection_processor)
: incoming_bluetooth_connection_processor_(
incoming_bluetooth_connection_processor) {}
void onConnectionAccepted(Ptr<BluetoothSocket> socket) override {
incoming_bluetooth_connection_processor_->onIncomingBluetoothConnection(
socket);
}
private:
ScopedPtr<Ptr<IncomingBluetoothConnectionProcessor> >
incoming_bluetooth_connection_processor_;
};
template <typename Platform>
bool MediumManager<Platform>::startListeningForIncomingBluetoothConnections(
const string& service_name, Ptr<IncomingBluetoothConnectionProcessor>
incoming_bluetooth_connection_processor) {
Synchronized s(bluetooth_classic_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
mediums_->bluetoothClassic()->startAcceptingConnections(
service_name,
MakePtr(new BluetoothAcceptedConnectionCallback<Platform>(
incoming_bluetooth_connection_processor)));
}
template <typename Platform>
void MediumManager<Platform>::stopListeningForIncomingBluetoothConnections(
const string& service_name) {
Synchronized s(bluetooth_classic_lock_.get());
mediums_->bluetoothClassic()->stopAcceptingConnections(service_name);
}
template <typename Platform>
Ptr<BluetoothSocket> MediumManager<Platform>::connectToBluetoothDevice(
Ptr<BluetoothDevice> bluetooth_device, const string& service_name) {
Synchronized s(bluetooth_classic_lock_.get());
if (!mediums_->bluetoothRadio()->enable()) {
return Ptr<BluetoothSocket>();
}
return mediums_->bluetoothClassic()->connect(bluetooth_device, service_name);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~ BLE ~~~~~~~~~~~~~~~~~~~~~~~~
template <typename Platform>
bool MediumManager<Platform>::isBleAvailable() {
Synchronized s(ble_lock_.get());
#if BLE_V2_IMPLEMENTED
return mediums_->bleV2()->isAvailable();
#else
return mediums_->ble()->isAvailable();
#endif
}
// TODO(ahlee): Add nearbyNotificationsBeaconData for phase 2 of implementation.
// TODO(ahlee): Add fast_advertisement_service_uuid and power_level to
// AdvertisingOptions and pass it through.
template <typename Platform>
bool MediumManager<Platform>::startBleAdvertising(
const string& service_id, ConstPtr<ByteArray> advertisement_data) {
Synchronized s(ble_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->startAdvertising(
service_id, advertisement_data, BLEMediumV2::PowerMode::HIGH,
/* fast_advertisement_service_uuid= */ "");
#else
mediums_->ble()->startAdvertising(service_id, advertisement_data);
#endif
}
template <typename Platform>
void MediumManager<Platform>::stopBleAdvertising(const string& service_id) {
Synchronized s(ble_lock_.get());
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->stopAdvertising();
#else
mediums_->ble()->stopAdvertising();
#endif
}
#if BLE_V2_IMPLEMENTED
template <typename Platform>
class BLEAcceptedConnectionCallback
: public mediums::BLEV2<Platform>::AcceptedConnectionCallback {
public:
BLEAcceptedConnectionCallback() {}
};
#else
template <typename Platform>
class BLEAcceptedConnectionCallback
: public BLE<Platform>::AcceptedConnectionCallback {
public:
typedef typename MediumManager<Platform>::IncomingBleConnectionProcessor
IncomingBleConnectionProcessor;
explicit BLEAcceptedConnectionCallback(
Ptr<IncomingBleConnectionProcessor> incoming_ble_connection_processor)
: incoming_ble_connection_processor_(incoming_ble_connection_processor) {}
void onConnectionAccepted(Ptr<BLESocket> socket,
const string& service_id) override {
incoming_ble_connection_processor_->onIncomingBleConnection(socket,
service_id);
}
private:
ScopedPtr<Ptr<IncomingBleConnectionProcessor> >
incoming_ble_connection_processor_;
};
#endif
template <typename Platform>
bool MediumManager<Platform>::isListeningForIncomingBleConnections(
const string& service_id) {
Synchronized s(ble_lock_.get());
#if BLE_V2_IMPLEMENTED
return mediums_->bleV2()->isAcceptingConnections();
#else
return mediums_->ble()->isAcceptingConnections();
#endif
}
template <typename Platform>
bool MediumManager<Platform>::startListeningForIncomingBleConnections(
const string& service_id,
Ptr<IncomingBleConnectionProcessor> incoming_ble_connection_processor) {
Synchronized s(ble_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->startAcceptingConnections(
service_id,
MakePtr(new BLEAcceptedConnectionCallback<Platform>()));
#else
mediums_->ble()->startAcceptingConnections(
service_id, MakePtr(new BLEAcceptedConnectionCallback<Platform>(
incoming_ble_connection_processor)));
#endif
}
template <typename Platform>
void MediumManager<Platform>::stopListeningForIncomingBleConnections(
const string& service_id) {
Synchronized s(ble_lock_.get());
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->stopAcceptingConnections();
#else
mediums_->ble()->stopAcceptingConnections();
#endif
}
template <typename Platform>
class DiscoveredPeripheralCallback : public DISCOVERED_PERIPHERAL_CALLBACK {
public:
typedef typename MediumManager<Platform>::FoundBlePeripheralProcessor
FoundBlePeripheralProcessor;
explicit DiscoveredPeripheralCallback(
Ptr<FoundBlePeripheralProcessor> found_ble_peripheral_processor)
: found_ble_peripheral_processor_(found_ble_peripheral_processor) {}
void onPeripheralDiscovered(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id,
#if BLE_V2_IMPLEMENTED
ConstPtr<ByteArray> advertisement_data,
// TODO(ahlee): Add is_fast_advertisement to
// FoundBlePeripheralProcessor.
bool is_fast_advertisement) override {
#else
ConstPtr<ByteArray> advertisement_data) {
#endif
found_ble_peripheral_processor_->onFoundBlePeripheral(
ble_peripheral, service_id, advertisement_data);
}
void onPeripheralLost(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id) override {
found_ble_peripheral_processor_->onLostBlePeripheral(ble_peripheral,
service_id);
}
private:
ScopedPtr<Ptr<FoundBlePeripheralProcessor> > found_ble_peripheral_processor_;
};
// TODO(ahlee): Add fast_advertisement_service_uuid and power_level to
// DiscoveryOptions and pass it through.
template <typename Platform>
bool MediumManager<Platform>::startBleScanning(
const string& service_id,
Ptr<FoundBlePeripheralProcessor> found_ble_peripheral_processor) {
Synchronized s(ble_lock_.get());
return mediums_->bluetoothRadio()->enable() &&
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->startScanning(
service_id,
MakePtr(new DiscoveredPeripheralCallback<Platform>(
found_ble_peripheral_processor)),
BLEMediumV2::PowerMode::HIGH,
/* fast_advertisement_service_uuid= */ "");
#else
mediums_->ble()->startScanning(
service_id, MakePtr(new DiscoveredPeripheralCallback<Platform>(
found_ble_peripheral_processor)));
#endif
}
template <typename Platform>
void MediumManager<Platform>::stopBleScanning(const string& service_id) {
Synchronized s(ble_lock_.get());
#if BLE_V2_IMPLEMENTED
mediums_->bleV2()->stopScanning();
#else
mediums_->ble()->stopScanning();
#endif
}
template <typename Platform>
Ptr<BLESocket> MediumManager<Platform>::connectToBlePeripheral(
Ptr<BLE_PERIPHERAL> ble_peripheral, const string& service_id) {
Synchronized s(ble_lock_.get());
if (!mediums_->bluetoothRadio()->enable()) {
return Ptr<BLESocket>();
}
#if BLE_V2_IMPLEMENTED
// TODO(ahlee): Replace when connecting logic is implemented.
return Ptr<BLESocket>();
#else
return mediums_->ble()->connect(ble_peripheral, service_id);
#endif
}
} // namespace connections
} // namespace nearby
} // namespace location
+140
View File
@@ -0,0 +1,140 @@
#ifndef CORE_INTERNAL_MEDIUM_MANAGER_H_
#define CORE_INTERNAL_MEDIUM_MANAGER_H_
#include "core/internal/ble_compat.h"
#include "core/internal/mediums/mediums.h"
#include "platform/api/bluetooth_classic.h"
#include "platform/api/lock.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
/**
* Manages everything related to the mediums used by Nearby Connections, acting
* as a simplifying layer around the different APIs used for said management.
*
* <p>An overview of thread safety:
*
* <ul>
* <li>Methods are synchronized at a per-medium level. For example, all
* Bluetooth Classic calls are synchronized under the same
* 'bluetooth_classic_lock_'. This ensures work on a particular medium is
* well-ordered without blocking other mediums from running. Nearby
* Mediums as a whole is already threadsafe, which is why we don't need to
* synchronize at a per-radio level.
* <li>All calls are guarded by the flag 'mediums_are_available_', which
* defaults to true and is set to false in shutdown(). This flag ensures
* that no further work is done after shutdown() has been called.
* Note: shutdown() is the one and only time we grab every
* medium-specific lock, to ensure everything stops at once.
* </ul>
*
* <p>Note: For methods that start an action (eg. startAdvertising()), the radio
* is first enabled. This is a prerequisite before doing any work on a medium;
* they will otherwise fail if the radio is off. Calls that stop an action (eg.
* stopAdvertising()) do not attempt to enable the radio because, if the radio
* was off, there is no work for them to stop.
*/
template <typename Platform>
class MediumManager {
public:
MediumManager();
~MediumManager();
// ~~~~~~~~~~~~~~~~~~~~~~~~ BLUETOOTH ~~~~~~~~~~~~~~~~~~~~~~~~
bool isBluetoothAvailable();
bool turnOnBluetoothDiscoverability(const string& device_name);
void turnOffBluetoothDiscoverability();
class FoundBluetoothDeviceProcessor {
public:
virtual ~FoundBluetoothDeviceProcessor() {}
virtual void onFoundBluetoothDevice(
Ptr<BluetoothDevice> bluetooth_device) = 0;
virtual void onLostBluetoothDevice(
Ptr<BluetoothDevice> bluetooth_device) = 0;
};
bool startScanningForBluetoothDevices(
Ptr<FoundBluetoothDeviceProcessor> found_bluetooth_device_processor);
void stopScanningForBluetoothDevices();
class IncomingBluetoothConnectionProcessor {
public:
virtual ~IncomingBluetoothConnectionProcessor() {}
virtual void onIncomingBluetoothConnection(
Ptr<BluetoothSocket> bluetooth_socket) = 0;
};
bool isListeningForIncomingBluetoothConnections(const string& service_name);
bool startListeningForIncomingBluetoothConnections(
const string& service_name, Ptr<IncomingBluetoothConnectionProcessor>
incoming_bluetooth_connection_processor);
void stopListeningForIncomingBluetoothConnections(const string& service_name);
Ptr<BluetoothSocket> connectToBluetoothDevice(
Ptr<BluetoothDevice> bluetooth_device, const string& service_name);
// ~~~~~~~~~~~~~~~~~~~~~~~~ BLE ~~~~~~~~~~~~~~~~~~~~~~~~
bool isBleAvailable();
bool startBleAdvertising(const string& service_id,
ConstPtr<ByteArray> advertisement_data);
void stopBleAdvertising(const string& service_id);
class IncomingBleConnectionProcessor {
public:
virtual ~IncomingBleConnectionProcessor() {}
virtual void onIncomingBleConnection(Ptr<BLESocket> ble_socket,
const string& service_id) = 0;
};
bool isListeningForIncomingBleConnections(const string& service_id);
bool startListeningForIncomingBleConnections(
const string& service_id,
Ptr<IncomingBleConnectionProcessor> incoming_ble_connection_processor);
void stopListeningForIncomingBleConnections(const string& service_id);
class FoundBlePeripheralProcessor {
public:
virtual ~FoundBlePeripheralProcessor() {}
virtual void onFoundBlePeripheral(
Ptr<BLE_PERIPHERAL> ble_peripheral, const string& service_id,
ConstPtr<ByteArray> advertisement_data) = 0;
virtual void onLostBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id) = 0;
};
bool startBleScanning(
const string& service_id,
Ptr<FoundBlePeripheralProcessor> found_ble_peripheral_processor);
void stopBleScanning(const string& service_id);
Ptr<BLESocket> connectToBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id);
private:
// The destructor for this needs to be manually invoked after the locks below
// are acquired, so it cannot be a ScopedPtr.
Ptr<Mediums<Platform> > mediums_;
ScopedPtr<Ptr<Lock> > bluetooth_classic_lock_;
ScopedPtr<Ptr<Lock> > ble_lock_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/medium_manager.cc"
#endif // CORE_INTERNAL_MEDIUM_MANAGER_H_
+107
View File
@@ -0,0 +1,107 @@
cc_library(
name = "mediums",
srcs = [
"ble_advertisement.cc",
"ble_advertisement_header.cc",
"ble_packet.cc",
"ble_peripheral.cc",
"utils.cc",
"utils.h",
],
hdrs = [
"advertisement_read_result.cc",
"advertisement_read_result.h",
"ble.cc",
"ble.h",
"ble_advertisement.h",
"ble_advertisement_header.h",
"ble_packet.h",
"ble_peripheral.h",
"ble_v2.cc",
"ble_v2.h",
"bloom_filter.cc",
"bloom_filter.h",
"bluetooth_classic.cc",
"bluetooth_classic.h",
"bluetooth_radio.cc",
"bluetooth_radio.h",
"discovered_peripheral_callback.h",
"discovered_peripheral_tracker.cc",
"discovered_peripheral_tracker.h",
"lost_entity_tracker.cc",
"lost_entity_tracker.h",
"mediums.cc",
"mediums.h",
"uuid.cc",
"uuid.h",
],
visibility = ["//core/internal:__pkg__"],
deps = [
"//platform:logging",
"//platform:types",
"//platform:utils",
"//platform/api",
"//platform/port:string",
"//absl/numeric:int128",
"//absl/strings",
"//smhasher:libmurmur3",
],
)
cc_test(
name = "advertisement_read_result_test",
srcs = ["advertisement_read_result_test.cc"],
deps = [
":mediums",
"//platform/impl/default",
"//testing/base/public:gunit_main",
"//absl/time",
],
)
cc_test(
name = "ble_advertisement_header_test",
srcs = ["ble_advertisement_header_test.cc"],
deps = [
":mediums",
"//platform:utils",
"//testing/base/public:gunit_main",
],
)
cc_test(
name = "ble_advertisement_test",
srcs = ["ble_advertisement_test.cc"],
deps = [
":mediums",
"//testing/base/public:gunit_main",
],
)
cc_test(
name = "ble_packet_test",
srcs = ["ble_packet_test.cc"],
deps = [
":mediums",
"//testing/base/public:gunit_main",
],
)
cc_test(
name = "bloom_filter_test",
srcs = ["bloom_filter_test.cc"],
deps = [
":mediums",
"//testing/base/public:gunit_main",
],
)
cc_test(
name = "lost_entity_tracker_test",
srcs = ["lost_entity_tracker_test.cc"],
deps = [
":mediums",
"//platform/impl/default",
"//testing/base/public:gunit_main",
],
)
@@ -0,0 +1,186 @@
#include "core/internal/mediums/advertisement_read_result.h"
#include <algorithm>
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
template <typename K, typename V>
void eraseOwnedPtrFromMap(std::map<K, ConstPtr<V> >& m, const K& k) {
typename std::map<K, ConstPtr<V> >::iterator it = m.find(k);
if (it != m.end()) {
it->second.destroy();
m.erase(it);
}
}
} // namespace
// How much to multiply the backoff duration by with every failure to read
// from the advertisement GATT server. This should never be below 1!
template <typename Platform>
const float AdvertisementReadResult<Platform>::kAdvertisementBackoffMultiplier =
2.0;
// The initial backoff duration when we fail to read from an advertisement
// GATT server.
template <typename Platform>
const std::int64_t
AdvertisementReadResult<Platform>::kAdvertisementBaseBackoffDurationMillis =
1 * 1000; // 1 second
// The maximum backoff duration allowed between advertisement GATT server
// reads.
template <typename Platform>
const std::int64_t
AdvertisementReadResult<Platform>::kAdvertisementMaxBackoffDurationMillis =
5 * 60 * 1000; // 5 minutes
template <typename Platform>
AdvertisementReadResult<Platform>::AdvertisementReadResult()
: lock_(Platform::createLock()),
system_clock_(Platform::createSystemClock()),
advertisements_(),
backoff_duration_millis_(kAdvertisementBaseBackoffDurationMillis),
// We need a long enough duration such that we always trigger a read
// retry AND we always connect to it without delay. The former case
// helps us initialize an AdvertisementReadResult so that we
// unconditionally try reading on the first sighting. And the latter
// case helps us connect immediately when we initialize a dummy read
// result for fast advertisements (which don't use the GATT server).
last_read_timestamp_millis_(system_clock_->elapsedRealtime() -
kAdvertisementMaxBackoffDurationMillis),
result_(Result::Value::UNKNOWN) {}
template <typename Platform>
AdvertisementReadResult<Platform>::~AdvertisementReadResult() {
Synchronized s(lock_.get());
for (AdvertisementMap::iterator it = advertisements_.begin();
it != advertisements_.end(); ++it) {
it->second.destroy();
}
advertisements_.clear();
}
// Adds a successfully read advertisement for the specified slot to this read
// result. This is fundamentally different from
// {@link #recordLastReadStatus(boolean)} because we can report a read
// failure, but still manage to read some advertisements.
// Note: advertisement should be passed in as a RefCounted Ptr. It is not the
// responsibility of AdvertisementReadResult to make it RefCounted.
template <typename Platform>
void AdvertisementReadResult<Platform>::addAdvertisement(
std::int32_t slot, /* RefCounted */ ConstPtr<ByteArray> advertisement) {
Synchronized s(lock_.get());
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
// Blindly remove from the advertisements map to make sure any existing
// key-value pair is destroyed.
eraseOwnedPtrFromMap(advertisements_, slot);
advertisements_.insert(std::make_pair(slot, scoped_advertisement.release()));
}
// Determines whether or not an advertisement was successfully read at the
// specified slot.
template <typename Platform>
bool AdvertisementReadResult<Platform>::hasAdvertisement(std::int32_t slot) {
Synchronized s(lock_.get());
return advertisements_.find(slot) != advertisements_.end();
}
// Retrieves all raw advertisements that were successfully read.
template <typename Platform>
std::set<ConstPtr<ByteArray>>
AdvertisementReadResult<Platform>::getAdvertisements() {
Synchronized s(lock_.get());
std::set<ConstPtr<ByteArray>> all_advertisements;
for (AdvertisementMap::iterator it = advertisements_.begin();
it != advertisements_.end(); ++it) {
all_advertisements.insert(it->second);
}
return all_advertisements;
}
// Determines what stage we're in for retrying a read from an advertisement
// GATT server.
template <typename Platform>
typename AdvertisementReadResult<Platform>::RetryStatus::Value
AdvertisementReadResult<Platform>::evaluateRetryStatus() {
Synchronized s(lock_.get());
// Check if we have already succeeded reading this advertisement.
if (result_ == Result::SUCCESS) {
return RetryStatus::PREVIOUSLY_SUCCEEDED;
}
// Check if we have recently failed to read this advertisement.
if (getDurationSinceReadMillis() < backoff_duration_millis_) {
return RetryStatus::TOO_SOON;
}
return RetryStatus::RETRY;
}
// Records the status of the latest read, and updates the next backoff
// duration for subsequent reads. Be sure to also call
// {@link #addAdvertisement(int, byte[])} if any advertisements were read.
template <typename Platform>
void AdvertisementReadResult<Platform>::recordLastReadStatus(bool is_success) {
Synchronized s(lock_.get());
// Update the last read timestamp.
last_read_timestamp_millis_ = system_clock_->elapsedRealtime();
// Update the backoff duration.
if (is_success) {
// Reset the backoff duration now that we had a successful read.
backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis;
} else {
// Determine whether or not we were already failing before. If we were, we
// should increase the backoff duration.
if (result_ == Result::FAILURE) {
// Use exponential backoff to determine the next backoff duration. This
// simply involves multiplying our current backoff duration by some
// multiplier.
std::int64_t next_backoff_duration =
kAdvertisementBackoffMultiplier * backoff_duration_millis_;
// Update the backoff duration, making sure not to blow past the
// ceiling.
backoff_duration_millis_ = std::min(
next_backoff_duration, kAdvertisementMaxBackoffDurationMillis);
} else {
// This is our first time failing, so we should only backoff for the
// initial duration.
backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis;
}
}
// Update the internal result.
result_ = is_success ? Result::SUCCESS : Result::FAILURE;
}
// Returns how much time has passed since we last tried reading from an
// advertisement GATT server.
template <typename Platform>
std::int64_t AdvertisementReadResult<Platform>::getDurationSinceReadMillis() {
Synchronized s(lock_.get());
return system_clock_->elapsedRealtime() - last_read_timestamp_millis_;
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,73 @@
#ifndef CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
#define CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
#include <cstdint>
#include <map>
#include <set>
#include "platform/api/lock.h"
#include "platform/api/system_clock.h"
#include "platform/byte_array.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Representation of a GATT advertisement read result. This object helps us
// determine whether or not we need to retry GATT reads.
template <typename Platform>
class AdvertisementReadResult {
public:
AdvertisementReadResult();
~AdvertisementReadResult();
struct RetryStatus {
enum Value {
UNKNOWN = 0,
RETRY = 1,
PREVIOUSLY_SUCCEEDED = 2,
TOO_SOON = 3,
};
};
void addAdvertisement(std::int32_t slot, ConstPtr<ByteArray> advertisement);
bool hasAdvertisement(std::int32_t slot);
std::set<ConstPtr<ByteArray>> getAdvertisements();
typename RetryStatus::Value evaluateRetryStatus();
void recordLastReadStatus(bool is_success);
std::int64_t getDurationSinceReadMillis();
private:
struct Result {
enum Value { UNKNOWN = 0, SUCCESS = 1, FAILURE = 2 };
};
static const float kAdvertisementBackoffMultiplier;
static const std::int64_t kAdvertisementBaseBackoffDurationMillis;
static const std::int64_t kAdvertisementMaxBackoffDurationMillis;
// ------------ GENERAL ------------
ScopedPtr<Ptr<Lock>> lock_;
ScopedPtr<Ptr<SystemClock>> system_clock_;
// ------ ADVERTISEMENTREADRESULT STATE ------
// Maps slot numbers to the GATT advertisement found in that slot.
typedef std::map<std::int32_t, /* RefCounted */ ConstPtr<ByteArray>>
AdvertisementMap;
AdvertisementMap advertisements_;
std::int64_t backoff_duration_millis_;
std::int64_t last_read_timestamp_millis_;
typename Result::Value result_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/advertisement_read_result.cc"
#endif // CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
@@ -0,0 +1,148 @@
#include "core/internal/mediums/advertisement_read_result.h"
#include "platform/impl/default/default_platform.h"
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
class SampleSystemClock : public SystemClock {
public:
SampleSystemClock() {}
~SampleSystemClock() override {}
std::int64_t elapsedRealtime() override {
return absl::ToUnixMillis(absl::Now());
}
};
class SamplePlatform {
public:
static Ptr<Lock> createLock() { return DefaultPlatform::createLock(); }
static Ptr<SystemClock> createSystemClock() {
return MakePtr(new SampleSystemClock());
}
};
// We keep a copy of these constants because this is an old-school test (so we
// can't delare it as a friend class of AdvertisementReadResult).
const absl::Duration kAdvertisementBaseBackoffDuration =
absl::Milliseconds(1000); // 1 second
const absl::Duration kAdvertisementMaxBackoffDuration =
absl::Milliseconds(6000); // 6 seconds
const char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C};
TEST(AdvertisementReadResultTest, AdvertisementExists) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
std::int32_t slot = 6;
advertisement_read_result.addAdvertisement(
slot,
MakeConstPtr(new ByteArray(kAdvertisementBytes,
sizeof(kAdvertisementBytes) / sizeof(char))));
ASSERT_TRUE(advertisement_read_result.hasAdvertisement(slot));
}
TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
std::int32_t slot = 6;
ASSERT_FALSE(advertisement_read_result.hasAdvertisement(slot));
}
TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
}
TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<
SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED);
}
TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
// Sleep for some time, but not long enough to warrant a retry.
absl::SleepFor(absl::Milliseconds(
absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2));
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<SamplePlatform>::RetryStatus::TOO_SOON);
}
TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
// Sleep long enough to warrant a retry.
absl::SleepFor(kAdvertisementBaseBackoffDuration);
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
}
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
// Record an additional failure so our backoff duration increases.
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
// Sleep for the backoff duration. We shouldn't trigger a retry because the
// backoff should have increased from failing a second time.
absl::SleepFor(kAdvertisementBaseBackoffDuration);
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<SamplePlatform>::RetryStatus::TOO_SOON);
}
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
// Record an absurd amount of failures so we hit the maximum backoff duration.
for (std::int32_t i = 0; i < 1000; i++) {
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
}
// Sleep for the maximum backoff duration. This should be enough to warrant a
// retry.
absl::SleepFor(kAdvertisementMaxBackoffDuration);
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
AdvertisementReadResult<SamplePlatform>::RetryStatus::RETRY);
}
TEST(AdvertisementReadResultTest, GetDurationSinceRead) {
AdvertisementReadResult<SamplePlatform> advertisement_read_result;
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
std::int64_t sleepTime = 420;
absl::SleepFor(absl::Milliseconds(sleepTime));
ASSERT_GE(advertisement_read_result.getDurationSinceReadMillis(), sleepTime);
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+281
View File
@@ -0,0 +1,281 @@
#include "core/internal/mediums/ble.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
const std::int32_t BLE<Platform>::kMaxAdvertisementLength = 512;
template <typename Platform>
BLE<Platform>::BLE(Ptr<BluetoothRadio<Platform>> bluetooth_radio)
: lock_(Platform::createLock()),
bluetooth_radio_(bluetooth_radio),
bluetooth_adapter_(Platform::createBluetoothAdapter()),
ble_medium_(Platform::createBLEMedium()),
scanning_info_(),
advertising_info_(),
accepting_connections_info_() {}
template <typename Platform>
BLE<Platform>::~BLE() {
stopAdvertising();
stopAcceptingConnections();
stopScanning();
}
template <typename Platform>
bool BLE<Platform>::isAvailable() {
Synchronized s(lock_.get());
return !ble_medium_.isNull() && !bluetooth_adapter_.isNull();
}
// TODO(ahlee): Add fastPairData for phase 2 of C++ implementation.
template <typename Platform>
bool BLE<Platform>::startAdvertising(const string& service_id,
ConstPtr<ByteArray> advertisement) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
if (scoped_advertisement.isNull() || service_id.empty()) {
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising
// because a null parameter was passed in.");
return false;
}
if (scoped_advertisement->size() > kMaxAdvertisementLength) {
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising
// because the advertisement was too long. Expected at most %d bytes but
// received %d.", kMaxAdvertisementLength, advertisement->size());
return false;
}
if (isAdvertising()) {
// TODO(ahlee): logger.atSevere().log("Failed to BLE advertise because we're
// already advertising.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because
// Bluetooth isn't enabled.");
return false;
}
if (!isAvailable()) {
// TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because
// BLE isn't enabled.");
return false;
}
if (!ble_medium_->startAdvertising(service_id,
scoped_advertisement.release())) {
// TODO(ahlee) logger.atSevere().log("Failed to start BLE advertising");
return false;
}
advertising_info_ = MakePtr(new AdvertisingInfo(service_id));
return true;
}
template <typename Platform>
void BLE<Platform>::stopAdvertising() {
Synchronized s(lock_.get());
if (!isAdvertising()) {
// TODO(ahlee): logger.atDebug().log("Can't turn off BLE advertising because
// it never started.");
return;
}
ble_medium_->stopAdvertising(advertising_info_->service_id);
// Reset our bundle of advertising state to mark that we're no longer
// advertising.
advertising_info_.destroy();
// TODO(ahlee): logger.atVerbose().log("Turned BLE advertising off");
}
template <typename Platform>
bool BLE<Platform>::isAdvertising() {
Synchronized s(lock_.get());
return !advertising_info_.isNull();
}
template <typename Platform>
bool BLE<Platform>::startScanning(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<Ptr<DiscoveredPeripheralCallback>>
scoped_discovered_peripheral_callback(discovered_peripheral_callback);
if (scoped_discovered_peripheral_callback.isNull() || service_id.empty()) {
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning
// because a null parameter was passed in.");
return false;
}
if (isScanning()) {
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning
// because we are already scanning.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because
// Bluetooth was never turned on");
return false;
}
if (!isAvailable()) {
// TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because
// BLE isn't available.");
return false;
}
// Avoid leaks.
ScopedPtr<Ptr<BLEDiscoveredPeripheralCallback>>
scoped_ble_discovered_peripheral_callback(
new BLEDiscoveredPeripheralCallback(
scoped_discovered_peripheral_callback.release()));
if (!ble_medium_->startScanning(
service_id, scoped_ble_discovered_peripheral_callback.get())) {
// TODO(ahlee): logger.atSevere().log("Failed to start BLE scanning.");
return false;
}
scanning_info_ = MakePtr(new ScanningInfo(
service_id, scoped_ble_discovered_peripheral_callback.release()));
return true;
}
template <typename Platform>
void BLE<Platform>::stopScanning() {
Synchronized s(lock_.get());
if (!isScanning()) {
// TODO(ahlee): logger.atDebug().log("Can't turn off BLE scanning because we
// never started scanning.");
return;
}
ble_medium_->stopScanning(scanning_info_->service_id);
// Reset our bundle of scanning state to mark that we're no longer scanning.
scanning_info_.destroy();
}
template <typename Platform>
bool BLE<Platform>::isScanning() {
Synchronized s(lock_.get());
return !scanning_info_.isNull();
}
template <typename Platform>
bool BLE<Platform>::startAcceptingConnections(
const string& service_id,
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<Ptr<AcceptedConnectionCallback>>
scoped_accepted_connection_callback(accepted_connection_callback);
if (scoped_accepted_connection_callback.isNull() || service_id.empty()) {
// TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE
// connections because a null parameter was passed in.");
return false;
}
if (isAcceptingConnections()) {
// TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE
// connections for %s because another BLE server socket is already
// in-progress.", service_id);
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections
// for %s because Bluetooth isn't enabled.", serviceId);
return false;
}
if (!isAvailable()) {
// TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections
// for %s because BLE isn't available.", serviceId);
return false;
}
// Avoid leaks.
ScopedPtr<Ptr<BLEAcceptedConnectionCallback>>
scoped_ble_accepted_connection_callback(new BLEAcceptedConnectionCallback(
scoped_accepted_connection_callback.release()));
if (!ble_medium_->startAcceptingConnections(
service_id, scoped_ble_accepted_connection_callback.get())) {
return false;
}
accepting_connections_info_ = MakePtr(new AcceptingConnectionsInfo(
service_id, scoped_ble_accepted_connection_callback.release()));
return true;
}
template <typename Platform>
void BLE<Platform>::stopAcceptingConnections() {
Synchronized s(lock_.get());
if (!isAcceptingConnections()) {
// TODO(ahlee): logger.atDebug().log("Can't stop accepting BLE connections
// because it was never started.");
return;
}
ble_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_.destroy();
}
template <typename Platform>
bool BLE<Platform>::isAcceptingConnections() {
Synchronized s(lock_.get());
return !accepting_connections_info_.isNull();
}
template <typename Platform>
Ptr<BLESocket> BLE<Platform>::connect(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id) {
Synchronized s(lock_.get());
if (ble_peripheral.isNull() || service_id.empty()) {
// TODO(ahlee): logger.atSevere().log("Refusing to create client BLE socket
// because at least one of blePeripheral or serviceId is null.");
return Ptr<BLESocket>();
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s
// because Bluetooth isn't enabled.", blePeripheral);
return Ptr<BLESocket>();
}
if (!isAvailable()) {
// TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s
// because BLE isn't available.", blePeripheral);
return Ptr<BLESocket>();
}
return ble_medium_->connect(ble_peripheral, service_id);
}
} // namespace connections
} // namespace nearby
} // namespace location
+197
View File
@@ -0,0 +1,197 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_H_
#define CORE_INTERNAL_MEDIUMS_BLE_H_
#include <cstdint>
#include "core/internal/mediums/bluetooth_radio.h"
#include "platform/api/ble.h"
#include "platform/api/bluetooth_adapter.h"
#include "platform/api/lock.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class BLE {
public:
explicit BLE(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
~BLE();
bool isAvailable();
bool startAdvertising(const string& service_id,
ConstPtr<ByteArray> advertisement);
void stopAdvertising();
class DiscoveredPeripheralCallback {
public:
virtual ~DiscoveredPeripheralCallback() {}
virtual void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id,
ConstPtr<ByteArray> advertisement) = 0;
virtual void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id) = 0;
};
bool startScanning(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback);
void stopScanning();
class AcceptedConnectionCallback {
public:
virtual ~AcceptedConnectionCallback() {}
virtual void onConnectionAccepted(Ptr<BLESocket> socket,
const string& service_id) = 0;
};
bool startAcceptingConnections(
const string& service_id,
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
void stopAcceptingConnections();
bool isAcceptingConnections();
Ptr<BLESocket> connect(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id);
private:
// TODO(ahlee): Rename to DiscoveredPeripheralCallbackBridge
class BLEDiscoveredPeripheralCallback
: public BLEMedium::DiscoveredPeripheralCallback {
public:
explicit BLEDiscoveredPeripheralCallback(
Ptr<BLE::DiscoveredPeripheralCallback> discovered_peripheral_callback)
: discovered_peripheral_callback_(discovered_peripheral_callback) {}
~BLEDiscoveredPeripheralCallback() override {
// Nothing to do.
}
void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id,
ConstPtr<ByteArray> advertisement) override {
discovered_peripheral_callback_->onPeripheralDiscovered(
ble_peripheral, service_id, advertisement);
}
void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id) override {
discovered_peripheral_callback_->onPeripheralLost(ble_peripheral,
service_id);
}
private:
ScopedPtr<Ptr<BLE::DiscoveredPeripheralCallback>>
discovered_peripheral_callback_;
};
// TODO(ahlee): Rename to AcceptedConnectionCallbackBridge
class BLEAcceptedConnectionCallback
: public BLEMedium::AcceptedConnectionCallback {
public:
explicit BLEAcceptedConnectionCallback(
Ptr<BLE::AcceptedConnectionCallback> accepted_connection_callback)
: accepted_connection_callback_(accepted_connection_callback) {}
~BLEAcceptedConnectionCallback() override {
// Nothing to do.
}
void onConnectionAccepted(Ptr<BLESocket> ble_socket,
const string& service_id) override {
accepted_connection_callback_->onConnectionAccepted(ble_socket,
service_id);
}
private:
ScopedPtr<Ptr<BLE::AcceptedConnectionCallback>>
accepted_connection_callback_;
};
struct ScanningInfo {
ScanningInfo(
const string& service_id,
Ptr<BLEDiscoveredPeripheralCallback> ble_discovered_peripheral_callback)
: service_id(service_id),
ble_discovered_peripheral_callback(
ble_discovered_peripheral_callback) {}
~ScanningInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
const string service_id;
ScopedPtr<Ptr<BLEDiscoveredPeripheralCallback>>
ble_discovered_peripheral_callback;
};
struct AdvertisingInfo {
explicit AdvertisingInfo(const string& service_id)
: service_id(service_id) {}
~AdvertisingInfo() {}
const string service_id;
};
struct AcceptingConnectionsInfo {
AcceptingConnectionsInfo(
const string& service_id,
Ptr<BLEAcceptedConnectionCallback> ble_accepted_connection_callback)
: service_id(service_id),
ble_accepted_connection_callback(ble_accepted_connection_callback) {}
~AcceptingConnectionsInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
const string service_id;
ScopedPtr<Ptr<BLEAcceptedConnectionCallback>>
ble_accepted_connection_callback;
};
static const std::int32_t kMaxAdvertisementLength;
bool isAdvertising();
bool isScanning();
// ------------ GENERAL ------------
ScopedPtr<Ptr<Lock>> lock_;
// ------------ CORE BLE ------------
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
// The underlying, per-platform implementation.
ScopedPtr<Ptr<BLEMedium>> ble_medium_;
// ------------ DISCOVERY ------------
// A bundle of state required to start/stop BLE scanning. When non-null,
// we are currently performing a BLE scan.
// In the Java code this maps to the bleListener and
// bleScanningMediumOperation.
Ptr<ScanningInfo> scanning_info_;
// ------------ ADVERTISING ------------
// A bundle of state required to start/stop BLE advertising. When non-null,
// we are currently advertising over BLE.
// In the Java code this maps to bleAdvertiser, advertiseCallback, and
// bleAdvertisingMediumOperation.
Ptr<AdvertisingInfo> advertising_info_;
// A bundle of state required to start/stop accepting BLE connections. When
// non-null, we are currently accepting BLE connections.
// In the Java code this maps to the bleServerSocket.
Ptr<AcceptingConnectionsInfo> accepting_connections_info_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/ble.cc"
#endif // CORE_INTERNAL_MEDIUMS_BLE_H_
@@ -0,0 +1,288 @@
#include "core/internal/mediums/ble_advertisement.h"
#include "platform/logging.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
const std::uint32_t BLEAdvertisement::kServiceIdHashLength = 3;
const std::uint32_t BLEAdvertisement::kVersionLength = 1;
// Length of one int. Be sure to re-evaluate how we compute data size in this
// class if this constant ever changes!
const std::uint32_t BLEAdvertisement::kDataSizeLength = 4;
const std::uint32_t BLEAdvertisement::kMinAdvertisementLength =
kVersionLength + kServiceIdHashLength + kDataSizeLength;
// The maximum length for a GATT characteristic value is 512 bytes, so make sure
// the entire advertisement is less than that. The data can take up whatever
// space is remaining after the bytes preceding it.
const std::uint32_t BLEAdvertisement::kMaxDataSize =
512 - kMinAdvertisementLength;
const std::uint16_t BLEAdvertisement::kVersionBitmask = 0x0E0;
const std::uint16_t BLEAdvertisement::kSocketVersionBitmask = 0x01C;
ConstPtr<BLEAdvertisement> BLEAdvertisement::fromBytes(
ConstPtr<ByteArray> ble_advertisement_bytes) {
if (ble_advertisement_bytes.isNull()) {
NEARBY_LOG(INFO,
"Cannot deserialize BLEAdvertisement: null bytes passed in");
return ConstPtr<BLEAdvertisement>();
}
if (ble_advertisement_bytes->size() < kMinAdvertisementLength) {
NEARBY_LOG(INFO,
"Cannot deserialize BLEAdvertisement: expecting min %u raw "
"bytes, got %zu",
kMinAdvertisementLength, ble_advertisement_bytes->size());
return ConstPtr<BLEAdvertisement>();
}
// Now, time to read the bytes!
const char *ble_advertisement_bytes_read_ptr =
ble_advertisement_bytes->getData();
// 1. Version.
Version::Value version = parseVersionFromByte(
static_cast<std::uint16_t>(*ble_advertisement_bytes_read_ptr));
if (!isSupportedVersion(version)) {
NEARBY_LOG(INFO,
"Cannot deserialize BLEAdvertisement: unsupported Version %u",
version);
return ConstPtr<BLEAdvertisement>();
}
// 2. Socket Version.
SocketVersion::Value socket_version = parseSocketVersionFromByte(
static_cast<std::uint16_t>(*ble_advertisement_bytes_read_ptr));
if (!isSupportedSocketVersion(socket_version)) {
NEARBY_LOG(
INFO,
"Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u",
socket_version);
return ConstPtr<BLEAdvertisement>();
}
ble_advertisement_bytes_read_ptr += kVersionLength;
// 3. Service ID hash.
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength)));
ble_advertisement_bytes_read_ptr += kServiceIdHashLength;
// 4.1. Data size.
size_t expected_data_size =
deserializeDataSize(ble_advertisement_bytes_read_ptr);
if (expected_data_size < 0) {
NEARBY_LOG(INFO,
"Cannot deserialize BLEAdvertisement: negative data size %zu",
expected_data_size);
return ConstPtr<BLEAdvertisement>();
}
ble_advertisement_bytes_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 %zu bytes",
expected_data_size, actual_data_size);
return ConstPtr<BLEAdvertisement>();
}
// 4.2. Data.
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
new ByteArray(ble_advertisement_bytes_read_ptr, expected_data_size)));
ble_advertisement_bytes_read_ptr += expected_data_size;
return MakeRefCountedConstPtr(new BLEAdvertisement(
version, socket_version, scoped_service_id_hash.release(),
scoped_data.release()));
}
ConstPtr<ByteArray> BLEAdvertisement::toBytes(
Version::Value version, SocketVersion::Value socket_version,
ConstPtr<ByteArray> service_id_hash, ConstPtr<ByteArray> data) {
// Check that the given input is valid.
if (!isSupportedVersion(version)) {
NEARBY_LOG(INFO,
"Cannot serialize BLEAdvertisement: unsupported Version %u",
version);
return ConstPtr<ByteArray>();
}
if (!isSupportedSocketVersion(socket_version)) {
NEARBY_LOG(
INFO, "Cannot serialize BLEAdvertisement: unsupported SocketVersion %u",
socket_version);
return ConstPtr<ByteArray>();
}
if (service_id_hash->size() != kServiceIdHashLength) {
NEARBY_LOG(INFO,
"Cannot serialize BLEAdvertisement: expected a service_id_hash "
"of %u bytes, but got %zu",
kServiceIdHashLength, service_id_hash->size());
return ConstPtr<ByteArray>();
}
if (data->size() > kMaxDataSize) {
NEARBY_LOG(INFO,
"Cannot serialize BLEAdvertisement: expected data of at most %u "
"bytes, but got %zu",
kMaxDataSize, data->size());
return ConstPtr<ByteArray>();
}
// Initialize the bytes.
size_t advertisement_length = computeAdvertisementLength(data);
Ptr<ByteArray> advertisement_bytes{new ByteArray{advertisement_length}};
char *advertisement_bytes_write_ptr = advertisement_bytes->getData();
// 1. Version.
serializeVersionByte(advertisement_bytes_write_ptr, version);
// 2. SocketVersion.
serializeSocketVersionByte(advertisement_bytes_write_ptr, socket_version);
advertisement_bytes_write_ptr += kVersionLength;
// 3. Service ID hash.
memcpy(advertisement_bytes_write_ptr, service_id_hash->getData(),
kServiceIdHashLength);
advertisement_bytes_write_ptr += kServiceIdHashLength;
// 4.1. Data length.
serializeDataSize(advertisement_bytes_write_ptr, data->size());
advertisement_bytes_write_ptr += kDataSizeLength;
// 4.2. Data.
memcpy(advertisement_bytes_write_ptr, data->getData(), data->size());
advertisement_bytes_write_ptr += data->size();
return ConstifyPtr(advertisement_bytes);
}
bool BLEAdvertisement::isSupportedVersion(Version::Value version) {
return version >= Version::V1 && version <= Version::V2;
}
bool BLEAdvertisement::isSupportedSocketVersion(
SocketVersion::Value socket_version) {
return socket_version >= SocketVersion::V1 &&
socket_version <= SocketVersion::V2;
}
BLEAdvertisement::Version::Value BLEAdvertisement::parseVersionFromByte(
std::uint16_t byte) {
return static_cast<BLEAdvertisement::Version::Value>(
(byte & kVersionBitmask) >> 5);
}
BLEAdvertisement::SocketVersion::Value
BLEAdvertisement::parseSocketVersionFromByte(std::uint16_t byte) {
return static_cast<SocketVersion::Value>((byte & kSocketVersionBitmask) >> 2);
}
size_t BLEAdvertisement::deserializeDataSize(
const char *data_size_bytes_read_ptr) {
// 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(
ConstPtr<ByteArray> ble_advertisement_bytes) {
return ble_advertisement_bytes->size() - kMinAdvertisementLength;
}
size_t BLEAdvertisement::computeAdvertisementLength(ConstPtr<ByteArray> data) {
// The advertisement length is the minimum length + the length of the data.
return kMinAdvertisementLength + data->size();
}
void BLEAdvertisement::serializeVersionByte(char *version_byte_write_ptr,
Version::Value version) {
*version_byte_write_ptr |=
static_cast<char>((version << 5) & kVersionBitmask);
}
void BLEAdvertisement::serializeSocketVersionByte(
char *socket_version_byte_write_ptr, SocketVersion::Value socket_version) {
*socket_version_byte_write_ptr |=
static_cast<char>((socket_version << 2) & kSocketVersionBitmask);
}
void BLEAdvertisement::serializeDataSize(char *data_size_bytes_write_ptr,
size_t data_size) {
// Get a raw representation of the data size bytes in memory.
char *data_size_bytes = reinterpret_cast<char *>(&data_size);
// Append these raw bytes to advertisement bytes, keeping in mind that we need
// to convert from Little Endian to Big Endian in the process.
for (int i = 0; i < kDataSizeLength; ++i) {
data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1];
}
}
BLEAdvertisement::BLEAdvertisement(Version::Value version,
SocketVersion::Value socket_version,
ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data)
: version_(version),
socket_version_(socket_version),
service_id_hash_(service_id_hash),
data_(data) {}
BLEAdvertisement::~BLEAdvertisement() {
// Nothing to do.
}
BLEAdvertisement::Version::Value BLEAdvertisement::getVersion() const {
return version_;
}
BLEAdvertisement::SocketVersion::Value BLEAdvertisement::getSocketVersion()
const {
return socket_version_;
}
ConstPtr<ByteArray> BLEAdvertisement::getServiceIdHash() const {
return service_id_hash_.get();
}
ConstPtr<ByteArray> BLEAdvertisement::getData() const { return data_.get(); }
bool BLEAdvertisement::operator==(const BLEAdvertisement &rhs) const {
return this->getVersion() == rhs.getVersion() &&
this->getSocketVersion() == rhs.getSocketVersion() &&
*(this->getServiceIdHash()) == *(rhs.getServiceIdHash()) &&
*(this->getData()) == *(rhs.getData());
}
bool BLEAdvertisement::operator<(const BLEAdvertisement &rhs) const {
if (this->getVersion() != rhs.getVersion()) {
return this->getVersion() < rhs.getVersion();
}
if (this->getSocketVersion() != rhs.getSocketVersion()) {
return this->getSocketVersion() < rhs.getSocketVersion();
}
if (*(this->getServiceIdHash()) != *(rhs.getServiceIdHash())) {
return *(this->getServiceIdHash()) < *(rhs.getServiceIdHash());
}
return *(this->getData()) < *(rhs.getData());
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,100 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
#include "platform/byte_array.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Represents the format of the Mediums BLE Advertisement used in advertising
// and discovery.
//
// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA]
//
// See go/nearby-ble-design for more information.
class BLEAdvertisement {
public:
// Versions of the BLEAdvertisement.
struct Version {
enum Value {
UNKNOWN = 0,
V1 = 1,
V2 = 2,
// Version is only allocated 3 bits in the BLEAdvertisement, so this can
// never go beyond V7.
};
};
// Versions of the BLESocket.
struct SocketVersion {
enum Value {
UNKNOWN = 0,
V1 = 1,
V2 = 2,
// SocketVersion is only allocated 3 bits in the BLEAdvertisement, so this
// can never go beyond V7.
};
};
static ConstPtr<BLEAdvertisement> fromBytes(
ConstPtr<ByteArray> ble_advertisement_bytes);
static ConstPtr<ByteArray> toBytes(Version::Value version,
SocketVersion::Value socket_version,
ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data);
static const std::uint32_t kServiceIdHashLength;
~BLEAdvertisement();
Version::Value getVersion() const;
SocketVersion::Value getSocketVersion() const;
ConstPtr<ByteArray> getServiceIdHash() const;
ConstPtr<ByteArray> getData() const;
// Operator overloads when comparing ConstPtr<BLEAdvertisement>.
bool operator==(const BLEAdvertisement &rhs) const;
bool operator<(const BLEAdvertisement &rhs) const;
private:
static bool isSupportedVersion(Version::Value version);
static bool isSupportedSocketVersion(SocketVersion::Value socket_version);
static Version::Value parseVersionFromByte(std::uint16_t byte);
static SocketVersion::Value parseSocketVersionFromByte(std::uint16_t byte);
static size_t deserializeDataSize(const char *data_size_bytes_read_ptr);
static size_t computeDataSize(ConstPtr<ByteArray> ble_advertisement_bytes);
static size_t computeAdvertisementLength(ConstPtr<ByteArray> data);
static void serializeVersionByte(char *version_byte_write_ptr,
Version::Value version);
static void serializeSocketVersionByte(char *socket_version_byte_write_ptr,
SocketVersion::Value socket_version);
static void serializeDataSize(char *data_size_bytes_write_ptr,
size_t data_size);
static const std::uint32_t kVersionLength;
static const std::uint32_t kDataSizeLength;
static const std::uint32_t kMinAdvertisementLength;
static const std::uint32_t kMaxDataSize;
static const std::uint16_t kVersionBitmask;
static const std::uint16_t kSocketVersionBitmask;
BLEAdvertisement(Version::Value version, SocketVersion::Value socket_version,
ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data);
const Version::Value version_;
const SocketVersion::Value socket_version_;
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
ScopedPtr<ConstPtr<ByteArray> > data_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
@@ -0,0 +1,208 @@
#include "core/internal/mediums/ble_advertisement_header.h"
#include "platform/base64_utils.h"
#include "platform/byte_array.h"
#include "platform/logging.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// The following IfThisThenThat is for BloomFilter length in
// ble_v2.createAdvertisementHeader
// LINT.IfChange
const std::uint32_t BLEAdvertisementHeader::kServiceIdBloomFilterLength = 10;
// LINT.ThenChange(//depot/google3/core/internal/\
// mediums/ble_v2.h)
const std::uint32_t BLEAdvertisementHeader::kAdvertisementHashLength = 4;
const std::uint32_t BLEAdvertisementHeader::kVersionAndNumSlotsLength = 1;
const std::uint32_t BLEAdvertisementHeader::kMinAdvertisementHeaderLength =
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
kAdvertisementHashLength;
const std::uint16_t BLEAdvertisementHeader::kVersionBitmask = 0x0E0;
const std::uint16_t BLEAdvertisementHeader::kNumSlotsBitmask = 0x01F;
ConstPtr<BLEAdvertisementHeader> BLEAdvertisementHeader::fromString(
const std::string &ble_advertisement_header_string) {
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
Base64Utils::decode(ble_advertisement_header_string));
if (scoped_ble_advertisement_header_bytes.isNull()) {
NEARBY_LOG(
INFO,
"Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding");
return ConstPtr<BLEAdvertisementHeader>();
}
if (scoped_ble_advertisement_header_bytes->size() <
kMinAdvertisementHeaderLength) {
NEARBY_LOG(INFO,
"Cannot deserialize BLEAdvertisementHeader: expecting min %u "
"raw bytes, got %zu instead",
kMinAdvertisementHeaderLength,
scoped_ble_advertisement_header_bytes->size());
return ConstPtr<BLEAdvertisementHeader>();
}
// Now, time to read the bytes!
const char *ble_advertisement_header_read_ptr =
scoped_ble_advertisement_header_bytes->getData();
// 1. Version.
// The first 3 bits of the first byte represent the version.
Version::Value version = parseVersionFromByte(
static_cast<std::uint16_t>(*ble_advertisement_header_read_ptr));
if (version != Version::V2) {
NEARBY_LOG(
INFO,
"Cannot deserialize BLEAdvertisementHeader, unsupported version %u",
version);
return ConstPtr<BLEAdvertisementHeader>();
}
// 2. Number of slots.
// The last 5 bits of the first byte represent the number of slots.
std::uint32_t num_slots = parseNumSlotsFromByte(
static_cast<std::uint16_t>(*ble_advertisement_header_read_ptr));
ble_advertisement_header_read_ptr += kVersionAndNumSlotsLength;
// 3. Service ID bloom filter.
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(
MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr,
kServiceIdBloomFilterLength)));
ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength;
// 4. Advertisement hash.
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr,
kAdvertisementHashLength)));
ble_advertisement_header_read_ptr += kAdvertisementHashLength;
return MakeRefCountedConstPtr(new BLEAdvertisementHeader(
version, num_slots, scoped_service_id_bloom_filter.release(),
scoped_advertisement_hash.release()));
}
std::string BLEAdvertisementHeader::asString(
Version::Value version, std::uint32_t num_slots,
ConstPtr<ByteArray> service_id_bloom_filter,
ConstPtr<ByteArray> advertisement_hash) {
// Check that the given input is valid.
if (version != Version::V2) {
NEARBY_LOG(
INFO, "Cannot serialize BLEAdvertisementHeader: unsupported Version %u",
version);
return "";
}
if (service_id_bloom_filter->size() != kServiceIdBloomFilterLength) {
NEARBY_LOG(INFO,
"Cannot serialize BLEAdvertisementHeader: expected "
"service_id_bloom_filter of %u bytes, but got %zu",
kServiceIdBloomFilterLength, service_id_bloom_filter->size());
return "";
}
if (advertisement_hash->size() != kAdvertisementHashLength) {
NEARBY_LOG(INFO,
"Cannot serialize BLEAdvertisementHeader: expected "
"advertisement_hash of %u bytes, but got %zu",
kAdvertisementHashLength, advertisement_hash->size());
return "";
}
// Initialize the bytes.
ByteArray advertisement_header_bytes{kMinAdvertisementHeaderLength};
char *advertisement_header_bytes_write_ptr =
advertisement_header_bytes.getData();
// 1. Version.
serializeVersionByte(advertisement_header_bytes_write_ptr, version);
// 2. Number of slots.
serializeNumSlots(advertisement_header_bytes_write_ptr, num_slots);
advertisement_header_bytes_write_ptr += kVersionAndNumSlotsLength;
// 3. Service ID bloom filter.
memcpy(advertisement_header_bytes_write_ptr,
service_id_bloom_filter->getData(), kServiceIdBloomFilterLength);
advertisement_header_bytes_write_ptr += kServiceIdBloomFilterLength;
// 4. Advertisement hash.
memcpy(advertisement_header_bytes_write_ptr, advertisement_hash->getData(),
kAdvertisementHashLength);
advertisement_header_bytes_write_ptr += kAdvertisementHashLength;
// Header needs to be binary safe, so apply a Base64 encoding.
return Base64Utils::encode(advertisement_header_bytes);
}
BLEAdvertisementHeader::Version::Value
BLEAdvertisementHeader::parseVersionFromByte(std::uint16_t byte) {
return static_cast<Version::Value>((byte & kVersionBitmask) >> 5);
}
std::uint32_t BLEAdvertisementHeader::parseNumSlotsFromByte(
std::uint16_t byte) {
return static_cast<std::uint32_t>((byte & kNumSlotsBitmask));
}
void BLEAdvertisementHeader::serializeVersionByte(char *version_byte_write_ptr,
Version::Value version) {
*version_byte_write_ptr |=
static_cast<char>((version << 5) & kVersionBitmask);
}
void BLEAdvertisementHeader::serializeNumSlots(char *num_slots_byte_write_ptr,
std::uint32_t num_slots) {
*num_slots_byte_write_ptr |= static_cast<char>(num_slots & kNumSlotsBitmask);
}
BLEAdvertisementHeader::BLEAdvertisementHeader(
BLEAdvertisementHeader::Version::Value version, std::uint32_t num_slots,
ConstPtr<ByteArray> service_id_bloom_filter,
ConstPtr<ByteArray> advertisement_hash)
: version_(version),
num_slots_(num_slots),
service_id_bloom_filter_(service_id_bloom_filter),
advertisement_hash_(advertisement_hash) {}
BLEAdvertisementHeader::~BLEAdvertisementHeader() {
// Nothing to do.
}
BLEAdvertisementHeader::Version::Value BLEAdvertisementHeader::getVersion()
const {
return version_;
}
std::uint32_t BLEAdvertisementHeader::getNumSlots() const { return num_slots_; }
ConstPtr<ByteArray> BLEAdvertisementHeader::getServiceIdBloomFilter() const {
return service_id_bloom_filter_.get();
}
ConstPtr<ByteArray> BLEAdvertisementHeader::getAdvertisementHash() const {
return advertisement_hash_.get();
}
bool BLEAdvertisementHeader::operator<(
const BLEAdvertisementHeader &rhs) const {
if (this->getVersion() != rhs.getVersion()) {
return this->getVersion() < rhs.getVersion();
}
if (this->getNumSlots() != rhs.getNumSlots()) {
return this->getNumSlots() < rhs.getNumSlots();
}
if (*(this->getServiceIdBloomFilter()) != *(rhs.getServiceIdBloomFilter())) {
return *(this->getServiceIdBloomFilter()) <
*(rhs.getServiceIdBloomFilter());
}
return *(this->getAdvertisementHash()) < *(rhs.getAdvertisementHash());
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,91 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
#include "platform/byte_array.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Represents the format of the Mediums BLE Advertisement Header used in
// Advertising + Discovery.
//
// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH]
//
// See go/nearby-ble-design for more information.
class BLEAdvertisementHeader {
public:
// Versions of the BLEAdvertisementHeader.
struct Version {
enum Value {
V2 = 2,
// Version is only allocated 3 bits in the BLEAdvertisementHeader, so this
// can never go beyond V7.
//
// V1 is not present because it's an old format used in Nearby Connections
// before this logic was pushed down into Nearby Mediums. V1 put
// everything in the service data, while V2 puts the data inside a GATT
// characteristic so the two are not compatible.
};
};
static ConstPtr<BLEAdvertisementHeader> fromString(
const std::string &ble_advertisement_header_string);
static std::string asString(Version::Value version, std::uint32_t num_slots,
ConstPtr<ByteArray> service_id_bloom_filter,
ConstPtr<ByteArray> advertisement_hash);
static const std::uint32_t kServiceIdBloomFilterLength;
static const std::uint32_t kAdvertisementHashLength;
~BLEAdvertisementHeader();
Version::Value getVersion() const;
std::uint32_t getNumSlots() const;
ConstPtr<ByteArray> getServiceIdBloomFilter() const;
ConstPtr<ByteArray> getAdvertisementHash() const;
// Operator overloads when comparing ConstPtr<BLEAdvertisementHeader>.
bool operator<(const BLEAdvertisementHeader &rhs) const;
private:
// DiscoveredPeripheralTracker needs to be a friend of this class because it
// directly calls the constructor (the Java code keeps the constructor package
// private).
// Calling the constuctor directly allows us to avoid the unnessary extra
// calls to parse and decode to get the BLEAdvertisementHeader.
template <typename>
friend class DiscoveredPeripheralTracker;
static Version::Value parseVersionFromByte(std::uint16_t byte);
static std::uint32_t parseNumSlotsFromByte(std::uint16_t byte);
static const std::uint32_t kVersionAndNumSlotsLength;
static const std::uint32_t kMinAdvertisementHeaderLength;
static const std::uint16_t kVersionBitmask;
static const std::uint16_t kNumSlotsBitmask;
BLEAdvertisementHeader(Version::Value version, std::uint32_t num_slots,
ConstPtr<ByteArray> service_id_bloom_filter,
ConstPtr<ByteArray> advertisement_hash);
static void serializeVersionByte(char *version_byte_write_ptr,
Version::Value version);
static void serializeNumSlots(char *num_slots_byte_write_ptr,
std::uint32_t num_slots);
const Version::Value version_;
const uint32_t num_slots_;
ScopedPtr<ConstPtr<ByteArray> > service_id_bloom_filter_;
ScopedPtr<ConstPtr<ByteArray> > advertisement_hash_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
@@ -0,0 +1,221 @@
#include "core/internal/mediums/ble_advertisement_header.h"
#include "platform/base64_utils.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
const BLEAdvertisementHeader::Version::Value kVersion =
BLEAdvertisementHeader::Version::V2;
const std::uint32_t kNumSlots = 2;
const char kServiceIDBloomFilter[] = {0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A};
const char kAdvertisementHash[] = {0x0A, 0x0B, 0x0C, 0x0D};
const size_t kAdvertisementHeaderLength = 15;
const size_t kLongAdvertisementHeaderLength = kAdvertisementHeaderLength + 1;
const size_t kShortAdvertisementHeaderLength = kAdvertisementHeaderLength - 1;
TEST(BLEAdvertisementHeader, SerializationDeserializationWorks) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
BLEAdvertisementHeader::fromString(ble_advertisement_header_string));
ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion());
ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots());
ASSERT_EQ(
0,
memcmp(
kServiceIDBloomFilter,
scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(),
scoped_ble_advertisement_header->getServiceIdBloomFilter()->size()));
ASSERT_EQ(
0,
memcmp(kAdvertisementHash,
scoped_ble_advertisement_header->getAdvertisementHash()->getData(),
scoped_ble_advertisement_header->getAdvertisementHash()->size()));
}
TEST(BLEAdvertisementHeader, SerializationFailsWithBadVersion) {
BLEAdvertisementHeader::Version::Value bad_version =
static_cast<BLEAdvertisementHeader::Version::Value>(666);
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
bad_version, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ASSERT_EQ("", ble_advertisement_header_string);
}
TEST(BLEAdvertisementHeader, SerializationFailsWithShortServiceIdBloomFilter) {
char short_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(short_service_id_bloom_filter,
sizeof(short_service_id_bloom_filter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ASSERT_EQ("", ble_advertisement_header_string);
}
TEST(BLEAdvertisementHeader, SerializationFailsWithLongServiceIdBloomFilter) {
char long_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0A, 0x0B};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(long_service_id_bloom_filter,
sizeof(long_service_id_bloom_filter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ASSERT_EQ("", ble_advertisement_header_string);
}
TEST(BLEAdvertisementHeader, SerializationFailsWithShortAdvertisementHash) {
char short_advertisement_hash[] = {0x0A, 0x0B, 0x0C};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(MakeConstPtr(
new ByteArray(short_advertisement_hash,
sizeof(short_advertisement_hash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ASSERT_EQ("", ble_advertisement_header_string);
}
TEST(BLEAdvertisementHeader, SerializationFailsWithLongAdvertisementHash) {
char long_advertisement_hash[] = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(MakeConstPtr(
new ByteArray(long_advertisement_hash,
sizeof(long_advertisement_hash) / sizeof(char))));
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get()));
ASSERT_EQ("", ble_advertisement_header_string);
}
TEST(BLEAdvertisementHeader, DeserializationWorksWithExtraBytes) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string =
BLEAdvertisementHeader::asString(kVersion, kNumSlots,
scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get());
// Base64 decode the string, add a character, and then re-encode it. We must
// explicitly define how long our array is because we can't use variable
// length arrays.
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
Base64Utils::decode(ble_advertisement_header_string));
char raw_long_ble_advertisement_header_bytes[kLongAdvertisementHeaderLength];
memcpy(raw_long_ble_advertisement_header_bytes,
scoped_ble_advertisement_header_bytes->getData(),
kLongAdvertisementHeaderLength);
ScopedPtr<ConstPtr<ByteArray> > scoped_long_ble_advertisement_header_bytes(
MakeConstPtr(new ByteArray(raw_long_ble_advertisement_header_bytes,
kLongAdvertisementHeaderLength)));
std::string long_ble_advertisement_header_string =
Base64Utils::encode(scoped_long_ble_advertisement_header_bytes.get());
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
BLEAdvertisementHeader::fromString(long_ble_advertisement_header_string));
ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion());
ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots());
ASSERT_EQ(
0,
memcmp(
kServiceIDBloomFilter,
scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(),
scoped_ble_advertisement_header->getServiceIdBloomFilter()->size()));
ASSERT_EQ(
0,
memcmp(kAdvertisementHash,
scoped_ble_advertisement_header->getAdvertisementHash()->getData(),
scoped_ble_advertisement_header->getAdvertisementHash()->size()));
}
TEST(BLEAdvertisementHeader, DeserializationFailsWithShortLength) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
new ByteArray(kServiceIDBloomFilter,
sizeof(kServiceIDBloomFilter) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
MakeConstPtr(new ByteArray(kAdvertisementHash,
sizeof(kAdvertisementHash) / sizeof(char))));
std::string ble_advertisement_header_string =
BLEAdvertisementHeader::asString(kVersion, kNumSlots,
scoped_service_id_bloom_filter.get(),
scoped_advertisement_hash.get());
// Base64 decode the string, remove a character, and then re-encode it. We
// must explicitly define how long our array is because we can't use variable
// length arrays.
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
Base64Utils::decode(ble_advertisement_header_string));
char
raw_short_ble_advertisement_header_bytes[kShortAdvertisementHeaderLength];
memcpy(raw_short_ble_advertisement_header_bytes,
scoped_ble_advertisement_header_bytes->getData(),
kShortAdvertisementHeaderLength);
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_advertisement_header_bytes(
MakeConstPtr(new ByteArray(raw_short_ble_advertisement_header_bytes,
kShortAdvertisementHeaderLength)));
std::string short_ble_advertisement_header_string =
Base64Utils::encode(scoped_short_ble_advertisement_header_bytes.get());
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
BLEAdvertisementHeader::fromString(
short_ble_advertisement_header_string));
ASSERT_TRUE(scoped_ble_advertisement_header.isNull());
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,319 @@
#include "core/internal/mediums/ble_advertisement.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
const BLEAdvertisement::Version::Value kVersion = BLEAdvertisement::Version::V2;
const BLEAdvertisement::SocketVersion::Value kSocketVersion =
BLEAdvertisement::SocketVersion::V2;
const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C};
const char kData[] =
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?";
// This corresponds to the length of a specific BLEAdvertisement packed with the
// kData given above. Be sure to update this if kData ever changes.
const size_t kAdvertisementLength = 77;
const size_t kLongAdvertisementLength = kAdvertisementLength + 1000;
TEST(BLEAdvertisementTest, SerializationDeserializationWorksV1) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(
BLEAdvertisement::Version::V1, BLEAdvertisement::SocketVersion::V1,
scoped_service_id_hash.get(), scoped_data.get()));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(BLEAdvertisement::Version::V1,
scoped_ble_advertisement->getVersion());
ASSERT_EQ(BLEAdvertisement::SocketVersion::V1,
scoped_ble_advertisement->getSocketVersion());
ASSERT_EQ(scoped_service_id_hash->size(),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
scoped_ble_advertisement->getData()->size()));
}
TEST(BLEAdvertisementTest, SerializationDeserializationWorks) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion());
ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion());
ASSERT_EQ(scoped_service_id_hash->size(),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
scoped_ble_advertisement->getData()->size()));
}
TEST(BLEAdvertisementTest, SerializationDeserializationWorksWithEmptyData) {
char empty_data[0];
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
new ByteArray(empty_data, sizeof(empty_data) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion());
ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion());
ASSERT_EQ(scoped_service_id_hash->size(),
scoped_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
scoped_ble_advertisement->getServiceIdHash()->getData(),
scoped_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
scoped_ble_advertisement->getData()->size()));
}
TEST(BLEAdvertisementTest, SerializationDeserializationFailsWithLargeData) {
// Create data that's larger than the allowed size.
char large_data[513];
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
new ByteArray(large_data, sizeof(large_data) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
ASSERT_TRUE(scoped_ble_advertisement.isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithBadVersion) {
BLEAdvertisement::Version::Value bad_version =
static_cast<BLEAdvertisement::Version::Value>(666);
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(bad_version, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithBadSocketVersion) {
BLEAdvertisement::SocketVersion::Value bad_socket_version =
static_cast<BLEAdvertisement::SocketVersion::Value>(666);
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, bad_socket_version,
scoped_service_id_hash.get(),
scoped_data.get()));
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithShortServiceIdHash) {
char short_service_id_hash_bytes[] = {0x0A, 0x0B};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(short_service_id_hash_bytes,
sizeof(short_service_id_hash_bytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithLongServiceIdHash) {
char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(long_service_id_hash_bytes,
sizeof(long_service_id_hash_bytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
}
TEST(BLEAdvertisementTest, SerializationFailsWithLongData) {
// BLEAdvertisement shouldn't be able to support data with the max GATT
// attribute length because it needs some room for the preceding fields.
char long_data[512];
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(long_data, sizeof(long_data) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
}
TEST(BLEAdvertisementTest, DeserializationWorksWithExtraBytes) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
// Copy the bytes into a new array with extra bytes. We must explicitly
// define how long our array is because we can't use variable length arrays.
char raw_ble_advertisement_bytes[kLongAdvertisementLength];
memcpy(raw_ble_advertisement_bytes, scoped_ble_advertisement_bytes->getData(),
kLongAdvertisementLength);
// Re-parse the BLE advertisement using our extra long advertisement bytes.
ScopedPtr<ConstPtr<ByteArray> > scoped_long_ble_advertisement_bytes(
MakeConstPtr(new ByteArray(raw_ble_advertisement_bytes,
kLongAdvertisementLength)));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_long_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_long_ble_advertisement_bytes.get()));
ASSERT_EQ(kVersion, scoped_long_ble_advertisement->getVersion());
ASSERT_EQ(kSocketVersion, scoped_long_ble_advertisement->getSocketVersion());
ASSERT_EQ(scoped_service_id_hash->size(),
scoped_long_ble_advertisement->getServiceIdHash()->size());
ASSERT_EQ(0,
memcmp(kServiceIDHashBytes,
scoped_long_ble_advertisement->getServiceIdHash()->getData(),
scoped_long_ble_advertisement->getServiceIdHash()->size()));
ASSERT_EQ(scoped_data->size(),
scoped_long_ble_advertisement->getData()->size());
ASSERT_EQ(0,
memcmp(kData, scoped_long_ble_advertisement->getData()->getData(),
scoped_long_ble_advertisement->getData()->size()));
}
TEST(BLEAdvertisementTest, DeserializationFailsWithNullBytes) {
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(ConstPtr<ByteArray>()));
ASSERT_TRUE(scoped_ble_advertisement.isNull());
}
TEST(BLEAdvertisementTest, DeserializationFailsWithShortLength) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
// Cut off the advertisement so that it's too short.
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_advertisement_bytes(
MakeConstPtr(
new ByteArray(scoped_ble_advertisement_bytes->getData(), 7)));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(scoped_short_ble_advertisement_bytes.get()));
ASSERT_TRUE(scoped_ble_advertisement.isNull());
}
TEST(BLEAdvertisementTest, DeserializationFailsWithInvalidDataLength) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
sizeof(kServiceIDHashBytes) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
scoped_service_id_hash.get(),
scoped_data.get()));
// Corrupt the DATA_SIZE bits. Start by making a raw copy of the BLE
// advertisement bytes so we can modify it. We must explicitly define how long
// our array is because we can't use variable length arrays.
char raw_ble_advertisement_bytes[kAdvertisementLength];
memcpy(raw_ble_advertisement_bytes, scoped_ble_advertisement_bytes->getData(),
kAdvertisementLength);
// The data size field lives in indices 4-7. Corrupt it.
memset(raw_ble_advertisement_bytes + 4, 0xFF, 4);
// Try to parse the BLE advertisement using our corrupted advertisement bytes.
ScopedPtr<ConstPtr<ByteArray> > scoped_corrupted_ble_advertisement_bytes(
MakeConstPtr(
new ByteArray(raw_ble_advertisement_bytes, kAdvertisementLength)));
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
BLEAdvertisement::fromBytes(
scoped_corrupted_ble_advertisement_bytes.get()));
ASSERT_TRUE(scoped_ble_advertisement.isNull());
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+112
View File
@@ -0,0 +1,112 @@
#include "core/internal/mediums/ble_packet.h"
#include <limits>
#include "platform/logging.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
const std::uint32_t BLEPacket::kServiceIdHashLength = 3;
const std::uint32_t BLEPacket::kMinPacketLength = kServiceIdHashLength;
const std::uint32_t BLEPacket::kMaxDataSize =
std::numeric_limits<int32_t>::max() - kMinPacketLength;
ConstPtr<BLEPacket> BLEPacket::fromBytes(ConstPtr<ByteArray> ble_packet_bytes) {
if (ble_packet_bytes.isNull()) {
NEARBY_LOG(INFO, "Cannot deserialize BLEPacket: null bytes passed in");
return ConstPtr<BLEPacket>();
}
if (ble_packet_bytes->size() < kMinPacketLength) {
NEARBY_LOG(
INFO,
"Cannot deserialize BLEPacket: expecting min %u raw bytes, got %zu",
kMinPacketLength, ble_packet_bytes->size());
return ConstPtr<BLEPacket>();
}
// Now, time to read the bytes!
const char *ble_packet_bytes_read_ptr = ble_packet_bytes->getData();
// 1. Service ID hash.
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength)));
ble_packet_bytes_read_ptr += kServiceIdHashLength;
// 2. Data.
size_t data_size = computeDataSize(ble_packet_bytes);
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(ble_packet_bytes_read_ptr, data_size)));
ble_packet_bytes_read_ptr += data_size;
return MakeConstPtr(
new BLEPacket(scoped_service_id_hash.release(), scoped_data.release()));
}
ConstPtr<ByteArray> BLEPacket::toBytes(ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data) {
if (service_id_hash->size() != kServiceIdHashLength) {
NEARBY_LOG(
INFO,
"Cannot serialize BLEPacket: expected a service_id_hash of %u bytes, "
"but got %zu",
kServiceIdHashLength, service_id_hash->size());
return ConstPtr<ByteArray>();
}
if (data->size() > kMaxDataSize) {
NEARBY_LOG(INFO,
"Cannot serialize BLEPacket: expected data of at most %u bytes, "
"but got %zu",
kMaxDataSize, data->size());
return ConstPtr<ByteArray>();
}
// Initialize the bytes.
size_t packet_length = computePacketLength(data);
Ptr<ByteArray> packet_bytes{new ByteArray{packet_length}};
char *packet_bytes_write_ptr = packet_bytes->getData();
// 1. Service ID hash.
memcpy(packet_bytes_write_ptr, service_id_hash->getData(),
kServiceIdHashLength);
packet_bytes_write_ptr += kServiceIdHashLength;
// 2. Data.
memcpy(packet_bytes_write_ptr, data->getData(), data->size());
packet_bytes_write_ptr += data->size();
return ConstifyPtr(packet_bytes);
}
size_t BLEPacket::computeDataSize(ConstPtr<ByteArray> ble_packet_bytes) {
return ble_packet_bytes->size() - kMinPacketLength;
}
size_t BLEPacket::computePacketLength(ConstPtr<ByteArray> data) {
// The packet length is the minimum length + the length of the data.
return kMinPacketLength + data->size();
}
BLEPacket::BLEPacket(ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data)
: service_id_hash_(service_id_hash), data_(data) {}
BLEPacket::~BLEPacket() {
// Nothing to do.
}
ConstPtr<ByteArray> BLEPacket::getServiceIdHash() const {
return service_id_hash_.get();
}
ConstPtr<ByteArray> BLEPacket::getData() const { return data_.get(); }
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+49
View File
@@ -0,0 +1,49 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
#define CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
#include "platform/byte_array.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Represents the format of data sent over BLE sockets.
//
// [SERVICE_ID_HASH][DATA]
//
// See go/nearby-ble-design for more information.
class BLEPacket {
public:
static ConstPtr<BLEPacket> fromBytes(ConstPtr<ByteArray> ble_packet_bytes);
static ConstPtr<ByteArray> toBytes(ConstPtr<ByteArray> service_id_hash,
ConstPtr<ByteArray> data);
static const std::uint32_t kServiceIdHashLength;
~BLEPacket();
ConstPtr<ByteArray> getServiceIdHash() const;
ConstPtr<ByteArray> getData() const;
private:
static size_t computeDataSize(ConstPtr<ByteArray> ble_packet_bytes);
static size_t computePacketLength(ConstPtr<ByteArray> data);
static const std::uint32_t kMinPacketLength;
static const std::uint32_t kMaxDataSize;
BLEPacket(ConstPtr<ByteArray> service_id_hash, ConstPtr<ByteArray> data);
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
ScopedPtr<ConstPtr<ByteArray> > data_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
@@ -0,0 +1,108 @@
#include "core/internal/mediums/ble_packet.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
const char kServiceIDHash[] = {0x0A, 0x0B, 0x0C};
const char kData[] = {0x00, 0x01, 0x02, 0x03, 0x04};
TEST(BLEPacket, SerializationDeserializationWorks) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
BLEPacket::fromBytes(scoped_ble_packet_bytes.get()));
ASSERT_EQ(0, memcmp(kServiceIDHash,
scoped_ble_packet->getServiceIdHash()->getData(),
scoped_ble_packet->getServiceIdHash()->size()));
ASSERT_EQ(0, memcmp(kData, scoped_ble_packet->getData()->getData(),
scoped_ble_packet->getData()->size()));
}
TEST(BLEPacket, SerializationDeserializationWorksWithEmptyData) {
char empty_data[] = {};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
new ByteArray(empty_data, sizeof(empty_data) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
BLEPacket::fromBytes(scoped_ble_packet_bytes.get()));
ASSERT_EQ(0, memcmp(kServiceIDHash,
scoped_ble_packet->getServiceIdHash()->getData(),
scoped_ble_packet->getServiceIdHash()->size()));
ASSERT_EQ(0, memcmp(empty_data, scoped_ble_packet->getData()->getData(),
scoped_ble_packet->getData()->size()));
}
TEST(BLEPacket, SerializationFailsWithShortServiceIdHash) {
char short_service_id_hash[] = {0x0A, 0x0B};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(short_service_id_hash,
sizeof(short_service_id_hash) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
ASSERT_TRUE(scoped_ble_packet_bytes.isNull());
}
TEST(BLEPacket, SerializationFailsWithLongServiceIdHash) {
char long_service_id_hash[]{0x0A, 0x0B, 0x0C, 0x0D};
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
MakeConstPtr(new ByteArray(long_service_id_hash,
sizeof(long_service_id_hash) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
ASSERT_TRUE(scoped_ble_packet_bytes.isNull());
}
TEST(BLEPacket, DeserializationFailsWithNullBytes) {
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
BLEPacket::fromBytes(ConstPtr<ByteArray>()));
ASSERT_TRUE(scoped_ble_packet.isNull());
}
TEST(BLEPacket, DeserializationFailsWithShortLength) {
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
// Cut off the packet so that it's too short
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_packet_bytes(
MakeConstPtr(new ByteArray(scoped_ble_packet_bytes->getData(), 2)));
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
BLEPacket::fromBytes(scoped_short_ble_packet_bytes.get()));
ASSERT_TRUE(scoped_ble_packet.isNull());
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,19 @@
#include "core/internal/mediums/ble_peripheral.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
BLEPeripheral::BLEPeripheral(ConstPtr<ByteArray> id) : id_(id) {}
BLEPeripheral::~BLEPeripheral() {
// Nothing to do.
}
ConstPtr<ByteArray> BLEPeripheral::getId() const { return id_.get(); }
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,30 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
#define CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
#include "platform/byte_array.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
class BLEPeripheral {
public:
explicit BLEPeripheral(ConstPtr<ByteArray> id);
~BLEPeripheral();
ConstPtr<ByteArray> getId() const;
private:
// A unique identifier for this peripheral. It can be the BLE advertisement it
// was found on, or even simply the BLE MAC address.
ScopedPtr<ConstPtr<ByteArray>> id_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
+831
View File
@@ -0,0 +1,831 @@
#include "core/internal/mediums/ble.h"
#include "core/internal/mediums/ble_advertisement_header.h"
#include "core/internal/mediums/bloom_filter.h"
#include "core/internal/mediums/utils.h"
#include "core/internal/mediums/uuid.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace ble_v2 {
template <typename Platform>
class ProcessOnLostRunnable : public Runnable {
public:
explicit ProcessOnLostRunnable(Ptr<BLEV2<Platform>> ble_v2)
: ble_v2_(ble_v2) {}
void run() override { ble_v2_->processOnLostTimeout(); }
private:
Ptr<BLEV2<Platform>> ble_v2_;
};
template <typename Platform>
class OnAdvertisementFoundRunnable : public Runnable {
public:
OnAdvertisementFoundRunnable(
Ptr<BLEV2<Platform>> ble_v2, Ptr<BLEPeripheralV2> peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data)
: ble_v2_(ble_v2),
peripheral_(peripheral),
advertisement_data_(advertisement_data) {}
// This method is synchronized because it affects class state, but is called
// from a separate thread that fires whenever a BLE advertisement is seen.
void run() override {
Synchronized s(ble_v2_->lock_.get());
ble_v2_->discovered_peripheral_tracker_->processFoundBleAdvertisement(
peripheral_, advertisement_data_.release(),
MakePtr(new typename BLEV2<Platform>::GATTAdvertisementFetcherFacade(
ble_v2_)));
}
private:
Ptr<BLEV2<Platform>> ble_v2_;
Ptr<BLEPeripheralV2> peripheral_;
ScopedPtr<ConstPtr<BLEAdvertisementData>> advertisement_data_;
};
} // namespace ble_v2
template <typename Platform>
const std::int32_t BLEV2<Platform>::kNumAdvertisementSlots = 2;
template <typename Platform>
const std::int32_t BLEV2<Platform>::kMaxAdvertisementLength = 512;
template <typename Platform>
const std::int32_t BLEV2<Platform>::kDummyServiceIdLength = 512;
template <typename Platform>
const char* BLEV2<Platform>::kCopresenceServiceUuid =
"0000FEF3-0000-1000-8000-00805F9B34FB";
template <typename Platform>
const std::int64_t BLEV2<Platform>::kOnLostTimeoutMillis = 15000;
template <typename Platform>
const std::int64_t BLEV2<Platform>::kGattAdvertisementOperationTimeoutMillis =
5000;
template <typename Platform>
const std::int64_t
BLEV2<Platform>::kMinConnectionAttemptRecoveryDurationMillis = 1000;
template <typename Platform>
const std::int32_t
BLEV2<Platform>::kMaxConnectionAttemptRecoveryFuzzDurationMillis = 10000;
template <typename Platform>
const std::uint32_t BLEV2<Platform>::kDefaultMtu = 512;
// These two values make up the base UUID we use when advertising a slot. The
// base is an all zero Version-3 name-based UUID. To turn this into an
// advertisement slot UUID, we simply OR the least significant bits with the
// slot number.
//
// More info about the format can be found here:
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
template <typename Platform>
const std::int64_t BLEV2<Platform>::kAdvertisementUuidMsb = 0x0000000000003000;
template <typename Platform>
const std::int64_t BLEV2<Platform>::kAdvertisementUuidLsb = 0x8000000000000000;
template <typename Platform>
BLEV2<Platform>::BLEV2(Ptr<BluetoothRadio<Platform>> bluetooth_radio)
: lock_(Platform::createLock()),
platform_thread_offloader_(Platform::createSingleThreadExecutor()),
prng_(MakePtr(new Prng())),
hash_utils_(Platform::createHashUtils()),
bluetooth_radio_(bluetooth_radio),
bluetooth_adapter_(Platform::createBluetoothAdapter()),
ble_medium_(Platform::createBLEMediumV2()),
scanning_info_(),
discovered_peripheral_tracker_(
new DiscoveredPeripheralTracker<Platform>()),
on_lost_executor_(Platform::createScheduledExecutor()),
advertising_info_(),
gatt_server_info_(),
accepting_connections_info_() {}
template <typename Platform>
BLEV2<Platform>::~BLEV2() {
Synchronized s(lock_.get());
on_lost_executor_->shutdown();
platform_thread_offloader_->shutdown();
stopAdvertising();
stopAdvertisementGattServer();
stopAcceptingConnections();
stopScanning();
// discovered_peripheral_tracker is a ScopedPtr member and will take care of
// itself.
}
template <typename Platform>
bool BLEV2<Platform>::isAvailable() {
// This is purposefully left un-synchronized like its java counterpart.
// Callers should be able to query this without waiting for other operations
// to complete first and this should be safe to call after shutdown. We would
// have made it static, but it relies on variables from the constructor (like
// ble_medium_ and bluetooth_adapter_).
return !ble_medium_.isNull() && !bluetooth_adapter_.isNull();
}
// Returns true if currently scanning for BLE advertisements.
template <typename Platform>
bool BLEV2<Platform>::isAdvertising() {
Synchronized s(lock_.get());
return !advertising_info_.isNull();
}
// Starts BLE advertising, delivering additional information through a GATT
// server.
template <typename Platform>
bool BLEV2<Platform>::startAdvertising(
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
BLEMediumV2::PowerMode::Value power_mode,
const string& fast_advertisement_service_uuid) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement_bytes(
advertisement_bytes);
if (service_id.empty() || scoped_advertisement_bytes.isNull()) {
// logger.atSevere().log("Refusing to start BLE advertising because a null
// parameter was passed in.");
return false;
}
if (scoped_advertisement_bytes->size() > kMaxAdvertisementLength) {
// logger.atSevere().log("Refusing to start BLE advertising because the
// advertisement was too long. Expected at most %d bytes but received %d.",
// kMaxAdvertisementLength, scoped_advertisement_bytes->size());
return false;
}
// Note: We don't include logic checking/using the fast_pair_model_id because
// that is a java-only concept for now.
if (isAdvertising()) {
// logger.atSevere().log("Failed to BLE advertise because we're already
// advertising.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// logger.atSevere().log("Can't start BLE advertising because Bluetooth
// isn't enabled.");
return false;
}
if (!isAvailable()) {
// logger.atSevere().log("Can't start BLE advertising because BLE is not
// available.");
return false;
}
// TODO(ahlee): Remove this check here and in the java code (redundant)
// Stop any existing advertisement GATT servers. We don't stop it in
// stopAdvertising() to avoid GATT issues with BLE sockets.
if (isAdvertisementGattServerRunning()) {
stopAdvertisementGattServer();
}
// Start a GATT server to deliver the full advertisement data. If we fail to
// advertise the header, we must shut this down before the method returns.
bool is_fast_advertisement = !fast_advertisement_service_uuid.empty();
if (!is_fast_advertisement) {
if (!startAdvertisementGattServer(service_id,
scoped_advertisement_bytes.get())) {
// logger.atSevere().log("Failed to to BLE advertise because the
// advertisement GATT server failed to start");
return false;
}
}
ScopedPtr<ConstPtr<ByteArray>> advertisement_header_bytes(
createAdvertisementHeader(service_id, scoped_advertisement_bytes.get(),
is_fast_advertisement));
if (advertisement_header_bytes.isNull()) {
// logger.atSevere().log("Failed to to BLE advertise because we could not
// create an advertisement header");
// We failed to start BLE advertising, so stop the advertisement GATT
// server.
stopAdvertisementGattServer();
return false;
}
ScopedPtr<Ptr<BLEAdvertisementData>> advertisement(
new BLEAdvertisementData());
advertisement->is_connectable = true;
advertisement->tx_power_level =
BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL;
ScopedPtr<Ptr<BLEAdvertisementData>> scan_response(
new BLEAdvertisementData());
scan_response->is_connectable = true;
scan_response->tx_power_level =
BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL;
scan_response->service_uuids.insert(kCopresenceServiceUuid);
scan_response->service_data.insert(std::make_pair(
kCopresenceServiceUuid, advertisement_header_bytes.release()));
// Note: We don't use fast pair data because that is java-only for now.
// TODO(ahlee): Fix this if check in the java code.
if (is_fast_advertisement) {
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
generateServiceIdHash(BLEAdvertisement::Version::V2, service_id));
ScopedPtr<ConstPtr<ByteArray>> fast_advertisement_bytes(
BLEAdvertisement::toBytes(
BLEAdvertisement::Version::V2, BLEAdvertisement::SocketVersion::V2,
service_id_hash.get(), scoped_advertisement_bytes.get()));
if (fast_advertisement_bytes.isNull()) {
// logger.atSevere().log("Failed to BLE advertise because we could not
// create a fast advertisement for service UUID %s.",
// fast_advertisement_service_uuid);
// We shouldn't have started an advertisement GATT server in the first
// place if we are using fast advertisements. However, to avoid careless
// leaks, try shutting down the server anyway.
stopAdvertisementGattServer();
return false;
}
advertisement->service_data.insert(std::make_pair(
fast_advertisement_service_uuid, fast_advertisement_bytes.release()));
scan_response->service_uuids.insert(fast_advertisement_service_uuid);
}
if (!ble_medium_->startAdvertising(ConstifyPtr(advertisement.release()),
ConstifyPtr(scan_response.release()),
power_mode)) {
// If BLE advertising was not successful, stop the advertisement GATT
// server.
stopAdvertisementGattServer();
return false;
}
// logger.atVerbose().flog("Started BLE advertising with advertisement %s for
// serviceID %s.", advertisement_header, service_id);
advertising_info_ = MakePtr(new AdvertisingInfo(service_id));
return true;
}
template <typename Platform>
ConstPtr<ByteArray> BLEV2<Platform>::createAdvertisementHeader(
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
bool is_fast_advertisement) {
// Create a randomized dummy service ID to anonymize our header with.
string dummy_service_id;
dummy_service_id.reserve(kDummyServiceIdLength);
for (int i = 0; i < kDummyServiceIdLength; i++) {
dummy_service_id[i] = static_cast<char>(prng_->nextInt32() & 0x000000FF);
}
// Put the service ID along with the dummy service ID into our bloom filter
// Note: BloomFilter length should always match
// BLEAdvertisementHeader::kServiceIdBloomFilterLength
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(new BloomFilter<10>());
bloom_filter->add(dummy_service_id);
// Only add the service ID to our bloom filter if it's not a fast
// advertisement. Fast advertisements want discoverers to avoid reading our
// GATT advertisement.
if (!is_fast_advertisement) {
bloom_filter->add(service_id);
}
// Create a hash seeded from dummy_service_id + advertisementBytes
//
// First, populate advertisement_bodies with the dummy_service_id and
// advertisement_bytes.
string advertisement_bodies;
advertisement_bodies.reserve(dummy_service_id.size() +
advertisement_bytes->size());
advertisement_bodies.append(dummy_service_id.data(), dummy_service_id.size());
advertisement_bodies.append(advertisement_bytes->getData(),
advertisement_bytes->size());
// Then, generate the advertisement hash from the populated
// advertisement_bodies string.
ScopedPtr<ConstPtr<ByteArray>> advertisement_bodies_byte_array(MakeConstPtr(
new ByteArray(advertisement_bodies.data(), advertisement_bodies.size())));
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
generateAdvertisementHash(advertisement_bodies_byte_array.get()));
ScopedPtr<ConstPtr<ByteArray>> bloom_filter_bytes(bloom_filter->asBytes());
string ble_advertisement_header_string = BLEAdvertisementHeader::asString(
BLEAdvertisementHeader::Version::V2, kNumAdvertisementSlots,
bloom_filter_bytes.get(), advertisement_hash.get());
return MakeConstPtr(new ByteArray(ble_advertisement_header_string.data(),
ble_advertisement_header_string.size()));
}
// Stops BLE advertising.
template <typename Platform>
void BLEV2<Platform>::stopAdvertising() {
Synchronized s(lock_.get());
if (!isAdvertising()) {
// logger.atDebug().log("Can't turn off BLE advertising because it never
// started.");
return;
}
ble_medium_->stopAdvertising();
// Reset advertising_info_to mark that we're no longer advertising.
advertising_info_.destroy();
// Do NOT stop the advertisement GATT server here. Doing so will cause any
// other existing GATT connections to stop receiving callbacks. This affects
// our BLE sockets. Therefore, we only stop it in shutdown() and
// startAdvertising(), where it is safe to do so. At those two points, we
// shouldn't expect any BLE sockets to be connected.
// logger.atVerbose().log("Turned BLE advertising off");
}
// Returns true if currently scanning for BLE advertisements.
template <typename Platform>
bool BLEV2<Platform>::isScanning() {
Synchronized s(lock_.get());
return !scanning_info_.isNull();
}
// Starts scanning for BLE advertisements (if it is possible for the device).
template <typename Platform>
bool BLEV2<Platform>::startScanning(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
BLEMediumV2::PowerMode::Value power_mode,
const string& fast_advertisement_service_uuid) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<Ptr<DiscoveredPeripheralCallback>>
scoped_discovered_peripheral_callback(discovered_peripheral_callback);
if (service_id.empty() || scoped_discovered_peripheral_callback.isNull()) {
// logger.atSevere().log("Refusing to start BLE scanning because at least
// one of workSource, serviceId, or discoveredPeripheralCallback is null.");
return false;
}
if (isScanning()) {
// logger.atSevere().log("Refusing to start BLE scanning because we are
// already scanning.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// logger.atSevere().log("Can't start BLE scanning because Bluetooth was
// never turned on");
return false;
}
if (!isAvailable()) {
// logger.atSevere().log("Can't start BLE scanning because BLE is not
// available.");
return false;
}
discovered_peripheral_tracker_->startTracking(
service_id, scoped_discovered_peripheral_callback.release(),
fast_advertisement_service_uuid);
// Avoid leaks.
ScopedPtr<Ptr<ScanCallbackFacade>> scan_callback_facade(
new ScanCallbackFacade(MakePtr(this)));
std::set<string> service_uuids;
service_uuids.insert(kCopresenceServiceUuid);
if (!ble_medium_->startScanning(service_uuids, power_mode,
scan_callback_facade.get())) {
discovered_peripheral_tracker_->stopTracking(service_id);
return false;
}
// logger.atVerbose().log("Started BLE scanning for serviceID %s.",
// service_id);
scanning_info_ = MakePtr(new ScanningInfo(
service_id, scan_callback_facade.release(), createOnLostAlarm()));
return true;
}
template <typename Platform>
void BLEV2<Platform>::onAdvertisementFoundImpl(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data) {
offloadFromPlatformThread(
MakePtr(new ble_v2::OnAdvertisementFoundRunnable<Platform>(
MakePtr(this), ble_peripheral, advertisement_data)));
}
// This method is synchronized because it affects class state, but is called
// from a separate thread that has a recurring alarm running on it.
template <typename Platform>
void BLEV2<Platform>::processOnLostTimeout() {
Synchronized s(lock_.get());
discovered_peripheral_tracker_->processLostGattAdvertisements();
}
// Stops scanning for BLE advertisements.
template <typename Platform>
void BLEV2<Platform>::stopScanning() {
Synchronized s(lock_.get());
if (!isScanning()) {
// logger.atDebug().log("Can't turn off BLE scanning because we never
// started scanning.");
return;
}
scanning_info_->on_lost_alarm->cancel();
ble_medium_->stopScanning();
discovered_peripheral_tracker_->stopTracking(scanning_info_->service_id);
// Reset our bundle of scanning state to mark that we're no longer scanning.
scanning_info_.destroy();
}
// TODO(b/112199086) Change to RecurringCancelableAlarm
template <typename Platform>
Ptr<CancelableAlarm<Platform>> BLEV2<Platform>::createOnLostAlarm() {
// return MakePtr(new CancelableAlarm<Platform>(
// "BluetoothLowEnergy.startScanning() onLost",
// MakePtr(new
// ble_v2::ProcessOnLostRunnable<Platform>(MakePtr(this))),
// kOnLostTimeoutMillis, on_lost_executor_.get()));
return Ptr<CancelableAlarm<Platform>>();
}
// Returns true if the device is currently accepting incoming BLE socket
// connections.
template <typename Platform>
bool BLEV2<Platform>::isAcceptingConnections() {
Synchronized s(lock_.get());
return !accepting_connections_info_.isNull();
}
// Starts accepting incoming BLE socket connections.
template <typename Platform>
bool BLEV2<Platform>::startAcceptingConnections(
const string& service_id,
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<Ptr<AcceptedConnectionCallback>>
scoped_accepted_connection_callback(accepted_connection_callback);
if (service_id.empty() || scoped_accepted_connection_callback.isNull()) {
// logger.atSevere().log("Refusing to start accepting BLE connections
// because at least one of serviceId or acceptedConnectionCallback is
// null.");
return false;
}
if (isAcceptingConnections()) {
// logger.atSevere().log("Refusing to start accepting BLE connections for %s
// because another BLE server socket is already in-progress.", service_id);
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// logger.atSevere().log("Can't start accepting BLE connections for %s
// because Bluetooth isn't enabled.", service_id);
return false;
}
if (!isAvailable()) {
// logger.atSevere().log("Can't start accepting BLE connections for %s
// because BLE is not available.", service_id);
return false;
}
// TODO(ahlee): Implement w/ the rest of the connecting logic.
// Default to returning true and creating accepting_connections_info_ so we
// can test the advertising and discovery flow fully.
accepting_connections_info_ =
MakePtr(new AcceptingConnectionsInfo(service_id));
return true;
}
// Stops accepting incoming BLE socket connections.
template <typename Platform>
void BLEV2<Platform>::stopAcceptingConnections() {
Synchronized s(lock_.get());
if (!isAcceptingConnections()) {
// logger.atDebug().log("Can't stop accepting BLE connections because it was
// never started.");
return;
}
ble_medium_->stopListeningForIncomingBLESockets();
// Reset our bundle of accepting connections state to mark that we're no
// longer accepting connections.
accepting_connections_info_.destroy();
}
// Note: getGattConnectionBackoffPeriodMillis is only used in the java version
// of reliablyConnect() for now.
// Returns true if the advertisement GATT server is currently running.
template <typename Platform>
bool BLEV2<Platform>::isAdvertisementGattServerRunning() {
return !gatt_server_info_.isNull();
}
// Starts a GATT server to deliver additional advertisement data. Returns true
// if the server was started successfully.
template <typename Platform>
bool BLEV2<Platform>::startAdvertisementGattServer(
const string& service_id, ConstPtr<ByteArray> advertisement) {
// advertisement is not being wrapped in a ScopedPtr because ownership is not
// passed on from startAdvertising().
if (isAdvertisementGattServerRunning()) {
// logger.atSevere().log("Refusing to start an advertisement GATT server
// because one is already running.");
return false;
}
// Create a BleAdvertisement to wrap over the passed in advertisement.
ScopedPtr<ConstPtr<ByteArray>> legacy_service_id_hash(
generateServiceIdHash(BLEAdvertisement::Version::V1, service_id));
ScopedPtr<ConstPtr<ByteArray>> legacy_ble_advertisement_bytes(
BLEAdvertisement::toBytes(BLEAdvertisement::Version::V1,
BLEAdvertisement::SocketVersion::V1,
legacy_service_id_hash.get(), advertisement));
if (legacy_ble_advertisement_bytes.isNull()) {
// logger.atSevere().log("Refusing to start an advertisement GATT server
// because creating a legacy BleAdvertisement with service ID %s failed.",
// service_id);
return false;
}
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
generateServiceIdHash(BLEAdvertisement::Version::V2, service_id));
ScopedPtr<ConstPtr<ByteArray>> ble_advertisement_bytes(
BLEAdvertisement::toBytes(BLEAdvertisement::Version::V2,
BLEAdvertisement::SocketVersion::V2,
service_id_hash.get(), advertisement));
if (ble_advertisement_bytes.isNull()) {
// logger.atSevere().log("Refusing to start an advertisement GATT server
// because creating a BleAdvertisement with service ID %s failed.",
// service_id);
return false;
}
return internalStartAdvertisementGattServer(
legacy_ble_advertisement_bytes.release(),
ble_advertisement_bytes.release());
}
template <typename Platform>
bool BLEV2<Platform>::internalStartAdvertisementGattServer(
ConstPtr<ByteArray> legacy_ble_advertisement_bytes,
ConstPtr<ByteArray> ble_advertisement_bytes) {
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray>> scoped_legacy_ble_advertisement_bytes(
legacy_ble_advertisement_bytes);
ScopedPtr<ConstPtr<ByteArray>> scoped_ble_advertisement_bytes(
ble_advertisement_bytes);
ScopedPtr<Ptr<ServerGATTConnectionLifecycleCallbackFacade>>
connection_lifecycle_callback(
new ServerGATTConnectionLifecycleCallbackFacade(MakePtr(this)));
ScopedPtr<Ptr<GATTServer>> gatt_server(
ble_medium_->startGATTServer(connection_lifecycle_callback.get()));
if (gatt_server.isNull()) {
// logger.atSevere().withCause(e).log("Unable to start an advertisement GATT
// server.");
return false;
}
if (!generateAdvertisementCharacteristic(
/* slot= */ 0, scoped_legacy_ble_advertisement_bytes.release(),
gatt_server.get())) {
gatt_server->stop();
return false;
}
if (!generateAdvertisementCharacteristic(
/* slot= */ 1, scoped_ble_advertisement_bytes.release(),
gatt_server.get())) {
gatt_server->stop();
return false;
}
// GattCharacteristic is not included in GATTServerInfo because we don't need
// it after it's been updated.
gatt_server_info_ = MakePtr(new GATTServerInfo(
gatt_server.release(), connection_lifecycle_callback.release()));
return true;
}
template <typename Platform>
bool BLEV2<Platform>::generateAdvertisementCharacteristic(
std::int32_t slot, ConstPtr<ByteArray> advertisement,
Ptr<GATTServer> gatt_server) {
// Avoid leaks.
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
std::set<GATTCharacteristic::Permission::Value> permissions;
permissions.insert(GATTCharacteristic::Permission::READ);
std::set<GATTCharacteristic::Property::Value> properties;
properties.insert(GATTCharacteristic::Property::READ);
Ptr<GATTCharacteristic> gatt_characteristic(gatt_server->createCharacteristic(
kCopresenceServiceUuid, generateAdvertisementUuid(slot), permissions,
properties));
if (gatt_characteristic.isNull()) {
// logger.atSevere().withCause(e).log("Unable to create and add a
// characterstic to the gatt server for the advertisement.");
return false;
}
if (!gatt_server->updateCharacteristic(gatt_characteristic,
scoped_advertisement.release())) {
// logger.atSevere().withCause(e).log("Unable to write a value to the GATT
// characteristic.");
return false;
}
return true;
}
// Note: In the java counterpart this in a utils class.
// Generates a characteristic UUID for an advertisement at the given slot.
template <typename Platform>
string BLEV2<Platform>::generateAdvertisementUuid(std::int32_t slot) {
return UUID<Platform>(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot)
.str();
}
// Stops a GATT server used for additional advertisement data.
template <typename Platform>
void BLEV2<Platform>::stopAdvertisementGattServer() {
Synchronized s(lock_.get());
if (!isAdvertisementGattServerRunning()) {
// logger.atSevere().log("Unable to stop the advertisement GATT server
// because it's not running.");
return;
}
gatt_server_info_->gatt_server->stop();
gatt_server_info_.destroy();
}
// Connects to a GATT server, reads advertisement data, and then disconnects
// from the GATT server. This method blocks until all advertisements are read,
// or a connection error occurs.
template <typename Platform>
Ptr<AdvertisementReadResult<Platform>>
BLEV2<Platform>::processFetchGattAdvertisementsRequest(
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) {
Synchronized s(lock_.get());
if (advertisement_read_result.isNull()) {
advertisement_read_result =
MakeRefCountedPtr(new AdvertisementReadResult<Platform>());
}
if (peripheral.isNull()) {
// logger.atSevere().log("Can't read from an advertisement GATT server
// because ble peripheral is null.");
return advertisement_read_result;
}
if (!bluetooth_radio_->isEnabled()) {
// logger.atSevere().log("Can't read from an advertisement GATT server
// because Bluetooth was never turned on.");
return advertisement_read_result;
}
if (!isAvailable()) {
// logger.atSevere().log("Can't read from an advertisement GATT server
// because BLE is not available.");
return advertisement_read_result;
}
return internalReadFromAdvertisementGattServer(peripheral, num_slots,
advertisement_read_result);
}
template <typename Platform>
Ptr<AdvertisementReadResult<Platform>>
BLEV2<Platform>::internalReadFromAdvertisementGattServer(
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) {
// Attempt to connect and read some GATT characteristics.
bool read_success = true;
ScopedPtr<Ptr<ClientGATTConnectionLifecycleCallbackFacade>>
connection_lifecycle_callback(
new ClientGATTConnectionLifecycleCallbackFacade(MakePtr(this)));
ScopedPtr<Ptr<ClientGATTConnection>> gatt_connection(
ble_medium_->connectToGATTServer(peripheral, kDefaultMtu,
BLEMediumV2::PowerMode::HIGH,
connection_lifecycle_callback.get()));
if (!gatt_connection.isNull() && gatt_connection->discoverServices()) {
// Read all advertisements from all slots that we haven't read from yet.
for (std::int32_t slot = 0; slot < num_slots; ++slot) {
// Make sure we haven't already read this advertisement before.
if (advertisement_read_result->hasAdvertisement(slot)) {
continue;
}
// Make sure the characteristic even exists for this slot number. If the
// characteristic doesn't exist, we shouldn't count the fetch as a
// failure because there's nothing we could've done about a non-existent
// characteristic.
Ptr<GATTCharacteristic> gatt_characteristic(
gatt_connection->getCharacteristic(kCopresenceServiceUuid,
generateAdvertisementUuid(slot)));
if (/* !advertisementSlotExists()= */ gatt_characteristic.isNull()) {
continue;
}
// Read advertisement data from the characteristic associated with this
// slot.
ScopedPtr<ConstPtr<ByteArray>> characteristic_value(
gatt_connection->readCharacteristic(gatt_characteristic));
if (!characteristic_value.isNull()) {
advertisement_read_result->addAdvertisement(
slot, characteristic_value.release());
// logger.atVerbose().log("Successfully read advertisement at slot %d
// on peripheral %s.", slot, peripheral);
} else {
// logger.atWarning().withCause(characteristicReadException).log("Can't
// read advertisement for slot %d on peripheral %s.", slot,
// peripheral);
read_success = false;
}
// Whether or not we succeeded with this slot, we should try reading the
// other slots to get as many advertisements as possible before
// returning a success or failure.
}
gatt_connection->disconnect();
} else {
// logger.atWarning().withCause(connectException).log("Can't connect to an
// advertisement GATT server for peripheral %s.", peripheral);
read_success = false;
}
advertisement_read_result->recordLastReadStatus(read_success);
return advertisement_read_result;
}
template <typename Platform>
void BLEV2<Platform>::offloadFromPlatformThread(Ptr<Runnable> runnable) {
platform_thread_offloader_->execute(runnable);
}
template <typename Platform>
ConstPtr<ByteArray> BLEV2<Platform>::generateAdvertisementHash(
ConstPtr<ByteArray> advertisement_bytes) {
return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes,
BLEAdvertisementHeader::kAdvertisementHashLength);
}
template <typename Platform>
ConstPtr<ByteArray> BLEV2<Platform>::generateServiceIdHash(
BLEAdvertisement::Version::Value version, const string& service_id) {
ScopedPtr<ConstPtr<ByteArray>> service_id_bytes(
MakeConstPtr(new ByteArray(service_id.data(), service_id.size())));
switch (version) {
case BLEAdvertisement::Version::V1:
return Utils::legacySha256HashOnlyForPrinting(
hash_utils_.get(), service_id_bytes.get(),
BLEAdvertisement::kServiceIdHashLength);
case BLEAdvertisement::Version::V2:
// Fall through.
case BLEAdvertisement::Version::UNKNOWN:
// Fall through.
default:
// Use the latest known hashing scheme.
return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(),
BLEAdvertisement::kServiceIdHashLength);
}
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+312
View File
@@ -0,0 +1,312 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_H_
#define CORE_INTERNAL_MEDIUMS_BLE_V2_H_
#include <cstdint>
#include "core/internal/mediums/advertisement_read_result.h"
#include "core/internal/mediums/ble_advertisement.h"
#include "core/internal/mediums/bluetooth_radio.h"
#include "core/internal/mediums/discovered_peripheral_callback.h"
#include "core/internal/mediums/discovered_peripheral_tracker.h"
#include "platform/api/ble_v2.h"
#include "platform/api/bluetooth_adapter.h"
#include "platform/api/hash_utils.h"
#include "platform/api/lock.h"
#include "platform/byte_array.h"
#include "platform/cancelable_alarm.h"
#include "platform/port/string.h"
#include "platform/prng.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace ble_v2 {
template <typename>
class ProcessOnLostRunnable;
template <typename>
class OnAdvertisementFoundRunnable;
} // namespace ble_v2
template <typename Platform>
class BLEV2 {
public:
explicit BLEV2(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
~BLEV2();
bool isAvailable();
// While the start* functions for each action (advertising, scanning,
// accepting connections) take in a service_id, the stop* and is* functions do
// not. This is because the service_id isn't used. In the java code, shutdown
// calls all the stop* functions w/ a null service_id. The service_id is just
// passed through to the corresponding is* function, which ignores it.
// service_id should be added back in when C++ supports multi-client.
bool startAdvertising(const string& service_id,
ConstPtr<ByteArray> advertisement,
BLEMediumV2::PowerMode::Value power_mode,
const string& fast_advertisement_service_uuid);
void stopAdvertising();
bool startScanning(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
BLEMediumV2::PowerMode::Value power_mode,
const string& fast_advertisement_service_uuid);
void stopScanning();
class AcceptedConnectionCallback {
public:
virtual ~AcceptedConnectionCallback() {}
// TODO(ahlee): Add in connecting logic.
};
bool isAcceptingConnections();
bool startAcceptingConnections(
const string& service_id,
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
void stopAcceptingConnections();
private:
template <typename>
friend class ble_v2::ProcessOnLostRunnable;
template <typename>
friend class ble_v2::OnAdvertisementFoundRunnable;
class GATTAdvertisementFetcherFacade
: public DiscoveredPeripheralTracker<Platform>::GattAdvertisementFetcher {
public:
explicit GATTAdvertisementFetcherFacade(Ptr<BLEV2<Platform>> impl)
: impl_(impl) {}
~GATTAdvertisementFetcherFacade() override {}
Ptr<AdvertisementReadResult<Platform>> fetchGattAdvertisements(
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result)
override {
return impl_->processFetchGattAdvertisementsRequest(
ble_peripheral, num_slots, advertisement_read_result);
}
private:
Ptr<BLEV2<Platform>> impl_;
};
class ScanCallbackFacade : public BLEMediumV2::ScanCallback {
public:
explicit ScanCallbackFacade(Ptr<BLEV2<Platform>> impl) : impl_(impl) {}
~ScanCallbackFacade() override {}
void onAdvertisementFound(
Ptr<BLEPeripheralV2> peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data) override {
impl_->onAdvertisementFoundImpl(peripheral, advertisement_data);
}
private:
Ptr<BLEV2<Platform>> impl_;
};
class ClientGATTConnectionLifecycleCallbackFacade
: public ClientGATTConnectionLifecycleCallback {
public:
explicit ClientGATTConnectionLifecycleCallbackFacade(
Ptr<BLEV2<Platform>> impl)
: impl_(impl) {}
~ClientGATTConnectionLifecycleCallbackFacade() override {}
void onDisconnected(Ptr<ClientGATTConnection> connection) override {
// Avoid leaks.
ScopedPtr<Ptr<ClientGATTConnection>> scoped_connection(connection);
// Nothing else to do for now.
}
private:
Ptr<BLEV2<Platform>> impl_;
};
class ServerGATTConnectionLifecycleCallbackFacade
: public ServerGATTConnectionLifecycleCallback {
public:
explicit ServerGATTConnectionLifecycleCallbackFacade(
Ptr<BLEV2<Platform>> impl)
: impl_(impl) {}
~ServerGATTConnectionLifecycleCallbackFacade() override {}
void onCharacteristicSubscription(
Ptr<ServerGATTConnection> connection,
Ptr<GATTCharacteristic> characteristic) override {
// Avoid leaks. Do not scope the characteristic because it is ref counted
// by the per-platform ble_v2 implementation.
ScopedPtr<Ptr<ServerGATTConnection>> scoped_connection(connection);
// Nothing else to do for now.
}
void onCharacteristicUnsubscription(
Ptr<ServerGATTConnection> connection,
Ptr<GATTCharacteristic> characteristic) override {
// Avoid leaks. Do not scope the characteristic because it is ref counted
// by the per-platform ble_v2 implementation.
ScopedPtr<Ptr<ServerGATTConnection>> scoped_connection(connection);
// Nothing else to do for now.
}
private:
Ptr<BLEV2<Platform>> impl_;
};
struct ScanningInfo {
ScanningInfo(const string& service_id,
Ptr<ScanCallbackFacade> scan_callback_facade,
Ptr<CancelableAlarm<Platform>> on_lost_alarm)
: service_id(service_id),
scan_callback_facade(scan_callback_facade),
on_lost_alarm(on_lost_alarm) {}
~ScanningInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
const string service_id;
ScopedPtr<Ptr<ScanCallbackFacade>> scan_callback_facade;
// TODO(ahlee): Change to recurring cancelable alarm
ScopedPtr<Ptr<CancelableAlarm<Platform>>> on_lost_alarm;
};
struct AdvertisingInfo {
explicit AdvertisingInfo(const string& service_id)
: service_id(service_id) {}
~AdvertisingInfo() {}
const string service_id;
};
struct GATTServerInfo {
GATTServerInfo(Ptr<GATTServer> gatt_server,
Ptr<ServerGATTConnectionLifecycleCallbackFacade>
connection_lifecycle_callback)
: gatt_server(gatt_server),
connection_lifecycle_callback(connection_lifecycle_callback) {}
~GATTServerInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
ScopedPtr<Ptr<GATTServer>> gatt_server;
ScopedPtr<Ptr<ServerGATTConnectionLifecycleCallbackFacade>>
connection_lifecycle_callback;
};
struct AcceptingConnectionsInfo {
explicit AcceptingConnectionsInfo(const string& service_id)
: service_id(service_id) {}
~AcceptingConnectionsInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
const string service_id;
// TODO(ahlee): Fill in.
};
static const std::int32_t kNumAdvertisementSlots;
static const std::int32_t kMaxAdvertisementLength;
static const std::int32_t kDummyServiceIdLength;
static const char* kCopresenceServiceUuid;
static const std::int64_t kOnLostTimeoutMillis;
static const std::int64_t kGattAdvertisementOperationTimeoutMillis;
static const std::int64_t kMinConnectionAttemptRecoveryDurationMillis;
static const std::int32_t kMaxConnectionAttemptRecoveryFuzzDurationMillis;
static const std::uint32_t kDefaultMtu;
static const std::int64_t kAdvertisementUuidMsb;
static const std::int64_t kAdvertisementUuidLsb;
bool isAdvertising();
ConstPtr<ByteArray> createAdvertisementHeader(
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
bool is_fast_advertisement);
bool isScanning();
void onAdvertisementFoundImpl(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data);
void processOnLostTimeout();
Ptr<CancelableAlarm<Platform>> createOnLostAlarm();
bool isAdvertisementGattServerRunning();
bool startAdvertisementGattServer(const string& service_id,
ConstPtr<ByteArray> advertisement);
bool internalStartAdvertisementGattServer(
ConstPtr<ByteArray> legacy_ble_advertisement_bytes,
ConstPtr<ByteArray> ble_advertisement_bytes);
bool generateAdvertisementCharacteristic(
std::int32_t slot, ConstPtr<ByteArray> advertisement,
Ptr<GATTServer> gatt_server);
void stopAdvertisementGattServer();
Ptr<AdvertisementReadResult<Platform>> processFetchGattAdvertisementsRequest(
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result);
Ptr<AdvertisementReadResult<Platform>>
internalReadFromAdvertisementGattServer(
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result);
void offloadFromPlatformThread(Ptr<Runnable> runnable);
// TODO(ahlee): Move these out to utils (also used by
// DiscoveredPeripheralTracker).
ConstPtr<ByteArray> generateAdvertisementHash(
ConstPtr<ByteArray> advertisement_bytes);
ConstPtr<ByteArray> generateServiceIdHash(
BLEAdvertisement::Version::Value version, const string& service_id);
// This maps to a helper function found in bluetoothlowenergy/Utils.java. In
// the C++ code we moved it because it's only used here.
string generateAdvertisementUuid(std::int32_t slot);
// ------------ GENERAL ------------
ScopedPtr<Ptr<Lock>> lock_;
// Where we throw potentially blocking work off of the platform thread.
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType>>
platform_thread_offloader_;
ScopedPtr<Ptr<Prng>> prng_;
ScopedPtr<Ptr<HashUtils>> hash_utils_;
// ------------ CORE BLE ------------
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
// The underlying, per-platform implementation.
ScopedPtr<Ptr<BLEMediumV2>> ble_medium_;
// ------------ DISCOVERY ------------
// scanning_info_ is not scoped because it's nullable.
Ptr<ScanningInfo> scanning_info_;
ScopedPtr<Ptr<DiscoveredPeripheralTracker<Platform>>>
discovered_peripheral_tracker_;
ScopedPtr<Ptr<typename Platform::ScheduledExecutorType>> on_lost_executor_;
// ------------ ADVERTISING ------------
// advertising_info_, gatt_server_info_, and accepting_connections_info_ are
// not scoped because they are nullable.
Ptr<AdvertisingInfo> advertising_info_;
Ptr<GATTServerInfo> gatt_server_info_;
Ptr<AcceptingConnectionsInfo> accepting_connections_info_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/ble_v2.cc"
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_H_
+109
View File
@@ -0,0 +1,109 @@
#include "core/internal/mediums/bloom_filter.h"
#include "absl/numeric/int128.h"
#include "absl/strings/numbers.h"
#include "smhasher/MurmurHash3.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
template <size_t CapacityInBytes>
const std::int32_t BloomFilter<CapacityInBytes>::kHasherNumberOfRepetitions = 5;
template <size_t CapacityInBytes>
BloomFilter<CapacityInBytes>::BloomFilter() : bits_() {}
template <size_t CapacityInBytes>
BloomFilter<CapacityInBytes>::BloomFilter(ConstPtr<ByteArray> bytes) : bits_() {
const char* bytes_read_ptr = bytes->getData();
for (size_t byte_index = 0; byte_index < bytes->size(); byte_index++) {
for (size_t bit_index = 0; bit_index < 8; bit_index++) {
bits_.set((byte_index * 8) + bit_index,
(*bytes_read_ptr >> bit_index) & 0x01);
}
bytes_read_ptr++;
}
}
template <size_t CapacityInBytes>
BloomFilter<CapacityInBytes>::~BloomFilter() {
// Nothing to do.
}
template <size_t CapacityInBytes>
ConstPtr<ByteArray> BloomFilter<CapacityInBytes>::asBytes() {
// Gets a binary string representation of the bitset where the leftmost
// character corresponds to bitset position (total size) - 1.
//
// If the bitset's internal representation is:
// [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11]
// The string representation will be outputted like this:
// "1 0 1 0 1 0 0 0 1 1 0 0"
std::string bitset_binary_string = bits_.to_string();
Ptr<ByteArray> result_bytes{new ByteArray{CapacityInBytes}};
char* result_bytes_write_ptr = result_bytes->getData();
// We go through the string backwards because the rightmost character
// corresponds to position 0 in the bitset.
for (size_t i = bits_.size(); i > 0; i -= 8) {
std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8);
std::uint32_t byte_value;
absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value,
/* base= */ 2);
*result_bytes_write_ptr = static_cast<char>(byte_value & 0x000000FF);
result_bytes_write_ptr++;
}
return ConstifyPtr(result_bytes);
}
template <size_t CapacityInBytes>
void BloomFilter<CapacityInBytes>::add(const std::string& s) {
std::vector<std::int32_t> hashes = getHashes(s);
for (std::vector<std::int32_t>::iterator it = hashes.begin();
it != hashes.end(); ++it) {
size_t position = static_cast<size_t>(*it) % bits_.size();
bits_.set(position);
}
}
template <size_t CapacityInBytes>
bool BloomFilter<CapacityInBytes>::possiblyContains(const std::string& s) {
std::vector<std::int32_t> hashes = getHashes(s);
for (std::vector<std::int32_t>::iterator i = hashes.begin();
i != hashes.end(); ++i) {
size_t position = static_cast<size_t>(*i) % bits_.size();
if (!bits_.test(position)) {
return false;
}
}
return true;
}
template <size_t CapacityInBytes>
std::vector<std::int32_t> BloomFilter<CapacityInBytes>::getHashes(
const std::string& s) {
std::vector<std::int32_t> hashes(kHasherNumberOfRepetitions, 0);
absl::uint128 hash128;
MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128);
std::uint64_t hash64 =
absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash
std::int32_t hash1 = static_cast<std::int32_t>(
hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash
std::int32_t hash2 = static_cast<std::int32_t>(
(hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash
for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) {
std::int32_t combinedHash = static_cast<std::int32_t>(hash1 + (i * hash2));
// Flip all the bits if it's negative (guaranteed positive number)
if (combinedHash < 0) combinedHash = ~combinedHash;
hashes[i - 1] = combinedHash;
}
return hashes;
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+54
View File
@@ -0,0 +1,54 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
#define CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
#include <bitset>
#include <cstdint>
#include <vector>
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
/**
* A bloom filter that gives access to the underlying BitSet. The implementation
* is copied from our Java version of Bloom filter, which in turn copies from
* Guava's BloomFilter.
*
* BloomFilter is templatized on the size of the byte array and not the size of
* the bit set to ensure the bit set's length is a multiple of 8 (and can
* neatly be returned as a ByteArray).
*/
template <size_t CapacityInBytes>
class BloomFilter {
public:
BloomFilter();
explicit BloomFilter(ConstPtr<ByteArray> bytes);
~BloomFilter();
ConstPtr<ByteArray> asBytes();
void add(const std::string& s);
bool possiblyContains(const std::string& s);
private:
static const std::int32_t kHasherNumberOfRepetitions;
std::vector<std::int32_t> getHashes(const std::string& s);
std::bitset<CapacityInBytes * 8> bits_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/bloom_filter.cc"
#endif // CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
@@ -0,0 +1,162 @@
#include "core/internal/mediums/bloom_filter.h"
#include <algorithm>
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
const size_t kByteArrayLength = 100;
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
scoped_bloom_filter->asBytes());
std::string empty_string(kByteArrayLength, '\0');
ASSERT_EQ(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(),
empty_string.size()));
}
TEST(BloomFilterTest, EmptyFilterNeverContains) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
}
TEST(BloomFilterTest, AddSuccess) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
scoped_bloom_filter->add("ELEMENT_1");
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
}
TEST(BloomFilterTest, AddOnlyGivenArg) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
scoped_bloom_filter->add("ELEMENT_1");
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
}
TEST(BloomFilterTest, AddMultipleArgs) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
scoped_bloom_filter->add("ELEMENT_1");
scoped_bloom_filter->add("ELEMENT_2");
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
}
TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) {
ScopedPtr<Ptr<BloomFilter<10>>> scoped_bloom_filter(new BloomFilter<10>());
scoped_bloom_filter->add("ELEMENT_1");
scoped_bloom_filter->add("ELEMENT_2");
scoped_bloom_filter->add("ELEMENT_3");
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
scoped_bloom_filter->asBytes());
std::string empty_string(kByteArrayLength, '\0');
ASSERT_NE(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(),
empty_string.size()));
}
/**
* This test was added because of a bug where the BloomFilter doesn't utilize
* all bits given. Functionally, the filter still works, but we just have a much
* higher false positive rate. The bug was caused by confusing bit length and
* byte length, which made our BloomFilter only set bits on the first byteLength
* (bitLength / 8) bits rather than the whole bitLength bits.
*
* <p>Here, we're verifying that the bits set are somewhat scattered. So instead
* of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting
* something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0].
*/
TEST(BloomFilterTest, RandomnessNoEndBias) {
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
new BloomFilter<kByteArrayLength>());
// Add one element to our BloomFilter.
scoped_bloom_filter->add("ELEMENT_1");
std::int32_t non_zero_count = 0;
std::int32_t longest_zero_streak = 0;
std::int32_t current_zero_streak = 0;
// Record the amount of non-zero bytes and the longest streak of zero bytes in
// the resulting BloomFilter. This is an approximation of reasonable
// distribution since we're recording by bytes instead of bits.
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
scoped_bloom_filter->asBytes());
const char* bloom_filter_bytes_read_ptr =
scoped_bloom_filter_bytes->getData();
for (int i = 0; i < scoped_bloom_filter_bytes->size(); i++) {
if (*bloom_filter_bytes_read_ptr == '\0') {
current_zero_streak++;
} else {
// Increment the number of non-zero bytes we've seen, update the longest
// zero streak, and then reset the current zero streak.
non_zero_count++;
longest_zero_streak = std::max(longest_zero_streak, current_zero_streak);
current_zero_streak = 0;
}
bloom_filter_bytes_read_ptr++;
}
// Update the longest zero streak again for the tail case.
longest_zero_streak = std::min(longest_zero_streak, current_zero_streak);
// Since randomness is hard to measure within one unit test, we instead do a
// sanity check. All non-zero bytes should not be packed into one end of the
// array.
//
// In this case, the size of one end is approximated to be:
// kByteArrayLength / nonZeroCount.
// Therefore, the longest zero streak should be less than:
// kByteArrayLength - one end of the array.
std::int32_t longest_acceptable_zero_streak =
kByteArrayLength - (kByteArrayLength / non_zero_count);
ASSERT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
}
TEST(BloomFilterTest, RandomnessFalsePositiveRate) {
ScopedPtr<Ptr<BloomFilter<10>>> scoped_bloom_filter(new BloomFilter<10>());
// Add 5 distinct elements to the BloomFilter.
scoped_bloom_filter->add("ELEMENT_1");
scoped_bloom_filter->add("ELEMENT_2");
scoped_bloom_filter->add("ELEMENT_3");
scoped_bloom_filter->add("ELEMENT_4");
scoped_bloom_filter->add("ELEMENT_5");
std::int32_t false_positives = 0;
// Now test 100 other elements and record the number of false positives.
for (int i = 5; i < 105; i++) {
false_positives +=
scoped_bloom_filter->possiblyContains("ELEMENT_" + std::to_string(i))
? 1
: 0;
}
// We expect the false positive rate to be 3% with 5 elements in a 10 byte
// filter. Thus, we give a little leeway and verify that the false positive
// rate is no more than 5%.
ASSERT_LE(false_positives, 5);
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,468 @@
#include "core/internal/mediums/bluetooth_classic.h"
#include <utility>
#include "core/internal/mediums/uuid.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
const std::int32_t BluetoothClassic<Platform>::kMaxConcurrentAcceptLoops = 5;
template <typename Platform>
BluetoothClassic<Platform>::BluetoothClassic(
Ptr<BluetoothRadio<Platform>> bluetooth_radio)
: lock_(Platform::createLock()),
bluetooth_radio_(bluetooth_radio),
bluetooth_adapter_(Platform::createBluetoothAdapter()),
bluetooth_classic_medium_(Platform::createBluetoothClassicMedium()),
scan_info_(),
original_scan_mode_(BluetoothAdapter::ScanMode::UNKNOWN),
original_device_name_(),
accept_loops_thread_pool_(
Platform::createMultiThreadExecutor(kMaxConcurrentAcceptLoops)),
bluetooth_server_sockets_() {}
template <typename Platform>
BluetoothClassic<Platform>::~BluetoothClassic() {
stopDiscovery();
for (BluetoothServerSocketMap::iterator it =
bluetooth_server_sockets_.begin();
it != bluetooth_server_sockets_.end(); ++it) {
stopAcceptingConnections(it->first);
}
turnOffDiscoverability();
// All the AcceptLoopRunnable objects in here should already have gotten an
// opportunity to shut themselves down cleanly in the calls to
// stopAcceptingConnections() above.
accept_loops_thread_pool_->shutdown();
original_device_name_.destroy();
scan_info_.destroy();
}
template <typename Platform>
bool BluetoothClassic<Platform>::isAvailable() {
Synchronized s(lock_.get());
return !bluetooth_classic_medium_.isNull() && !bluetooth_adapter_.isNull();
}
template <typename Platform>
bool BluetoothClassic<Platform>::turnOnDiscoverability(
const string& device_name) {
Synchronized s(lock_.get());
if (device_name.empty()) {
// TODO(ahlee): logger.atSevere().log("Refusing to turn on Bluetooth
// discoverability because a null deviceName was passed in.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability
// because Bluetooth isn't enabled.");
return false;
}
if (!isAvailable()) {
// TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability
// because Bluetooth isn't available.");
return false;
}
if (isDiscoverable()) {
// TODO(reznor): log.atSevere().log("Refusing to turn on Bluetooth
// discoverability with device name %s because we're already discoverable
// with device name %s.", deviceName, bluetoothAdapter.getName());
return false;
}
if (!modifyDeviceName(device_name)) {
// TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth
// discoverability because we couldn't set the device name to %s",
// deviceName);
return false;
}
if (!modifyScanMode(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE)) {
// TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth
// discoverability because we couldn't set the scan mode to %d",
// BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
// Don't forget to perform this rollback of the partial state changes we've
// made til now.
restoreDeviceName();
return false;
}
// TODO(reznor): log.atVerbose().log("Turned on Bluetooth discoverability with
// deviceName %s", deviceName);
return true;
}
template <typename Platform>
void BluetoothClassic<Platform>::turnOffDiscoverability() {
Synchronized s(lock_.get());
if (!isDiscoverable()) {
// TODO(reznor): log.atDebug().log("Can't turn off Bluetooth discoverability
// because it was never turned on.");
return;
}
restoreScanMode();
restoreDeviceName();
// TODO(reznor): log.atVerbose().log("Turned Bluetooth discoverability off");
}
template <typename Platform>
bool BluetoothClassic<Platform>::isDiscoverable() const {
return ((!original_device_name_.isNull()) &&
(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE ==
bluetooth_adapter_->getScanMode()));
}
template <typename Platform>
bool BluetoothClassic<Platform>::modifyDeviceName(const string& device_name) {
original_device_name_ = bluetooth_adapter_->getName();
if (!bluetooth_adapter_->setName(device_name)) {
original_device_name_.destroy();
return false;
}
return true;
}
template <typename Platform>
bool BluetoothClassic<Platform>::modifyScanMode(
BluetoothAdapter::ScanMode::Value scan_mode) {
original_scan_mode_ = bluetooth_adapter_->getScanMode();
if (!bluetooth_adapter_->setScanMode(scan_mode)) {
original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN;
return false;
}
return true;
}
template <typename Platform>
void BluetoothClassic<Platform>::restoreScanMode() {
if (!bluetooth_adapter_->setScanMode(original_scan_mode_)) {
// TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth
// scan mode to %d", originalScanMode);
}
// Regardless of whether or not we could actually restore the Bluetooth scan
// mode, reset our relevant state.
original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN;
}
template <typename Platform>
void BluetoothClassic<Platform>::restoreDeviceName() {
if (!bluetooth_adapter_->setName(*original_device_name_)) {
// TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth
// device name to %s", originalDeviceName);
}
// Regardless of whether or not we could actually restore the Bluetooth device
// name, reset the marker that opens us up for business for the next time
// 'round.
original_device_name_.destroy();
}
template <typename Platform>
bool BluetoothClassic<Platform>::startDiscovery(
Ptr<DiscoveredDeviceCallback> discovered_device_callback) {
Synchronized s(lock_.get());
if (discovered_device_callback.isNull()) {
// TODO(reznor): log.atSevere().log("Refusing to start discovery of
// Bluetooth devices because a null discoveredDeviceCallback was passed
// in.");
return false;
}
// Avoid leaks.
ScopedPtr<Ptr<DiscoveredDeviceCallback>> scoped_discovered_device_callback(
discovered_device_callback);
if (!bluetooth_radio_->isEnabled()) {
// TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices
// because Bluetooth isn't enabled.");
return false;
}
if (!isAvailable()) {
// TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices
// because Bluetooth isn't available.");
return false;
}
if (isDiscovering()) {
// TODO(reznor): log.atSevere().log("Refusing to start discovery of
// Bluetooth devices because another discovery is already in-progress.");
return false;
}
// Avoid leaks.
ScopedPtr<Ptr<BluetoothDiscoveryCallback>>
scoped_bluetooth_discovery_callback(new BluetoothDiscoveryCallback(
scoped_discovered_device_callback.get()));
if (!bluetooth_classic_medium_->startDiscovery(
scoped_bluetooth_discovery_callback.get())) {
// TODO(reznor): log.atSevere().log("Failed to start discovery of Bluetooth
// devices.");
return false;
}
// Mark the fact that we're currently performing a Bluetooth scan.
scan_info_ =
MakePtr(new ScanInfo(scoped_discovered_device_callback.release(),
scoped_bluetooth_discovery_callback.release()));
return true;
}
template <typename Platform>
void BluetoothClassic<Platform>::stopDiscovery() {
Synchronized s(lock_.get());
if (!isDiscovering()) {
// TODO(reznor): log.atDebug().log("Can't stop discovery of Bluetooth
// devices because it never started.");
return;
}
if (!bluetooth_classic_medium_->stopDiscovery()) {
// TODO(reznor): log.atWarning().log("Failed to stop discovery of Bluetooth
// devices.");
}
// Regardless of whether or not stopDiscovery() succeeded, destroy scan_info_
// to:
//
// a) Avoid a leak.
// b) Mark the fact that we're no longer performing a Bluetooth discovery.
scan_info_.destroy();
}
template <typename Platform>
bool BluetoothClassic<Platform>::isDiscovering() const {
return !scan_info_.isNull();
}
template <typename Platform>
class AcceptLoopRunnable : public Runnable {
public:
AcceptLoopRunnable(
Ptr<typename BluetoothClassic<Platform>::AcceptedConnectionCallback>
accepted_connection_callback,
Ptr<BluetoothServerSocket> listening_socket, const string& service_name)
: accepted_connection_callback_(accepted_connection_callback),
listening_socket_(listening_socket),
service_name_(service_name) {}
void run() override {
while (true) {
ExceptionOr<Ptr<BluetoothSocket>> bluetooth_socket =
listening_socket_->accept();
if (!bluetooth_socket.ok()) {
if (Exception::IO == bluetooth_socket.exception()) {
Utils::closeSocket(listening_socket_, "Bluetooth", service_name_);
}
break;
}
accepted_connection_callback_->onConnectionAccepted(
bluetooth_socket.result());
}
}
private:
ScopedPtr<
Ptr<typename BluetoothClassic<Platform>::AcceptedConnectionCallback>>
accepted_connection_callback_;
Ptr<BluetoothServerSocket> listening_socket_;
const string service_name_;
};
template <typename Platform>
bool BluetoothClassic<Platform>::startAcceptingConnections(
const string& service_name,
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<Ptr<AcceptedConnectionCallback>>
scoped_accepted_connection_callback(accepted_connection_callback);
if (scoped_accepted_connection_callback.isNull() || service_name.empty()) {
// TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth
// connections because at least one of serviceName or
// acceptedConnectionCallback is null.");
return false;
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(reznor): log.atSevere().log("Can't create Bluetooth server socket
// for %s because Bluetooth isn't enabled.", serviceName);
return false;
}
if (!isAvailable()) {
// TODO(reznor): log.atSevere().log("Can't start accepting BLuetooth
// connections for %s because Bluetooth isn't available.", serviceName);
return false;
}
if (isAcceptingConnections(service_name)) {
// TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth
// connections for %s because a Bluetooth server is already in-progress for
// that service name.", serviceName);
return false;
}
ExceptionOr<Ptr<BluetoothServerSocket>> listening_socket =
bluetooth_classic_medium_->listenForService(
service_name, generateUUIDFromString(service_name));
if (!listening_socket.ok()) {
if (Exception::IO == listening_socket.exception()) {
// TODO(reznor): log.atSevere().withCause(e).log("Failed to start
// accepting Bluetooth connections for %s.", serviceName);
return false;
}
}
// Start the accept loop on a dedicated thread - this stays alive and
// listening for new incoming connections until stopAcceptingConnections() is
// invoked.
accept_loops_thread_pool_->execute(MakePtr(new AcceptLoopRunnable<Platform>(
scoped_accepted_connection_callback.release(), listening_socket.result(),
service_name)));
// Mark the fact that there's an in-progress Bluetooth server accepting
// connections.
bluetooth_server_sockets_.insert(
std::make_pair(service_name, listening_socket.result()));
return true;
}
template <typename Platform>
bool BluetoothClassic<Platform>::isAcceptingConnections(
const string& service_name) {
Synchronized s(lock_.get());
return bluetooth_server_sockets_.find(service_name) !=
bluetooth_server_sockets_.end();
}
template <typename Platform>
void BluetoothClassic<Platform>::stopAcceptingConnections(
const string& service_name) {
Synchronized s(lock_.get());
if (service_name.empty()) {
// TODO(ahlee): logger.atSevere().log("Unable to stop accepting Bluetooth
// connections because the serviceName is empty.");
return;
}
if (!isAcceptingConnections(service_name)) {
// TODO(reznor): log.atDebug().log("Can't stop accepting Bluetooth
// connections for %s because it was never started.", serviceName);
return;
}
// Closing the BluetoothServerSocket will kick off the suicide of the thread
// in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept().
// That may take some time to complete, but there's no particular reason to
// wait around for it.
BluetoothServerSocketMap::iterator listening_socket_iter =
bluetooth_server_sockets_.find(service_name);
// Store a handle to the BluetoothServerSocket, so we can use it after
// removing the entry from bluetooth_server_sockets_; making it scoped
// is a bonus that takes care of deallocation before we leave this method.
ScopedPtr<Ptr<BluetoothServerSocket>> scoped_listening_socket(
listening_socket_iter->second);
// Regardless of whether or not we fail to close the existing
// BluetoothServerSocket, remove it from bluetooth_server_sockets_ so that it
// frees up this service for another round.
bluetooth_server_sockets_.erase(listening_socket_iter);
// Finally, close the BluetoothServerSocket.
Exception::Value e = scoped_listening_socket->close();
if (Exception::NONE != e) {
if (Exception::IO == e) {
// TODO(reznor): log.atSevere().withCause(e).log("Failed to close
// Bluetooth server socket for %s.", serviceName);
}
}
}
template <typename Platform>
Ptr<BluetoothSocket> BluetoothClassic<Platform>::connect(
Ptr<BluetoothDevice> bluetooth_device, const string& service_name) {
Synchronized s(lock_.get());
if (bluetooth_device.isNull() || service_name.empty()) {
// TODO(reznor): log.atSevere().log("Refusing to create client Bluetooth
// socket because at least one of bluetoothDevice or serviceName is null.");
return Ptr<BluetoothSocket>();
}
if (!bluetooth_radio_->isEnabled()) {
// TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to
// %s because Bluetooth isn't enabled.", bluetoothSocketName);
return Ptr<BluetoothSocket>();
}
if (!isAvailable()) {
// TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to
// %s because Bluetooth isn't available.", bluetoothSocketName);
return Ptr<BluetoothSocket>();
}
// WARNING WARNING WARNING
//
// This block deviates from the corresponding Java code.
//
// In Java, we pause an in-progress discovery before attempting this
// connection, and then resume it after, but the memory management of the
// DiscoveredDeviceCallback is complicated in C++, and would need a severe
// deviation from the Java code, so we're choosing the lesser of 2 evils, and
// introducing this (simplifying) deviation instead -- also, this deviation is
// fairly inconsequential since we don't yet have a use-case that needs a
// device that:
//
// a) uses the C++ code,
// b) has Bluetooth Classic support, and
// c) plays the role of Discoverer.
ExceptionOr<Ptr<BluetoothSocket>> bluetooth_socket =
bluetooth_classic_medium_->connectToService(
bluetooth_device, generateUUIDFromString(service_name));
if (!bluetooth_socket.ok()) {
if (Exception::IO == bluetooth_socket.exception()) {
// TODO(reznor): log.atSevere().log("Failed to connect via Bluetooth
// socket to %s.", bluetoothSocketName);
}
return Ptr<BluetoothSocket>();
}
return bluetooth_socket.result();
}
template <typename Platform>
string BluetoothClassic<Platform>::generateUUIDFromString(const string& data) {
return UUID<Platform>(data).str();
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,169 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
#include <cstdint>
#include <map>
#include "core/internal/mediums/bluetooth_radio.h"
#include "core/internal/mediums/utils.h"
#include "platform/api/bluetooth_adapter.h"
#include "platform/api/bluetooth_classic.h"
#include "platform/api/lock.h"
#include "platform/api/multi_thread_executor.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "platform/runnable.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class BluetoothClassic {
public:
explicit BluetoothClassic(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
~BluetoothClassic();
bool isAvailable();
bool turnOnDiscoverability(const string& device_name);
void turnOffDiscoverability();
// Callback that is invoked when a nearby Bluetooth device is discovered.
class DiscoveredDeviceCallback {
public:
virtual ~DiscoveredDeviceCallback() {}
virtual void onDeviceDiscovered(Ptr<BluetoothDevice> device) = 0;
virtual void onDeviceNameChanged(Ptr<BluetoothDevice> device) = 0;
virtual void onDeviceLost(Ptr<BluetoothDevice> device) = 0;
};
bool startDiscovery(Ptr<DiscoveredDeviceCallback> discovered_device_callback);
void stopDiscovery();
// Callback that is invoked when a new connection is accepted.
class AcceptedConnectionCallback {
public:
virtual ~AcceptedConnectionCallback() {}
virtual void onConnectionAccepted(Ptr<BluetoothSocket> socket) = 0;
};
bool startAcceptingConnections(
const string& service_name,
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
bool isAcceptingConnections(const string& service_name);
void stopAcceptingConnections(const string& service_name);
Ptr<BluetoothSocket> connect(Ptr<BluetoothDevice> bluetooth_device,
const string& service_name);
private:
class BluetoothDiscoveryCallback
: public BluetoothClassicMedium::DiscoveryCallback {
public:
explicit BluetoothDiscoveryCallback(
Ptr<DiscoveredDeviceCallback> discovered_device_callback)
: discovered_device_callback_(discovered_device_callback) {}
~BluetoothDiscoveryCallback() override {
// Nothing to do.
}
void onDeviceDiscovered(Ptr<BluetoothDevice> bluetooth_device) override {
discovered_device_callback_->onDeviceDiscovered(bluetooth_device);
}
void onDeviceNameChanged(Ptr<BluetoothDevice> bluetooth_device) override {
discovered_device_callback_->onDeviceNameChanged(bluetooth_device);
}
void onDeviceLost(Ptr<BluetoothDevice> bluetooth_device) override {
discovered_device_callback_->onDeviceLost(bluetooth_device);
}
private:
// This could well have been a ScopedPtr, with BluetoothDiscoveryCallback in
// turn being owned by ScanInfo (and it would have been cleaner overall,
// since the chain of wrapped callbacks starting from
// BluetoothDiscoveryCallback would then destruct like a stack of dominoes
// falling, triggered by the destruction of ScanInfo), but we instead give
// ownership of this DiscoveredDeviceCallback *and*
// BluetoothDiscoveryCallback to ScanInfo, to maintain compatibility with
// the Java code.
Ptr<DiscoveredDeviceCallback> discovered_device_callback_;
};
struct ScanInfo {
ScanInfo(Ptr<DiscoveredDeviceCallback> discovered_device_callback,
Ptr<BluetoothDiscoveryCallback> bluetooth_discovery_callback)
: discovered_device_callback(discovered_device_callback),
bluetooth_discovery_callback(bluetooth_discovery_callback) {}
~ScanInfo() {
// Nothing to do (the ScopedPtr members take care of themselves).
}
// Stores the DiscoveredDeviceCallback passed in to startDiscovery() by
// clients so that we can internally stop and start Bluetooth scans
// transparently as needed (for example, when a call to connect() is
// invoked).
ScopedPtr<Ptr<DiscoveredDeviceCallback>> discovered_device_callback;
// The ordering of bluetooth_discovery_callback_ coming after
// discovered_device_callback_ is very deliberate --
// bluetooth_discovery_callback_ contains a reference to
// discovered_device_callback_, so it should be destroyed first.
ScopedPtr<Ptr<BluetoothDiscoveryCallback>> bluetooth_discovery_callback;
};
static string generateUUIDFromString(const string& data);
static const std::int32_t kMaxConcurrentAcceptLoops;
bool isDiscoverable() const;
bool modifyDeviceName(const string& device_name);
bool modifyScanMode(BluetoothAdapter::ScanMode::Value scan_mode);
void restoreScanMode();
void restoreDeviceName();
bool isDiscovering() const;
// ------------ GENERAL ------------
ScopedPtr<Ptr<Lock>> lock_;
// ------------ CORE BLUETOOTH ------------
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
// The underlying, per-platform implementation.
ScopedPtr<Ptr<BluetoothClassicMedium>> bluetooth_classic_medium_;
// ------------ DISCOVERY ------------
// A bundle of state required to do a Bluetooth Classic scan. When non-null,
// we are currently performing a Bluetooth scan.
Ptr<ScanInfo> scan_info_;
// ------------ ADVERTISING ------------
// The original scan mode (that controls visibility to scanners) of the device
// before we modified it. Restored when we stop advertising.
BluetoothAdapter::ScanMode::Value original_scan_mode_;
// The original Bluetooth device name, before we modified it. If non-null, we
// are currently Bluetooth discoverable. Restored when we stop advertising.
Ptr<string> original_device_name_;
// A thread pool dedicated to running all the accept loops from
// startAcceptingConnections().
ScopedPtr<Ptr<typename Platform::MultiThreadExecutorType>>
accept_loops_thread_pool_;
// A map of service name -> ServerSocket. While this map is non-empty, we
// are currently listening for incoming connections.
typedef std::map<string, Ptr<BluetoothServerSocket>> BluetoothServerSocketMap;
BluetoothServerSocketMap bluetooth_server_sockets_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/bluetooth_classic.cc"
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
@@ -0,0 +1,122 @@
#include "core/internal/mediums/bluetooth_radio.h"
#include "platform/exception.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
std::int64_t BluetoothRadio<Platform>::kPauseBetweenToggleDurationMillis = 3000;
template <typename Platform>
BluetoothRadio<Platform>::BluetoothRadio()
: bluetooth_adapter_(Platform::createBluetoothAdapter()),
thread_utils_(Platform::createThreadUtils()),
originally_enabled_() {
if (bluetooth_adapter_.isNull()) {
// TODO(reznor): log.atSevere().log("Failed to retrieve default
// BluetoothAdapter, Bluetooth is unsupported.");
}
}
template <typename Platform>
BluetoothRadio<Platform>::~BluetoothRadio() {
// We never enabled Bluetooth, nothing to do.
if (originally_enabled_.isNull()) {
return;
}
// Make sure we cleanup the one non-ScopedPtr member before we leave the
// destructor.
ScopedPtr<Ptr<AtomicBoolean> > scoped_originally_enabled(originally_enabled_);
// Toggle Bluetooth regardless of our original state. Some devices/chips can
// start to freak out after some time (e.g. b/37775337), and this helps to
// ensure BT resets properly.
toggle();
if (!setBluetoothState(originally_enabled_->get())) {
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth back to its
// original state.");
}
}
template <typename Platform>
bool BluetoothRadio<Platform>::enable() {
if (!saveOriginalState()) {
return false;
}
return setBluetoothState(true);
}
template <typename Platform>
bool BluetoothRadio<Platform>::disable() {
if (!saveOriginalState()) {
return false;
}
return setBluetoothState(false);
}
template <typename Platform>
bool BluetoothRadio<Platform>::isEnabled() {
return !bluetooth_adapter_.isNull() && isInDesiredState(true);
}
template <typename Platform>
void BluetoothRadio<Platform>::toggle() {
if (!saveOriginalState()) {
return;
}
if (!setBluetoothState(false)) {
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth off while
// toggling state.");
}
if (Exception::INTERRUPTED ==
thread_utils_->sleep(kPauseBetweenToggleDurationMillis)) {
// TODO(reznor): log.atSevere().withCause(e).log("Interrupted while waiting
// in between a Bluetooth toggle.");
return;
}
if (!setBluetoothState(true)) {
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth on while
// toggling state.");
}
}
template <typename Platform>
bool BluetoothRadio<Platform>::setBluetoothState(bool enable) {
return bluetooth_adapter_->setStatus(
enable ? BluetoothAdapter::Status::ENABLED
: BluetoothAdapter::Status::DISABLED);
}
template <typename Platform>
bool BluetoothRadio<Platform>::isInDesiredState(bool should_be_enabled) const {
return ((should_be_enabled && bluetooth_adapter_->isEnabled()) ||
(!should_be_enabled && !bluetooth_adapter_->isEnabled()));
}
template <typename Platform>
bool BluetoothRadio<Platform>::saveOriginalState() {
if (bluetooth_adapter_.isNull()) {
return false;
}
// If we haven't saved the original state of the radio, save it.
if (originally_enabled_.isNull()) {
originally_enabled_ =
Platform::createAtomicBoolean(bluetooth_adapter_->isEnabled());
}
return true;
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,69 @@
#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
#include <cstdint>
#include "platform/api/atomic_boolean.h"
#include "platform/api/bluetooth_adapter.h"
#include "platform/api/thread_utils.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Provides the operations that can be performed on the Bluetooth radio.
template <typename Platform>
class BluetoothRadio {
public:
BluetoothRadio();
// Reverts the Bluetooth radio to its original state.
~BluetoothRadio();
// Enables Bluetooth.
//
// This must be called before attempting to invoke any other methods of
// this class.
//
// Returns true if enabled successfully.
bool enable();
// Disables Bluetooth.
//
// Returns true if disabled successfully.
bool disable();
// Returns true if the Bluetooth radio is currently enabled.
bool isEnabled();
void toggle();
private:
static std::int64_t kPauseBetweenToggleDurationMillis;
bool setBluetoothState(bool enable);
bool isInDesiredState(bool should_be_enabled) const;
// To be called in enable(), disable(), and toggle(). This will remember the
// original state of the radio before any radio state has been modified.
// Returns false if Bluetooth doesn't exist on the device and the state cannot
// be obtained.
bool saveOriginalState();
// Null if the device does not support Bluetooth.
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
ScopedPtr<Ptr<ThreadUtils>> thread_utils_;
// The Bluetooth radio's original state, before we modified it. True if
// originally enabled, false if originally disabled, null if we never modified
// the radio state. We restore the radio to its original state in the
// destructor.
//
// This is a Ptr instead of a ScopedPtr because it's lazily initialized
// (and ScopedPtr doesn't support re-assignment).
Ptr<AtomicBoolean> originally_enabled_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/bluetooth_radio.cc"
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
@@ -0,0 +1,32 @@
#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
#include "core/internal/mediums/ble_peripheral.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
/** Callback that is invoked when a {@link BLEPeripheral} is discovered. */
class DiscoveredPeripheralCallback {
public:
virtual ~DiscoveredPeripheralCallback() {}
virtual void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id,
ConstPtr<ByteArray> advertisement,
bool is_fast_advertisement) = 0;
virtual void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
const string& service_id);
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
@@ -0,0 +1,744 @@
#include "core/internal/mediums/discovered_peripheral_tracker.h"
#include "core/internal/mediums/ble_packet.h"
#include "core/internal/mediums/bloom_filter.h"
#include "core/internal/mediums/utils.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace dpt {
template <typename K, typename V>
void eraseOwnedPtrFromMap(std::map<K, V>& m, const K& k) {
typename std::map<K, V>::iterator it = m.find(k);
if (it != m.end()) {
it->second.destroy();
m.erase(it);
}
}
template <typename K, typename V>
void eraseAllOwnedPtrsFromMap(std::map<K, Ptr<V>>& m) {
for (typename std::map<K, Ptr<V>>::iterator it = m.begin(); it != m.end();
++it) {
it->second.destroy();
}
m.clear();
}
template <typename K, typename V>
V removeOwnedPtrFromMap(std::map<K, V>& m, const K& k) {
V removed_ptr;
typename std::map<K, V>::iterator it = m.find(k);
if (it != m.end()) {
removed_ptr = it->second;
m.erase(it);
}
return removed_ptr;
}
} // namespace dpt
// The maximum number of advertisement slots to assume if we don't know the
// exact number.
template <typename Platform>
const std::int32_t DiscoveredPeripheralTracker<Platform>::kMaxSlots = 10;
// Amount of time to wait before attempting a connection. This is needed to
// prevent the GATT server from operation overload if we just came from a GATT
// discovery.
template <typename Platform>
const std::int64_t
DiscoveredPeripheralTracker<Platform>::kMinConnectionDelayMillis =
5 * 1000; // 5 seconds
template <typename Platform>
const char* DiscoveredPeripheralTracker<Platform>::kCopresenceServiceUuid =
"0000FEF3-0000-1000-8000-00805F9B34FB";
template <typename Platform>
DiscoveredPeripheralTracker<Platform>::DiscoveredPeripheralTracker()
: lock_(Platform::createLock()),
thread_utils_(Platform::createThreadUtils()),
system_clock_(Platform::createSystemClock()),
hash_utils_(Platform::createHashUtils()),
discovered_peripheral_callbacks_(),
lost_entity_trackers_(),
fast_advertisement_service_uuids_(),
advertisement_read_results_(),
gatt_advertisements_(),
advertisement_service_ids_(),
advertisement_headers_(),
mac_addresses_() {}
template <typename Platform>
DiscoveredPeripheralTracker<Platform>::~DiscoveredPeripheralTracker() {
Synchronized s(lock_.get());
mac_addresses_.clear();
advertisement_headers_.clear();
advertisement_service_ids_.clear();
// gatt_advertisements_ maps a string to a Ptr to a set of ConstPtrs. We do
// not go and iterate through every set because those values are RefCounted.
dpt::eraseAllOwnedPtrsFromMap(gatt_advertisements_);
dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_);
fast_advertisement_service_uuids_.clear();
dpt::eraseAllOwnedPtrsFromMap(lost_entity_trackers_);
dpt::eraseAllOwnedPtrsFromMap(discovered_peripheral_callbacks_);
}
// Starts tracking discoveries for a particular service ID.
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::startTracking(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
const string& fast_advertisement_service_uuid) {
Synchronized s(lock_.get());
dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id);
discovered_peripheral_callbacks_.insert(
std::make_pair(service_id, discovered_peripheral_callback));
// We create a new LostEntityTracker because any pre-existing ones only
// contain stale advertisements. LostEntityTracker also doesn't provide a
// reset method, so creating a new one is the right way to go.
dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id);
lost_entity_trackers_.insert(std::make_pair(
service_id,
MakePtr(new LostEntityTracker<Platform, BLEAdvertisement>())));
if (!fast_advertisement_service_uuid.empty()) {
fast_advertisement_service_uuids_.erase(service_id);
fast_advertisement_service_uuids_.insert(
std::make_pair(service_id, fast_advertisement_service_uuid));
}
// Clear all of the GATT read results. With this cleared, we will now attempt
// to reconnect to every peripheral we see, giving us a chance to search for
// the new service we're now tracking.
// See the documentation of advertisementReadResults for more information.
dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_);
// Remove stale data from any previous sessions.
clearDataForServiceId(service_id);
}
// Stops tracking discoveries for a particular service ID.
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::stopTracking(
const string& service_id) {
Synchronized s(lock_.get());
fast_advertisement_service_uuids_.erase(service_id);
dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id);
dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id);
}
// Processes a found BLE advertisement.
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::processFoundBleAdvertisement(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
Synchronized s(lock_.get());
// Avoid leaks.
ScopedPtr<ConstPtr<BLEAdvertisementData>> scoped_advertisement_data(
advertisement_data);
ScopedPtr<Ptr<GattAdvertisementFetcher>> scoped_gatt_advertisement_fetcher(
gatt_advertisement_fetcher);
if (getTrackedServiceIds().empty()) {
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header
// because we are not tracking any service IDs.");
return;
}
if (ble_peripheral.isNull() || scoped_advertisement_data.isNull()) {
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header
// because the given BleSighting is null or incomplete.");
return;
}
handleFastAdvertisement(ble_peripheral, scoped_advertisement_data.get());
handleAdvertisementHeader(ble_peripheral, scoped_advertisement_data.get(),
scoped_gatt_advertisement_fetcher.get());
}
// Processes the set of lost GATT advertisements and notifies the client of any
// lost peripherals.
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::processLostGattAdvertisements() {
Synchronized s(lock_.get());
std::set<string> tracked_service_ids = getTrackedServiceIds();
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
tsi_it != tracked_service_ids.end(); ++tsi_it) {
BLEAdvertisementSet lost_gatt_advertisements =
lost_entity_trackers_.find(*tsi_it)->second->computeLostEntities();
// Clear the map state for each lost GATT advertisement and report it to the
// client.
for (BLEAdvertisementSet::iterator lga_it =
lost_gatt_advertisements.begin();
lga_it != lost_gatt_advertisements.end(); ++lga_it) {
clearGattAdvertisement(*lga_it);
discovered_peripheral_callbacks_.find(*tsi_it)->second->onPeripheralLost(
generateBlePeripheral(*lga_it), *tsi_it);
}
}
}
template <typename Platform>
Ptr<BLEPeripheral> DiscoveredPeripheralTracker<Platform>::generateBlePeripheral(
ConstPtr<BLEAdvertisement> gatt_advertisement) {
// TODO(ahlee): Reminder to port over deviceToken change.
return MakePtr(new BLEPeripheral(BLEAdvertisement::toBytes(
gatt_advertisement->getVersion(), gatt_advertisement->getSocketVersion(),
gatt_advertisement->getServiceIdHash(), gatt_advertisement->getData())));
}
template <typename Platform>
std::set<string> DiscoveredPeripheralTracker<Platform>::getTrackedServiceIds() {
std::set<string> tracked_service_ids;
for (DiscoveredPeripheralCallbackMap::iterator dpc_it =
discovered_peripheral_callbacks_.begin();
dpc_it != discovered_peripheral_callbacks_.end(); ++dpc_it) {
tracked_service_ids.insert(dpc_it->first);
}
return tracked_service_ids;
}
// Note: There is no C++ equivalent for getTrackedGattAdvertisements() because
// we make a copy of the subset of the keys in directly in
// clearDataForServiceId().
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::clearDataForServiceId(
const string& service_id) {
BLEAdvertisementSet gatt_advertisements_to_clear;
for (AdvertisementServiceIdMap::iterator it =
advertisement_service_ids_.begin();
it != advertisement_service_ids_.end(); ++it) {
if (it->second != service_id) {
continue;
}
gatt_advertisements_to_clear.insert(it->first);
}
for (BLEAdvertisementSet::iterator it = gatt_advertisements_to_clear.begin();
it != gatt_advertisements_to_clear.end(); ++it) {
clearGattAdvertisement(*it);
}
}
// Clears out all data related to the provided GATT advertisement. This
// includes:
// 1. Removing the GATT advertisement from GATT advertisement keyed maps. This
// includes advertisementServiceIds, AdvertisementHeaders, and
// macAddresses.
// 2. Removing the corresponding advertisement header from
// advertisementReadResults.
// 3. Removing the corresponding advertisement header from gattAdvertisements,
// only if there are no remaining GATT advertisements related to that
// header.
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::clearGattAdvertisement(
ConstPtr<BLEAdvertisement> gatt_advertisement) {
// BLEAdvertisement is RefCounted, so it does not need to be scoped.
advertisement_service_ids_.erase(gatt_advertisement);
mac_addresses_.erase(gatt_advertisement);
ConstPtr<BLEAdvertisementHeader> advertisement_header =
dpt::removeOwnedPtrFromMap(advertisement_headers_, gatt_advertisement);
typename GattAdvertisementMap::iterator ga_it =
gatt_advertisements_.find(advertisement_header);
if (ga_it != gatt_advertisements_.end()) {
// Remove the GATT advertisement from the advertisement header it's
// associated with.
Ptr<BLEAdvertisementSet> header_gatt_advertisements = ga_it->second;
header_gatt_advertisements->erase(gatt_advertisement);
// Unconditionally remove the header from advertisementReadResults so we
// can attempt to reread the GATT advertisement if they return.
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
advertisement_header);
// If there are no more tracked GATT advertisements under this header, go
// ahead and remove it from gattAdvertisements.
if (header_gatt_advertisements->empty()) {
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header);
}
}
}
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::handleFastAdvertisement(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data) {
// Extract the fast advertisement bytes, if any.
ScopedPtr<ConstPtr<ByteArray>> fast_advertisement_bytes(
extractFastAdvertisementBytes(advertisement_data));
if (fast_advertisement_bytes.isNull()) {
return;
}
// Create a header tied to this fast advertisement. This helps us track the
// advertisement when reporting it as lost or connecting.
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> fast_advertisement_header =
createFastAdvertisementHeader(fast_advertisement_bytes.get());
// Process the fast advertisement like we would a GATT advertisement and
// insert a placeholder AdvertisementReadResult.
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
fast_advertisement_header);
advertisement_read_results_.insert(
std::make_pair(fast_advertisement_header,
MakePtr(new AdvertisementReadResult<Platform>())));
std::set<ConstPtr<ByteArray>> fast_advertisement_bytes_set;
fast_advertisement_bytes_set.insert(fast_advertisement_bytes.get());
handleRawGattAdvertisements(fast_advertisement_header,
fast_advertisement_bytes_set,
/* are_fast_advertisements= */ true);
updateCommonStateForFoundBleAdvertisement(fast_advertisement_header,
ble_peripheral->getId());
}
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::handleAdvertisementHeader(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
// Attempt to parse the advertisement header.
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header =
BLEAdvertisementHeader::fromString(
extractAdvertisementHeaderBytes(ble_peripheral, advertisement_data));
if (advertisement_header.isNull()) {
// TODO(ahlee) logger.atVerbose().log("Failed to deserialize BLE
// advertisement header %s. Ignoring.",
// bytesToString(advertisementHeaderBytes));
return;
}
// Check if the advertisement header contains a service ID we're tracking.
if (!isInterestingAdvertisementHeader(advertisement_header)) {
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header %s
// because it does not contain any service IDs we're interested in.",
// advertisementHeader);
return;
}
// Determine whether or not we need to read a fresh GATT advertisement.
if (shouldReadFromAdvertisementGattServer(advertisement_header)) {
// Determine whether or not we need to read a fresh GATT advertisement.
std::set<ConstPtr<ByteArray>> raw_gatt_advertisements =
fetchRawGattAdvertisements(ble_peripheral, advertisement_header,
gatt_advertisement_fetcher);
if (!raw_gatt_advertisements.empty()) {
handleRawGattAdvertisements(advertisement_header, raw_gatt_advertisements,
/* are_fast_advertisements= */ false);
}
}
// Regardless of whether or not we read a new GATT advertisement, the maps
// should now be up-to-date. With this information, do some general
// housekeeping.
updateCommonStateForFoundBleAdvertisement(
advertisement_header, /* mac_address= */ ble_peripheral->getId());
}
template <typename Platform>
string DiscoveredPeripheralTracker<Platform>::extractAdvertisementHeaderBytes(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data) {
ConstPtr<ByteArray> service_data;
std::map<string, ConstPtr<ByteArray>>::const_iterator sd_it =
advertisement_data->service_data.find(kCopresenceServiceUuid);
if (sd_it != advertisement_data->service_data.end()) {
service_data = sd_it->second;
}
const string& local_name = advertisement_data->local_name; // alias
// A valid advertisement header lives in either the local name (iOS) or the
// service data (Android).
if (!service_data.isNull()) {
// TODO(ahlee) logger.atVerbose().log("Service data found on possible
// Android BLE peripheral at address %s",
// bleSighting.getDevice().getAddress());
return string(service_data->getData(), service_data->size());
} else if (!local_name.empty()) {
// TODO(ahlee) logger.atVerbose().log("Local name found on possible iOS BLE
// peripheral at address %s", bleSighting.getDevice().getAddress());
return local_name;
} else {
// iOS peripherals have a bug where the local name sometimes doesn't appear.
// In that case, we should still take a look at the advertisement in case
// there's something valuable on the peripheral's GATT server.
// TODO(ahlee) logger.atVerbose().log("BLE advertisement found with no
// service data or local name from BLE peripheral at address %s (could be a
// buggy iOS peripheral with a missing local name).",
// bleSighting.getDevice().getAddress());
// Create a phony BloomFilter that always contains the service ID we're
// looking for.
return createDummyAdvertisementHeaderBytes(ble_peripheral);
}
}
template <typename Platform>
ConstPtr<ByteArray>
DiscoveredPeripheralTracker<Platform>::extractFastAdvertisementBytes(
ConstPtr<BLEAdvertisementData> advertisement_data) {
ConstPtr<ByteArray> fast_advertisement_bytes;
// Iterate through all tracked service IDs to see if any of their fast
// advertisements are contained within this BLE advertisement.
std::set<string> tracked_service_ids = getTrackedServiceIds();
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
tsi_it != tracked_service_ids.end(); ++tsi_it) {
// First, check if a service UUID is tied to this service ID.
typename FastAdvertisementServiceUUIDMap::iterator fasu_it =
fast_advertisement_service_uuids_.find(*tsi_it);
if (fasu_it != fast_advertisement_service_uuids_.end()) {
const string& fast_advertisement_service_uuid = fasu_it->second; // alias
// Then, check if there's service data for this fast advertisement
// service UUID. If so, we can short-circuit since all BLE
// advertisements can contain at most ONE fast advertisement.
typename std::map<string, ConstPtr<ByteArray>>::const_iterator sd_it =
advertisement_data->service_data.find(
fast_advertisement_service_uuid);
if (sd_it != advertisement_data->service_data.end()) {
// TODO(b/117432693): Remove this copy once Ptr is fully RefCounted.
fast_advertisement_bytes = MakeConstPtr(
new ByteArray(sd_it->second->getData(), sd_it->second->size()));
break;
}
}
}
return fast_advertisement_bytes;
}
// Creates an advertisement header that's purely a hash of the fast
// advertisement, since they come with no header.
template <typename Platform>
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>
DiscoveredPeripheralTracker<Platform>::createFastAdvertisementHeader(
ConstPtr<ByteArray> fast_advertisement_bytes) {
// Our end goal is to have a fully zeroed-out byte array of the correct length
// representing an empty bloom filter.
// TODO(b/149938110): remove ScopedPtr.
ScopedPtr<ConstPtr<ByteArray>> bloom_filter_bytes{ConstPtr<ByteArray>{
new ByteArray{BLEAdvertisementHeader::kServiceIdBloomFilterLength}}};
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
generateAdvertisementHash(fast_advertisement_bytes));
return MakeRefCountedConstPtr(new BLEAdvertisementHeader(
BLEAdvertisementHeader::Version::V2, /* num_slots= */ 1,
bloom_filter_bytes.get(), advertisement_hash.get()));
}
// Creates a dummy advertisement header that possibly contains all tracked
// service IDs.
template <typename Platform>
string
DiscoveredPeripheralTracker<Platform>::createDummyAdvertisementHeaderBytes(
Ptr<BLEPeripheralV2> ble_peripheral) {
// Put the service ID along with the dummy service ID into our bloom filter
// Note: BloomFilter length should always match
// BLEAdvertisementHeader::kServiceIdBloomFilterLength
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(new BloomFilter<10>());
std::set<string> tracked_service_ids = getTrackedServiceIds();
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
tsi_it != tracked_service_ids.end(); ++tsi_it) {
bloom_filter->add(*tsi_it);
}
const string& ble_peripheral_id = ble_peripheral->getId(); // alias
ScopedPtr<ConstPtr<ByteArray>> ble_peripheral_id_bytes(MakeConstPtr(
new ByteArray(ble_peripheral_id.data(), ble_peripheral_id.size())));
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
generateAdvertisementHash(ble_peripheral_id_bytes.get()));
return BLEAdvertisementHeader::asString(BLEAdvertisementHeader::Version::V2,
kMaxSlots, bloom_filter->asBytes(),
advertisement_hash.get());
}
template <typename Platform>
bool DiscoveredPeripheralTracker<Platform>::isInterestingAdvertisementHeader(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header) {
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(
new BloomFilter<10>(advertisement_header->getServiceIdBloomFilter()));
std::set<string> tracked_service_ids = getTrackedServiceIds();
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
tsi_it != tracked_service_ids.end(); ++tsi_it) {
if (bloom_filter->possiblyContains(*tsi_it)) {
return true;
}
}
return false;
}
template <typename Platform>
bool DiscoveredPeripheralTracker<Platform>::
shouldReadFromAdvertisementGattServer(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>
advertisement_header) {
// Check if we have never seen this header. New headers should always be read.
typename AdvertisementReadResultMap::iterator arr_it =
advertisement_read_results_.find(advertisement_header);
if (arr_it == advertisement_read_results_.end()) {
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but
// we have never seen it before. Will try reading its GATT advertisement.",
// advertisementHeader);
return true;
}
// Extract the last read result for this particular header.
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result =
arr_it->second; // alias
// Now evaluate if we should retry reading.
switch (advertisement_read_result->evaluateRetryStatus()) {
case AdvertisementReadResult<Platform>::RetryStatus::RETRY:
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s.
// Will retry reading its GATT advertisement.", advertisementHeader);
return true;
case AdvertisementReadResult<Platform>::RetryStatus::PREVIOUSLY_SUCCEEDED:
// TODO(ahlee) logger.atVerbose().log("Received advertisement header %s,
// but we have already read its GATT advertisement.",
// advertisementHeader);
return false;
case AdvertisementReadResult<Platform>::RetryStatus::TOO_SOON:
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but
// we have recently failed to read its GATT advertisement.",
// advertisementHeader);
return false;
case AdvertisementReadResult<Platform>::RetryStatus::UNKNOWN:
// Fall through.
break;
}
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but we
// do not know whether or not to retry reading its GATT advertisement. Will
// retry to be safe.", advertisementHeader);
return true;
}
template <typename Platform>
std::set<ConstPtr<ByteArray>>
DiscoveredPeripheralTracker<Platform>::fetchRawGattAdvertisements(
Ptr<BLEPeripheralV2> ble_peripheral,
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
Ptr<AdvertisementReadResult<Platform>> old_advertisement_read_result;
typename AdvertisementReadResultMap::iterator arr_it =
advertisement_read_results_.find(advertisement_header);
if (arr_it != advertisement_read_results_.end()) {
old_advertisement_read_result = arr_it->second; // alias
}
/* RefCounted */ Ptr<AdvertisementReadResult<Platform>>
advertisement_read_result =
gatt_advertisement_fetcher->fetchGattAdvertisements(
ble_peripheral, advertisement_header->getNumSlots(),
old_advertisement_read_result);
dpt::eraseOwnedPtrFromMap(advertisement_read_results_, advertisement_header);
arr_it = advertisement_read_results_
.insert(std::make_pair(advertisement_header,
advertisement_read_result))
.first;
return arr_it->second->getAdvertisements();
}
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::handleRawGattAdvertisements(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements,
bool are_fast_advertisements) {
typedef std::map<string, ConstPtr<BLEAdvertisement>> BLEAdvertisementMap;
// Parse the raw GATT advertisements. The output of this method is a mapping
// of service ID -> GATT advertisement.
BLEAdvertisementMap parsed_gatt_advertisements =
parseRawGattAdvertisements(raw_gatt_advertisements);
ScopedPtr<Ptr<BLEAdvertisementSet>> parsed_gatt_advertisement_values(
new BLEAdvertisementSet());
// Update state for each GATT advertisement.
for (BLEAdvertisementMap::iterator pga_it =
parsed_gatt_advertisements.begin();
pga_it != parsed_gatt_advertisements.end(); ++pga_it) {
const string& service_id = pga_it->first; // alias
ConstPtr<BLEAdvertisement> gatt_advertisement = pga_it->second; // alias
parsed_gatt_advertisement_values->insert(gatt_advertisement);
// TODO(ahlee): Update the java code to create old_advertisement_header
// within the if/else block.
AdvertisementHeaderMap::iterator ah_it =
advertisement_headers_.find(gatt_advertisement);
if (ah_it == advertisement_headers_.end()) {
discovered_peripheral_callbacks_.find(service_id)
->second->onPeripheralDiscovered(
generateBlePeripheral(gatt_advertisement), service_id,
gatt_advertisement->getData(), are_fast_advertisements);
} else {
ConstPtr<BLEAdvertisementHeader> old_advertisement_header =
ah_it->second; // alias
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
old_advertisement_header);
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, old_advertisement_header);
}
dpt::eraseOwnedPtrFromMap(advertisement_headers_, gatt_advertisement);
advertisement_headers_.insert(
std::make_pair(gatt_advertisement, advertisement_header));
advertisement_service_ids_.erase(gatt_advertisement);
advertisement_service_ids_.insert(
std::make_pair(gatt_advertisement, service_id));
}
// Insert the list of read GATT advertisements for this advertisement header.
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header);
gatt_advertisements_.insert(std::make_pair(
advertisement_header, parsed_gatt_advertisement_values.release()));
}
// Returns a map of service IDs to GATT advertisements who belong to a tracked
// service ID.
template <typename Platform>
std::map<string, ConstPtr<BLEAdvertisement>>
DiscoveredPeripheralTracker<Platform>::parseRawGattAdvertisements(
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements) {
std::set<string> tracked_service_ids = getTrackedServiceIds();
typedef std::map<string, ConstPtr<BLEAdvertisement>> BLEAdvertisementMap;
BLEAdvertisementMap parsed_gatt_advertisements;
for (std::set<ConstPtr<ByteArray>>::iterator rga_it =
raw_gatt_advertisements.begin();
rga_it != raw_gatt_advertisements.end(); ++rga_it) {
/* RefCounted */ ConstPtr<BLEAdvertisement> gatt_advertisement =
BLEAdvertisement::fromBytes(*rga_it);
if (gatt_advertisement.isNull()) {
// logger.atDebug().log("Unable to parse raw GATT advertisement %s",
// *rga_it);
continue;
}
// Make sure the advertisement belongs to a service ID we're tracking.
for (typename std::set<string>::iterator tsi_it =
tracked_service_ids.begin();
tsi_it != tracked_service_ids.end(); ++tsi_it) {
// If we already found a higher version advertisement for this service ID,
// there's no point in comparing this advertisement against it.
BLEAdvertisementMap::iterator pga_it =
parsed_gatt_advertisements.find(*tsi_it);
if (pga_it != parsed_gatt_advertisements.end()) {
if (pga_it->second->getVersion() > gatt_advertisement->getVersion()) {
continue;
}
}
// Map the service ID to the advertisement if the service ID hashes match.
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
generateServiceIdHash(gatt_advertisement->getVersion(), *tsi_it));
if (*service_id_hash == *(gatt_advertisement->getServiceIdHash())) {
// logger.atDebug().log("Matched service ID %s to GATT advertisement
// %s.", serviceId, gattAdvertisement);
parsed_gatt_advertisements.insert(
std::make_pair(*tsi_it, gatt_advertisement));
break;
}
}
}
return parsed_gatt_advertisements;
}
template <typename Platform>
void DiscoveredPeripheralTracker<Platform>::
updateCommonStateForFoundBleAdvertisement(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
const string& mac_address) {
typename GattAdvertisementMap::iterator ga_it =
gatt_advertisements_.find(advertisement_header);
if (ga_it == gatt_advertisements_.end()) {
// logger.atDebug().log("No GATT advertisements found for advertisement
// header %s.", advertisementHeader);
return;
}
Ptr<BLEAdvertisementSet> saved_gatt_advertisements = ga_it->second; // alias
for (BLEAdvertisementSet::iterator sga_it =
saved_gatt_advertisements->begin();
sga_it != saved_gatt_advertisements->end(); ++sga_it) {
ConstPtr<BLEAdvertisement> gatt_advertisement = *sga_it; // alias
AdvertisementServiceIdMap::iterator asi_it =
advertisement_service_ids_.find(gatt_advertisement);
if (asi_it == advertisement_service_ids_.end()) {
continue;
}
const string& service_id = asi_it->second; // alias
// Make sure the stored GATT advertisement is still being tracked.
std::set<string> tracked_service_ids = getTrackedServiceIds();
if (tracked_service_ids.find(service_id) == tracked_service_ids.end()) {
continue;
}
// The iterator returned from find() is guaranteed to be valid because it's
// tied to discovered_peripheral_callbacks_, whose keyset is checked through
// getTrackedServiceIds() above.
lost_entity_trackers_.find(service_id)
->second->recordFoundEntity(gatt_advertisement);
// The iterator returned from find() is guaranteed to be valid because it's
// tied to advertisement_service_ids_ which is checked at the beginning of
// the for loop.
mac_addresses_.erase(gatt_advertisement);
mac_addresses_.insert(std::make_pair(gatt_advertisement, mac_address));
}
}
template <typename Platform>
ConstPtr<ByteArray>
DiscoveredPeripheralTracker<Platform>::generateAdvertisementHash(
ConstPtr<ByteArray> advertisement_bytes) {
return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes,
BLEAdvertisementHeader::kAdvertisementHashLength);
}
template <typename Platform>
ConstPtr<ByteArray>
DiscoveredPeripheralTracker<Platform>::generateServiceIdHash(
BLEAdvertisement::Version::Value version, const string& service_id) {
ScopedPtr<ConstPtr<ByteArray>> service_id_bytes(
MakeConstPtr(new ByteArray(service_id.data(), service_id.size())));
switch (version) {
case BLEAdvertisement::Version::V1:
return Utils::legacySha256HashOnlyForPrinting(
hash_utils_.get(), service_id_bytes.get(),
BLEPacket::kServiceIdHashLength);
case BLEAdvertisement::Version::V2:
// Fall through.
case BLEAdvertisement::Version::UNKNOWN:
// Fall through.
default:
// Use the latest known hashing scheme.
return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(),
BLEPacket::kServiceIdHashLength);
}
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,218 @@
#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
#include <cstdint>
#include <map>
#include <set>
#include "core/internal/mediums/advertisement_read_result.h"
#include "core/internal/mediums/ble_advertisement.h"
#include "core/internal/mediums/ble_advertisement_header.h"
#include "core/internal/mediums/ble_peripheral.h"
#include "core/internal/mediums/discovered_peripheral_callback.h"
#include "core/internal/mediums/lost_entity_tracker.h"
#include "platform/api/ble_v2.h"
#include "platform/api/hash_utils.h"
#include "platform/api/lock.h"
#include "platform/api/system_clock.h"
#include "platform/api/thread_utils.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Manages all discovered peripheral logic for {@link BluetoothLowEnergy}. This
// includes tracking found peripherals, lost peripherals, and MAC addresses
// associated with those peripherals.
//
// See go/ble-on-lost for more information. It includes the algorithms used to
// compute found and lost peripherals.
template <typename Platform>
class DiscoveredPeripheralTracker {
public:
DiscoveredPeripheralTracker();
~DiscoveredPeripheralTracker();
void startTracking(
const string& service_id,
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
const string& fast_advertisement_service_uuid);
void stopTracking(const string& service_id);
// GATT advertisement fetcher.
class GattAdvertisementFetcher {
public:
virtual ~GattAdvertisementFetcher() {}
// Fetches relevant GATT advertisements for the peripheral found in {@link
// DiscoveredPeripheralTracker#processFoundBleAdvertisement(BleSighting,
// GattAdvertisementFetcher)}.
virtual Ptr<AdvertisementReadResult<Platform>> fetchGattAdvertisements(
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) = 0;
};
void processFoundBleAdvertisement(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
void processLostGattAdvertisements();
// TODO(ahlee): Add connecting logic.
private:
static Ptr<BLEPeripheral> generateBlePeripheral(
ConstPtr<BLEAdvertisement> gatt_advertisement);
static const std::int32_t kMaxSlots;
static const std::int64_t kMinConnectionDelayMillis;
static const char* kCopresenceServiceUuid;
std::set<string> getTrackedServiceIds();
void clearDataForServiceId(const string& service_id);
void clearGattAdvertisement(ConstPtr<BLEAdvertisement> gatt_advertisement);
void handleFastAdvertisement(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data);
void handleAdvertisementHeader(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
string extractAdvertisementHeaderBytes(
Ptr<BLEPeripheralV2> ble_peripheral,
ConstPtr<BLEAdvertisementData> advertisement_data);
ConstPtr<ByteArray> extractFastAdvertisementBytes(
ConstPtr<BLEAdvertisementData> advertisement_data);
/*RefCounted */ ConstPtr<BLEAdvertisementHeader>
createFastAdvertisementHeader(ConstPtr<ByteArray> fast_advertisement_bytes);
string createDummyAdvertisementHeaderBytes(
Ptr<BLEPeripheralV2> ble_peripheral);
bool isInterestingAdvertisementHeader(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header);
bool shouldReadFromAdvertisementGattServer(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header);
std::set<ConstPtr<ByteArray>> fetchRawGattAdvertisements(
Ptr<BLEPeripheralV2> ble_peripheral,
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
void handleRawGattAdvertisements(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements,
bool are_fast_advertisements);
std::map<string, ConstPtr<BLEAdvertisement>> parseRawGattAdvertisements(
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements);
void updateCommonStateForFoundBleAdvertisement(
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
const string& mac_address);
// TODO(ahlee): Add in connecting logic.
// TODO(ahlee): Move these out to utils (also used by BLE V2).
ConstPtr<ByteArray> generateAdvertisementHash(
ConstPtr<ByteArray> advertisement_bytes);
ConstPtr<ByteArray> generateServiceIdHash(
BLEAdvertisement::Version::Value version, const string& service_id);
// ------------ GENERAL ------------
ScopedPtr<Ptr<Lock>> lock_;
ScopedPtr<Ptr<ThreadUtils>> thread_utils_;
ScopedPtr<Ptr<SystemClock>> system_clock_;
ScopedPtr<Ptr<HashUtils>> hash_utils_;
// ------------ SERVICE ID MAPS ------------
// Entries in these maps all follow the same lifecycle. Entries are added in
// startTracking, and removed in stopTracking.
// Maps service IDs to DiscoveredPeripheralCallbacks. Tracks what service IDs
// are currently active and gives us client callbacks to call.
typedef std::map<string, Ptr<DiscoveredPeripheralCallback>>
DiscoveredPeripheralCallbackMap;
DiscoveredPeripheralCallbackMap discovered_peripheral_callbacks_;
// Maps service IDs to LostEntityTrackers. Used to periodically compute lost
// GATT advertisements, grouped by service ID.
typedef std::map<string, Ptr<LostEntityTracker<Platform, BLEAdvertisement>>>
LostEntityTrackerMap;
LostEntityTrackerMap lost_entity_trackers_;
// Maps service IDs to BLE service UUIDs. Used to check for fast
// advertisements delivered through BLE advertisement service data, under the
// given UUID.
// UUIDs are represented as strings in this map because they are coming from
// AdvertisingOptions and our UUID class is an internal concept that we don't
// want to expose to clients.
typedef std::map<string, string> FastAdvertisementServiceUUIDMap;
FastAdvertisementServiceUUIDMap fast_advertisement_service_uuids_;
// ------------ ADVERTISEMENT HEADER MAPS ------------
// Maps advertisement headers to AdvertisementReadResults. Tells us when to
// retry reading a GATT advertisement. If no entry exists for a particular
// header, we should try reading a GATT advertisement. Entries are added
// whenever a GATT advertisement read is attempted, and removed when GATT
// advertisements are lost. Entries are also removed whenever
// gattAdvertisements removes its entry.
//
// The map is also cleared whenever startTracking is called, due to client
// changes. For example, say clients A and B start scanning and discover
// advertisements A and B (for both clients) on advertisement header 1. Then,
// A restarts scanning, causing us to clear stale advertisement A. However,
// since B was still scanning, we don't remove advertisement header 1 from the
// map. This causes us to never re-read advertisement A.
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisementHeader>,
Ptr<AdvertisementReadResult<Platform>>>
AdvertisementReadResultMap;
AdvertisementReadResultMap advertisement_read_results_;
// Maps advertisement headers to a set of GATT advertisements from a single
// peripheral. Used to retrieve GATT advertisements that we need to reprocess
// every time a header is seen. Entries are added when GATT advertisements are
// read, removed when all associated GATT advertisements are lost or become
// stale, and replaced when the advertisement header is updated for a single
// remote peripheral.
typedef std::set</* RefCounted */ ConstPtr<BLEAdvertisement>>
BLEAdvertisementSet;
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisementHeader>,
Ptr<BLEAdvertisementSet>>
GattAdvertisementMap;
GattAdvertisementMap gatt_advertisements_;
// ------------ GATT ADVERTISEMENT MAPS ------------
// Entries in these maps all follow the same lifecycle. Entries are added when
// GATT advertisements are read, and removed when GATT advertisements are lost
// or become stale.
// Maps GATT advertisements to the service ID it's associated with. Tracks
// what GATT advertisements are currently active. Used to determine which
// LostEntityTracker to invoke when advertisements are rediscovered.
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>, string>
AdvertisementServiceIdMap;
AdvertisementServiceIdMap advertisement_service_ids_;
// Maps GATT advertisements to advertisement headers. Used to efficiently find
// advertisement headers to delete when GATT advertisements are updated. This
// is a reverse map of gatt_advertisements_.
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>,
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>>
AdvertisementHeaderMap;
AdvertisementHeaderMap advertisement_headers_;
// Maps GATT advertisements to MAC addresses. Used when we need to make a
// socket connection based off of the GATT advertisement alone. Entries are
// modified every time a GATT advertisement's advertisement header is seen.
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>, string>
MacAddressMap;
MacAddressMap mac_addresses_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/discovered_peripheral_tracker.cc"
#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
@@ -0,0 +1,56 @@
#include "core/internal/mediums/lost_entity_tracker.h"
#include "platform/synchronized.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
template <typename Platform, typename Entity>
LostEntityTracker<Platform, Entity>::LostEntityTracker()
: lock_(Platform::createLock()),
current_entities_(),
previously_found_entities_() {}
template <typename Platform, typename Entity>
LostEntityTracker<Platform, Entity>::~LostEntityTracker() {
previously_found_entities_.clear();
current_entities_.clear();
}
template <typename Platform, typename Entity>
void LostEntityTracker<Platform, Entity>::recordFoundEntity(
ConstPtr<Entity> entity) {
Synchronized s(lock_.get());
current_entities_.insert(entity);
}
template <typename Platform, typename Entity>
typename LostEntityTracker<Platform, Entity>::EntitySet
LostEntityTracker<Platform, Entity>::computeLostEntities() {
Synchronized s(lock_.get());
// The set of lost entities is the previously found set MINUS the currently
// found set.
for (typename EntitySet::iterator it = current_entities_.begin();
it != current_entities_.end(); ++it) {
previously_found_entities_.erase(*it);
}
EntitySet lost_entities(previously_found_entities_.begin(),
previously_found_entities_.end());
// Update our previous and current sets.
previously_found_entities_.clear();
previously_found_entities_.insert(current_entities_.begin(),
current_entities_.end());
current_entities_.clear();
return lost_entities;
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,49 @@
#ifndef CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
#define CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
#include <set>
#include "platform/api/lock.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Tracks "lost" entities based on a manual update/compute model. Used by
// mediums that only report found devices. Lost entities are computed based off
// of whether a specific entity was rediscovered since the last call to
// computeLostEntities.
//
// Note: Entity must overload the < and == operators.
template <typename Platform, typename Entity>
class LostEntityTracker {
public:
typedef std::set<ConstPtr<Entity> > EntitySet;
LostEntityTracker();
~LostEntityTracker();
// Records the given entity as being recently found, whether or not this is
// our first time discovering the entity.
void recordFoundEntity(ConstPtr<Entity> entity);
// Computes and returns the set of entities considered lost since the last
// time this method was called.
EntitySet computeLostEntities();
private:
ScopedPtr<Ptr<Lock> > lock_;
EntitySet current_entities_;
EntitySet previously_found_entities_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/lost_entity_tracker.cc"
#endif // CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
@@ -0,0 +1,121 @@
#include "core/internal/mediums/lost_entity_tracker.h"
#include "platform/impl/default/default_platform.h"
#include "gtest/gtest.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
struct TestEntity {
int id;
explicit TestEntity(int givenId) : id(givenId) {}
bool operator<(const TestEntity &other) const { return id < other.id; }
};
TEST(LostEntityTracker, NoEntitiesLost) {
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
// Discover some entities.
lost_entity_tracker.recordFoundEntity(entity_1.get());
lost_entity_tracker.recordFoundEntity(entity_2.get());
lost_entity_tracker.recordFoundEntity(entity_3.get());
// Make sure none are lost on the first round.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
// Rediscover the same entities.
lost_entity_tracker.recordFoundEntity(entity_1.get());
lost_entity_tracker.recordFoundEntity(entity_2.get());
lost_entity_tracker.recordFoundEntity(entity_3.get());
// Make sure we still didn't lose any entities.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
}
TEST(LostEntityTracker, AllEntitiesLost) {
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
// Discover some entities.
lost_entity_tracker.recordFoundEntity(entity_1.get());
lost_entity_tracker.recordFoundEntity(entity_2.get());
lost_entity_tracker.recordFoundEntity(entity_3.get());
// Make sure none are lost on the first round.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
// Go through a round without rediscovering any entities.
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
lost_entities = lost_entity_tracker.computeLostEntities();
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
ASSERT_TRUE(lost_entities.find(entity_3.get()) != lost_entities.end());
}
TEST(LostEntityTracker, SomeEntitiesLost) {
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
// Discover some entities.
lost_entity_tracker.recordFoundEntity(entity_1.get());
lost_entity_tracker.recordFoundEntity(entity_2.get());
// Make sure none are lost on the first round.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
// Go through the next round only rediscovering one of our entities and
// discovering an additional entity as well. Then, verify that only one entity
// was lost after the check.
lost_entity_tracker.recordFoundEntity(entity_1.get());
lost_entity_tracker.recordFoundEntity(entity_3.get());
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
lost_entities = lost_entity_tracker.computeLostEntities();
ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end());
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
ASSERT_TRUE(lost_entities.find(entity_3.get()) == lost_entities.end());
}
TEST(LostEntityTracker, SameEntityMultipleCopies) {
LostEntityTracker<DefaultPlatform, TestEntity> lost_entity_tracker;
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
ScopedPtr<ConstPtr<TestEntity> > entity_1_copy(
MakeConstPtr(new TestEntity(1)));
// Discover an entity.
lost_entity_tracker.recordFoundEntity(entity_1.get());
// Make sure none are lost on the first round.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
// Rediscover the same entity, but through a copy of it.
lost_entity_tracker.recordFoundEntity(entity_1_copy.get());
// Make sure none are lost on the second round.
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
// Go through a round without rediscovering any entities and verify that we
// lost an entity equivalent to both copies of it.
typename LostEntityTracker<DefaultPlatform, TestEntity>::EntitySet
lost_entities = lost_entity_tracker.computeLostEntities();
ASSERT_EQ(lost_entities.size(), 1);
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
ASSERT_TRUE(lost_entities.find(entity_1_copy.get()) != lost_entities.end());
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
+42
View File
@@ -0,0 +1,42 @@
#include "core/internal/mediums/mediums.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
Mediums<Platform>::Mediums()
: bluetooth_radio_(new BluetoothRadio<Platform>()),
bluetooth_classic_(
new BluetoothClassic<Platform>(bluetooth_radio_.get())),
ble_(new BLE<Platform>(bluetooth_radio_.get())),
ble_v2_(new mediums::BLEV2<Platform>(bluetooth_radio_.get())) {}
template <typename Platform>
Mediums<Platform>::~Mediums() {
// Nothing to do.
}
template <typename Platform>
Ptr<BluetoothRadio<Platform> > Mediums<Platform>::bluetoothRadio() const {
return bluetooth_radio_.get();
}
template <typename Platform>
Ptr<BluetoothClassic<Platform> > Mediums<Platform>::bluetoothClassic() const {
return bluetooth_classic_.get();
}
template <typename Platform>
Ptr<BLE<Platform> > Mediums<Platform>::ble() const {
return ble_.get();
}
template <typename Platform>
Ptr<mediums::BLEV2<Platform> > Mediums<Platform>::bleV2() const {
return ble_v2_.get();
}
} // namespace connections
} // namespace nearby
} // namespace location
+52
View File
@@ -0,0 +1,52 @@
#ifndef CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
#define CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
#include "core/internal/mediums/ble.h"
#include "core/internal/mediums/ble_v2.h"
#include "core/internal/mediums/bluetooth_classic.h"
#include "core/internal/mediums/bluetooth_radio.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Facilitates convenient and reliable usage of various wireless mediums.
template <typename Platform>
class Mediums {
public:
Mediums();
// Reverts all the mediums to their original state.
~Mediums();
// Returns a handle to the Bluetooth radio.
Ptr<BluetoothRadio<Platform> > bluetoothRadio() const;
// Returns a handle to the Bluetooth Classic medium.
Ptr<BluetoothClassic<Platform> > bluetoothClassic() const;
// Returns a handle to the Bluetooth Low Energy (BLE) medium.
Ptr<BLE<Platform> > ble() const;
// Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium.
Ptr<mediums::BLEV2<Platform> > bleV2() const;
private:
// The order of declaration is critical for both construction and
// destruction.
//
// 1) Construction: The individual mediums have a dependency on the
// corresponding radio, so the radio must be initialized first.
//
// 2) Destruction: The individual mediums should be shut down before the
// corresponding radio.
ScopedPtr<Ptr<BluetoothRadio<Platform> > > bluetooth_radio_;
ScopedPtr<Ptr<BluetoothClassic<Platform> > > bluetooth_classic_;
ScopedPtr<Ptr<BLE<Platform> > > ble_;
ScopedPtr<Ptr<mediums::BLEV2<Platform> > > ble_v2_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/mediums.cc"
#endif // CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
+73
View File
@@ -0,0 +1,73 @@
#include "core/internal/mediums/utils.h"
#include <sstream>
#include "platform/exception.h"
#include "absl/strings/escaping.h"
namespace location {
namespace nearby {
namespace connections {
void Utils::closeSocket(Ptr<BluetoothServerSocket> socket,
const std::string& type, const std::string& name) {
if (!socket.isNull()) {
Exception::Value e = socket->close();
if (Exception::NONE != e) {
if (Exception::IO == e) {
// TODO(reznor): log.atWarning().withCause(e).log("Failed to close
// %sSocket %s", type, name);
}
return;
}
// TODO(reznor): log.atVerbose().log("Closed %sSocket %s", type, name);
}
}
ConstPtr<ByteArray> Utils::sha256Hash(Ptr<HashUtils> hash_utils,
ConstPtr<ByteArray> source,
size_t length) {
if (source.isNull()) {
return ConstPtr<ByteArray>();
}
ScopedPtr<ConstPtr<ByteArray>> full_hash(
hash_utils->sha256(std::string(source->getData(), source->size())));
return MakeConstPtr(new ByteArray(full_hash->getData(), length));
}
ConstPtr<ByteArray> Utils::legacySha256HashOnlyForPrinting(
Ptr<HashUtils> hash_utils, ConstPtr<ByteArray> source, size_t length) {
if (source.isNull()) {
return ConstPtr<ByteArray>();
}
std::string formatted_hex_string = Utils::bytesToPrintableHexString(source);
ScopedPtr<ConstPtr<ByteArray>> formatted_hex_byte_array(MakeConstPtr(
new ByteArray(formatted_hex_string.data(), formatted_hex_string.size())));
return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length);
}
std::string Utils::bytesToPrintableHexString(ConstPtr<ByteArray> bytes) {
std::string hex_string(
absl::BytesToHexString(std::string(bytes->getData(), bytes->size())));
// Print out the byte array as a space separated listing of hex bytes.
std::ostringstream formatted_hex_string_stream;
formatted_hex_string_stream << "[ ";
for (int i = 0; i < hex_string.size(); i += 2) {
formatted_hex_string_stream << "0x";
// This is safe because we have the guarantee that hex_string is of even
// length (because a hex encoding will always be double the size of its
// input).
formatted_hex_string_stream << hex_string[i] << hex_string[i + 1];
formatted_hex_string_stream << " ";
}
formatted_hex_string_stream << "]";
return formatted_hex_string_stream.str();
}
} // namespace connections
} // namespace nearby
} // namespace location
+32
View File
@@ -0,0 +1,32 @@
#ifndef CORE_INTERNAL_MEDIUMS_UTILS_H_
#define CORE_INTERNAL_MEDIUMS_UTILS_H_
#include "platform/api/bluetooth_classic.h"
#include "platform/api/hash_utils.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
class Utils {
public:
static void closeSocket(Ptr<BluetoothServerSocket> socket,
const std::string& type, const std::string& name);
static ConstPtr<ByteArray> sha256Hash(Ptr<HashUtils> hash_utils,
ConstPtr<ByteArray> source,
size_t length);
static ConstPtr<ByteArray> legacySha256HashOnlyForPrinting(
Ptr<HashUtils> hash_utils, ConstPtr<ByteArray> source, size_t length);
private:
static std::string bytesToPrintableHexString(ConstPtr<ByteArray> bytes);
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_UTILS_H_
+100
View File
@@ -0,0 +1,100 @@
#include "core/internal/mediums/uuid.h"
#include <iomanip>
#include <sstream>
#include "platform/api/hash_utils.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
UUID<Platform>::UUID(const string& data) {
// Based on the Java counterpart at
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162.
ScopedPtr<Ptr<HashUtils> > scoped_hash_utils(Platform::createHashUtils());
ScopedPtr<ConstPtr<ByteArray> > scoped_md5_bytes(
scoped_hash_utils->md5(data));
data_.assign(scoped_md5_bytes->getData(), scoped_md5_bytes->size());
data_[6] &= 0x0f; // Clear version.
data_[6] |= 0x30; // Set to version 3.
data_[8] &= 0x3f; // Clear variant.
data_[8] |= 0x80; // Set to IETF variant.
}
template <typename Platform>
UUID<Platform>::UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits) {
// Base on the Java counterpart at
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104.
data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits));
data_[0] = static_cast<char>((most_sig_bits >> 56) & 0x0ff);
data_[1] = static_cast<char>((most_sig_bits >> 48) & 0x0ff);
data_[2] = static_cast<char>((most_sig_bits >> 40) & 0x0ff);
data_[3] = static_cast<char>((most_sig_bits >> 32) & 0x0ff);
data_[4] = static_cast<char>((most_sig_bits >> 24) & 0x0ff);
data_[5] = static_cast<char>((most_sig_bits >> 16) & 0x0ff);
data_[6] = static_cast<char>((most_sig_bits >> 8) & 0x0ff);
data_[7] = static_cast<char>((most_sig_bits >> 0) & 0x0ff);
data_[8] = static_cast<char>((least_sig_bits >> 56) & 0x0ff);
data_[9] = static_cast<char>((least_sig_bits >> 48) & 0x0ff);
data_[10] = static_cast<char>((least_sig_bits >> 40) & 0x0ff);
data_[11] = static_cast<char>((least_sig_bits >> 32) & 0x0ff);
data_[12] = static_cast<char>((least_sig_bits >> 24) & 0x0ff);
data_[13] = static_cast<char>((least_sig_bits >> 16) & 0x0ff);
data_[14] = static_cast<char>((least_sig_bits >> 8) & 0x0ff);
data_[15] = static_cast<char>((least_sig_bits >> 0) & 0x0ff);
}
template <typename Platform>
UUID<Platform>::~UUID() {}
template <typename Platform>
string UUID<Platform>::str() {
// Based on the Java counterpart at
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375.
// The masking with 0x0ff is essential because we're taking 8-bit bytes and
// casting them to integers (which, depending on the platform, are 16- or
// 32-bits wide); without that, we get a leading FF (16-bit) or FFFFFF
// (32-bit) when the MSB of the 8-bit byte is 1.
//
// And the cast to an integer is required because std::hex only takes effect
// on integral types (and no, uint8_t doesn't activate it).
#define BYTE_TO_HEX(b) \
std::setfill('0') << std::setw(2) << std::hex \
<< (static_cast<unsigned int>(b) & 0x0ff)
std::ostringstream md5_hex;
md5_hex << BYTE_TO_HEX(data_[0]);
md5_hex << BYTE_TO_HEX(data_[1]);
md5_hex << BYTE_TO_HEX(data_[2]);
md5_hex << BYTE_TO_HEX(data_[3]);
md5_hex << "-";
md5_hex << BYTE_TO_HEX(data_[4]);
md5_hex << BYTE_TO_HEX(data_[5]);
md5_hex << "-";
md5_hex << BYTE_TO_HEX(data_[6]);
md5_hex << BYTE_TO_HEX(data_[7]);
md5_hex << "-";
md5_hex << BYTE_TO_HEX(data_[8]);
md5_hex << BYTE_TO_HEX(data_[9]);
md5_hex << "-";
md5_hex << BYTE_TO_HEX(data_[10]);
md5_hex << BYTE_TO_HEX(data_[11]);
md5_hex << BYTE_TO_HEX(data_[12]);
md5_hex << BYTE_TO_HEX(data_[13]);
md5_hex << BYTE_TO_HEX(data_[14]);
md5_hex << BYTE_TO_HEX(data_[15]);
return md5_hex.str();
}
} // namespace connections
} // namespace nearby
} // namespace location
+39
View File
@@ -0,0 +1,39 @@
#ifndef CORE_INTERNAL_MEDIUMS_UUID_H_
#define CORE_INTERNAL_MEDIUMS_UUID_H_
#include <cstdint>
#include "platform/port/string.h"
namespace location {
namespace nearby {
namespace connections {
// A type 3 name-based
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
// UUID.
//
// https://developer.android.com/reference/java/util/UUID.html
template <typename Platform>
class UUID {
public:
explicit UUID(const string& data);
UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits);
~UUID();
// Returns the canonical textual representation
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the
// UUID.
string str();
private:
string data_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/mediums/uuid.cc"
#endif // CORE_INTERNAL_MEDIUMS_UUID_H_
+268
View File
@@ -0,0 +1,268 @@
#include "core/internal/offline_frames.h"
#include "platform/port/down_cast.h"
namespace location {
namespace nearby {
namespace connections {
namespace {
template <typename T>
T *downcastToRaw(Ptr<proto_ns::MessageLite> message) {
return DOWN_CAST<T *>(message.operator->());
}
// This method takes ownership of the passed-in 'message'.
//
// This can be implemented more efficiently by taking in a reference to an
// OfflineFrame object created on the caller's stack, but we instead create it
// on the heap and return a Ptr to it for the sake of consistency.
ConstPtr<OfflineFrame> newOfflineFrame(V1Frame::FrameType frame_type,
Ptr<proto_ns::MessageLite> message) {
V1Frame *v1_frame = new V1Frame();
v1_frame->set_type(frame_type);
switch (frame_type) {
case V1Frame::CONNECTION_REQUEST:
v1_frame->set_allocated_connection_request(
downcastToRaw<ConnectionRequestFrame>(message));
break;
case V1Frame::CONNECTION_RESPONSE:
v1_frame->set_allocated_connection_response(
downcastToRaw<ConnectionResponseFrame>(message));
break;
case V1Frame::PAYLOAD_TRANSFER:
v1_frame->set_allocated_payload_transfer(
downcastToRaw<PayloadTransferFrame>(message));
break;
case V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION:
v1_frame->set_allocated_bandwidth_upgrade_negotiation(
downcastToRaw<BandwidthUpgradeNegotiationFrame>(message));
break;
case V1Frame::KEEP_ALIVE:
v1_frame->set_allocated_keep_alive(
downcastToRaw<KeepAliveFrame>(message));
break;
default:
break;
}
Ptr<OfflineFrame> offline_frame(new OfflineFrame());
offline_frame->set_version(OfflineFrame::V1);
offline_frame->set_allocated_v1(v1_frame);
return ConstifyPtr(offline_frame);
}
// This method takes ownership of the passed-in 'offline_frame' and destroys it
// before returning.
ConstPtr<ByteArray> toBytes(ConstPtr<OfflineFrame> offline_frame) {
ScopedPtr<ConstPtr<OfflineFrame> > scoped_offline_frame(offline_frame);
size_t serialized_size = offline_frame->ByteSizeLong();
Ptr<ByteArray> bytes{new ByteArray{serialized_size}};
offline_frame->SerializeToArray(bytes->getData(), serialized_size);
return ConstifyPtr(bytes);
}
} // namespace
ExceptionOr<ConstPtr<OfflineFrame> > OfflineFrames::fromBytes(
ConstPtr<ByteArray> offline_frame_bytes) {
ScopedPtr<Ptr<OfflineFrame> > offline_frame(new OfflineFrame());
if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(),
offline_frame_bytes->size())) {
return ExceptionOr<ConstPtr<OfflineFrame> >(
Exception::INVALID_PROTOCOL_BUFFER);
}
return ExceptionOr<ConstPtr<OfflineFrame> >(
ConstifyPtr(offline_frame.release()));
}
V1Frame::FrameType OfflineFrames::getFrameType(
ConstPtr<OfflineFrame> offline_frame) {
if ((offline_frame->version() == OfflineFrame::V1) &&
offline_frame->has_v1()) {
return offline_frame->v1().type();
}
return V1Frame::UNKNOWN_FRAME_TYPE;
}
ConstPtr<ByteArray> OfflineFrames::forConnectionRequest(
const std::string &endpoint_id, const std::string &endpoint_name,
std::int32_t nonce,
const std::vector<proto::connections::Medium> &mediums) {
Ptr<ConnectionRequestFrame> connection_request(new ConnectionRequestFrame());
connection_request->set_endpoint_id(endpoint_id);
connection_request->set_endpoint_name(endpoint_name);
connection_request->set_nonce(nonce);
for (std::vector<proto::connections::Medium>::const_iterator it =
mediums.begin();
it != mediums.end(); it++) {
connection_request->add_mediums(mediumToConnectionRequestMedium(*it));
}
return toBytes(
newOfflineFrame(V1Frame::CONNECTION_REQUEST, connection_request));
}
ConstPtr<ByteArray> OfflineFrames::forConnectionResponse(std::int32_t status) {
Ptr<ConnectionResponseFrame> connection_response(
new ConnectionResponseFrame());
connection_response->set_status(status);
return toBytes(
newOfflineFrame(V1Frame::CONNECTION_RESPONSE, connection_response));
}
ConstPtr<ByteArray> OfflineFrames::forDataPayloadTransferFrame(
const PayloadTransferFrame::PayloadHeader &header,
const PayloadTransferFrame::PayloadChunk &chunk) {
Ptr<PayloadTransferFrame> payload_transfer(new PayloadTransferFrame());
payload_transfer->set_packet_type(PayloadTransferFrame::DATA);
*payload_transfer->mutable_payload_header() = header;
*payload_transfer->mutable_payload_chunk() = chunk;
return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer));
}
ConstPtr<ByteArray> OfflineFrames::forControlPayloadTransferFrame(
const PayloadTransferFrame::PayloadHeader &header,
const PayloadTransferFrame::ControlMessage &control) {
Ptr<PayloadTransferFrame> payload_transfer(new PayloadTransferFrame());
payload_transfer->set_packet_type(PayloadTransferFrame::CONTROL);
*payload_transfer->mutable_payload_header() = header;
*payload_transfer->mutable_control_message() = control;
return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer));
}
ConstPtr<ByteArray> OfflineFrames::
forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent(
const std::string &ssid, const std::string &password,
std::int32_t port) {
BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials
*wifi_hotspot_credentials = new BandwidthUpgradeNegotiationFrame::
UpgradePathInfo::WifiHotspotCredentials();
wifi_hotspot_credentials->set_ssid(ssid);
wifi_hotspot_credentials->set_password(password);
wifi_hotspot_credentials->set_port(port);
BandwidthUpgradeNegotiationFrame::UpgradePathInfo *upgrade_path_info =
new BandwidthUpgradeNegotiationFrame::UpgradePathInfo();
upgrade_path_info->set_medium(
BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT);
upgrade_path_info->set_allocated_wifi_hotspot_credentials(
wifi_hotspot_credentials);
Ptr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation(
new BandwidthUpgradeNegotiationFrame());
bandwidth_upgrade_negotiation->set_event_type(
BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE);
bandwidth_upgrade_negotiation->set_allocated_upgrade_path_info(
upgrade_path_info);
return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION,
bandwidth_upgrade_negotiation));
}
ConstPtr<ByteArray>
OfflineFrames::forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent() {
Ptr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation(
new BandwidthUpgradeNegotiationFrame());
bandwidth_upgrade_negotiation->set_event_type(
BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL);
return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION,
bandwidth_upgrade_negotiation));
}
ConstPtr<ByteArray>
OfflineFrames::forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent() {
Ptr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation(
new BandwidthUpgradeNegotiationFrame());
bandwidth_upgrade_negotiation->set_event_type(
BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL);
return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION,
bandwidth_upgrade_negotiation));
}
ConstPtr<ByteArray>
OfflineFrames::forClientIntroductionBandwidthUpgradeNegotiationEvent(
const std::string &endpoint_id) {
BandwidthUpgradeNegotiationFrame::ClientIntroduction *client_introduction =
new BandwidthUpgradeNegotiationFrame::ClientIntroduction();
client_introduction->set_endpoint_id(endpoint_id);
Ptr<BandwidthUpgradeNegotiationFrame> bandwidth_upgrade_negotiation(
new BandwidthUpgradeNegotiationFrame());
bandwidth_upgrade_negotiation->set_event_type(
BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION);
bandwidth_upgrade_negotiation->set_allocated_client_introduction(
client_introduction);
return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION,
bandwidth_upgrade_negotiation));
}
ConstPtr<ByteArray> OfflineFrames::forKeepAlive() {
Ptr<KeepAliveFrame> keep_alive_frame(new KeepAliveFrame());
return toBytes(newOfflineFrame(V1Frame::KEEP_ALIVE, keep_alive_frame));
}
ConnectionRequestFrame::Medium OfflineFrames::mediumToConnectionRequestMedium(
proto::connections::Medium medium) {
switch (medium) {
case proto::connections::MDNS:
return ConnectionRequestFrame::MDNS;
case proto::connections::BLUETOOTH:
return ConnectionRequestFrame::BLUETOOTH;
case proto::connections::WIFI_HOTSPOT:
return ConnectionRequestFrame::WIFI_HOTSPOT;
case proto::connections::BLE:
return ConnectionRequestFrame::BLE;
case proto::connections::WIFI_LAN:
return ConnectionRequestFrame::WIFI_LAN;
default:
return ConnectionRequestFrame::UNKNOWN_MEDIUM;
}
}
proto::connections::Medium OfflineFrames::connectionRequestMediumToMedium(
ConnectionRequestFrame::Medium medium) {
switch (medium) {
case ConnectionRequestFrame::MDNS:
return proto::connections::Medium::MDNS;
case ConnectionRequestFrame::BLUETOOTH:
return proto::connections::Medium::BLUETOOTH;
case ConnectionRequestFrame::WIFI_HOTSPOT:
return proto::connections::Medium::WIFI_HOTSPOT;
case ConnectionRequestFrame::BLE:
return proto::connections::Medium::BLE;
case ConnectionRequestFrame::WIFI_LAN:
return proto::connections::Medium::WIFI_LAN;
default:
return proto::connections::Medium::UNKNOWN_MEDIUM;
}
}
std::vector<proto::connections::Medium>
OfflineFrames::connectionRequestMediumsToMediums(
const ConnectionRequestFrame &connection_request_frame) {
std::vector<proto::connections::Medium> result;
for (size_t i = 0; i < connection_request_frame.mediums_size(); i++) {
result.push_back(
connectionRequestMediumToMedium(connection_request_frame.mediums(i)));
}
return result;
}
} // namespace connections
} // namespace nearby
} // namespace location
+70
View File
@@ -0,0 +1,70 @@
#ifndef CORE_INTERNAL_OFFLINE_FRAMES_H_
#define CORE_INTERNAL_OFFLINE_FRAMES_H_
#include <cstdint>
#include <vector>
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/byte_array.h"
#include "platform/exception.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
// Detects the right usage.
#include "google/protobuf/message_lite.h"
#define proto_ns google3_proto_compat
namespace location {
namespace nearby {
namespace connections {
class OfflineFrames {
public:
static ExceptionOr<ConstPtr<OfflineFrame> > fromBytes(
ConstPtr<ByteArray>
offline_frame_bytes); // throws Exception::INVALID_PROTOCOL_BUFFER
static V1Frame::FrameType getFrameType(ConstPtr<OfflineFrame> offline_frame);
static ConstPtr<ByteArray> forConnectionRequest(
const std::string& endpoint_id, const std::string& endpoint_name,
std::int32_t nonce,
const std::vector<proto::connections::Medium>& mediums);
static ConstPtr<ByteArray> forConnectionResponse(std::int32_t status);
static ConstPtr<ByteArray> forDataPayloadTransferFrame(
const PayloadTransferFrame::PayloadHeader& header,
const PayloadTransferFrame::PayloadChunk& chunk);
static ConstPtr<ByteArray> forControlPayloadTransferFrame(
const PayloadTransferFrame::PayloadHeader& header,
const PayloadTransferFrame::ControlMessage& control);
static ConstPtr<ByteArray>
forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent(
const std::string& ssid, const std::string& password, std::int32_t port);
static ConstPtr<ByteArray>
forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent();
static ConstPtr<ByteArray>
forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent();
static ConstPtr<ByteArray>
forClientIntroductionBandwidthUpgradeNegotiationEvent(
const std::string& endpoint_id);
static ConstPtr<ByteArray> forKeepAlive();
static ConnectionRequestFrame::Medium mediumToConnectionRequestMedium(
proto::connections::Medium medium);
static proto::connections::Medium connectionRequestMediumToMedium(
ConnectionRequestFrame::Medium medium);
static std::vector<proto::connections::Medium>
connectionRequestMediumsToMediums(
const ConnectionRequestFrame& connection_request_frame);
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_OFFLINE_FRAMES_H_
@@ -0,0 +1,110 @@
#include "core/internal/offline_service_controller.h"
#include <string>
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
OfflineServiceController<Platform>::OfflineServiceController()
: ServiceController<Platform>(),
medium_manager_(new MediumManager<Platform>()),
endpoint_channel_manager_(
new EndpointChannelManager<Platform>(medium_manager_.get())),
endpoint_manager_(
new EndpointManager<Platform>(endpoint_channel_manager_.get())),
payload_manager_(new PayloadManager<Platform>(endpoint_manager_.get())),
bandwidth_upgrade_manager_(new BandwidthUpgradeManager<Platform>(
medium_manager_.get(), endpoint_channel_manager_.get(),
endpoint_manager_.get())),
pcp_manager_(new PCPManager<Platform>(
medium_manager_.get(), endpoint_channel_manager_.get(),
endpoint_manager_.get(), bandwidth_upgrade_manager_.get())) {}
template <typename Platform>
OfflineServiceController<Platform>::~OfflineServiceController() {}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& service_id, const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
return pcp_manager_->startAdvertising(client_proxy, endpoint_name, service_id,
advertising_options,
connection_lifecycle_listener);
}
template <typename Platform>
void OfflineServiceController<Platform>::stopAdvertising(
Ptr<ClientProxy<Platform> > client_proxy) {
pcp_manager_->stopAdvertising(client_proxy);
}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) {
return pcp_manager_->startDiscovery(client_proxy, service_id,
discovery_options, discovery_listener);
}
template <typename Platform>
void OfflineServiceController<Platform>::stopDiscovery(
Ptr<ClientProxy<Platform> > client_proxy) {
pcp_manager_->stopDiscovery(client_proxy);
}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::requestConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
return pcp_manager_->requestConnection(
client_proxy, endpoint_name, endpoint_id, connection_lifecycle_listener);
}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<PayloadListener> payload_listener) {
return pcp_manager_->acceptConnection(client_proxy, endpoint_id,
payload_listener);
}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {
return pcp_manager_->rejectConnection(client_proxy, endpoint_id);
}
template <typename Platform>
void OfflineServiceController<Platform>::initiateBandwidthUpgrade(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {
bandwidth_upgrade_manager_->initiateBandwidthUpgradeForEndpoint(
client_proxy, endpoint_id, pcp_manager_->getBandwidthUpgradeMedium());
}
template <typename Platform>
void OfflineServiceController<Platform>::sendPayload(
Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& endpoint_ids, ConstPtr<Payload> payload) {
payload_manager_->sendPayload(client_proxy, endpoint_ids, payload);
}
template <typename Platform>
Status::Value OfflineServiceController<Platform>::cancelPayload(
Ptr<ClientProxy<Platform> > client_proxy, std::int64_t payload_id) {
return payload_manager_->cancelPayload(client_proxy, payload_id);
}
template <typename Platform>
void OfflineServiceController<Platform>::disconnectFromEndpoint(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {
endpoint_manager_->unregisterEndpoint(client_proxy, endpoint_id);
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,85 @@
#ifndef CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_
#define CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_
#include <cstdint>
#include <vector>
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "core/internal/payload_manager.h"
#include "core/internal/pcp_manager.h"
#include "core/internal/service_controller.h"
#include "core/listeners.h"
#include "core/options.h"
#include "core/payload.h"
#include "core/status.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class OfflineServiceController : public ServiceController<Platform> {
public:
OfflineServiceController();
~OfflineServiceController() override;
Status::Value startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& service_id, const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) override;
void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy) override;
Status::Value startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) override;
void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy) override;
Status::Value requestConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) override;
Status::Value acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<PayloadListener> payload_listener) override;
Status::Value rejectConnection(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id) override;
void initiateBandwidthUpgrade(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id) override;
void sendPayload(Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& endpoint_ids,
ConstPtr<Payload> payload) override;
Status::Value cancelPayload(Ptr<ClientProxy<Platform> > client_proxy,
std::int64_t payload_id) override;
void disconnectFromEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id) override;
private:
// Note that the order of declaration of these is crucial, because we depend
// on the destructors running (strictly) in the reverse order; a deviation
// from that will lead to crashes at runtime.
ScopedPtr<Ptr<MediumManager<Platform> > > medium_manager_;
ScopedPtr<Ptr<EndpointChannelManager<Platform> > > endpoint_channel_manager_;
ScopedPtr<Ptr<EndpointManager<Platform> > > endpoint_manager_;
ScopedPtr<Ptr<PayloadManager<Platform> > > payload_manager_;
ScopedPtr<Ptr<BandwidthUpgradeManager<Platform> > >
bandwidth_upgrade_manager_;
ScopedPtr<Ptr<PCPManager<Platform> > > pcp_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/offline_service_controller.cc"
#endif // CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_
@@ -0,0 +1,788 @@
#include "core/internal/p2p_cluster_pcp_handler.h"
#include "platform/api/hash_utils.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
const BluetoothDeviceName::Version::Value
P2PClusterPCPHandler<Platform>::kBluetoothDeviceNameVersion =
BluetoothDeviceName::Version::V1;
template <typename Platform>
const BLEAdvertisement::Version::Value
P2PClusterPCPHandler<Platform>::kBleAdvertisementVersion =
BLEAdvertisement::Version::V1;
template <typename Platform>
ConstPtr<ByteArray> P2PClusterPCPHandler<Platform>::generateHash(
const string& source, size_t size) {
// Initiazing a new HashUtils each time instead of making it a class member
// because FoundBluetoothAdvertisementProcessor uses generateHash in its
// constructor so this method has to be static. We *could* make a static
// ScopedPtr for HashUtils, but that can get into dangerous territory in terms
// of time of destruction of that object, so we'll avoid it for now, and stick
// with this.
ScopedPtr<Ptr<HashUtils>> hash_utils(Platform::createHashUtils());
ScopedPtr<ConstPtr<ByteArray>> scoped_hash(hash_utils->sha256(source));
return MakeConstPtr(new ByteArray(scoped_hash->getData(), size));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::P2PClusterPCPHandler(
Ptr<MediumManager<Platform>> medium_manager,
Ptr<EndpointManager<Platform>> endpoint_manager,
Ptr<EndpointChannelManager<Platform>> endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform>> bandwidth_upgrade_manager)
: BasePCPHandler<Platform>(endpoint_manager, endpoint_channel_manager,
bandwidth_upgrade_manager),
medium_manager_(medium_manager) {}
template <typename Platform>
P2PClusterPCPHandler<Platform>::~P2PClusterPCPHandler() {}
template <typename Platform>
Strategy P2PClusterPCPHandler<Platform>::getStrategy() {
return Strategy::kP2PCluster;
}
template <typename Platform>
PCP::Value P2PClusterPCPHandler<Platform>::getPCP() {
return PCP::P2P_CLUSTER;
}
template <typename Platform>
std::vector<proto::connections::Medium>
P2PClusterPCPHandler<Platform>::getConnectionMediumsByPriority() {
std::vector<proto::connections::Medium> mediums;
if (medium_manager_->isBluetoothAvailable()) {
mediums.push_back(proto::connections::BLUETOOTH);
}
if (medium_manager_->isBleAvailable()) {
mediums.push_back(proto::connections::BLE);
}
return mediums;
}
template <typename Platform>
proto::connections::Medium
P2PClusterPCPHandler<Platform>::getDefaultUpgradeMedium() {
return proto::connections::WIFI_LAN;
}
template <typename Platform>
Ptr<typename BasePCPHandler<Platform>::StartOperationResult>
P2PClusterPCPHandler<Platform>::startAdvertisingImpl(
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id,
const string& local_endpoint_id, const string& local_endpoint_name,
const AdvertisingOptions& options) {
std::vector<proto::connections::Medium> mediums_started_successfully;
ScopedPtr<ConstPtr<ByteArray>> scoped_bluetooth_service_id_hash(
generateHash(service_id, BluetoothDeviceName::kServiceIdHashLength));
proto::connections::Medium bluetooth_medium = startBluetoothAdvertising(
client_proxy, service_id, scoped_bluetooth_service_id_hash.get(),
local_endpoint_id, local_endpoint_name);
if (proto::connections::UNKNOWN_MEDIUM != bluetooth_medium) {
mediums_started_successfully.push_back(bluetooth_medium);
}
ScopedPtr<ConstPtr<ByteArray>> scoped_ble_service_id_hash(
generateHash(service_id, BLEAdvertisement::kServiceIdHashLength));
proto::connections::Medium ble_medium = startBleAdvertising(
client_proxy, service_id, scoped_ble_service_id_hash.get(),
local_endpoint_id, local_endpoint_name);
if (proto::connections::UNKNOWN_MEDIUM != ble_medium) {
mediums_started_successfully.push_back(ble_medium);
}
if (mediums_started_successfully.empty()) {
// TODO(tracyzhou): Add logging.
return BasePCPHandler<Platform>::StartOperationResult::error(
Status::BLUETOOTH_ERROR);
}
// 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 BasePCPHandler<Platform>::StartOperationResult::success(
mediums_started_successfully);
}
template <typename Platform>
Status::Value P2PClusterPCPHandler<Platform>::stopAdvertisingImpl(
Ptr<ClientProxy<Platform>> client_proxy) {
medium_manager_->stopBleAdvertising(client_proxy->getAdvertisingServiceId());
medium_manager_->turnOffBluetoothDiscoverability();
medium_manager_->stopListeningForIncomingBleConnections(
client_proxy->getAdvertisingServiceId());
medium_manager_->stopListeningForIncomingBluetoothConnections(
client_proxy->getAdvertisingServiceId());
return Status::SUCCESS;
}
template <typename Platform>
Ptr<typename BasePCPHandler<Platform>::StartOperationResult>
P2PClusterPCPHandler<Platform>::startDiscoveryImpl(
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id,
const DiscoveryOptions& options) {
std::vector<proto::connections::Medium> mediums_started_successfully;
proto::connections::Medium bluetooth_medium =
startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor(
MakePtr(this), client_proxy, service_id)),
client_proxy, service_id);
if (proto::connections::UNKNOWN_MEDIUM != bluetooth_medium) {
mediums_started_successfully.push_back(bluetooth_medium);
}
proto::connections::Medium ble_medium = startBleDiscovery(
MakePtr(new FoundBleAdvertisementProcessor(MakePtr(this), client_proxy)),
client_proxy, service_id);
if (proto::connections::UNKNOWN_MEDIUM != ble_medium) {
mediums_started_successfully.push_back(ble_medium);
}
if (mediums_started_successfully.empty()) {
// TODO(tracyzhou): Add logging.
return BasePCPHandler<Platform>::StartOperationResult::error(
Status::BLUETOOTH_ERROR);
}
return BasePCPHandler<Platform>::StartOperationResult::success(
mediums_started_successfully);
}
template <typename Platform>
Status::Value P2PClusterPCPHandler<Platform>::stopDiscoveryImpl(
Ptr<ClientProxy<Platform>> client_proxy) {
medium_manager_->stopBleScanning(client_proxy->getDiscoveryServiceId());
medium_manager_->stopScanningForBluetoothDevices();
return Status::SUCCESS;
}
template <typename Platform>
typename BasePCPHandler<Platform>::ConnectImplResult
P2PClusterPCPHandler<Platform>::connectImpl(
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<typename BasePCPHandler<Platform>::DiscoveredEndpoint> endpoint) {
Ptr<BluetoothEndpoint> bluetooth_endpoint =
DowncastPtr<BluetoothEndpoint>(endpoint);
if (!bluetooth_endpoint.isNull()) {
return bluetoothConnectImpl(client_proxy, bluetooth_endpoint);
}
Ptr<BLEEndpoint> ble_endpoint = DowncastPtr<BLEEndpoint>(endpoint);
if (!ble_endpoint.isNull()) {
return bleConnectImpl(client_proxy, ble_endpoint);
}
return typename BasePCPHandler<Platform>::ConnectImplResult(
proto::connections::Medium::UNKNOWN_MEDIUM, Status::ERROR);
}
/////////////////// START IMPLEMENTATIONS FOR NESTED CLASSES ///////////////////
///////// P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor //////////
template <typename Platform>
P2PClusterPCPHandler<Platform>::IncomingBluetoothConnectionProcessor::
IncomingBluetoothConnectionProcessor(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
const string& local_endpoint_name)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
local_endpoint_name_(local_endpoint_name) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::IncomingBluetoothConnectionProcessor::
onIncomingBluetoothConnection(Ptr<BluetoothSocket> bluetooth_socket) {
pcp_handler_->runOnPCPHandlerThread(
MakePtr(new OnIncomingBluetoothConnectionRunnable(
pcp_handler_, client_proxy_, bluetooth_socket)));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::IncomingBluetoothConnectionProcessor::
OnIncomingBluetoothConnectionRunnable::
OnIncomingBluetoothConnectionRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<BluetoothSocket> bluetooth_socket)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
bluetooth_socket_(bluetooth_socket) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::IncomingBluetoothConnectionProcessor::
OnIncomingBluetoothConnectionRunnable::run() {
string remote_device_name = bluetooth_socket_->getRemoteDevice()->getName();
ScopedPtr<Ptr<EndpointChannel>> scoped_bluetooth_endpoint_channel(
pcp_handler_->endpoint_channel_manager_
->createIncomingBluetoothEndpointChannel(remote_device_name,
bluetooth_socket_));
if (!scoped_bluetooth_endpoint_channel.isNull()) {
// TODO(tracyzhou): Add logging.
} else {
Exception::Value exception = bluetooth_socket_->close();
bluetooth_socket_.destroy();
if (Exception::NONE != exception) {
if (Exception::IO == exception) {
// TODO(tracyzhou): Add logging.
}
}
}
pcp_handler_->onIncomingConnection(
client_proxy_, remote_device_name,
scoped_bluetooth_endpoint_channel.release(),
proto::connections::Medium::BLUETOOTH);
}
//////////// P2PClusterPCPHandler::IncomingBleConnectionProcessor /////////////
template <typename Platform>
P2PClusterPCPHandler<Platform>::IncomingBleConnectionProcessor::
IncomingBleConnectionProcessor(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
const string& local_endpoint_name)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
local_endpoint_name_(local_endpoint_name) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::IncomingBleConnectionProcessor::
onIncomingBleConnection(Ptr<BLESocket> ble_socket,
const string& service_id) {
pcp_handler_->runOnPCPHandlerThread(
MakePtr(new OnIncomingBleConnectionRunnable(pcp_handler_, client_proxy_,
ble_socket)));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::IncomingBleConnectionProcessor::
OnIncomingBleConnectionRunnable::OnIncomingBleConnectionRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy, Ptr<BLESocket> ble_socket)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
ble_socket_(ble_socket) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::IncomingBleConnectionProcessor::
OnIncomingBleConnectionRunnable::run() {
string remote_device_name =
ble_socket_->getRemotePeripheral()->getBluetoothDevice()->getName();
ScopedPtr<Ptr<EndpointChannel>> scoped_ble_endpoint_channel(
pcp_handler_->endpoint_channel_manager_->createIncomingBLEEndpointChannel(
remote_device_name, ble_socket_));
if (!scoped_ble_endpoint_channel.isNull()) {
// TODO(ahlee): Add logging.
} else {
Exception::Value exception = ble_socket_->close();
ble_socket_.destroy();
if (Exception::NONE != exception) {
if (Exception::IO == exception) {
// TODO(ahlee): Add logging.
}
}
}
pcp_handler_->onIncomingConnection(client_proxy_, remote_device_name,
scoped_ble_endpoint_channel.release(),
proto::connections::Medium::BLE);
}
///////// P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor //////////
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
FoundBluetoothAdvertisementProcessor(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
service_id_(service_id),
expected_service_id_hash_(generateHash(
service_id, BluetoothDeviceName::kServiceIdHashLength)) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
onFoundBluetoothDevice(Ptr<BluetoothDevice> bluetooth_device) {
pcp_handler_->runOnPCPHandlerThread(
MakePtr(new OnFoundBluetoothDeviceRunnable(pcp_handler_, client_proxy_,
MakePtr(this), service_id_,
bluetooth_device)));
}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
onLostBluetoothDevice(Ptr<BluetoothDevice> bluetooth_device) {
pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBluetoothDeviceRunnable(
pcp_handler_, client_proxy_, MakePtr(this), service_id_,
bluetooth_device)));
}
template <typename Platform>
bool P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
isRecognizedBluetoothEndpoint(
const string& found_bluetooth_device_name,
Ptr<BluetoothDeviceName> bluetooth_device_name) {
if (bluetooth_device_name.isNull()) {
// TODO(tracyzhou): Add logging.
return false;
}
if (bluetooth_device_name->getPCP() != pcp_handler_->getPCP()) {
// TODO(tracyzhou): Add logging.
return false;
}
if (*(bluetooth_device_name->getServiceIdHash()) !=
*(expected_service_id_hash_.get())) {
// TODO(tracyzhou): Add logging.
return false;
}
return true;
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
OnFoundBluetoothDeviceRunnable::OnFoundBluetoothDeviceRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor,
const string& service_id, Ptr<BluetoothDevice> bluetooth_device)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
found_bluetooth_advertisement_processor_(
found_bluetooth_advertisement_processor),
service_id_(service_id),
bluetooth_device_(bluetooth_device) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
OnFoundBluetoothDeviceRunnable::run() {
// Make sure we are still discovering before proceeding.
if (!client_proxy_->isDiscovering()) {
// TODO(tracyzhou): Add logging.
return;
}
// Parse the Bluetooth device name.
ScopedPtr<Ptr<BluetoothDeviceName>> bluetooth_device_name(
BluetoothDeviceName::fromString(bluetooth_device_->getName()));
// Make sure the Bluetooth device name points to a valid endpoint we're
// discovering.
if (!found_bluetooth_advertisement_processor_->isRecognizedBluetoothEndpoint(
bluetooth_device_->getName(), bluetooth_device_name.get())) {
return;
}
// Report the discovered endpoint to the client.
// TODO(tracyzhou): Add logging.
pcp_handler_->onEndpointFound(
client_proxy_,
MakePtr(new BluetoothEndpoint(
bluetooth_device_.release(), bluetooth_device_name->getEndpointId(),
bluetooth_device_name->getEndpointName(), service_id_)));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
OnLostBluetoothDeviceRunnable::OnLostBluetoothDeviceRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor,
const string& service_id, Ptr<BluetoothDevice> bluetooth_device)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
found_bluetooth_advertisement_processor_(
found_bluetooth_advertisement_processor),
service_id_(service_id),
bluetooth_device_(bluetooth_device) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBluetoothAdvertisementProcessor::
OnLostBluetoothDeviceRunnable::run() {
// Make sure we are still discovering before proceeding.
if (!client_proxy_->isDiscovering()) {
// TODO(tracyzhou): Add logging.
return;
}
// Parse the Bluetooth device name.
ScopedPtr<Ptr<BluetoothDeviceName>> bluetooth_device_name(
BluetoothDeviceName::fromString(bluetooth_device_->getName()));
// Make sure the Bluetooth device name points to a valid endpoint we're
// discovering.
if (!found_bluetooth_advertisement_processor_->isRecognizedBluetoothEndpoint(
bluetooth_device_->getName(), bluetooth_device_name.get())) {
return;
}
// Report the endpoint as lost to the client.
// TODO(tracyzhou): Add logging.
pcp_handler_->onEndpointLost(
client_proxy_,
MakePtr(new BluetoothEndpoint(
bluetooth_device_.release(), bluetooth_device_name->getEndpointId(),
bluetooth_device_name->getEndpointName(), service_id_)));
}
//////////// P2PClusterPCPHandler::FoundBleAdvertisementProcessor /////////////
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
FoundBleAdvertisementProcessor(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy)
: pcp_handler_(pcp_handler), client_proxy_(client_proxy) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
onFoundBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id,
ConstPtr<ByteArray> advertisement_bytes) {
pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundBlePeripheralRunnable(
pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral,
advertisement_bytes)));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
OnFoundBlePeripheralRunnable::OnFoundBlePeripheralRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor,
const string& service_id, Ptr<BLE_PERIPHERAL> ble_peripheral,
ConstPtr<ByteArray> advertisement_bytes)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
found_ble_advertisement_processor_(found_ble_advertisement_processor),
service_id_(service_id),
ble_peripheral_(ble_peripheral),
advertisement_bytes_(advertisement_bytes),
expected_service_id_hash_(
generateHash(service_id, BLEAdvertisement::kServiceIdHashLength)) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
OnFoundBlePeripheralRunnable::run() {
// Make sure we are still discovering before proceeding.
if (!client_proxy_->isDiscovering()) {
// TODO(ahlee): logger.atWarning().log("Skipping discovery of
// BLEAdvertisement header %s because we are no longer discovering.",
// bytesToString(advertisementBytes));
return;
}
ScopedPtr<Ptr<BLEAdvertisement>> scoped_ble_advertisement(
BLEAdvertisement::fromBytes(advertisement_bytes_.get()));
if (scoped_ble_advertisement.isNull()) {
// TODO(ahlee): logger.atVerbose().log("%s doesn't conform to the
// BLEAdvertisement format, discarding.",
// bytesToSTring(advertisementBytes));
return;
}
if (scoped_ble_advertisement->getVersion() != BLEAdvertisement::Version::V1) {
// TODO(ahlee): logging
return;
}
if (scoped_ble_advertisement->getPCP() != pcp_handler_->getPCP()) {
// TODO(ahlee): Add logging
return;
}
if (*(scoped_ble_advertisement->getServiceIdHash()) !=
*(expected_service_id_hash_.get())) {
// TODO(ahlee): Add logging
return;
}
// TODO(ahlee): Add logging.
// Store all the state we need to be able to re-create a BLEEndpoint in
// OnLostBlePeripheralRunnable::run(), since that isn't privy to the bytes of
// the BLE advertisement itself.
found_ble_advertisement_processor_->found_ble_endpoints_.insert(
std::make_pair(
getBlePeripheralId(ble_peripheral_.get()),
BLEEndpointState(scoped_ble_advertisement->getEndpointId(),
scoped_ble_advertisement->getEndpointName())));
pcp_handler_->onEndpointFound(
client_proxy_,
MakePtr(new BLEEndpoint(
ble_peripheral_.release(), scoped_ble_advertisement->getEndpointId(),
scoped_ble_advertisement->getEndpointName(), service_id_)));
// TODO(b/75047971): Add functionality to connect over Bluetooth.
}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
onLostBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id) {
pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBlePeripheralRunnable(
pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral)));
}
template <typename Platform>
P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
OnLostBlePeripheralRunnable::OnLostBlePeripheralRunnable(
Ptr<P2PClusterPCPHandler<Platform>> pcp_handler,
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor,
const string& service_id, Ptr<BLE_PERIPHERAL> ble_peripheral)
: pcp_handler_(pcp_handler),
client_proxy_(client_proxy),
found_ble_advertisement_processor_(found_ble_advertisement_processor),
service_id_(service_id),
ble_peripheral_(ble_peripheral) {}
template <typename Platform>
void P2PClusterPCPHandler<Platform>::FoundBleAdvertisementProcessor::
OnLostBlePeripheralRunnable::run() {
// Make sure we are still discovering before proceeding.
if (!client_proxy_->isDiscovering()) {
// TODO(reznor): logger.atWarning().log("Ignoring lost BlePeripheral %s
// because we are no longer discovering.", blePeripheral);
return;
}
// Remove this BLEPeripheral from
// found_ble_advertisement_processor_->found_ble_endpoints_, and report the
// endpoint as lost to the client.
typename FoundBLEEndpointsMap::iterator it =
found_ble_advertisement_processor_->found_ble_endpoints_.find(
getBlePeripheralId(ble_peripheral_.get()));
if (it != found_ble_advertisement_processor_->found_ble_endpoints_.end()) {
// TODO(reznor): logger.atDebug().log("Lost BlePeripheral %s (with
// EndpointId %s and EndpointName %s)", blePeripheral,
// bleEndpoint.getEndpointId(), bleEndpoint.getEndpointName());
// Make a copy since it->second will get destroyed once we call erase()
// below.
BLEEndpointState ble_endpoint_state(it->second);
found_ble_advertisement_processor_->found_ble_endpoints_.erase(it);
pcp_handler_->onEndpointLost(
client_proxy_,
MakePtr(new BLEEndpoint(
ble_peripheral_.release(), ble_endpoint_state.endpoint_id,
ble_endpoint_state.endpoint_name, service_id_)));
}
}
//////////////////// END IMPLEMENTATIONS FOR NESTED CLASSES ////////////////////
template <typename Platform>
proto::connections::Medium
P2PClusterPCPHandler<Platform>::startBluetoothAdvertising(
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id,
ConstPtr<ByteArray> service_id_hash, const string& local_endpoint_id,
const string& local_endpoint_name) {
// Start listening for connections before advertising in case a connection
// request comes in very quickly.
if (!medium_manager_->isListeningForIncomingBluetoothConnections(
service_id)) {
if (!medium_manager_->startListeningForIncomingBluetoothConnections(
service_id,
MakePtr(new IncomingBluetoothConnectionProcessor(
MakePtr(this), client_proxy, local_endpoint_name)))) {
// TODO(tracyzhou): Add logging.
return proto::connections::UNKNOWN_MEDIUM;
}
// TODO(tracyzhou): Add logging.
}
// Generate a BluetoothDeviceName with which to become Bluetooth discoverable.
const string bluetooth_device_name = BluetoothDeviceName::asString(
kBluetoothDeviceNameVersion, getPCP(), local_endpoint_id, service_id_hash,
local_endpoint_name);
if (bluetooth_device_name.empty()) {
// TODO(tracyzhou): Add logging.
medium_manager_->stopListeningForIncomingBluetoothConnections(service_id);
return proto::connections::UNKNOWN_MEDIUM;
} else {
// TODO(tracyzhou): Add logging.
}
// Become Bluetooth discoverable.
if (!medium_manager_->turnOnBluetoothDiscoverability(bluetooth_device_name)) {
// TODO(tracyzhou): Add logging.
medium_manager_->stopListeningForIncomingBluetoothConnections(service_id);
return proto::connections::UNKNOWN_MEDIUM;
} else {
// TODO(tracyzhou): Add logging.
}
return proto::connections::BLUETOOTH;
}
template <typename Platform>
proto::connections::Medium
P2PClusterPCPHandler<Platform>::startBluetoothDiscovery(
Ptr<FoundBluetoothAdvertisementProcessor> processor,
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id) {
if (!medium_manager_->startScanningForBluetoothDevices(processor)) {
// TODO(tracyzhou): Add logging.
return proto::connections::UNKNOWN_MEDIUM;
} else {
// TODO(tracyzhou): Add logging.
}
return proto::connections::BLUETOOTH;
}
template <typename Platform>
proto::connections::Medium P2PClusterPCPHandler<Platform>::startBleAdvertising(
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id,
ConstPtr<ByteArray> service_id_hash, const string& local_endpoint_id,
const string& local_endpoint_name) {
// Start listening for connections before advertising in case a connection
// request comes in very quickly.
if (!medium_manager_->isListeningForIncomingBleConnections(service_id)) {
if (!medium_manager_->startListeningForIncomingBleConnections(
service_id,
MakePtr(new IncomingBleConnectionProcessor(
MakePtr(this), client_proxy, local_endpoint_name)))) {
// TODO(ahlee): logger.atWarning().log("In startBleAdvertising(%s), client
// %d failed to start listening for incoming BLE connections to ServiceId
// %s", local_endpoint_name, clientProxy.getClientId(), service_id);
return proto::connections::UNKNOWN_MEDIUM;
}
// TODO(ahlee): Add logging.
}
// TODO(b/75047971): Add functionality to connect over Bluetooth.
// Create a BLEAdvertisement.
// TODO(b/75047971): Add a bluetooth_adapter method to get the mac address.
string bluetooth_mac_address;
ScopedPtr<ConstPtr<ByteArray>> scoped_ble_advertisement_bytes(
BLEAdvertisement::toBytes(kBleAdvertisementVersion, getPCP(),
service_id_hash, local_endpoint_id,
local_endpoint_name, bluetooth_mac_address));
if (scoped_ble_advertisement_bytes.isNull()) {
// TODO(ahlee): Add logging
medium_manager_->stopListeningForIncomingBleConnections(service_id);
return proto::connections::UNKNOWN_MEDIUM;
}
// TODO(ahlee): Add logging
if (!medium_manager_->startBleAdvertising(
service_id, scoped_ble_advertisement_bytes.release())) {
// TODO(ahlee): Add logging
medium_manager_->stopListeningForIncomingBleConnections(service_id);
return proto::connections::UNKNOWN_MEDIUM;
}
// TODO(ahlee): Add logging
return proto::connections::BLE;
}
template <typename Platform>
proto::connections::Medium P2PClusterPCPHandler<Platform>::startBleDiscovery(
Ptr<FoundBleAdvertisementProcessor> processor,
Ptr<ClientProxy<Platform>> client_proxy, const string& service_id) {
if (!medium_manager_->startBleScanning(service_id, processor)) {
// TODO(ahlee): logger.atDebug().log("In startBleDiscover(), client %d
// couldn't start scanning on BLE for service id %s.",
// client_proxy.getClientId(), service_id);
return proto::connections::UNKNOWN_MEDIUM;
}
// TODO(ahlee): logger.atVerbose().log("In startBleDiscovery(), client %d
// started scanning for BLE advertisements for serviceId %s.",
// client_proxy.getClietnId(), service_id);
return proto::connections::BLE;
}
template <typename Platform>
typename BasePCPHandler<Platform>::ConnectImplResult
P2PClusterPCPHandler<Platform>::bluetoothConnectImpl(
Ptr<ClientProxy<Platform>> client_proxy,
Ptr<BluetoothEndpoint> bluetooth_endpoint) {
Ptr<BluetoothDevice> remote_bluetooth_device =
bluetooth_endpoint->getBluetoothDevice();
Ptr<BluetoothSocket> bluetooth_socket =
medium_manager_->connectToBluetoothDevice(
remote_bluetooth_device, bluetooth_endpoint->getServiceId());
if (bluetooth_socket.isNull()) {
return typename BasePCPHandler<Platform>::ConnectImplResult(
proto::connections::Medium::BLUETOOTH, Status::BLUETOOTH_ERROR);
}
ScopedPtr<Ptr<EndpointChannel>> scoped_bluetooth_endpoint_channel(
this->endpoint_channel_manager_->createOutgoingBluetoothEndpointChannel(
bluetooth_endpoint->getEndpointId(), bluetooth_socket));
if (scoped_bluetooth_endpoint_channel.isNull()) {
bluetooth_socket->close();
bluetooth_socket.destroy(); // Avoid leaks.
return typename BasePCPHandler<Platform>::ConnectImplResult(
proto::connections::Medium::BLUETOOTH, Status::ERROR);
}
// TODO(tracyzhou): Add logging.
return typename BasePCPHandler<Platform>::ConnectImplResult(
scoped_bluetooth_endpoint_channel.release());
}
template <typename Platform>
typename BasePCPHandler<Platform>::ConnectImplResult
P2PClusterPCPHandler<Platform>::bleConnectImpl(
Ptr<ClientProxy<Platform>> client_proxy, Ptr<BLEEndpoint> ble_endpoint) {
Ptr<BLE_PERIPHERAL> remote_ble_peripheral = ble_endpoint->getBlePeripheral();
Ptr<BLESocket> ble_socket = medium_manager_->connectToBlePeripheral(
remote_ble_peripheral, ble_endpoint->getServiceId());
if (ble_socket.isNull()) {
return typename BasePCPHandler<Platform>::ConnectImplResult(
proto::connections::Medium::BLE, Status::BLUETOOTH_ERROR);
}
ScopedPtr<Ptr<EndpointChannel>> scoped_ble_endpoint_channel(
this->endpoint_channel_manager_->createOutgoingBLEEndpointChannel(
ble_endpoint->getEndpointId(), ble_socket));
if (scoped_ble_endpoint_channel.isNull()) {
ble_socket->close();
ble_socket.destroy(); // Avoid leaks.
return typename BasePCPHandler<Platform>::ConnectImplResult(
proto::connections::Medium::BLE, Status::ERROR);
}
// TODO(tracyzhou): Add logging.
return typename BasePCPHandler<Platform>::ConnectImplResult(
scoped_ble_endpoint_channel.release());
}
template <typename Platform>
string P2PClusterPCPHandler<Platform>::getBlePeripheralId(
Ptr<BLE_PERIPHERAL> ble_peripheral) {
#if BLE_V2_IMPLEMENTED
return string(ble_peripheral->getId()->getData(),
ble_peripheral->getId()->size());
#else
return ble_peripheral->getBluetoothDevice()->getName();
#endif
}
} // namespace connections
} // namespace nearby
} // namespace location
+378
View File
@@ -0,0 +1,378 @@
#ifndef CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
#define CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
#include <vector>
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/base_pcp_handler.h"
#include "core/internal/ble_advertisement.h"
#include "core/internal/ble_compat.h"
#include "core/internal/bluetooth_device_name.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "core/internal/pcp.h"
#include "core/options.h"
#include "core/strategy.h"
#include "platform/api/bluetooth_classic.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.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.
//
// <p>Currently, this implementation advertises/discovers over BLE and Bluetooth
// and connects over Bluetooth.
template <typename Platform>
class P2PClusterPCPHandler : public BasePCPHandler<Platform> {
public:
P2PClusterPCPHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager);
~P2PClusterPCPHandler() override;
Strategy getStrategy() override;
PCP::Value getPCP() override;
protected:
std::vector<proto::connections::Medium> getConnectionMediumsByPriority()
override;
proto::connections::Medium getDefaultUpgradeMedium() override;
// @PCPHandlerThread
Ptr<typename BasePCPHandler<Platform>::StartOperationResult>
startAdvertisingImpl(Ptr<ClientProxy<Platform> > client_proxy,
const string& service_id,
const string& local_endpoint_id,
const string& local_endpoint_name,
const AdvertisingOptions& options) override;
// @PCPHandlerThread
Status::Value stopAdvertisingImpl(
Ptr<ClientProxy<Platform> > client_proxy) override;
// @PCPHandlerThread
Ptr<typename BasePCPHandler<Platform>::StartOperationResult>
startDiscoveryImpl(Ptr<ClientProxy<Platform> > client_proxy,
const string& service_id,
const DiscoveryOptions& options) override;
// @PCPHandlerThread
Status::Value stopDiscoveryImpl(
Ptr<ClientProxy<Platform> > client_proxy) override;
// @PCPHandlerThread
typename BasePCPHandler<Platform>::ConnectImplResult connectImpl(
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<typename BasePCPHandler<Platform>::DiscoveredEndpoint> endpoint)
override;
private:
template <typename>
friend class IncomingBluetoothConnectionProcessor;
template <typename>
friend class IncomingBleConnectionProcessor;
template <typename>
friend class FoundBluetoothAdvertisementProcessor;
template <typename>
friend class FoundBleAdvertisementProcessor;
class IncomingBluetoothConnectionProcessor
: public MediumManager<Platform>::IncomingBluetoothConnectionProcessor {
public:
IncomingBluetoothConnectionProcessor(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
const string& local_endpoint_name);
void onIncomingBluetoothConnection(
Ptr<BluetoothSocket> bluetooth_socket) override;
private:
class OnIncomingBluetoothConnectionRunnable : public Runnable {
public:
OnIncomingBluetoothConnectionRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<BluetoothSocket> bluetooth_socket);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<BluetoothSocket> bluetooth_socket_;
};
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
const string local_endpoint_name_;
};
class IncomingBleConnectionProcessor
: public MediumManager<Platform>::IncomingBleConnectionProcessor {
public:
IncomingBleConnectionProcessor(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
const string& local_endpoint_name);
void onIncomingBleConnection(Ptr<BLESocket> ble_socket,
const string& service_id) override;
private:
class OnIncomingBleConnectionRunnable : public Runnable {
public:
OnIncomingBleConnectionRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy, Ptr<BLESocket> ble_socket);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<BLESocket> ble_socket_;
};
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
const string local_endpoint_name_;
};
class FoundBluetoothAdvertisementProcessor
: public MediumManager<Platform>::FoundBluetoothDeviceProcessor {
public:
FoundBluetoothAdvertisementProcessor(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id);
void onFoundBluetoothDevice(Ptr<BluetoothDevice> bluetooth_device) override;
void onLostBluetoothDevice(Ptr<BluetoothDevice> bluetooth_device) override;
private:
class OnFoundBluetoothDeviceRunnable : public Runnable {
public:
OnFoundBluetoothDeviceRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor,
const string& service_id, Ptr<BluetoothDevice> bluetooth_device);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor_;
const string service_id_;
ScopedPtr<Ptr<BluetoothDevice> > bluetooth_device_;
};
class OnLostBluetoothDeviceRunnable : public Runnable {
public:
OnLostBluetoothDeviceRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor,
const string& service_id, Ptr<BluetoothDevice> bluetooth_device);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<FoundBluetoothAdvertisementProcessor>
found_bluetooth_advertisement_processor_;
const string service_id_;
ScopedPtr<Ptr<BluetoothDevice> > bluetooth_device_;
};
bool isRecognizedBluetoothEndpoint(
const string& found_bluetooth_device_name,
Ptr<BluetoothDeviceName> bluetooth_device_name);
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
const string service_id_;
ScopedPtr<ConstPtr<ByteArray> > expected_service_id_hash_;
};
class FoundBleAdvertisementProcessor
: public MediumManager<Platform>::FoundBlePeripheralProcessor {
public:
FoundBleAdvertisementProcessor(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy);
void onFoundBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id,
ConstPtr<ByteArray> advertisement_bytes) override;
void onLostBlePeripheral(Ptr<BLE_PERIPHERAL> ble_peripheral,
const string& service_id) override;
private:
class OnFoundBlePeripheralRunnable : public Runnable {
public:
OnFoundBlePeripheralRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor,
const string& service_id, Ptr<BLE_PERIPHERAL> ble_peripheral,
ConstPtr<ByteArray> advertisement_bytes);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor_;
const string service_id_;
ScopedPtr<Ptr<BLE_PERIPHERAL> > ble_peripheral_;
ScopedPtr<ConstPtr<ByteArray> > advertisement_bytes_;
ScopedPtr<ConstPtr<ByteArray> > expected_service_id_hash_;
};
class OnLostBlePeripheralRunnable : public Runnable {
public:
OnLostBlePeripheralRunnable(
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler,
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor,
const string& service_id, Ptr<BLE_PERIPHERAL> ble_peripheral);
void run() override;
private:
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
Ptr<FoundBleAdvertisementProcessor> found_ble_advertisement_processor_;
const string service_id_;
ScopedPtr<Ptr<BLE_PERIPHERAL> > ble_peripheral_;
};
// Holds the state required to re-create a BLEEndpoint we see on a
// BLEPeripheral, so OnLostBlePeripheralRunnable::run() can call
// BasePCPHandler::onEndpointLost() with the same information as was passed
// in to BasePCPHandler::onEndpointFound().
struct BLEEndpointState {
public:
BLEEndpointState(const string& endpoint_id, const string& endpoint_name)
: endpoint_id(endpoint_id), endpoint_name(endpoint_name) {}
const string endpoint_id;
const string endpoint_name;
};
Ptr<P2PClusterPCPHandler<Platform> > pcp_handler_;
Ptr<ClientProxy<Platform> > client_proxy_;
// Maps a BLEPeripheral to its corresponding BLEEndpointState.
typedef std::map<string, BLEEndpointState> FoundBLEEndpointsMap;
FoundBLEEndpointsMap found_ble_endpoints_;
};
class BluetoothEndpoint
: public BasePCPHandler<Platform>::DiscoveredEndpoint {
public:
Ptr<BluetoothDevice> getBluetoothDevice() {
return bluetooth_device_.get();
}
string getEndpointId() override { return endpoint_id_; }
string getEndpointName() override { return endpoint_name_; }
string getServiceId() override { return service_id_; }
proto::connections::Medium getMedium() override {
return proto::connections::Medium::BLUETOOTH;
}
private:
BluetoothEndpoint(Ptr<BluetoothDevice> bluetooth_device,
const string& endpoint_id, const string& endpoint_name,
const string& service_id)
: bluetooth_device_(bluetooth_device),
endpoint_id_(endpoint_id),
endpoint_name_(endpoint_name),
service_id_(service_id) {}
friend class FoundBluetoothAdvertisementProcessor;
ScopedPtr<Ptr<BluetoothDevice> > bluetooth_device_;
const string endpoint_id_;
const string endpoint_name_;
const string service_id_;
};
class BLEEndpoint : public BasePCPHandler<Platform>::DiscoveredEndpoint {
public:
Ptr<BLE_PERIPHERAL> getBlePeripheral() { return ble_peripheral_.get(); }
string getEndpointId() override { return endpoint_id_; }
string getEndpointName() override { return endpoint_name_; }
string getServiceId() override { return service_id_; }
proto::connections::Medium getMedium() override {
return proto::connections::Medium::BLE;
}
private:
BLEEndpoint(Ptr<BLE_PERIPHERAL> ble_peripheral, const string& endpoint_id,
const string& endpoint_name, const string& service_id)
: ble_peripheral_(ble_peripheral),
endpoint_id_(endpoint_id),
endpoint_name_(endpoint_name),
service_id_(service_id) {}
friend class FoundBleAdvertisementProcessor;
ScopedPtr<Ptr<BLE_PERIPHERAL> > ble_peripheral_;
const string endpoint_id_;
const string endpoint_name_;
const string service_id_;
};
static const BluetoothDeviceName::Version::Value kBluetoothDeviceNameVersion;
static const BLEAdvertisement::Version::Value kBleAdvertisementVersion;
static ConstPtr<ByteArray> generateHash(const string& source, size_t size);
static string getBlePeripheralId(Ptr<BLE_PERIPHERAL> ble_peripheral);
proto::connections::Medium startBluetoothAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
ConstPtr<ByteArray> service_id_hash, const string& local_endpoint_id,
const string& local_endpoint_name);
proto::connections::Medium startBluetoothDiscovery(
Ptr<FoundBluetoothAdvertisementProcessor> processor,
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id);
typename BasePCPHandler<Platform>::ConnectImplResult bluetoothConnectImpl(
Ptr<ClientProxy<Platform> > client_proxy,
Ptr<BluetoothEndpoint> bluetooth_endpoint);
proto::connections::Medium startBleAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
ConstPtr<ByteArray> service_id_hash, const string& local_endpoint_id,
const string& local_endpoint_name);
proto::connections::Medium startBleDiscovery(
Ptr<FoundBleAdvertisementProcessor> processor,
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id);
typename BasePCPHandler<Platform>::ConnectImplResult bleConnectImpl(
Ptr<ClientProxy<Platform> > client_proxy, Ptr<BLEEndpoint> ble_endpoint);
Ptr<MediumManager<Platform> > medium_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/p2p_cluster_pcp_handler.cc"
#endif // CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_
@@ -0,0 +1,61 @@
#include "core/internal/p2p_point_to_point_pcp_handler.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
P2PPointToPointPCPHandler<Platform>::P2PPointToPointPCPHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager)
: P2PStarPCPHandler<Platform>(medium_manager, endpoint_manager,
endpoint_channel_manager,
bandwidth_upgrade_manager),
medium_manager_(medium_manager) {}
template <typename Platform>
Strategy P2PPointToPointPCPHandler<Platform>::getStrategy() {
return Strategy::kP2PPointToPoint;
}
template <typename Platform>
PCP::Value P2PPointToPointPCPHandler<Platform>::getPCP() {
return PCP::P2P_POINT_TO_POINT;
}
template <typename Platform>
std::vector<proto::connections::Medium>
P2PPointToPointPCPHandler<Platform>::getConnectionMediumsByPriority() {
std::vector<proto::connections::Medium> mediums;
if (medium_manager_->isBluetoothAvailable()) {
mediums.push_back(proto::connections::BLUETOOTH);
}
if (medium_manager_->isBleAvailable()) {
mediums.push_back(proto::connections::BLE);
}
return mediums;
}
template <typename Platform>
bool P2PPointToPointPCPHandler<Platform>::canSendOutgoingConnection(
Ptr<ClientProxy<Platform> > client_proxy) {
// For point to point, we can only send an outgoing connection while we have
// no other connections.
return !this->hasOutgoingConnections(client_proxy) &&
!this->hasIncomingConnections(client_proxy);
}
template <typename Platform>
bool P2PPointToPointPCPHandler<Platform>::canReceiveIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy) {
// For point to point, we can only receive an incoming connection while we
// have no other connections.
return !this->hasOutgoingConnections(client_proxy) &&
!this->hasIncomingConnections(client_proxy);
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,55 @@
#ifndef CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
#define CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "core/internal/p2p_star_pcp_handler.h"
#include "core/internal/pcp.h"
#include "core/strategy.h"
#include "platform/ptr.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.
//
// <p>Currently, this implementation advertises/discovers over BLE and Bluetooth
// and connects over Bluetooth, eventually upgrading to Wifi Hotspot.
template <typename Platform>
class P2PPointToPointPCPHandler : public P2PStarPCPHandler<Platform> {
public:
P2PPointToPointPCPHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager);
Strategy getStrategy() override;
PCP::Value getPCP() override;
protected:
std::vector<proto::connections::Medium> getConnectionMediumsByPriority()
override;
bool canSendOutgoingConnection(
Ptr<ClientProxy<Platform> > client_proxy) override;
bool canReceiveIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy) override;
private:
Ptr<MediumManager<Platform> > medium_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/p2p_point_to_point_pcp_handler.cc"
#endif // CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_
+71
View File
@@ -0,0 +1,71 @@
#include "core/internal/p2p_star_pcp_handler.h"
#include <vector>
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
P2PStarPCPHandler<Platform>::P2PStarPCPHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager)
: P2PClusterPCPHandler<Platform>(medium_manager, endpoint_manager,
endpoint_channel_manager,
bandwidth_upgrade_manager),
medium_manager_(medium_manager) {}
template <typename Platform>
P2PStarPCPHandler<Platform>::~P2PStarPCPHandler() {}
template <typename Platform>
Strategy P2PStarPCPHandler<Platform>::getStrategy() {
return Strategy::kP2PStar;
}
template <typename Platform>
PCP::Value P2PStarPCPHandler<Platform>::getPCP() {
return PCP::P2P_STAR;
}
template <typename Platform>
std::vector<proto::connections::Medium>
P2PStarPCPHandler<Platform>::getConnectionMediumsByPriority() {
std::vector<proto::connections::Medium> mediums;
if (medium_manager_->isBluetoothAvailable()) {
mediums.push_back(proto::connections::BLUETOOTH);
}
if (medium_manager_->isBleAvailable()) {
mediums.push_back(proto::connections::BLE);
}
return mediums;
}
template <typename Platform>
proto::connections::Medium
P2PStarPCPHandler<Platform>::getDefaultUpgradeMedium() {
return proto::connections::Medium::WIFI_HOTSPOT;
}
template <typename Platform>
bool P2PStarPCPHandler<Platform>::canSendOutgoingConnection(
Ptr<ClientProxy<Platform> > client_proxy) {
// For star, we can only send an outgoing connection while we have no other
// connections.
return !this->hasOutgoingConnections(client_proxy) &&
!this->hasIncomingConnections(client_proxy);
}
template <typename Platform>
bool P2PStarPCPHandler<Platform>::canReceiveIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy) {
// For star, we can only receive an incoming connection if we've sent no
// outgoing connections.
return !this->hasOutgoingConnections(client_proxy);
}
} // namespace connections
} // namespace nearby
} // namespace location
+60
View File
@@ -0,0 +1,60 @@
#ifndef CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_
#define CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_
#include <vector>
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "core/internal/p2p_cluster_pcp_handler.h"
#include "core/internal/pcp.h"
#include "core/strategy.h"
#include "platform/ptr.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.
//
// <p>Currently, this implementation advertises/discovers over BLE and Bluetooth
// and connects over Bluetooth, eventually upgrading to a Wifi Hotspot.
template <typename Platform>
class P2PStarPCPHandler : public P2PClusterPCPHandler<Platform> {
public:
P2PStarPCPHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager);
~P2PStarPCPHandler() override;
Strategy getStrategy() override;
PCP::Value getPCP() override;
protected:
std::vector<proto::connections::Medium> getConnectionMediumsByPriority()
override;
proto::connections::Medium getDefaultUpgradeMedium() override;
bool canSendOutgoingConnection(
Ptr<ClientProxy<Platform> > client_proxy) override;
bool canReceiveIncomingConnection(
Ptr<ClientProxy<Platform> > client_proxy) override;
private:
Ptr<MediumManager<Platform> > medium_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/p2p_star_pcp_handler.cc"
#endif // CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_
File diff suppressed because it is too large Load Diff
+288
View File
@@ -0,0 +1,288 @@
#ifndef CORE_INTERNAL_PAYLOAD_MANAGER_H_
#define CORE_INTERNAL_PAYLOAD_MANAGER_H_
#include <cstdint>
#include <map>
#include <vector>
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/internal_payload.h"
#include "core/internal/internal_payload_factory.h"
#include "core/internal/loop_runner.h"
#include "core/listeners.h"
#include "core/payload.h"
#include "core/status.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/count_down_latch.h"
#include "platform/api/lock.h"
#include "platform/byte_array.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "platform/runnable.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace payload_manager {
template <typename>
class SendPayloadRunnable;
template <typename>
class ProcessEndpointDisconnectionRunnable;
template <typename>
class SendClientCallbacksForFinishedOutgoingPayloadRunnable;
template <typename>
class SendClientCallbacksForFinishedIncomingPayloadRunnable;
template <typename>
class HandleSuccessfulOutgoingChunkRunnable;
template <typename>
class HandleSuccessfulIncomingChunkRunnable;
} // namespace payload_manager
template <typename Platform>
class PayloadManager
: public EndpointManager<Platform>::IncomingOfflineFrameProcessor {
public:
explicit PayloadManager(Ptr<EndpointManager<Platform> > endpoint_manager);
~PayloadManager() override;
void sendPayload(Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& endpoint_ids,
ConstPtr<Payload> payload);
Status::Value cancelPayload(Ptr<ClientProxy<Platform> > client_proxy,
std::int64_t payload_id);
// @EndpointManagerReaderThread
void processIncomingOfflineFrame(
ConstPtr<OfflineFrame> offline_frame, const string& from_endpoint_id,
Ptr<ClientProxy<Platform> > to_client_proxy,
proto::connections::Medium current_medium) override;
// @EndpointManagerThread
void processEndpointDisconnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<CountDownLatch> process_disconnection_barrier) override;
private:
// Information about an endpoint for a particular payload.
class EndpointInfo {
public:
// Status set for the endpoint out-of-band via a ControlMessage.
struct Status {
enum Value { UNKNOWN, AVAILABLE, CANCELED, ERROR };
};
explicit EndpointInfo(string id);
string getId() const;
typename EndpointInfo::Status::Value getStatus() const;
std::int64_t getOffset() const;
void setStatus(const PayloadTransferFrame::ControlMessage& control_message);
void setOffset(std::int64_t offset);
private:
static typename Status::Value controlMessageEventToEndpointInfoStatus(
PayloadTransferFrame::ControlMessage::EventType event);
const string id_;
typename Status::Value status_;
std::int64_t offset_;
};
// Tracks state for an InternalPayload and the endpoints associated with it.
class PendingPayload {
public:
static Ptr<PendingPayload> createIncoming(
Ptr<InternalPayload> internal_payload, const string& endpoint_id);
static Ptr<PendingPayload> createOutgoing(
Ptr<InternalPayload> internal_payload,
const std::vector<string>& endpoint_ids);
~PendingPayload();
std::int64_t getId();
Ptr<InternalPayload> getInternalPayload();
bool isLocallyCanceled();
void markLocallyCanceled();
bool isIncoming();
// Gets the EndpointInfo objects for the endpoints (still) associated with
// this payload.
std::vector<Ptr<EndpointInfo> > getEndpoints() const;
// Returns the EndpointInfo for a given endpoint ID. Returns null if the
// endpoint is not associated with this payload.
Ptr<EndpointInfo> getEndpoint(const string& endpoint_id);
// Removes the given endpoints, e.g. on error.
void removeEndpoints(const std::vector<string>& endpoint_ids_to_remove);
// Sets the status for a particular endpoint.
void setEndpointStatusFromControlMessage(
const string& endpoint_id,
const PayloadTransferFrame::ControlMessage& control_message);
// Sets the offset for a particular endpoint.
void setOffsetForEndpoint(const string& endpoint_id, std::int64_t offset);
void close();
private:
PendingPayload(Ptr<InternalPayload> internal_payload,
const std::vector<string>& endpoint_ids, bool is_incoming);
ScopedPtr<Ptr<Lock> > lock_;
ScopedPtr<Ptr<InternalPayload> > internal_payload_;
const bool is_incoming_;
ScopedPtr<Ptr<AtomicBoolean> > is_locally_cancelled_;
typedef std::map<string, Ptr<EndpointInfo> > EndpointsMap;
EndpointsMap endpoints_;
};
// Tracks and manages PendingPayload objects in a synchronized manner.
class PendingPayloads {
public:
PendingPayloads();
~PendingPayloads();
void startTrackingPayload(std::int64_t payload_id,
Ptr<PendingPayload> pending_payload);
Ptr<PendingPayload> stopTrackingPayload(std::int64_t payload_id);
Ptr<PendingPayload> getPayload(std::int64_t payload_id);
std::vector<Ptr<PendingPayload> > getAllPayloads();
private:
ScopedPtr<Ptr<Lock> > lock_;
typedef std::map<std::int64_t, Ptr<PendingPayload> > PendingPayloadsMap;
PendingPayloadsMap pending_payloads_;
};
template <typename>
friend class payload_manager::SendPayloadRunnable;
template <typename>
friend class payload_manager::ProcessEndpointDisconnectionRunnable;
template <typename>
friend class payload_manager::
SendClientCallbacksForFinishedOutgoingPayloadRunnable;
template <typename>
friend class payload_manager::
SendClientCallbacksForFinishedIncomingPayloadRunnable;
template <typename>
friend class payload_manager::HandleSuccessfulOutgoingChunkRunnable;
template <typename>
friend class payload_manager::HandleSuccessfulIncomingChunkRunnable;
// 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(
typename EndpointInfo::Status::Value 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 PayloadTransferUpdate::Status::Value
payloadStatusToTransferUpdateStatus(proto::connections::PayloadStatus status);
ConstPtr<PayloadTransferFrame::PayloadHeader> createPayloadHeader(
ConstPtr<InternalPayload> internal_payload);
ConstPtr<PayloadTransferFrame::PayloadChunk> createPayloadChunk(
std::int64_t payload_chunk_offset,
ConstPtr<ByteArray> payload_chunk_body);
Ptr<PendingPayload> createIncomingPayload(
const PayloadTransferFrame& payload_transfer_frame,
const string& endpoint_id);
void sendClientCallbacksForFinishedOutgoingPayload(
Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& finished_endpoint_ids,
const PayloadTransferFrame::PayloadHeader& payload_header,
std::int64_t num_bytes_successfully_transferred,
proto::connections::PayloadStatus status);
void sendClientCallbacksForFinishedIncomingPayload(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
const PayloadTransferFrame::PayloadHeader& payload_header,
std::int64_t offset_bytes, proto::connections::PayloadStatus status);
void sendControlMessage(
const std::vector<string>& 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(
Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& finished_endpoint_ids,
const PayloadTransferFrame::PayloadHeader& payload_header,
std::int64_t num_bytes_successfully_transferred,
proto::connections::PayloadStatus status);
void handleFinishedIncomingPayload(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
const PayloadTransferFrame::PayloadHeader& payload_header,
std::int64_t offset_bytes, proto::connections::PayloadStatus status);
void handleSuccessfulOutgoingChunk(
Ptr<ClientProxy<Platform> > client_proxy, const 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(
Ptr<ClientProxy<Platform> > client_proxy, const 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(Ptr<ClientProxy<Platform> > to_client_proxy,
const string& from_endpoint_id,
const PayloadTransferFrame& payload_transfer_frame);
void processControlPacket(Ptr<ClientProxy<Platform> > to_client_proxy,
const string& from_endpoint_id,
const PayloadTransferFrame& payload_transfer_frame);
// @PayloadStatusUpdateThread
void notifyClientOfIncomingPayloadTransferUpdate(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
const PayloadTransferUpdate& payload_transfer_update,
bool done_with_payload);
Ptr<typename Platform::SingleThreadExecutorType> getOutgoingPayloadExecutor(
Payload::Type::Value payload_type);
void enqueueOutgoingPayload(
Ptr<typename Platform::SingleThreadExecutorType> executor,
Ptr<Runnable> runnable);
ScopedPtr<Ptr<InternalPayloadFactory<Platform> > > internal_payload_factory_;
ScopedPtr<Ptr<LoopRunner> > send_payload_loop_runner_;
ScopedPtr<Ptr<PendingPayloads> > pending_payloads_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> >
bytes_payload_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> >
file_payload_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> >
stream_payload_executor_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> >
payload_status_update_executor_;
Ptr<EndpointManager<Platform> > endpoint_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/payload_manager.cc"
#endif // CORE_INTERNAL_PAYLOAD_MANAGER_H_
+21
View File
@@ -0,0 +1,21 @@
#ifndef CORE_INTERNAL_PCP_H_
#define CORE_INTERNAL_PCP_H_
namespace location {
namespace nearby {
namespace connections {
struct PCP {
enum Value {
UNKNOWN = 0,
P2P_STAR = 1,
P2P_CLUSTER = 2,
P2P_POINT_TO_POINT = 3,
};
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_PCP_H_
+63
View File
@@ -0,0 +1,63 @@
#ifndef CORE_INTERNAL_PCP_HANDLER_H_
#define CORE_INTERNAL_PCP_HANDLER_H_
#include <vector>
#include "core/internal/client_proxy.h"
#include "core/internal/pcp.h"
#include "core/listeners.h"
#include "core/options.h"
#include "core/status.h"
#include "core/strategy.h"
#include "platform/port/string.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
// Defines the set of methods that need to be implemented to handle the
// per-PCP-specific operations in the OfflineServiceController.
//
// <p>These methods are all meant to be synchronous, and should return only
// after knowing they've done what they were supposed to do (or unequivocally
// failed to do so).
template <typename Platform>
class PCPHandler {
public:
virtual ~PCPHandler() {}
virtual Strategy getStrategy() = 0;
virtual PCP::Value getPCP() = 0;
virtual Status::Value startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const string& local_endpoint_name,
const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) = 0;
virtual void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy) = 0;
virtual Status::Value startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) = 0;
virtual void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy) = 0;
virtual Status::Value requestConnection(
Ptr<ClientProxy<Platform> > client_proxy,
const string& local_endpoint_name, const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) = 0;
virtual Status::Value acceptConnection(
Ptr<ClientProxy<Platform> > clientProxy, const string& endpoint_id,
Ptr<PayloadListener> payload_listener) = 0;
virtual Status::Value rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) = 0;
virtual proto::connections::Medium getBandwidthUpgradeMedium() = 0;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_PCP_HANDLER_H_
+160
View File
@@ -0,0 +1,160 @@
#include "core/internal/pcp_manager.h"
#include "core/internal/p2p_cluster_pcp_handler.h"
#include "core/internal/p2p_point_to_point_pcp_handler.h"
#include "core/internal/p2p_star_pcp_handler.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
PCPManager<Platform>::PCPManager(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager)
: pcp_handlers_(), current_pcp_handler_() {
pcp_handlers_[PCP::P2P_CLUSTER] = MakePtr(new P2PClusterPCPHandler<Platform>(
medium_manager, endpoint_manager, endpoint_channel_manager,
bandwidth_upgrade_manager));
pcp_handlers_[PCP::P2P_STAR] = MakePtr(new P2PStarPCPHandler<Platform>(
medium_manager, endpoint_manager, endpoint_channel_manager,
bandwidth_upgrade_manager));
pcp_handlers_[PCP::P2P_POINT_TO_POINT] =
MakePtr(new P2PPointToPointPCPHandler<Platform>(
medium_manager, endpoint_manager, endpoint_channel_manager,
bandwidth_upgrade_manager));
}
template <typename Platform>
PCPManager<Platform>::~PCPManager() {
// TODO(tracyzhou): Add logging.
// clear() instead of destroy() because this is just a reference -- the real
// object will be destroyed in the loop below.
current_pcp_handler_.clear();
for (typename PCPHandlersMap::iterator it = pcp_handlers_.begin();
it != pcp_handlers_.end(); it++) {
it->second.destroy();
}
pcp_handlers_.clear();
}
template <typename Platform>
Status::Value PCPManager<Platform>::startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& service_id, const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
if (!setCurrentPCPHandler(advertising_options.strategy)) {
return Status::ERROR;
}
return current_pcp_handler_->startAdvertising(
client_proxy, service_id, endpoint_name, advertising_options,
connection_lifecycle_listener);
}
template <typename Platform>
void PCPManager<Platform>::stopAdvertising(
Ptr<ClientProxy<Platform> > client_proxy) {
if (!current_pcp_handler_.isNull()) {
current_pcp_handler_->stopAdvertising(client_proxy);
}
}
template <typename Platform>
Status::Value PCPManager<Platform>::startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) {
if (!setCurrentPCPHandler(discovery_options.strategy)) {
return Status::ERROR;
}
return current_pcp_handler_->startDiscovery(
client_proxy, service_id, discovery_options, discovery_listener);
}
template <typename Platform>
void PCPManager<Platform>::stopDiscovery(
Ptr<ClientProxy<Platform> > client_proxy) {
if (!current_pcp_handler_.isNull()) {
current_pcp_handler_->stopDiscovery(client_proxy);
}
}
template <typename Platform>
Status::Value PCPManager<Platform>::requestConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) {
if (current_pcp_handler_.isNull()) {
return Status::OUT_OF_ORDER_API_CALL;
}
return current_pcp_handler_->requestConnection(
client_proxy, endpoint_name, endpoint_id, connection_lifecycle_listener);
}
template <typename Platform>
Status::Value PCPManager<Platform>::acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id,
Ptr<PayloadListener> payload_listener) {
if (current_pcp_handler_.isNull()) {
return Status::OUT_OF_ORDER_API_CALL;
}
return current_pcp_handler_->acceptConnection(client_proxy, endpoint_id,
payload_listener);
}
template <typename Platform>
Status::Value PCPManager<Platform>::rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_id) {
if (current_pcp_handler_.isNull()) {
return Status::OUT_OF_ORDER_API_CALL;
}
return current_pcp_handler_->rejectConnection(client_proxy, endpoint_id);
}
template <typename Platform>
proto::connections::Medium PCPManager<Platform>::getBandwidthUpgradeMedium() {
if (current_pcp_handler_.isNull()) {
return proto::connections::Medium::UNKNOWN_MEDIUM;
}
return current_pcp_handler_->getBandwidthUpgradeMedium();
}
template <typename Platform>
bool PCPManager<Platform>::setCurrentPCPHandler(const Strategy& strategy) {
current_pcp_handler_ = getPCPHandler(deducePCP(strategy));
return !current_pcp_handler_.isNull();
}
template <typename Platform>
PCP::Value PCPManager<Platform>::deducePCP(const Strategy& strategy) {
if (Strategy::kP2PCluster == strategy) {
return PCP::P2P_CLUSTER;
} else if (Strategy::kP2PStar == strategy) {
return PCP::P2P_STAR;
} else if (Strategy::kP2PPointToPoint == strategy) {
return PCP::P2P_POINT_TO_POINT;
} else {
// TODO(tracyzhou): Add logging.
return PCP::UNKNOWN;
}
}
template <typename Platform>
Ptr<PCPHandler<Platform> > PCPManager<Platform>::getPCPHandler(PCP::Value pcp) {
return pcp_handlers_[pcp];
}
} // namespace connections
} // namespace nearby
} // namespace location
+77
View File
@@ -0,0 +1,77 @@
#ifndef CORE_INTERNAL_PCP_MANAGER_H_
#define CORE_INTERNAL_PCP_MANAGER_H_
#include <map>
#include "core/internal/bandwidth_upgrade_manager.h"
#include "core/internal/client_proxy.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/endpoint_manager.h"
#include "core/internal/medium_manager.h"
#include "core/internal/pcp_handler.h"
#include "core/listeners.h"
#include "core/options.h"
#include "core/status.h"
#include "core/strategy.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
// Manages all known PCPHandler implementations, delegating operations to the
// appropriate one as per the parameters passed in.
//
// <p>This will only ever be used by the OfflineServiceController, which has all
// of its entrypoints invoked serially, so there's no synchronization needed.
template <typename Platform>
class PCPManager {
public:
PCPManager(Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager,
Ptr<EndpointManager<Platform> > endpoint_manager,
Ptr<BandwidthUpgradeManager<Platform> > bandwidth_upgrade_manager);
~PCPManager();
Status::Value startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& service_id, const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener);
void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy);
Status::Value startDiscovery(Ptr<ClientProxy<Platform> > client_proxy,
const string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener);
void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy);
Status::Value requestConnection(
Ptr<ClientProxy<Platform> > client_proxy, const string& endpoint_name,
const string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener);
Status::Value acceptConnection(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id,
Ptr<PayloadListener> payload_listener);
Status::Value rejectConnection(Ptr<ClientProxy<Platform> > client_proxy,
const string& endpoint_id);
proto::connections::Medium getBandwidthUpgradeMedium();
private:
bool setCurrentPCPHandler(const Strategy& strategy);
PCP::Value deducePCP(const Strategy& strategy);
Ptr<PCPHandler<Platform> > getPCPHandler(PCP::Value pcp);
typedef std::map<PCP::Value, Ptr<PCPHandler<Platform> > > PCPHandlersMap;
PCPHandlersMap pcp_handlers_;
Ptr<PCPHandler<Platform> > current_pcp_handler_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/pcp_manager.cc"
#endif // CORE_INTERNAL_PCP_MANAGER_H_
+67
View File
@@ -0,0 +1,67 @@
#ifndef CORE_INTERNAL_SERVICE_CONTROLLER_H_
#define CORE_INTERNAL_SERVICE_CONTROLLER_H_
#include <cstdint>
#include <vector>
#include "core/internal/client_proxy.h"
#include "core/listeners.h"
#include "core/options.h"
#include "core/payload.h"
#include "core/status.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
namespace location {
namespace nearby {
namespace connections {
template <typename Platform>
class ServiceController {
public:
virtual ~ServiceController() {}
virtual Status::Value startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_name, const std::string& service_id,
const AdvertisingOptions& advertising_options,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) = 0;
virtual void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy) = 0;
virtual Status::Value startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy, const std::string& service_id,
const DiscoveryOptions& discovery_options,
Ptr<DiscoveryListener> discovery_listener) = 0;
virtual void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy) = 0;
virtual Status::Value requestConnection(
Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_name, const std::string& endpoint_id,
Ptr<ConnectionLifecycleListener> connection_lifecycle_listener) = 0;
virtual Status::Value acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy, const std::string& endpoint_id,
Ptr<PayloadListener> payload_listener) = 0;
virtual Status::Value rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_id) = 0;
virtual void initiateBandwidthUpgrade(
Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_id) = 0;
virtual void sendPayload(Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<std::string>& endpoint_ids,
ConstPtr<Payload> payload) = 0;
virtual Status::Value cancelPayload(Ptr<ClientProxy<Platform> > client_proxy,
std::int64_t payload_id) = 0;
virtual void disconnectFromEndpoint(Ptr<ClientProxy<Platform> > client_proxy,
const std::string& endpoint_id) = 0;
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_SERVICE_CONTROLLER_H_
@@ -0,0 +1,750 @@
#include "core/internal/service_controller_router.h"
#include "core/internal/offline_service_controller.h"
namespace location {
namespace nearby {
namespace connections {
namespace service_controller_router {
// Base class for the following Runnable classes. They all need a
// ServiceControllerRouter object and a ClientProxy object.
// ServiceControllerRouter is kept as a reference because the passed in
// Ptr<ServiceControllerRouter<Platform> > should outlive it.
template <typename Platform>
class ServiceControllerRouterRunnable : public Runnable {
protected:
ServiceControllerRouterRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy)
: service_controller_router_(service_controller_router),
client_proxy_(client_proxy) {}
Ptr<ServiceControllerRouter<Platform> > service_controller_router_;
Ptr<ClientProxy<Platform> > client_proxy_;
};
template <typename Platform>
class StartAdvertisingRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
StartAdvertisingRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartAdvertisingParams> start_advertising_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(start_advertising_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
Status::Value status =
this->service_controller_router_->acquireServiceControllerForClient(
this->client_proxy_, params_->advertising_options.strategy);
if (Status::SUCCESS != status) {
result_listener->onResult(status);
return;
}
if (this->client_proxy_->isAdvertising()) {
result_listener->onResult(Status::ALREADY_ADVERTISING);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->startAdvertising(this->client_proxy_, params_->name,
params_->service_id,
params_->advertising_options,
params_->connection_lifecycle_listener));
}
private:
ScopedPtr<ConstPtr<StartAdvertisingParams> > params_;
};
template <typename Platform>
class StopAdvertisingRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
StopAdvertisingRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAdvertisingParams> stop_advertising_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(stop_advertising_params) {}
void run() override {
if (this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_) &&
this->client_proxy_->isAdvertising()) {
this->service_controller_router_->current_service_controller_
->stopAdvertising(this->client_proxy_);
}
}
private:
ScopedPtr<ConstPtr<StopAdvertisingParams> > params_;
};
template <typename Platform>
class StartDiscoveryRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
StartDiscoveryRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartDiscoveryParams> start_discovery_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(start_discovery_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
Status::Value status =
this->service_controller_router_->acquireServiceControllerForClient(
this->client_proxy_, params_->discovery_options.strategy);
if (Status::SUCCESS != status) {
result_listener->onResult(status);
return;
}
if (this->client_proxy_->isDiscovering()) {
result_listener->onResult(Status::ALREADY_DISCOVERING);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->startDiscovery(this->client_proxy_, params_->service_id,
params_->discovery_options,
params_->discovery_listener));
}
private:
ScopedPtr<ConstPtr<StartDiscoveryParams> > params_;
};
template <typename Platform>
class StopDiscoveryRunnable : public ServiceControllerRouterRunnable<Platform> {
public:
StopDiscoveryRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopDiscoveryParams> stop_discovery_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(stop_discovery_params) {}
void run() override {
if (this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_) &&
this->client_proxy_->isDiscovering()) {
this->service_controller_router_->current_service_controller_
->stopDiscovery(this->client_proxy_);
}
}
private:
ScopedPtr<ConstPtr<StopDiscoveryParams> > params_;
};
template <typename Platform>
class SendConnectionRequestRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
SendConnectionRequestRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RequestConnectionParams> request_connection_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(request_connection_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
const string& remote_endpoint_id = params_->remote_endpoint_id;
if (this->client_proxy_->hasPendingConnectionToEndpoint(
remote_endpoint_id) ||
this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) {
result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->requestConnection(this->client_proxy_, params_->name,
remote_endpoint_id,
params_->connection_lifecycle_listener));
}
private:
ScopedPtr<ConstPtr<RequestConnectionParams> > params_;
};
template <typename Platform>
class AcceptConnectionRequestRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
AcceptConnectionRequestRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<AcceptConnectionParams> accept_connection_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(accept_connection_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
const string& remote_endpoint_id = params_->remote_endpoint_id;
if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) {
result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT);
return;
}
if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) {
// TODO(tracyzhou): logging
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->acceptConnection(this->client_proxy_, remote_endpoint_id,
params_->payload_listener));
}
private:
ScopedPtr<ConstPtr<AcceptConnectionParams> > params_;
};
template <typename Platform>
class RejectConnectionRequestRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
RejectConnectionRequestRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RejectConnectionParams> reject_connection_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(reject_connection_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
const string& remote_endpoint_id = params_->remote_endpoint_id;
if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) {
result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT);
return;
}
if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) {
// TODO(tracyzhou): logging
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->rejectConnection(this->client_proxy_, remote_endpoint_id));
}
private:
ScopedPtr<ConstPtr<RejectConnectionParams> > params_;
};
template <typename Platform>
class InitiateBandwidthUpgradeRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
InitiateBandwidthUpgradeRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<InitiateBandwidthUpgradeParams>
initiate_bandwidth_upgrade_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(initiate_bandwidth_upgrade_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_) ||
!this->client_proxy_->isConnectedToEndpoint(
params_->remote_endpoint_id)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
this->service_controller_router_->current_service_controller_
->initiateBandwidthUpgrade(this->client_proxy_,
params_->remote_endpoint_id);
// The caller can listen to
// ConnectionLifecycleListener.onBandwidthChanged() to determine success.
result_listener->onResult(Status::SUCCESS);
}
private:
ScopedPtr<ConstPtr<InitiateBandwidthUpgradeParams> > params_;
};
template <typename Platform>
class SendPayloadRunnable : public ServiceControllerRouterRunnable<Platform> {
public:
SendPayloadRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<SendPayloadParams> send_payload_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(send_payload_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
if (!ServiceControllerRouter<Platform>::
clientHasConnectionToAtLeastOneEndpoint(
this->client_proxy_, params_->remote_endpoint_ids)) {
result_listener->onResult(Status::ENDPOINT_UNKNOWN);
return;
}
this->service_controller_router_->current_service_controller_->sendPayload(
this->client_proxy_, params_->remote_endpoint_ids, params_->payload);
// At this point, we've queued up the send Payload request with the
// ServiceController; any further failures (e.g. one of the endpoints is
// unknown, goes away, or otherwise fails) will be returned to the client
// as a PayloadTransferUpdate.
result_listener->onResult(Status::SUCCESS);
}
private:
ScopedPtr<ConstPtr<SendPayloadParams> > params_;
};
template <typename Platform>
class CancelPayloadRunnable : public ServiceControllerRouterRunnable<Platform> {
public:
CancelPayloadRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<CancelPayloadParams> cancel_payload_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(cancel_payload_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
result_listener->onResult(Status::OUT_OF_ORDER_API_CALL);
return;
}
result_listener->onResult(
this->service_controller_router_->current_service_controller_
->cancelPayload(this->client_proxy_, params_->payload_id));
}
private:
ScopedPtr<ConstPtr<CancelPayloadParams> > params_;
};
template <typename Platform>
class DisconnectFromEndpointRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
DisconnectFromEndpointRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<DisconnectFromEndpointParams> disconnect_from_endpoint_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(disconnect_from_endpoint_params) {}
void run() override {
if (this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
const string& remote_endpoint_id = params_->remote_endpoint_id;
if (!this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id) &&
!this->client_proxy_->hasPendingConnectionToEndpoint(
remote_endpoint_id)) {
return;
}
this->service_controller_router_->current_service_controller_
->disconnectFromEndpoint(this->client_proxy_, remote_endpoint_id);
}
}
private:
ScopedPtr<ConstPtr<DisconnectFromEndpointParams> > params_;
};
template <typename Platform>
class StopAllEndpointsRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
StopAllEndpointsRunnable(
Ptr<ServiceControllerRouter<Platform> > service_controller_router,
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAllEndpointsParams> stop_all_endpoints_params)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy),
params_(stop_all_endpoints_params) {}
void run() override {
ScopedPtr<Ptr<ResultListener> > result_listener(params_->result_listener);
if (this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
this->service_controller_router_->doneWithStrategySessionForClient(
this->client_proxy_);
}
result_listener->onResult(Status::SUCCESS);
}
private:
ScopedPtr<ConstPtr<StopAllEndpointsParams> > params_;
};
template <typename Platform>
class ClientDisconnectingRunnable
: public ServiceControllerRouterRunnable<Platform> {
public:
ClientDisconnectingRunnable(
Ptr<ServiceControllerRouter<Platform>> service_controller_router,
Ptr<ClientProxy<Platform>> client_proxy)
: ServiceControllerRouterRunnable<Platform>(service_controller_router,
client_proxy) {}
void run() override {
if (!this->service_controller_router_->clientHasAquiredServiceController(
this->client_proxy_)) {
return;
}
this->service_controller_router_->doneWithStrategySessionForClient(
this->client_proxy_);
// Log the completion of this client's connection.
// TODO(tracyzhou): Add logging.
}
};
} // namespace service_controller_router
template <typename Platform>
ServiceControllerRouter<Platform>::ServiceControllerRouter()
: current_service_controller_clients_(),
current_service_controller_(new OfflineServiceController<Platform>()),
current_strategy_(),
serializer_(Platform::createSingleThreadExecutor()) {}
template <typename Platform>
ServiceControllerRouter<Platform>::~ServiceControllerRouter() {
// TODO(tracyzhou): Add logging.
// And make sure that cleanup is the last thing we do.
serializer_->shutdown();
current_service_controller_.destroy();
current_strategy_.destroy();
current_service_controller_clients_.clear();
}
template <typename Platform>
void ServiceControllerRouter<Platform>::startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartAdvertisingParams> start_advertising_params) {
routeToServiceController(
MakePtr(new service_controller_router::StartAdvertisingRunnable<Platform>(
MakePtr(this), client_proxy, start_advertising_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::stopAdvertising(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAdvertisingParams> stop_advertising_params) {
routeToServiceController(
MakePtr(new service_controller_router::StopAdvertisingRunnable<Platform>(
MakePtr(this), client_proxy, stop_advertising_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::startDiscovery(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartDiscoveryParams> start_discovery_params) {
routeToServiceController(
MakePtr(new service_controller_router::StartDiscoveryRunnable<Platform>(
MakePtr(this), client_proxy, start_discovery_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::stopDiscovery(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopDiscoveryParams> stop_discovery_params) {
routeToServiceController(
MakePtr(new service_controller_router::StopDiscoveryRunnable<Platform>(
MakePtr(this), client_proxy, stop_discovery_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::requestConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RequestConnectionParams> request_connection_params) {
routeToServiceController(MakePtr(
new service_controller_router::SendConnectionRequestRunnable<Platform>(
MakePtr(this), client_proxy, request_connection_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<AcceptConnectionParams> accept_connection_params) {
routeToServiceController(MakePtr(
new service_controller_router::AcceptConnectionRequestRunnable<Platform>(
MakePtr(this), client_proxy, accept_connection_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RejectConnectionParams> reject_connection_params) {
routeToServiceController(MakePtr(
new service_controller_router::RejectConnectionRequestRunnable<Platform>(
MakePtr(this), client_proxy, reject_connection_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::initiateBandwidthUpgrade(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<InitiateBandwidthUpgradeParams>
initiate_bandwidth_upgrade_params) {
routeToServiceController(MakePtr(
new service_controller_router::InitiateBandwidthUpgradeRunnable<Platform>(
MakePtr(this), client_proxy, initiate_bandwidth_upgrade_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::sendPayload(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<SendPayloadParams> send_payload_params) {
routeToServiceController(
MakePtr(new service_controller_router::SendPayloadRunnable<Platform>(
MakePtr(this), client_proxy, send_payload_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::cancelPayload(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<CancelPayloadParams> cancel_payload_params) {
routeToServiceController(
MakePtr(new service_controller_router::CancelPayloadRunnable<Platform>(
MakePtr(this), client_proxy, cancel_payload_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::disconnectFromEndpoint(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<DisconnectFromEndpointParams> disconnect_from_endpoint_params) {
routeToServiceController(MakePtr(
new service_controller_router::DisconnectFromEndpointRunnable<Platform>(
MakePtr(this), client_proxy, disconnect_from_endpoint_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::stopAllEndpoints(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAllEndpointsParams> stop_all_endpoint_params) {
routeToServiceController(
MakePtr(new service_controller_router::StopAllEndpointsRunnable<Platform>(
MakePtr(this), client_proxy, stop_all_endpoint_params)));
}
template <typename Platform>
void ServiceControllerRouter<Platform>::clientDisconnecting(
Ptr<ClientProxy<Platform>> client_proxy) {
routeToServiceController(MakePtr(
new service_controller_router::ClientDisconnectingRunnable<Platform>(
MakePtr(this), client_proxy)));
}
template <typename Platform>
Status::Value
ServiceControllerRouter<Platform>::acquireServiceControllerForClient(
Ptr<ClientProxy<Platform> > client_proxy, const Strategy& strategy) {
if (current_strategy_.isNull()) {
// Case 1: There is no existing Strategy at all.
// Set everything up for the first time.
Status::Value status = updateCurrentServiceControllerAndStrategy(strategy);
if (status != Status::SUCCESS) {
return status;
}
current_service_controller_clients_.insert(client_proxy);
return Status::SUCCESS;
} else if (strategy == *current_strategy_) {
// Case 2: The existing Strategy matches.
// The new client just needs to be added to the set of clients using the
// current ServiceController.
current_service_controller_clients_.insert(client_proxy);
return Status::SUCCESS;
} else {
// Case 3: The existing Strategy doesn't match.
// It's only safe for a client to cause a switch if it's the only client
// using the current ServiceController.
bool is_the_only_client_of_service_controller =
current_service_controller_clients_.size() == 1 &&
current_service_controller_clients_.find(client_proxy) !=
current_service_controller_clients_.end();
if (!is_the_only_client_of_service_controller) {
// TODO(tracyzhou): logging
return Status::ALREADY_HAVE_ACTIVE_STRATEGY;
}
// If the client still has connected endpoints, they must disconnect before
// they can switch.
if (!client_proxy->getConnectedEndpoints().empty()) {
// TODO(tracyzhou): logging
return Status::OUT_OF_ORDER_API_CALL;
}
// By this point, it's safe to switch the Strategy and ServiceController
// (and since it's the only client, there's no need to add it to the set of
// clients using the current ServiceController).
return updateCurrentServiceControllerAndStrategy(strategy);
}
}
template <typename Platform>
bool ServiceControllerRouter<Platform>::clientHasAquiredServiceController(
Ptr<ClientProxy<Platform> > client_proxy) {
return (current_service_controller_clients_.find(client_proxy) !=
current_service_controller_clients_.end());
}
template <typename Platform>
void ServiceControllerRouter<Platform>::releaseServiceControllerForClient(
Ptr<ClientProxy<Platform> > client_proxy) {
current_service_controller_clients_.erase(client_proxy);
if (current_service_controller_clients_.empty()) {
current_service_controller_.destroy();
current_strategy_.destroy();
}
}
/** Clean up all state for this client. The client is now free to switch
* strategies. */
template <typename Platform>
void ServiceControllerRouter<Platform>::doneWithStrategySessionForClient(
Ptr<ClientProxy<Platform> > client_proxy) {
// Disconnect from all the connected endpoints tied to this clientProxy.
std::vector<string> pending_connected_endpoints =
client_proxy->getPendingConnectedEndpoints();
for (std::vector<string>::iterator it = pending_connected_endpoints.begin();
it != pending_connected_endpoints.end(); it++) {
current_service_controller_->disconnectFromEndpoint(client_proxy, *it);
}
std::vector<string> connected_endpoints =
client_proxy->getConnectedEndpoints();
for (std::vector<string>::iterator it = connected_endpoints.begin();
it != connected_endpoints.end(); it++) {
current_service_controller_->disconnectFromEndpoint(client_proxy, *it);
}
// Stop any advertising and discovery that may be underway due to this
// clientProxy.
current_service_controller_->stopAdvertising(client_proxy);
current_service_controller_->stopDiscovery(client_proxy);
// Finally, clear all state maintained by this clientProxy.
client_proxy->reset();
releaseServiceControllerForClient(client_proxy);
}
template <typename Platform>
void ServiceControllerRouter<Platform>::routeToServiceController(
Ptr<Runnable> runnable) {
serializer_->execute(runnable);
}
template <typename Platform>
bool ServiceControllerRouter<Platform>::clientHasConnectionToAtLeastOneEndpoint(
Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<string>& remote_endpoint_ids) {
for (std::vector<string>::const_iterator it = remote_endpoint_ids.begin();
it != remote_endpoint_ids.end(); it++) {
if (client_proxy->isConnectedToEndpoint(*it)) {
return true;
}
}
return false;
}
template <typename Platform>
Status::Value
ServiceControllerRouter<Platform>::updateCurrentServiceControllerAndStrategy(
const Strategy& strategy) {
if (!strategy.isValid()) {
// TODO(tracyzhou): logging
return Status::ERROR;
}
current_service_controller_.destroy();
current_service_controller_ =
MakePtr(new OfflineServiceController<Platform>());
current_strategy_.destroy();
current_strategy_ = MakePtr(new Strategy(strategy));
return Status::SUCCESS;
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,151 @@
#ifndef CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_
#define CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_
#include <set>
#include <vector>
#include "core/internal/client_proxy.h"
#include "core/internal/service_controller.h"
#include "core/params.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "platform/runnable.h"
namespace location {
namespace nearby {
namespace connections {
namespace service_controller_router {
template <typename>
class StartAdvertisingRunnable;
template <typename>
class StopAdvertisingRunnable;
template <typename>
class StartDiscoveryRunnable;
template <typename>
class StopDiscoveryRunnable;
template <typename>
class SendConnectionRequestRunnable;
template <typename>
class AcceptConnectionRequestRunnable;
template <typename>
class RejectConnectionRequestRunnable;
template <typename>
class InitiateBandwidthUpgradeRunnable;
template <typename>
class SendPayloadRunnable;
template <typename>
class CancelPayloadRunnable;
template <typename>
class DisconnectFromEndpointRunnable;
template <typename>
class StopAllEndpointsRunnable;
template <typename>
class ClientDisconnectingRunnable;
} // namespace service_controller_router
template <typename Platform>
class ServiceControllerRouter {
public:
ServiceControllerRouter();
~ServiceControllerRouter();
void startAdvertising(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartAdvertisingParams> start_advertising_params);
void stopAdvertising(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAdvertisingParams> stop_advertising_params);
void startDiscovery(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StartDiscoveryParams> start_discovery_params);
void stopDiscovery(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopDiscoveryParams> stop_discovery_params);
void requestConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RequestConnectionParams> request_connection_params);
void acceptConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<AcceptConnectionParams> accept_connection_params);
void rejectConnection(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<RejectConnectionParams> reject_connection_params);
void initiateBandwidthUpgrade(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<InitiateBandwidthUpgradeParams>
initiate_bandwidth_upgrade_params);
void sendPayload(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<SendPayloadParams> send_payload_params);
void cancelPayload(Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<CancelPayloadParams> cancel_payload_params);
void disconnectFromEndpoint(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<DisconnectFromEndpointParams> disconnect_from_endpoint_params);
void stopAllEndpoints(
Ptr<ClientProxy<Platform> > client_proxy,
ConstPtr<StopAllEndpointsParams> stop_all_endpoint_params);
void clientDisconnecting(Ptr<ClientProxy<Platform> > client_proxy);
private:
template <typename>
friend class service_controller_router::StartAdvertisingRunnable;
template <typename>
friend class service_controller_router::StopAdvertisingRunnable;
template <typename>
friend class service_controller_router::StartDiscoveryRunnable;
template <typename>
friend class service_controller_router::StopDiscoveryRunnable;
template <typename>
friend class service_controller_router::SendConnectionRequestRunnable;
template <typename>
friend class service_controller_router::AcceptConnectionRequestRunnable;
template <typename>
friend class service_controller_router::RejectConnectionRequestRunnable;
template <typename>
friend class service_controller_router::InitiateBandwidthUpgradeRunnable;
template <typename>
friend class service_controller_router::SendPayloadRunnable;
template <typename>
friend class service_controller_router::CancelPayloadRunnable;
template <typename>
friend class service_controller_router::DisconnectFromEndpointRunnable;
template <typename>
friend class service_controller_router::StopAllEndpointsRunnable;
template <typename>
friend class service_controller_router::ClientDisconnectingRunnable;
static bool clientHasConnectionToAtLeastOneEndpoint(
Ptr<ClientProxy<Platform> > client_proxy,
const std::vector<std::string>& remote_endpoint_ids);
void routeToServiceController(Ptr<Runnable> runnable);
Status::Value acquireServiceControllerForClient(
Ptr<ClientProxy<Platform> > client_proxy, const Strategy& strategy);
bool clientHasAquiredServiceController(
Ptr<ClientProxy<Platform> > client_proxy);
void releaseServiceControllerForClient(
Ptr<ClientProxy<Platform> > client_proxy);
void doneWithStrategySessionForClient(
Ptr<ClientProxy<Platform> > client_proxy);
Status::Value updateCurrentServiceControllerAndStrategy(
const Strategy& strategy);
std::set<Ptr<ClientProxy<Platform> > > current_service_controller_clients_;
Ptr<ServiceController<Platform> > current_service_controller_;
Ptr<Strategy> current_strategy_;
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType> > serializer_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/service_controller_router.cc"
#endif // CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_
@@ -0,0 +1,63 @@
#include "core/internal/wifi_lan_upgrade_handler.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace wifi_lan_upgrade_handler {
template <typename Platform>
class OnIncomingWifiConnectionRunnable : public Runnable {
public:
void run() {}
};
} // namespace wifi_lan_upgrade_handler
template <typename Platform>
WifiLanUpgradeHandler<Platform>::WifiLanUpgradeHandler(
Ptr<MediumManager<Platform> > medium_manager,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager)
: BaseBandwidthUpgradeHandler<Platform>(endpoint_channel_manager),
medium_manager_(medium_manager) {}
template <typename Platform>
WifiLanUpgradeHandler<Platform>::~WifiLanUpgradeHandler() {}
template <typename Platform>
proto::connections::Medium WifiLanUpgradeHandler<Platform>::getUpgradeMedium() {
return proto::connections::Medium::WIFI_LAN;
}
template <typename Platform>
void WifiLanUpgradeHandler<Platform>::revertImpl() {}
template <typename Platform>
void WifiLanUpgradeHandler<Platform>::onIncomingWifiConnection(
Ptr<Socket> socket) {}
// TODO(ahlee): This will differ from the Java code (previously threw an
// UpgradeException). Leaving the return type simple for the skeleton - I'll
// switch to a pair if the result enum is needed.
template <typename Platform>
ConstPtr<ByteArray>
WifiLanUpgradeHandler<Platform>::initializeUpgradedMediumForEndpoint(
const string& endpoint_id) {
return ConstPtr<ByteArray>();
}
// TODO(ahlee): This will differ from the Java code (previously threw an
// exception).
template <typename Platform>
Ptr<EndpointChannel>
WifiLanUpgradeHandler<Platform>::createUpgradedEndpointChannel(
const string& endpoint_id,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info) {
return Ptr<EndpointChannel>();
}
} // namespace connections
} // namespace nearby
} // namespace location
@@ -0,0 +1,93 @@
#ifndef CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_
#define CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_
#include "core/internal/base_bandwidth_upgrade_handler.h"
#include "core/internal/endpoint_channel_manager.h"
#include "core/internal/medium_manager.h"
#include "proto/connections/offline_wire_formats.pb.h"
#include "platform/api/socket.h"
#include "platform/port/string.h"
#include "platform/ptr.h"
#include "proto/connections_enums.pb.h"
namespace location {
namespace nearby {
namespace connections {
namespace wifi_lan_upgrade_handler {
template <typename>
class OnIncomingWifiConnectionRunnable;
} // namespace wifi_lan_upgrade_handler
// Manages the WIFI_LAN-specific methods needed to upgrade an EndpointChannel
template <typename Platform>
class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler<Platform> {
// TODO(ahlee): Uncomment when WIFI_LAN plumbing is done.
// public MediumManager<Platform>::IncomingWifiConnectionProcessor {
public:
WifiLanUpgradeHandler(
Ptr<MediumManager<Platform> > medium_manager_,
Ptr<EndpointChannelManager<Platform> > endpoint_channel_manager);
~WifiLanUpgradeHandler();
void onIncomingWifiConnection(Ptr<Socket> socket);
protected:
// @BandwidthUpgradeHandlerThread
ConstPtr<ByteArray> initializeUpgradedMediumForEndpoint(
const string& endpoint_id);
// @BandwidthUpgradeHandlerThread
Ptr<EndpointChannel> createUpgradedEndpointChannel(
const string& endpoint_id,
ConstPtr<BandwidthUpgradeNegotiationFrame::UpgradePathInfo>
upgrade_path_info);
// TODO(ahlee): Change the java counterparts of these methods to private.
proto::connections::Medium getUpgradeMedium();
// @BandwidthUpgradeHandlerThread
void revertImpl();
private:
class IncomingWifiLanSocketConnection
: public BaseBandwidthUpgradeHandler<Platform>::IncomingSocketConnection {
public:
IncomingWifiLanSocketConnection(Ptr<Socket> socket)
: new_endpoint_channel_(Ptr<EndpointChannel>()),
// TODO(ahlee): Uncomment when plumbing for WIFI_LAN is done.
// new_endpoint_channel_(getEndpointChannelManager()
// .createOutgoingWifiLanEndpointChannel(socket)),
wifi_socket_(socket) {}
// TODO(ahlee): This is only used for logging which is not currently
// implemented. If we want to match the Java code in the future, we'll need
// to add toString() to socket.h.
string socketToString() { return string(); }
void closeSocket() {
// Ignore the potential Exception returned by close(), as a counterpart
// to Java's closeQuietly().
wifi_socket_->close();
}
// TODO(ahlee): Double check that the ownership of this is correct when
// this is fully implemented.
Ptr<EndpointChannel> getEndpointChannel() {
return new_endpoint_channel_.release();
}
private:
ScopedPtr<Ptr<EndpointChannel> > new_endpoint_channel_;
Ptr<Socket> wifi_socket_;
};
template <typename>
friend class wifi_lan_upgrade_handler::OnIncomingWifiConnectionRunnable;
Ptr<MediumManager<Platform> > medium_manager_;
};
} // namespace connections
} // namespace nearby
} // namespace location
#include "core/internal/wifi_lan_upgrade_handler.cc"
#endif // CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_