diff --git a/cpp/core/BUILD b/cpp/core/BUILD index fa226c20..f0bb59b5 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -49,7 +49,9 @@ cc_library( ":types", "//platform:types", "//platform:utils", - "//platform/impl/sample", + "//platform/api", + "//platform/impl/g3", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", ], ) diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index 23941a86..7bca2966 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -1,4 +1,3 @@ - #include #include "core/core.h" @@ -6,9 +5,10 @@ #include "core/params.h" #include "core/payload.h" #include "core/status.h" +#include "platform/api/platform.h" #include "platform/byte_array.h" #include "platform/file_impl.h" -#include "platform/impl/sample/sample_platform.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -16,6 +16,8 @@ namespace location { namespace nearby { namespace connections { +using TestPlatform = platform::ImplementationPlatform; + class ResultListenerImpl : public ResultListener { public: void onResult(Status::Value status) override {} @@ -53,7 +55,7 @@ class PayloadListenerImpl : public PayloadListener { }; void check_compilation() { - Core core; + Core core; const string name = "name"; const string service_id = "service_id"; diff --git a/cpp/core/core.h b/cpp/core/core.h index d148d6df..4643dea8 100644 --- a/cpp/core/core.h +++ b/cpp/core/core.h @@ -32,15 +32,20 @@ namespace connections { * SystemClock * ConditionVariable * - * The Platform class must also provide typedefs for the following subset of - * primitives to identify the concrete classes: + * A sample Platform definitions can be found at + * //platform/impl/shared/sample/sample_platform.cc * - * SingleThreadExecutorType - * MultiThreadExecutorType - * ScheduledExecutorType + * It is no longer necessary to parametrize system types with a platform type. + * New, recommended approach is to define platform support by implementing + * static methods of "location::nearby::platform::ImplementationPlatform" class. + * every library class that needs platform support, must include platform + * header "platform/api/platform.h" and use it. + * To keep textual compatibility, one could define the following alias + * "using Platform = platform::ImplementationPlatform;". + * this will replace the "template " declaration. * - * A sample Platform class can be found at - * //platform/impl/sample/sample_platform.h + * As an added benefit, this will allow to not include *.cc files from *.h, + * and let more static analysis happen at compiler stage. */ template class Core { diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index f54df70d..9c7f303b 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -1,38 +1,39 @@ cc_library( name = "internal", srcs = [ + "bandwidth_upgrade_manager.cc", + "base_bandwidth_upgrade_handler.cc", + "base_endpoint_channel.cc", "ble_advertisement.cc", + "ble_endpoint_channel.cc", "bluetooth_device_name.cc", + "bluetooth_endpoint_channel.cc", + "endpoint_channel_manager.cc", "internal_payload.cc", "internal_payload.h", "loop_runner.cc", "loop_runner.h", "offline_frames.cc", + "wifi_lan_endpoint_channel.cc", "wifi_lan_service_info.cc", ], 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", @@ -58,6 +59,7 @@ cc_library( "service_controller.h", "service_controller_router.cc", "service_controller_router.h", + "wifi_lan_endpoint_channel.h", "wifi_lan_service_info.h", "wifi_lan_upgrade_handler.cc", "wifi_lan_upgrade_handler.h", @@ -73,7 +75,6 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", - "//platform/port:down_cast", "//platform/port:string", "//proto:connections_enums_portable_proto", "//net/proto2/compat/public:proto2_lite", @@ -82,13 +83,29 @@ cc_library( ], ) +# TODO(apolyudov): remove when api v2 rework is done. +cc_library( + name = "message_lite", + hdrs = [ + "message_lite.h", + ], + visibility = [ + "//core:__subpackages__", + "//core_v2:__subpackages__", + ], + deps = [ + "//net/proto2/compat/public:proto2_lite", + ], +) + cc_test( name = "base_endpoint_channel_test", srcs = ["base_endpoint_channel_test.cc"], deps = [ ":internal", "//platform:utils", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//proto:connections_enums_portable_proto", "//testing/base/public:gunit_main", ], @@ -100,6 +117,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -110,6 +129,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":internal", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -121,6 +142,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -135,6 +158,8 @@ cc_test( ":internal", "//proto/connections:offline_wire_formats_portable_proto", "//platform:types", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h index 6e8c0775..2ce07b7a 100644 --- a/cpp/core/internal/bandwidth_upgrade_handler.h +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -4,6 +4,7 @@ #include "core/internal/client_proxy.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform/api/count_down_latch.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "proto/connections_enums.pb.h" @@ -13,9 +14,10 @@ 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 class BandwidthUpgradeHandler { public: + using Platform = platform::ImplementationPlatform; + virtual ~BandwidthUpgradeHandler() {} // Reverts any changes made to the device in the process of upgrading diff --git a/cpp/core/internal/bandwidth_upgrade_manager.cc b/cpp/core/internal/bandwidth_upgrade_manager.cc index 4c502beb..4eabbabe 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.cc +++ b/cpp/core/internal/bandwidth_upgrade_manager.cc @@ -6,38 +6,32 @@ namespace location { namespace nearby { namespace connections { -template -BandwidthUpgradeManager::BandwidthUpgradeManager( +BandwidthUpgradeManager::BandwidthUpgradeManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager) : endpoint_manager_(endpoint_manager), bandwidth_upgrade_handlers_(), current_bandwidth_upgrade_handler_() {} -template -BandwidthUpgradeManager::~BandwidthUpgradeManager() { +BandwidthUpgradeManager::~BandwidthUpgradeManager() { // TODO(ahlee): Make sure we don't repeat the mistake fixed in cl/201883908. } -template -void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( +void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id, proto::connections::Medium medium) {} -template -void BandwidthUpgradeManager::processIncomingOfflineFrame( +void BandwidthUpgradeManager::processIncomingOfflineFrame( ConstPtr offline_frame, const string& from_endpoint_id, Ptr > to_client_proxy, proto::connections::Medium current_medium) {} -template -void BandwidthUpgradeManager::processEndpointDisconnection( +void BandwidthUpgradeManager::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( +bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( proto::connections::Medium medium) { return false; } diff --git a/cpp/core/internal/bandwidth_upgrade_manager.h b/cpp/core/internal/bandwidth_upgrade_manager.h index 5aab4033..da7a3e8f 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.h +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -9,6 +9,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -19,14 +20,15 @@ namespace connections { // Manages all known {@link BandwidthUpgradeHandler} implementations, delegating // operations to the appropriate one as per the parameters passed in. -template class BandwidthUpgradeManager - : public EndpointManager::IncomingOfflineFrameProcessor { + : public EndpointManager< + platform::ImplementationPlatform>::IncomingOfflineFrameProcessor { public: - BandwidthUpgradeManager( - Ptr > medium_manager, - Ptr > endpoint_channel_manager, - Ptr > endpoint_manager); + using Platform = platform::ImplementationPlatform; + + BandwidthUpgradeManager(Ptr> medium_manager, + Ptr endpoint_channel_manager, + Ptr> endpoint_manager); ~BandwidthUpgradeManager() override; // This is the point on the initiator side where the @@ -50,17 +52,14 @@ class BandwidthUpgradeManager bool setCurrentBandwidthUpgradeHandler(proto::connections::Medium medium); Ptr > endpoint_manager_; - typedef std::map > > + typedef std::map> BandwidthUpgradeHandlersMap; BandwidthUpgradeHandlersMap bandwidth_upgrade_handlers_; - Ptr > current_bandwidth_upgrade_handler_; + Ptr current_bandwidth_upgrade_handler_; }; } // namespace connections } // namespace nearby } // namespace location -#include "core/internal/bandwidth_upgrade_manager.cc" - #endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.cc b/cpp/core/internal/base_bandwidth_upgrade_handler.cc index 9970bb8e..e58c6301 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.cc +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -4,138 +4,115 @@ namespace location { namespace nearby { namespace connections { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace base_bandwidth_upgrade_handler { -template class RevertRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class InitiateBandwidthUpgradeForEndpointRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessEndpointDisconnectionRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable : public Runnable { public: - void run() {} + void run() override {} }; } // namespace base_bandwidth_upgrade_handler -template -BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager) +BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager) : endpoint_channel_manager_(endpoint_channel_manager), - alarm_executor_(), - serial_executor_(), + alarm_executor_(nullptr), + serial_executor_(nullptr), previous_endpoint_channels_(), in_progress_upgrades_(), safe_to_close_write_timestamps_() {} -template -BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} +BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} -template -void BaseBandwidthUpgradeHandler::revert() {} +void BaseBandwidthUpgradeHandler::revert() {} -template -void BaseBandwidthUpgradeHandler::processEndpointDisconnection( +void BaseBandwidthUpgradeHandler::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( +void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradeNegotiationFrame( - ConstPtr - bandwidth_upgrade_negotiation, - Ptr > to_client_proxy, - const string& from_endpoint_id, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradeNegotiationFrame( + ConstPtr bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, const string& from_endpoint_id, + proto::connections::Medium current_medium) {} -template -Ptr > -BaseBandwidthUpgradeHandler::getEndpointChannelManager() { +Ptr +BaseBandwidthUpgradeHandler::getEndpointChannelManager() { return endpoint_channel_manager_; } -template -void BaseBandwidthUpgradeHandler::onIncomingConnection( +void BaseBandwidthUpgradeHandler::onIncomingConnection( Ptr incoming_socket_connection) {} -template -void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( +void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( Ptr runnable) {} -template -void BaseBandwidthUpgradeHandler::runUpgradeProtocol( +void BaseBandwidthUpgradeHandler::runUpgradeProtocol( Ptr > client_proxy, const string& endpoint_id, Ptr new_endpoint_channel) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEvent( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEvent( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info, + proto::connections::Medium current_medium) {} -template -Ptr BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEventInternal( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info) { +Ptr +BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEventInternal( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info) { return Ptr(); } -template -void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( +void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( +void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( +std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( const string& endpoint_id) { return 0; } -template -std::int64_t -BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( +std::int64_t BaseBandwidthUpgradeHandler::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 -void BaseBandwidthUpgradeHandler:: +void BaseBandwidthUpgradeHandler:: attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( proto::connections::BandwidthUpgradeResult result, proto::connections::BandwidthUpgradeErrorStage error_stage) {} // TODO(ahlee): This will differ from the Java code (previously threw an // UpgradeException). -template Ptr -BaseBandwidthUpgradeHandler::readClientIntroductionFrame( +BaseBandwidthUpgradeHandler::readClientIntroductionFrame( Ptr endpoint_channel) { return Ptr(); } diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.h b/cpp/core/internal/base_bandwidth_upgrade_handler.h index c4be0a7d..78320abe 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.h +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -19,13 +19,9 @@ namespace connections { namespace base_bandwidth_upgrade_handler { -template class RevertRunnable; -template class InitiateBandwidthUpgradeForEndpointRunnable; -template class ProcessEndpointDisconnectionRunnable; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable; } // namespace base_bandwidth_upgrade_handler @@ -55,26 +51,28 @@ class ProcessBandwidthUpgradeNegotiationFrameRunnable; // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the // other, and upon doing so, close the prior EndpointChannel. // -template -class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { +class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { public: - BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager); - ~BaseBandwidthUpgradeHandler(); + using Platform = platform::ImplementationPlatform; - void revert(); + explicit BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager); + ~BaseBandwidthUpgradeHandler() override; + + void revert() override; void processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, - Ptr process_disconnection_barrier); + Ptr process_disconnection_barrier) override; // Initiates the bandwidth upgrade and sends an UPGRADE_PATH_AVAILABLE // OfflineFrame. void initiateBandwidthUpgradeForEndpoint( - Ptr > client_proxy, const string& endpoint_id); + Ptr > client_proxy, + const string& endpoint_id) override; void processBandwidthUpgradeNegotiationFrame( ConstPtr bandwidth_upgrade_negotiation, Ptr > to_client_proxy, const string& from_endpoint_id, - proto::connections::Medium current_medium); + proto::connections::Medium current_medium) override; protected: // Represents the incoming Socket the Initiator has gotten after initializing @@ -117,7 +115,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { // @BandwidthUpgradeHandlerThread virtual proto::connections::Medium getUpgradeMedium() = 0; - Ptr > getEndpointChannelManager(); + Ptr getEndpointChannelManager(); // Common functionality to take an incoming connection and go through the // upgrade process. // @BandwidthUpgradeHandlerThread @@ -126,15 +124,11 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { void runOnBandwidthUpgradeHandlerThread(Ptr runnable); private: - template friend class base_bandwidth_upgrade_handler::RevertRunnable; - template friend class base_bandwidth_upgrade_handler:: InitiateBandwidthUpgradeForEndpointRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessEndpointDisconnectionRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessBandwidthUpgradeNegotiationFrameRunnable; @@ -162,7 +156,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { Ptr readClientIntroductionFrame(Ptr endpoint_channel); - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; ScopedPtr > alarm_executor_; ScopedPtr > serial_executor_; // Stores each upgraded endpoint's previous EndpointChannel (that was @@ -184,6 +178,4 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { } // namespace nearby } // namespace location -#include "core/internal/base_bandwidth_upgrade_handler.cc" - #endif // CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc index 6665a2b5..8df7d4bc 100644 --- a/cpp/core/internal/base_endpoint_channel.cc +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -2,6 +2,7 @@ #include +#include "platform/api/platform.h" #include "platform/synchronized.h" #include "proto/connections_enums.pb.h" @@ -11,6 +12,8 @@ namespace connections { namespace { +using Platform = platform::ImplementationPlatform; + std::int32_t bytesToInt(ConstPtr bytes) { const char* int_bytes = bytes->getData(); @@ -33,36 +36,36 @@ ConstPtr intToBytes(std::int32_t value) { return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes))); } -ExceptionOr > readExactly(Ptr reader, - std::int64_t size) { +ExceptionOr> readExactly(Ptr reader, + std::int64_t size) { string buffer; std::int64_t remaining_size = size; while (remaining_size > 0) { - ExceptionOr > read_bytes = reader->read(remaining_size); + ExceptionOr> read_bytes = reader->read(remaining_size); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); // In Java, EOFException is a sub-variant of IOException. if (scoped_read_bytes.isNull() || scoped_read_bytes->size() == 0) { - return ExceptionOr >(Exception::IO); + return ExceptionOr>(Exception::IO); } buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size()); remaining_size -= scoped_read_bytes->size(); } - return ExceptionOr >( + return ExceptionOr>( MakeConstPtr(new ByteArray(buffer.data(), buffer.size()))); } ExceptionOr readInt(Ptr reader) { - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader, sizeof(std::int32_t)); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { @@ -70,7 +73,7 @@ ExceptionOr readInt(Ptr reader) { } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); return ExceptionOr(bytesToInt(scoped_read_bytes.get())); } @@ -82,10 +85,9 @@ Exception::Value writeInt(Ptr writer, std::int32_t value) { } // namespace // TODO(b/150763574): Move implementatiopn to header or .inc file. -template -BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, - Ptr reader, - Ptr writer) +BaseEndpointChannel::BaseEndpointChannel(absl::string_view channel_name, + Ptr reader, + Ptr writer) : last_read_timestamp_(-1), channel_name_(channel_name), system_clock_(Platform::createSystemClock()), @@ -100,8 +102,7 @@ BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, Platform::createConditionVariable(is_paused_lock_.get())), is_paused_(Platform::createAtomicBoolean(false)) {} -template -BaseEndpointChannel::~BaseEndpointChannel() { +BaseEndpointChannel::~BaseEndpointChannel() { // WARNING: Make sure to never access reader_ and writer_ from here. // // They're owned by the specialized *Socket classes that are in turn @@ -114,14 +115,13 @@ BaseEndpointChannel::~BaseEndpointChannel() { // of this class). } -template -ExceptionOr > BaseEndpointChannel::read() { +ExceptionOr> BaseEndpointChannel::read() { Synchronized s(reader_lock_.get()); ExceptionOr read_int = readInt(reader_); if (!read_int.ok()) { if (Exception::IO == read_int.exception()) { - return ExceptionOr >(read_int.exception()); + return ExceptionOr>(read_int.exception()); } } @@ -131,11 +131,11 @@ ExceptionOr > BaseEndpointChannel::read() { return ExceptionOr>(Exception::IO); } - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader_, read_int.result()); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } @@ -154,7 +154,7 @@ ExceptionOr > BaseEndpointChannel::read() { // short-circuit out of here on error. read_bytes_result.destroy(); if (decoded_bytes == nullptr) { - return ExceptionOr >( + return ExceptionOr>( Exception::INVALID_PROTOCOL_BUFFER); } read_bytes_result = MakeConstPtr( @@ -162,16 +162,14 @@ ExceptionOr > BaseEndpointChannel::read() { } last_read_timestamp_ = system_clock_->elapsedRealtime(); - return ExceptionOr >(read_bytes_result); + return ExceptionOr>(read_bytes_result); } -template -Exception::Value BaseEndpointChannel::write( - ConstPtr data) { +Exception::Value BaseEndpointChannel::write(ConstPtr data) { Synchronized s(writer_lock_.get()); // Avoid leaks. - ScopedPtr > scoped_data(data); + ScopedPtr> scoped_data(data); if (isPaused()) { blockUntilUnpaused(); @@ -191,7 +189,7 @@ Exception::Value BaseEndpointChannel::write( data_to_write = scoped_data.release(); } // Avoid leaks. - ScopedPtr > scoped_data_to_write(data_to_write); + ScopedPtr> scoped_data_to_write(data_to_write); Exception::Value write_exception = writeInt( writer_, static_cast(scoped_data_to_write->size())); @@ -218,8 +216,7 @@ Exception::Value BaseEndpointChannel::write( return Exception::NONE; } -template -void BaseEndpointChannel::close() { +void BaseEndpointChannel::close() { // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. @@ -246,8 +243,7 @@ void BaseEndpointChannel::close() { // TODO(tracyzhou): Add logging. } -template -void BaseEndpointChannel::close( +void BaseEndpointChannel::close( proto::connections::DisconnectionReason reason) { // WARNING WARNING WARNING // @@ -259,8 +255,7 @@ void BaseEndpointChannel::close( // TODO(tracyzhou): Add logging. } -template -string BaseEndpointChannel::getType() { +string BaseEndpointChannel::getType() { string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : ""; switch (getMedium()) { case proto::connections::Medium::BLUETOOTH: @@ -278,46 +273,32 @@ string BaseEndpointChannel::getType() { } } -template -string BaseEndpointChannel::getName() { - return channel_name_; -} +string BaseEndpointChannel::getName() { return channel_name_; } -template -void BaseEndpointChannel::enableEncryption( +void BaseEndpointChannel::enableEncryption( Ptr encryption_context) { assert(!encryption_context.isNull()); encryption_context_->set(encryption_context); } -template -bool BaseEndpointChannel::isPaused() { - return is_paused_->get(); -} +bool BaseEndpointChannel::isPaused() { return is_paused_->get(); } -template -void BaseEndpointChannel::pause() { - is_paused_->set(true); -} +void BaseEndpointChannel::pause() { is_paused_->set(true); } -template -void BaseEndpointChannel::resume() { +void BaseEndpointChannel::resume() { is_paused_->set(false); unblockPausedWriter(); } -template -std::int64_t BaseEndpointChannel::getLastReadTimestamp() { +std::int64_t BaseEndpointChannel::getLastReadTimestamp() { return last_read_timestamp_; } -template -bool BaseEndpointChannel::isEncryptionEnabled() { +bool BaseEndpointChannel::isEncryptionEnabled() { return !encryption_context_->get().isNull(); } -template -void BaseEndpointChannel::unblockPausedWriter() { +void BaseEndpointChannel::unblockPausedWriter() { Synchronized s(is_paused_lock_.get()); // Notify to tell the thread calling wait() to check again. @@ -329,8 +310,7 @@ void BaseEndpointChannel::unblockPausedWriter() { is_paused_condition_variable_->notify(); } -template -void BaseEndpointChannel::blockUntilUnpaused() { +void BaseEndpointChannel::blockUntilUnpaused() { Synchronized s(is_paused_lock_.get()); // For more on how this works, see diff --git a/cpp/core/internal/base_endpoint_channel.h b/cpp/core/internal/base_endpoint_channel.h index 4e3c112e..e5b60160 100644 --- a/cpp/core/internal/base_endpoint_channel.h +++ b/cpp/core/internal/base_endpoint_channel.h @@ -16,15 +16,15 @@ #include "platform/ptr.h" #include "proto/connections_enums.pb.h" #include "securegcm/d2d_connection_context_v1.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { namespace connections { -template class BaseEndpointChannel : public EndpointChannel { public: - BaseEndpointChannel(const string& channel_name, Ptr reader, + BaseEndpointChannel(absl::string_view channel_name, Ptr reader, Ptr writer); ~BaseEndpointChannel() override; @@ -69,7 +69,7 @@ class BaseEndpointChannel : public EndpointChannel { private: // Used to sanity check that our frame sizes are reasonable. - static const std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB bool isEncryptionEnabled(); void unblockPausedWriter(); @@ -107,6 +107,4 @@ class BaseEndpointChannel : public EndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/base_endpoint_channel.cc" - #endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/base_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc index abdb5dbe..f954758a 100644 --- a/cpp/core/internal/base_endpoint_channel_test.cc +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -1,6 +1,6 @@ #include "core/internal/base_endpoint_channel.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "platform/pipe.h" #include "proto/connections_enums.pb.h" #include "gmock/gmock.h" @@ -11,21 +11,7 @@ namespace nearby { namespace connections { namespace { -class TestPlatform : public DefaultPlatform { - public: - static SystemClock* createSystemClock() { return nullptr; } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr> createAtomicReference(const T& initial_value) { - return Ptr>(); - } -}; - -class TestEndpointChannel : public BaseEndpointChannel { +class TestEndpointChannel : public BaseEndpointChannel { public: explicit TestEndpointChannel(Ptr input_stream) : BaseEndpointChannel("channel", input_stream, Ptr()) {} @@ -34,7 +20,7 @@ class TestEndpointChannel : public BaseEndpointChannel { MOCK_METHOD(void, closeImpl, (), (override)); }; -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index e885e42c..03235508 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -48,12 +48,7 @@ class StartAdvertisingCallable : public Callable { service_id_(service_id), local_endpoint_name_(local_endpoint_name), options_(options), - // Convert the passed in connection_lifecycle_listener Ptr into a - // reference counted one. The advertising session and any connected - // endpoints need a handle to the same connection_lifecycle_listener, so - // there is no clear model of who actually owns the listener. - connection_lifecycle_listener_( - MakeRefCountedPtr(&(*connection_lifecycle_listener))) {} + connection_lifecycle_listener_(connection_lifecycle_listener) {} ExceptionOr call() override { // Ask the implementation to attempt to start advertising. @@ -675,8 +670,8 @@ const std::int64_t template BasePCPHandler::BasePCPHandler( Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : endpoint_manager_(endpoint_manager), endpoint_channel_manager_(endpoint_channel_manager), bandwidth_upgrade_manager_(bandwidth_upgrade_manager), @@ -1137,6 +1132,14 @@ Exception::Value BasePCPHandler::onIncomingConnection( return Exception::IO; } + // The ConnectionRequest frame has two fields that both contain the + // EndpointInfo. The legacy field stores it as a string while the newer field + // stores it as a byte array. We'll attempt to grab from the newer field, but + // will accept the older string if it's all that exists. + const std::string& endpoint_name = connection_request.has_endpoint_info() + ? connection_request.endpoint_info() + : connection_request.endpoint_name(); + // We've successfully connected to the device, and are now about to jump on to // the EncryptionRunner thread to start running our encryption protocol. We'll // mark ourselves as pending in case we get another call to requestConnection @@ -1146,7 +1149,7 @@ Exception::Value BasePCPHandler::onIncomingConnection( .insert(std::make_pair( connection_request.endpoint_id(), PendingConnectionInfo::newIncomingPendingConnectionInfo( - client_proxy, connection_request.endpoint_name(), + client_proxy, endpoint_name, scoped_endpoint_channel.release(), connection_request.nonce(), start_time_millis, advertising_connection_lifecycle_listener_, OfflineFrames::connectionRequestMediumsToMediums( @@ -1378,7 +1381,7 @@ void BasePCPHandler::evaluateConnectionResult( } else { pending_rejected_connection_close_alarms_.insert(std::make_pair( endpoint_id, - MakePtr(new CancelableAlarm( + MakePtr(new CancelableAlarm( "BasePCPHandler.evaluateConnectionResult() delayed close", MakePtr( new base_pcp_handler:: @@ -1407,7 +1410,7 @@ BasePCPHandler::readConnectionRequestFrame( // To avoid a device connecting but never sending their introductory frame, we // time out the connection after a certain amount of time. - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "PCPHandler(" + this->getStrategy().getName() + ").readConnectionRequestFrame", MakePtr( diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h index 9019a9b9..8415cae0 100644 --- a/cpp/core/internal/base_pcp_handler.h +++ b/cpp/core/internal/base_pcp_handler.h @@ -69,10 +69,9 @@ class BasePCPHandler public EndpointManager::IncomingOfflineFrameProcessor { public: // TODO(tracyzhou): Add SecureRandom. - BasePCPHandler( - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + BasePCPHandler(Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~BasePCPHandler() override; // We have been asked by the client to start advertising. Once we successfully @@ -239,8 +238,8 @@ class BasePCPHandler virtual proto::connections::Medium getDefaultUpgradeMedium() = 0; Ptr > endpoint_manager_; - Ptr > endpoint_channel_manager_; - Ptr > bandwidth_upgrade_manager_; + Ptr endpoint_channel_manager_; + Ptr bandwidth_upgrade_manager_; private: template @@ -473,7 +472,7 @@ class BasePCPHandler // 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 > > + typedef std::map > PendingRejectedConnectionCloseAlarmsMap; PendingRejectedConnectionCloseAlarmsMap pending_rejected_connection_close_alarms_; diff --git a/cpp/core/internal/ble_endpoint_channel.cc b/cpp/core/internal/ble_endpoint_channel.cc index 8684dcc1..35924f94 100644 --- a/cpp/core/internal/ble_endpoint_channel.cc +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -6,42 +6,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BLEEndpointChannel::createOutgoing( +Ptr BLEEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -Ptr > -BLEEndpointChannel::createIncoming( +Ptr BLEEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -BLEEndpointChannel::BLEEndpointChannel( - const string& channel_name, Ptr ble_socket) - : BaseEndpointChannel(channel_name, - ble_socket->getInputStream(), - ble_socket->getOutputStream()), +BLEEndpointChannel::BLEEndpointChannel(const string& channel_name, + Ptr ble_socket) + : BaseEndpointChannel(channel_name, ble_socket->getInputStream(), + ble_socket->getOutputStream()), ble_socket_(ble_socket) {} -template -BLEEndpointChannel::~BLEEndpointChannel() {} +BLEEndpointChannel::~BLEEndpointChannel() {} -template -proto::connections::Medium BLEEndpointChannel::getMedium() { +proto::connections::Medium BLEEndpointChannel::getMedium() { return proto::connections::Medium::BLE; } -template -void BLEEndpointChannel::closeImpl() { +void BLEEndpointChannel::closeImpl() { Exception::Value exception = ble_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/ble_endpoint_channel.h b/cpp/core/internal/ble_endpoint_channel.h index fb92dc4d..fe336b9b 100644 --- a/cpp/core/internal/ble_endpoint_channel.h +++ b/cpp/core/internal/ble_endpoint_channel.h @@ -4,6 +4,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/ble.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -12,13 +13,14 @@ namespace location { namespace nearby { namespace connections { -template -class BLEEndpointChannel : public BaseEndpointChannel { +class BLEEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); @@ -39,6 +41,4 @@ class BLEEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/ble_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/bluetooth_endpoint_channel.cc b/cpp/core/internal/bluetooth_endpoint_channel.cc index f9525b36..9fa8069a 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.cc +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -6,42 +6,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BluetoothEndpointChannel::createOutgoing( +Ptr BluetoothEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -Ptr > -BluetoothEndpointChannel::createIncoming( +Ptr BluetoothEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -BluetoothEndpointChannel::BluetoothEndpointChannel( +BluetoothEndpointChannel::BluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) - : BaseEndpointChannel(channel_name, - bluetooth_socket->getInputStream(), - bluetooth_socket->getOutputStream()), + : BaseEndpointChannel(channel_name, bluetooth_socket->getInputStream(), + bluetooth_socket->getOutputStream()), bluetooth_socket_(bluetooth_socket) {} -template -BluetoothEndpointChannel::~BluetoothEndpointChannel() {} +BluetoothEndpointChannel::~BluetoothEndpointChannel() {} -template -proto::connections::Medium BluetoothEndpointChannel::getMedium() { +proto::connections::Medium BluetoothEndpointChannel::getMedium() { return proto::connections::Medium::BLUETOOTH; } -template -void BluetoothEndpointChannel::closeImpl() { +void BluetoothEndpointChannel::closeImpl() { Exception::Value exception = bluetooth_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/bluetooth_endpoint_channel.h b/cpp/core/internal/bluetooth_endpoint_channel.h index f9be2269..31c84216 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.h +++ b/cpp/core/internal/bluetooth_endpoint_channel.h @@ -4,6 +4,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/bluetooth_classic.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -12,13 +13,14 @@ namespace location { namespace nearby { namespace connections { -template -class BluetoothEndpointChannel : public BaseEndpointChannel { +class BluetoothEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); @@ -40,6 +42,4 @@ class BluetoothEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/bluetooth_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/encryption_runner.cc b/cpp/core/internal/encryption_runner.cc index ccd5f8d5..9e463442 100644 --- a/cpp/core/internal/encryption_runner.cc +++ b/cpp/core/internal/encryption_runner.cc @@ -101,7 +101,7 @@ class ServerRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startServer() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -112,7 +112,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -121,7 +121,7 @@ class ServerRunnable : public Runnable { if (!client_init.ok()) { if (Exception::IO == client_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -138,7 +138,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -151,7 +151,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -160,7 +160,7 @@ class ServerRunnable : public Runnable { if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -174,7 +174,7 @@ class ServerRunnable : public Runnable { if (!client_finish.ok()) { if (Exception::IO == client_finish.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -189,7 +189,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -202,7 +202,7 @@ class ServerRunnable : public Runnable { MakePtr(server.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -213,8 +213,8 @@ class ServerRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } @@ -258,7 +258,7 @@ class ClientRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startClient() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -270,7 +270,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -280,7 +280,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -289,7 +289,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_init_exception) { if (Exception::IO == write_init_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -303,7 +303,7 @@ class ClientRunnable : public Runnable { if (!server_init.ok()) { if (Exception::IO == server_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -319,7 +319,7 @@ class ClientRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -332,7 +332,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_finish == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -342,7 +342,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_finish_exception) { if (Exception::IO == write_finish_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -356,7 +356,7 @@ class ClientRunnable : public Runnable { MakePtr(client.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -367,8 +367,8 @@ class ClientRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc index 80222b6a..b366fff6 100644 --- a/cpp/core/internal/endpoint_channel_manager.cc +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -2,21 +2,20 @@ #include "core/internal/ble_endpoint_channel.h" #include "core/internal/bluetooth_endpoint_channel.h" +#include "core/internal/wifi_lan_endpoint_channel.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { -template -EndpointChannelManager::EndpointChannelManager( +EndpointChannelManager::EndpointChannelManager( Ptr > medium_manager) : lock_(Platform::createLock()), medium_manager_(medium_manager), channel_state_(new ChannelState()) {} -template -EndpointChannelManager::~EndpointChannelManager() { +EndpointChannelManager::~EndpointChannelManager() { Synchronized s(lock_.get()); // TODO(tracyzhou): logger.atDebug().log("Initiating shutdown of @@ -26,40 +25,47 @@ EndpointChannelManager::~EndpointChannelManager() { // down."); } -template Ptr -EndpointChannelManager::createOutgoingBluetoothEndpointChannel( +EndpointChannelManager::createOutgoingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createOutgoing( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createOutgoing(medium_manager_, channel_name, + bluetooth_socket); } -template Ptr -EndpointChannelManager::createIncomingBluetoothEndpointChannel( +EndpointChannelManager::createIncomingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createIncoming( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createIncoming(medium_manager_, channel_name, + bluetooth_socket); } -template -Ptr -EndpointChannelManager::createOutgoingBLEEndpointChannel( +Ptr EndpointChannelManager::createOutgoingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createOutgoing(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createOutgoing(medium_manager_, channel_name, + ble_socket); } -template -Ptr -EndpointChannelManager::createIncomingBLEEndpointChannel( +Ptr EndpointChannelManager::createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createIncoming(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createIncoming(medium_manager_, channel_name, + ble_socket); } -template -void EndpointChannelManager::registerChannelForEndpoint( +Ptr +EndpointChannelManager::CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateOutgoing( + medium_manager_, channel_name, wifi_lan_socket); +} + +Ptr +EndpointChannelManager::CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateIncoming( + medium_manager_, channel_name, wifi_lan_socket); +} + +void EndpointChannelManager::registerChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -74,9 +80,7 @@ void EndpointChannelManager::registerChannelForEndpoint( } #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED -template -Ptr -EndpointChannelManager::replaceChannelForEndpoint( +Ptr EndpointChannelManager::replaceChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -96,8 +100,7 @@ EndpointChannelManager::replaceChannelForEndpoint( } #endif -template -bool EndpointChannelManager::encryptChannelForEndpoint( +bool EndpointChannelManager::encryptChannelForEndpoint( const string& endpoint_id, Ptr encryption_context) { Synchronized s(lock_.get()); @@ -125,16 +128,14 @@ bool EndpointChannelManager::encryptChannelForEndpoint( return true; } -template -Ptr EndpointChannelManager::getChannelForEndpoint( +Ptr EndpointChannelManager::getChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); return channel_state_->getChannelForEndpoint(endpoint_id); } -template -void EndpointChannelManager::setActiveEndpointChannel( +void EndpointChannelManager::setActiveEndpointChannel( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED @@ -155,8 +156,7 @@ void EndpointChannelManager::setActiveEndpointChannel( channel_state_->updateChannelForEndpoint(endpoint_id, endpoint_channel)); } -template -void EndpointChannelManager::encryptChannel( +void EndpointChannelManager::encryptChannel( const string& endpoint_id, Ptr endpoint_channel, Ptr encryption_context) { // TODO(tracyzhou): Add logging. @@ -165,8 +165,7 @@ void EndpointChannelManager::encryptChannel( ///////////////////////////////// ChannelState ///////////////////////////////// -template -EndpointChannelManager::ChannelState::~ChannelState() { +EndpointChannelManager::ChannelState::~ChannelState() { while (!endpoint_id_to_metadata_.empty()) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.begin(); @@ -176,15 +175,13 @@ EndpointChannelManager::ChannelState::~ChannelState() { } } -template -bool EndpointChannelManager::ChannelState::isEndpointEncrypted( +bool EndpointChannelManager::ChannelState::isEndpointEncrypted( const string& endpoint_id) { return !getEncryptionContextForEndpoint(endpoint_id).isNull(); } -template Ptr -EndpointChannelManager::ChannelState::updateChannelForEndpoint( +EndpointChannelManager::ChannelState::updateChannelForEndpoint( const string& endpoint_id, Ptr endpoint_channel) { Ptr previous_endpoint_channel; Ptr endpoint_metadata; @@ -208,11 +205,10 @@ EndpointChannelManager::ChannelState::updateChannelForEndpoint( return scoped_previous_endpoint_channel.release(); } -template -Ptr EndpointChannelManager:: - ChannelState::updateEncryptionContextForEndpoint( - const string& endpoint_id, - Ptr encryption_context) { +Ptr +EndpointChannelManager::ChannelState::updateEncryptionContextForEndpoint( + const string& endpoint_id, + Ptr encryption_context) { Ptr previous_encryption_context; Ptr endpoint_metadata; @@ -234,8 +230,7 @@ Ptr EndpointChannelManager:: return scoped_previous_encryption_context.release(); } -template -bool EndpointChannelManager::ChannelState::removeEndpoint( +bool EndpointChannelManager::ChannelState::removeEndpoint( const string& endpoint_id, proto::connections::DisconnectionReason reason) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -249,9 +244,8 @@ bool EndpointChannelManager::ChannelState::removeEndpoint( return true; } -template Ptr -EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( +EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -262,9 +256,8 @@ EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( return it->second->encryption_context; } -template Ptr -EndpointChannelManager::ChannelState::getChannelForEndpoint( +EndpointChannelManager::ChannelState::getChannelForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -275,8 +268,7 @@ EndpointChannelManager::ChannelState::getChannelForEndpoint( return it->second->endpoint_channel; } -template -bool EndpointChannelManager::unregisterChannelForEndpoint( +bool EndpointChannelManager::unregisterChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); diff --git a/cpp/core/internal/endpoint_channel_manager.h b/cpp/core/internal/endpoint_channel_manager.h index 059085b7..18ec3641 100644 --- a/cpp/core/internal/endpoint_channel_manager.h +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -9,6 +9,8 @@ #include "platform/api/ble.h" #include "platform/api/bluetooth_classic.h" #include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "securegcm/d2d_connection_context_v1.h" @@ -22,10 +24,11 @@ namespace connections { // // The factory methods would be static, but for the fact that they need to use // the MediumManager. -template class EndpointChannelManager { public: - explicit EndpointChannelManager(Ptr > medium_manager); + using Platform = platform::ImplementationPlatform; + + explicit EndpointChannelManager(Ptr> medium_manager); ~EndpointChannelManager(); Ptr createOutgoingBluetoothEndpointChannel( @@ -38,6 +41,11 @@ class EndpointChannelManager { Ptr createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket); + Ptr CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket); + Ptr CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_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. @@ -138,6 +146,4 @@ class EndpointChannelManager { } // namespace nearby } // namespace location -#include "core/internal/endpoint_channel_manager.cc" - #endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc index d4d6c3de..f6757d06 100644 --- a/cpp/core/internal/endpoint_manager.cc +++ b/cpp/core/internal/endpoint_manager.cc @@ -466,7 +466,7 @@ const std::int32_t EndpointManager::kMaxConcurrentEndpoints = 50; template EndpointManager::EndpointManager( - Ptr> endpoint_channel_manager) + Ptr endpoint_channel_manager) : thread_utils_(Platform::createThreadUtils()), system_clock_(Platform::createSystemClock()), endpoint_channel_manager_(endpoint_channel_manager), diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h index 05263f2c..7fd5b5df 100644 --- a/cpp/core/internal/endpoint_manager.h +++ b/cpp/core/internal/endpoint_manager.h @@ -95,7 +95,7 @@ class EndpointManager { }; explicit EndpointManager( - Ptr > endpoint_channel_manager); + Ptr endpoint_channel_manager); ~EndpointManager(); // Invoked from the constructors of the various *Manager components that make @@ -211,7 +211,7 @@ class EndpointManager { ScopedPtr > thread_utils_; ScopedPtr > system_clock_; - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; typedef std::map > IncomingOfflineFrameProcessorsMap; diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc index 4deb40b7..c7cd5a45 100644 --- a/cpp/core/internal/internal_payload_factory.cc +++ b/cpp/core/internal/internal_payload_factory.cc @@ -108,7 +108,7 @@ class OutgoingStreamInternalPayload : public InternalPayload { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; template @@ -191,7 +191,7 @@ class OutgoingFileInternalPayload : public InternalPayload { void close() override { payload_->asFile()->asInputFile()->close(); } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; class IncomingFileInternalPayload : public InternalPayload { @@ -277,14 +277,13 @@ Ptr InternalPayloadFactory::createIncoming( case PayloadTransferFrame::PayloadHeader::STREAM: { // pipe will be auto-destroyed when it is no longer referenced. - auto pipe = MakeRefCountedPtr(new Pipe()); + auto pipe = MakeRefCountedPtr(new Pipe()); return MakePtr(new IncomingStreamInternalPayload( - MakeConstPtr(new Payload( - payload_id, - MakeConstPtr(new Payload::Stream( - Pipe::createInputStream(pipe))))), - Pipe::createOutputStream(pipe))); + MakeConstPtr( + new Payload(payload_id, MakeConstPtr(new Payload::Stream( + Pipe::createInputStream(pipe))))), + Pipe::createOutputStream(pipe))); } case PayloadTransferFrame::PayloadHeader::FILE: { diff --git a/cpp/core/internal/medium_manager.cc b/cpp/core/internal/medium_manager.cc index be6ca370..4035bd20 100644 --- a/cpp/core/internal/medium_manager.cc +++ b/cpp/core/internal/medium_manager.cc @@ -10,13 +10,15 @@ template MediumManager::MediumManager() : mediums_(new Mediums()), bluetooth_classic_lock_(Platform::createLock()), - ble_lock_(Platform::createLock()) {} + ble_lock_(Platform::createLock()), + wifi_lan_lock_(Platform::createLock()) {} template MediumManager::~MediumManager() { // TODO(reznor): log.atDebug().log("Initiating shutdown of MediumManager."); Synchronized s1(bluetooth_classic_lock_.get()); Synchronized s2(ble_lock_.get()); + Synchronized s3(wifi_lan_lock_.get()); mediums_.destroy(); // TODO(reznor): log.atDebug().log("MediumManager has shut down."); @@ -356,6 +358,131 @@ Ptr MediumManager::connectToBlePeripheral( #endif } +// ~~~~~~~~~~~~~~~~~~~~~~~~ WIFILAN ~~~~~~~~~~~~~~~~~~~~~~~~ +template +bool MediumManager::IsWifiLanAvailable() { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAvailable(); +} + +template +bool MediumManager::StartWifiLanAdvertising( + absl::string_view service_id, absl::string_view service_info_name) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAdvertising(service_id, service_info_name); +} + +template +void MediumManager::StopWifiLanAdvertising( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAdvertising(service_id); +} + +template +class DiscoveredServiceCallback : public mediums::DiscoveredServiceCallback { + public: + typedef typename MediumManager::FoundWifiLanServiceProcessor + FoundWifiLanServiceProcessor; + + explicit DiscoveredServiceCallback( + Ptr found_wifi_lan_service_processor) + : found_wifi_lan_service_processor_(found_wifi_lan_service_processor) {} + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnFoundWifiLanService(wifi_lan_service); + } + + void OnServiceLost(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnLostWifiLanService(wifi_lan_service); + } + + private: + ScopedPtr > + found_wifi_lan_service_processor_; +}; + +template +bool MediumManager::StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartDiscovery( + service_id, MakePtr(new DiscoveredServiceCallback( + found_wifi_lan_service_processor))); +} + +template +void MediumManager::StopWifiLanDiscovery( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopDiscovery(service_id); +} + +template +class WifiLanAcceptedConnectionCallback + : public mediums::WifiLan::AcceptedConnectionCallback { + public: + typedef typename MediumManager::IncomingWifiLanConnectionProcessor + IncomingWifiLanConnectionProcessor; + + explicit WifiLanAcceptedConnectionCallback( + Ptr + incoming_wifi_lan_connection_processor) + : incoming_wifi_lan_connection_processor_( + incoming_wifi_lan_connection_processor) {} + + void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) override { + incoming_wifi_lan_connection_processor_->OnIncomingWifiLanConnection( + socket); + } + + private: + ScopedPtr > + incoming_wifi_lan_connection_processor_; +}; + +template +bool MediumManager::IsListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAcceptingConnections(service_id); +} + +template +bool MediumManager::StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAcceptingConnections( + service_id, MakePtr(new WifiLanAcceptedConnectionCallback( + incoming_wifi_lan_connection_processor))); +} + +template +void MediumManager::StopListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAcceptingConnections(service_id); +} + +template +Ptr MediumManager::ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->Connect(wifi_lan_service, service_id); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/medium_manager.h b/cpp/core/internal/medium_manager.h index 93ba82af..b102865a 100644 --- a/cpp/core/internal/medium_manager.h +++ b/cpp/core/internal/medium_manager.h @@ -122,6 +122,45 @@ class MediumManager { Ptr connectToBlePeripheral(Ptr ble_peripheral, const string& service_id); + // ~~~~~~~~~~~~~~~~~~~~~~~~ WIFI-LAN ~~~~~~~~~~~~~~~~~~~~~~~~ + + bool IsWifiLanAvailable(); + + bool StartWifiLanAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopWifiLanAdvertising(absl::string_view service_id); + + class FoundWifiLanServiceProcessor { + public: + virtual ~FoundWifiLanServiceProcessor() {} + + virtual void OnFoundWifiLanService( + Ptr wifi_lan_service) = 0; + virtual void OnLostWifiLanService(Ptr wifi_lan_service) = 0; + }; + + bool StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor); + void StopWifiLanDiscovery(absl::string_view service_id); + + class IncomingWifiLanConnectionProcessor { + public: + virtual ~IncomingWifiLanConnectionProcessor() {} + + virtual void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) = 0; + }; + + bool IsListeningForIncomingWifiLanConnections(absl::string_view service_id); + bool StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor); + void StopListeningForIncomingWifiLanConnections(absl::string_view service_id); + + Ptr ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id); + private: // The destructor for this needs to be manually invoked after the locks below // are acquired, so it cannot be a ScopedPtr. @@ -129,6 +168,7 @@ class MediumManager { ScopedPtr > bluetooth_classic_lock_; ScopedPtr > ble_lock_; + ScopedPtr > wifi_lan_lock_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD index b0fca5fa..1cf244df 100644 --- a/cpp/core/internal/mediums/BUILD +++ b/cpp/core/internal/mediums/BUILD @@ -1,3 +1,23 @@ +cc_library( + name = "utils", + srcs = [ + "utils.cc", + ], + hdrs = [ + "utils.h", + ], + visibility = [ + "//core/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + cc_library( name = "mediums", srcs = [ @@ -5,8 +25,6 @@ cc_library( "ble_advertisement_header.cc", "ble_packet.cc", "ble_peripheral.cc", - "utils.cc", - "utils.h", ], hdrs = [ "advertisement_read_result.cc", @@ -34,9 +52,12 @@ cc_library( "mediums.h", "uuid.cc", "uuid.h", + "wifi_lan.cc", + "wifi_lan.h", ], visibility = ["//core/internal:__pkg__"], deps = [ + ":utils", "//platform:logging", "//platform:types", "//platform:utils", @@ -53,7 +74,8 @@ cc_test( srcs = ["advertisement_read_result_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", "//absl/time", ], @@ -65,6 +87,8 @@ cc_test( deps = [ ":mediums", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -74,6 +98,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -83,6 +109,8 @@ cc_test( srcs = ["ble_packet_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -92,6 +120,8 @@ cc_test( srcs = ["bloom_filter_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -101,7 +131,8 @@ cc_test( srcs = ["lost_entity_tracker_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index 158e01fb..db251240 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -1,6 +1,6 @@ #include "core/internal/mediums/advertisement_read_result.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -10,23 +10,7 @@ namespace nearby { namespace connections { namespace mediums { -class SampleSystemClock : public SystemClock { - public: - SampleSystemClock() {} - ~SampleSystemClock() override {} - - std::int64_t elapsedRealtime() override { - return absl::ToUnixMillis(absl::Now()); - } -}; - -class SamplePlatform { - public: - static Ptr createLock() { return DefaultPlatform::createLock(); } - static Ptr createSystemClock() { - return MakePtr(new SampleSystemClock()); - } -}; +using TestPlatform = platform::ImplementationPlatform; constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; @@ -39,16 +23,16 @@ const absl::Duration kAdvertisementMaxBackoffDuration = template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = + TestPlatform>::kAdvertisementMaxBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = + TestPlatform>::kAdvertisementBaseBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); TEST(AdvertisementReadResultTest, AdvertisementExists) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -61,7 +45,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) { } TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -70,23 +54,23 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { } TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), AdvertisementReadResult< - SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); + TestPlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Sleep for some time, but not long enough to warrant a retry. @@ -94,22 +78,22 @@ TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2)); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult 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::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an additional failure so our backoff duration increases. @@ -120,11 +104,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { absl::SleepFor(kAdvertisementBaseBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an absurd amount of failures so we hit the maximum backoff duration. @@ -137,11 +121,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { absl::SleepFor(kAdvertisementMaxBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, GetDurationSinceRead) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int64_t sleepTime = 420; diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc index 32ba762c..4b38ecd4 100644 --- a/cpp/core/internal/mediums/ble_v2.cc +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -460,8 +460,8 @@ void BLEV2::stopScanning() { // TODO(b/112199086) Change to RecurringCancelableAlarm template -Ptr> BLEV2::createOnLostAlarm() { - return Ptr>(); +Ptr BLEV2::createOnLostAlarm() { + return Ptr(); } // Returns true if the device is currently accepting incoming BLE socket diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h index d8f07bd2..2e13802b 100644 --- a/cpp/core/internal/mediums/ble_v2.h +++ b/cpp/core/internal/mediums/ble_v2.h @@ -166,7 +166,7 @@ class BLEV2 { struct ScanningInfo { ScanningInfo(const string& service_id, Ptr scan_callback_facade, - Ptr> on_lost_alarm) + Ptr on_lost_alarm) : service_id(service_id), scan_callback_facade(scan_callback_facade), on_lost_alarm(on_lost_alarm) {} @@ -177,7 +177,7 @@ class BLEV2 { const string service_id; ScopedPtr> scan_callback_facade; // TODO(ahlee): Change to recurring cancelable alarm - ScopedPtr>> on_lost_alarm; + ScopedPtr> on_lost_alarm; }; struct AdvertisingInfo { @@ -236,7 +236,7 @@ class BLEV2 { Ptr ble_peripheral, ConstPtr advertisement_data); void processOnLostTimeout(); - Ptr> createOnLostAlarm(); + Ptr createOnLostAlarm(); bool isAdvertisementGattServerRunning(); bool startAdvertisementGattServer(const string& service_id, diff --git a/cpp/core/internal/mediums/lost_entity_tracker_test.cc b/cpp/core/internal/mediums/lost_entity_tracker_test.cc index ce37d6e0..bb4b2ef9 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker_test.cc +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -1,6 +1,6 @@ #include "core/internal/mediums/lost_entity_tracker.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" namespace location { @@ -9,6 +9,8 @@ namespace connections { namespace mediums { namespace { +using TestPlatform = platform::ImplementationPlatform; + struct TestEntity { int id; @@ -18,7 +20,7 @@ struct TestEntity { }; TEST(LostEntityTracker, NoEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -41,7 +43,7 @@ TEST(LostEntityTracker, NoEntitiesLost) { } TEST(LostEntityTracker, AllEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -55,7 +57,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); // Go through a round without rediscovering any entities. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::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()); @@ -63,7 +65,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { } TEST(LostEntityTracker, SomeEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -80,7 +82,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { // was lost after the check. lost_entity_tracker.recordFoundEntity(entity_1.get()); lost_entity_tracker.recordFoundEntity(entity_3.get()); - typename LostEntityTracker::EntitySet + typename LostEntityTracker::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()); @@ -88,7 +90,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { } TEST(LostEntityTracker, SameEntityMultipleCopies) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_1_copy( MakeConstPtr(new TestEntity(1))); @@ -107,7 +109,7 @@ TEST(LostEntityTracker, SameEntityMultipleCopies) { // Go through a round without rediscovering any entities and verify that we // lost an entity equivalent to both copies of it. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_EQ(lost_entities.size(), 1); ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); diff --git a/cpp/core/internal/mediums/mediums.cc b/cpp/core/internal/mediums/mediums.cc index 22638499..65d19764 100644 --- a/cpp/core/internal/mediums/mediums.cc +++ b/cpp/core/internal/mediums/mediums.cc @@ -10,7 +10,8 @@ Mediums::Mediums() bluetooth_classic_( new BluetoothClassic(bluetooth_radio_.get())), ble_(new BLE(bluetooth_radio_.get())), - ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())) {} + ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())), + wifi_lan_(new mediums::WifiLan()) {} template Mediums::~Mediums() { @@ -37,6 +38,11 @@ Ptr > Mediums::bleV2() const { return ble_v2_.get(); } +template +Ptr > Mediums::wifi_lan() const { + return wifi_lan_.get(); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/mediums/mediums.h b/cpp/core/internal/mediums/mediums.h index f6d57d75..fed971fa 100644 --- a/cpp/core/internal/mediums/mediums.h +++ b/cpp/core/internal/mediums/mediums.h @@ -5,6 +5,7 @@ #include "core/internal/mediums/ble_v2.h" #include "core/internal/mediums/bluetooth_classic.h" #include "core/internal/mediums/bluetooth_radio.h" +#include "core/internal/mediums/wifi_lan.h" #include "platform/ptr.h" namespace location { @@ -27,6 +28,8 @@ class Mediums { Ptr > ble() const; // Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium. Ptr > bleV2() const; + // Returns a handle to the Wifi-Lan medium. + Ptr > wifi_lan() const; private: // The order of declaration is critical for both construction and @@ -41,6 +44,7 @@ class Mediums { ScopedPtr > > bluetooth_classic_; ScopedPtr > > ble_; ScopedPtr > > ble_v2_; + ScopedPtr > > wifi_lan_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc index 125359c7..72f08ef7 100644 --- a/cpp/core/internal/mediums/utils.cc +++ b/cpp/core/internal/mediums/utils.cc @@ -1,8 +1,10 @@ #include "core/internal/mediums/utils.h" +#include #include #include "platform/exception.h" +#include "platform/prng.h" #include "absl/strings/escaping.h" namespace location { @@ -48,6 +50,26 @@ ConstPtr Utils::legacySha256HashOnlyForPrinting( return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length); } +ConstPtr Utils::generateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.nextUInt32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return MakeConstPtr(new ByteArray(data)); +} + std::string Utils::bytesToPrintableHexString(ConstPtr bytes) { std::string hex_string( absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); diff --git a/cpp/core/internal/mediums/utils.h b/cpp/core/internal/mediums/utils.h index 665716a9..bb5e7704 100644 --- a/cpp/core/internal/mediums/utils.h +++ b/cpp/core/internal/mediums/utils.h @@ -21,6 +21,8 @@ class Utils { static ConstPtr legacySha256HashOnlyForPrinting( Ptr hash_utils, ConstPtr source, size_t length); + static ConstPtr generateRandomBytes(size_t length); + private: static std::string bytesToPrintableHexString(ConstPtr bytes); }; diff --git a/cpp/core/internal/mediums/webrtc/BUILD b/cpp/core/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..5ab6e446 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/BUILD @@ -0,0 +1,77 @@ +cc_library( + name = "webrtc", + hdrs = [ + "webrtc_socket.cc", + "webrtc_socket.h", + ], + deps = [ + "//platform:utils", + "//platform/api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_library( + name = "peer_id", + srcs = ["peer_id.cc"], + hdrs = ["peer_id.h"], + deps = [ + "//core/internal/mediums:utils", + "//platform:types", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + +cc_library( + name = "signaling_frames", + srcs = ["signaling_frames.cc"], + hdrs = ["signaling_frames.h"], + deps = [ + ":peer_id", + "//platform:types", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//absl/strings", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform:types", + "//platform/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep + ], +) diff --git a/cpp/core/internal/mediums/webrtc/peer_id.cc b/cpp/core/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..d6c03fe6 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,41 @@ +#include "core/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(ConstPtr bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +ConstPtr PeerId::FromRandom(Ptr hash_utils) { + return FromSeed(Utils::generateRandomBytes(kPeerIdLength), hash_utils); +} + +ConstPtr PeerId::FromSeed(ConstPtr seed, + Ptr hash_utils) { + ScopedPtr> full_hash( + Utils::sha256Hash(hash_utils, seed, kPeerIdLength)); + ScopedPtr> hashedSeed( + MakeConstPtr(new ByteArray(full_hash->getData(), kPeerIdLength / 2))); + return MakeConstPtr(new PeerId(BytesToStringUppercase(hashedSeed.get()))); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/peer_id.h b/cpp/core/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..984ed34c --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,36 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static ConstPtr FromRandom(Ptr hash_utils); + static ConstPtr FromSeed(ConstPtr seed, + Ptr hash_utils); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core/internal/mediums/webrtc/peer_id_test.cc b/cpp/core/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..de1235e9 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,76 @@ +#include "core/internal/mediums/webrtc/peer_id.h" + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +class MockHashUtils : public HashUtils { + public: + MOCK_METHOD(ConstPtr, md5, (const std::string& input), (override)); + MOCK_METHOD(ConstPtr, sha256, (const std::string& input), + (override)); +}; + +} // namespace + +TEST(PeerIdTest, GenerateRandomPeerId) { + // These are actual SHA-256 values for |seed| = "seed". + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::_)) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::_)); + + ConstPtr peer_id = PeerId::FromRandom(mock_hash_utils); + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "sesdfed"; + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))); + + ConstPtr peer_id = + PeerId::FromSeed(MakeConstPtr(new ByteArray(seed)), mock_hash_utils); + + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + ASSERT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.cc b/cpp/core/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..6af39230 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,125 @@ +#include "core/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ConstPtr FrameToByteArray( + const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return MakeConstPtr(new ByteArray(message.c_str(), message.size())); +} + +void SetSenderId(ConstPtr sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id->GetId()); +} + +ConstPtr DecodeIceCandidate( + const location::nearby::mediums::IceCandidate& ice_candidate_proto) { + webrtc::SdpParseError error; + return ConstPtr(webrtc::CreateIceCandidate( + ice_candidate_proto.sdp_mid(), ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.mutable_ready_for_signaling_poke(); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +Ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()) + .release()); +} + +Ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()) + .release()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.h b/cpp/core/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..fec7046c --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,49 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id); + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer); +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +Ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +Ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..3e468d23 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,184 @@ +#include "core/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/ptr.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + ConstPtr sender_id(new PeerId("abc")); + ConstPtr encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_poke->getData(), encoded_poke->size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ConstPtr encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer->getData(), encoded_offer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignalingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + Ptr decoded_offer = DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr answer = + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp); + ConstPtr encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer->getData(), encoded_answer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + Ptr decoded_answer = DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + ConstPtr sender_id(new PeerId("abc")); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate.get())); + } + ConstPtr encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates->getData(), encoded_candidates->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> decoded_candidates = + DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..49d76110 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,139 @@ +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +template +Exception::Value WebRtcSocket::OutputStreamImpl::write( + ConstPtr data) { + ScopedPtr> scoped_data(data); + + if (scoped_data->size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return Exception::IO; + } + + socket_->BlockUntilSufficientSpaceInBuffer(scoped_data->size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return Exception::IO; + } + + if (!socket_->SendMessage(scoped_data.release())) { + return Exception::IO; + } + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::flush() { + // Java implementation is empty. + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::close() { + socket_->close(); + return Exception::NONE; +} + +// WebRtcSocket +template +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), + data_channel_(std::move(data_channel)), + pipe_(MakeRefCountedPtr(new Pipe())), + incoming_data_piped_input_stream_(Pipe::createInputStream(pipe_)), + incoming_data_piped_output_stream_(Pipe::createOutputStream(pipe_)), + output_stream_(MakePtr(new OutputStreamImpl(this))), + closed_(Platform::createAtomicBoolean(false)), + backpressure_lock_(Platform::createLock()), + buffer_variable_( + Platform::createConditionVariable(backpressure_lock_.get())) {} + +template +Ptr WebRtcSocket::getInputStream() { + return incoming_data_piped_input_stream_.get(); +} + +template +Ptr WebRtcSocket::getOutputStream() { + return output_stream_.get(); +} + +template +void WebRtcSocket::close() { + if (IsClosed()) return; + + closed_->set(true); + incoming_data_piped_output_stream_->close(); + incoming_data_piped_input_stream_->close(); + data_channel_->Close(); + WakeUpWriter(); + if (!socket_closed_listener_.isNull()) { + socket_closed_listener_->OnSocketClosed(); + } +} + +template +void WebRtcSocket::NotifyDataChannelMsgReceived( + ConstPtr message) { + Exception::Value exception = + incoming_data_piped_output_stream_->write(message); + if (exception != Exception::NONE) close(); + + exception = incoming_data_piped_output_stream_->flush(); + if (exception != Exception::NONE) close(); +} + +template +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { + WakeUpWriter(); +} + +template +bool WebRtcSocket::SendMessage(ConstPtr data) { + ScopedPtr> scoped_data(data); + return data_channel_->Send(webrtc::DataBuffer( + std::string(scoped_data->getData(), scoped_data->size()))); +} + +template +bool WebRtcSocket::IsClosed() { + return closed_->get(); +} + +template +void WebRtcSocket::WakeUpWriter() { + Synchronized s(backpressure_lock_.get()); + buffer_variable_->notify(); +} + +template +void WebRtcSocket::SetOnSocketClosedListener( + Ptr listener) { + socket_closed_listener_ = listener; +} + +template +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + Synchronized s(backpressure_lock_.get()); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_->wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..5a55e9d9 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,104 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include "platform/api/atomic_boolean.h" +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/api/socket.h" +#include "platform/pipe.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +template +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + Ptr getInputStream() override; + Ptr getOutputStream() override; + void close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(ConstPtr message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + class SocketClosedListener { + public: + virtual ~SocketClosedListener() = default; + virtual void OnSocketClosed() = 0; + }; + void SetOnSocketClosedListener(Ptr listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) + : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception::Value write(ConstPtr data) override; + Exception::Value flush() override; + Exception::Value close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(ConstPtr data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Ptr pipe_; + ScopedPtr> incoming_data_piped_input_stream_; + ScopedPtr> incoming_data_piped_output_stream_; + + ScopedPtr> output_stream_; + + ScopedPtr> closed_; + + Ptr socket_closed_listener_; + + ScopedPtr> backpressure_lock_; + ScopedPtr> buffer_variable_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/webrtc/webrtc_socket.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..503b8cd8 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,155 @@ +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/api/platform.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +class MockSocketClosedListener + : public WebRtcSocket::SocketClosedListener { + public: + MOCK_METHOD(void, OnSocketClosed, ()); +}; + +TEST(WebRtcSocketTest, ReadFromSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr> result = + webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("Me"))); + webrtc_socket.NotifyDataChannelMsgReceived( + MakeConstPtr(new ByteArray("ssa"))); + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("ge"))); + ExceptionOr> result; + + // This behaviour is different from the Java code + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "Me"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ssa"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ge"); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::NONE); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + ConstPtr kMessage = MakeConstPtr(new ByteArray(kMaxDataSize + 1)); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + ScopedPtr> mock_listener( + MakePtr(new MockSocketClosedListener())); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.SetOnSocketClosedListener(mock_listener.get()); + + EXPECT_CALL(*mock_listener, OnSocketClosed()); + EXPECT_CALL(*mock_data_channel, Close()); + webrtc_socket.close(); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.getOutputStream()->write(kMessage); + webrtc_socket.close(); + + EXPECT_EQ(webrtc_socket.getInputStream()->read().exception(), Exception::IO); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.cc b/cpp/core/internal/mediums/wifi_lan.cc new file mode 100644 index 00000000..bc69220a --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.cc @@ -0,0 +1,213 @@ +#include "core/internal/mediums/wifi_lan.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +template +WifiLan::WifiLan() + : lock_(Platform::createLock()), + wifi_lan_medium_(Platform::createWifiLanMedium()) {} + +template +bool WifiLan::IsAvailable() { + Synchronized s(lock_.get()); + + return !wifi_lan_medium_.isNull(); +} + +template +bool WifiLan::StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) { + Synchronized s(lock_.get()); + + if (!IsAvailable()) { + return false; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StartAdvertising(service_id, + // wifi_lan_service_info_name)); + + advertising_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAdvertising(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAdvertising()) { + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopAdvertising(advertising_info_.service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.service_id.clear(); +} + +template +bool WifiLan::IsAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.service_id.empty(); +} + +template +bool WifiLan::StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback) { + Synchronized s(lock_.get()); + + if (discovered_service_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because a null parameter was passed in."); + return false; + } + + if (IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because we are already discovering."); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start WifiLan discovering + // because WifiLan isn't available."); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_discovered_service_callback_bridge( + new DiscoveredServiceCallbackBridge(discovered_service_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartDiscovery( + // service_id, Ptr( + // discovered_service_callback_bridge.release())); + + discovering_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopDiscovery(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't turn off WifiLan + // discovering because we never started discovering."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopDiscovery(discovering_info_.service_id); + // Reset our bundle of scanning state to mark that we're no longer scanning. + discovering_info_.service_id.clear(); +} + +template +bool WifiLan::IsDiscovering(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !discovering_info_.service_id.empty(); +} + +template +bool WifiLan::StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + if (accepted_connection_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections because a null parameter was passed in."); + return false; + } + + if (IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections for %s because another WifiLan service socket is + // already in-progress.", service_id); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start accepting WifiLan + // connections for %s because WifiLan isn't available.", serviceId); + return false; + } + + ScopedPtr> + scoped_wifi_lan_accepted_connection_callback( + new WifiLanAcceptedConnectionCallback( + accepted_connection_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartAcceptingConnections( + // service_id, Ptr( + // wifi_lan_accepted_connection_callback.release())); + + accepting_connections_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't stop accepting WifiLan + // connections because it was never started."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium.); + // A possible implementation is: + // wifi_lan_medium_->StopAcceptingConnections( + // accepting_connections_info_.service_id); + + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.service_id.clear(); +} + +template +bool WifiLan::IsAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !accepting_connections_info_.service_id.empty(); +} + +template +Ptr WifiLan::Connect( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (wifi_lan_service.isNull() || service_id.empty()) { + return Ptr(); + } + + if (!IsAvailable()) { + return Ptr(); + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // return wifi_lan_medium_->Connect(wifi_lan_service, service_id); + return Ptr(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.h b/cpp/core/internal/mediums/wifi_lan.h new file mode 100644 index 00000000..953cbf1f --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.h @@ -0,0 +1,160 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ +#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ + +#include + +#include "platform/api/lock.h" +#include "platform/api/wifi_lan.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(Ptr wifi_lan_service) = 0; + virtual void OnServiceLost(Ptr wifi_lan_service) = 0; +}; + +template +class WifiLan { + public: + WifiLan(); + virtual ~WifiLan() = default; + + bool IsAvailable(); + + bool StartAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopAdvertising(absl::string_view service_id); + bool IsAdvertising(); + + bool StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback); + void StopDiscovery(absl::string_view service_id); + bool IsDiscovering(absl::string_view service_id); + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) = 0; + }; + + bool StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback); + void StopAcceptingConnections(absl::string_view service_id); + bool IsAcceptingConnections(absl::string_view service_id); + + Ptr Connect(Ptr wifi_lan_service, + absl::string_view service_id); + + private: + class DiscoveredServiceCallbackBridge + : public WifiLanMedium::DiscoveredServiceCallback { + public: + explicit DiscoveredServiceCallbackBridge( + Ptr discovered_service_callback) + : discovered_service_callback_(discovered_service_callback) {} + ~DiscoveredServiceCallbackBridge() override = default; + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceDiscovered(wifi_lan_service); + } + void OnServiceLost(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceLost(wifi_lan_service); + } + + private: + ScopedPtr> + discovered_service_callback_; + }; + + class WifiLanAcceptedConnectionCallback + : public WifiLanMedium::AcceptedConnectionCallback { + public: + explicit WifiLanAcceptedConnectionCallback( + Ptr accepted_connection_callback) + : accepted_connection_callback_(accepted_connection_callback) {} + ~WifiLanAcceptedConnectionCallback() override = default; + + void OnConnectionAccepted(Ptr wifi_lan_socket, + absl::string_view service_id) override { + accepted_connection_callback_->OnConnectionAccepted(wifi_lan_socket, + service_id); + } + + private: + ScopedPtr> + accepted_connection_callback_; + }; + + struct DiscoveringInfo { + DiscoveringInfo() = default; + explicit DiscoveringInfo(absl::string_view service_id) + : service_id(service_id) {} + ~DiscoveringInfo() = default; + + string service_id; + }; + + struct AdvertisingInfo { + AdvertisingInfo() = default; + explicit AdvertisingInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AdvertisingInfo() = default; + + string service_id; + }; + + struct AcceptingConnectionsInfo { + AcceptingConnectionsInfo() = default; + explicit AcceptingConnectionsInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AcceptingConnectionsInfo() = default; + + string service_id; + }; + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + + // ---------- CORE WIFILAN------------ + + // The underlying, per-platform implementation. + ScopedPtr> wifi_lan_medium_; + + // ------------ DISCOVERY ------------ + + // discovering_info_ is not scoped because it's nullable. + DiscoveringInfo discovering_info_; + + // ------------ ADVERTISING ------------ + + // A bundle of state required to start/stop WifiLan service publishing. + AdvertisingInfo advertising_info_; + + // A bundle of state required to start/stop accepting WifiLan service + /// connections. + AcceptingConnectionsInfo accepting_connections_info_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/wifi_lan.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ diff --git a/cpp/core/internal/message_lite.h b/cpp/core/internal/message_lite.h new file mode 100644 index 00000000..4ca8a0b2 --- /dev/null +++ b/cpp/core/internal/message_lite.h @@ -0,0 +1,6 @@ +#ifndef CORE_INTERNAL_MESSAGE_LITE_H_ +#define CORE_INTERNAL_MESSAGE_LITE_H_ + +#include "google/protobuf/message_lite.h" + +#endif // CORE_INTERNAL_MESSAGE_LITE_H_ diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index 232a6c89..d077205d 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -61,7 +61,8 @@ ExceptionOrOfflineFrame OfflineFrames::fromBytes( ConstPtr offline_frame_bytes) { auto offline_frame = std::make_unique(); - if (!offline_frame->ParseFromString(offline_frame_bytes->asString())) { + if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(), + offline_frame_bytes->size())) { return ExceptionOrOfflineFrame(Exception::INVALID_PROTOCOL_BUFFER); } @@ -78,6 +79,7 @@ V1Frame::FrameType OfflineFrames::getFrameType( return V1Frame::UNKNOWN_FRAME_TYPE; } +// TODO(b/155752436): Use byte array endpoint_info instead of endpoint_name. ConstPtr OfflineFrames::forConnectionRequest( const std::string &endpoint_id, const std::string &endpoint_name, std::int32_t nonce, @@ -85,6 +87,7 @@ ConstPtr OfflineFrames::forConnectionRequest( auto connection_request = std::make_unique(); connection_request->set_endpoint_id(endpoint_id); connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); connection_request->set_nonce(nonce); for (std::vector::const_iterator it = diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc index 874b3a74..66460a1b 100644 --- a/cpp/core/internal/offline_frames_test.cc +++ b/cpp/core/internal/offline_frames_test.cc @@ -45,8 +45,8 @@ constexpr ConnectionRequestFrame::Medium ToConnectionRequestMedium( } // namespace TEST(OfflineFramesTest, CanParseMessageFromBytes) { - const string endpoint_id{"ABC"}; - const string endpoint_name{"XYZ"}; + const std::string endpoint_id{"ABC"}; + const std::string endpoint_name{"XYZ"}; const int32 nonce{1234}; const std::vector mediums{Medium::BLE, Medium::BLUETOOTH}; diff --git a/cpp/core/internal/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc index b5e45cfb..386eb171 100644 --- a/cpp/core/internal/offline_service_controller.cc +++ b/cpp/core/internal/offline_service_controller.cc @@ -11,11 +11,11 @@ OfflineServiceController::OfflineServiceController() : ServiceController(), medium_manager_(new MediumManager()), endpoint_channel_manager_( - new EndpointChannelManager(medium_manager_.get())), + new EndpointChannelManager(medium_manager_.get())), endpoint_manager_( new EndpointManager(endpoint_channel_manager_.get())), payload_manager_(new PayloadManager(endpoint_manager_.get())), - bandwidth_upgrade_manager_(new BandwidthUpgradeManager( + bandwidth_upgrade_manager_(new BandwidthUpgradeManager( medium_manager_.get(), endpoint_channel_manager_.get(), endpoint_manager_.get())), pcp_manager_(new PCPManager( diff --git a/cpp/core/internal/offline_service_controller.h b/cpp/core/internal/offline_service_controller.h index 743e7852..cbf1b13b 100644 --- a/cpp/core/internal/offline_service_controller.h +++ b/cpp/core/internal/offline_service_controller.h @@ -68,11 +68,10 @@ class OfflineServiceController : public ServiceController { // on the destructors running (strictly) in the reverse order; a deviation // from that will lead to crashes at runtime. ScopedPtr > > medium_manager_; - ScopedPtr > > endpoint_channel_manager_; + ScopedPtr> endpoint_channel_manager_; ScopedPtr > > endpoint_manager_; ScopedPtr > > payload_manager_; - ScopedPtr > > - bandwidth_upgrade_manager_; + ScopedPtr> bandwidth_upgrade_manager_; ScopedPtr > > pcp_manager_; }; diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index 84881eef..bd75a030 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -1,5 +1,4 @@ #include "core/internal/p2p_cluster_pcp_handler.h" - #include "platform/api/hash_utils.h" namespace location { @@ -16,6 +15,11 @@ const BLEAdvertisement::Version::Value P2PClusterPCPHandler::kBleAdvertisementVersion = BLEAdvertisement::Version::V1; +template +const WifiLanServiceInfo::Version + P2PClusterPCPHandler::kWifiLanServiceInfoVersion = + WifiLanServiceInfo::Version::kV1; + template ConstPtr P2PClusterPCPHandler::generateHash( const string& source, size_t size) { @@ -35,8 +39,8 @@ template P2PClusterPCPHandler::P2PClusterPCPHandler( Ptr> medium_manager, Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : BasePCPHandler(endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), medium_manager_(medium_manager) {} @@ -58,6 +62,9 @@ template std::vector P2PClusterPCPHandler::getConnectionMediumsByPriority() { std::vector mediums; + if (medium_manager_->IsWifiLanAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } if (medium_manager_->isBluetoothAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } @@ -81,6 +88,15 @@ P2PClusterPCPHandler::startAdvertisingImpl( const AdvertisingOptions& options) { std::vector mediums_started_successfully; + ScopedPtr> scoped_wifi_lan_service_id_hash( + generateHash(service_id, WifiLanServiceInfo::kServiceIdHashLength)); + proto::connections::Medium wifi_lan_medium = StartWifiLanAdvertising( + client_proxy, service_id, scoped_wifi_lan_service_id_hash.get(), + local_endpoint_id, local_endpoint_name); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + ScopedPtr> scoped_bluetooth_service_id_hash( generateHash(service_id, BluetoothDeviceName::kServiceIdHashLength)); proto::connections::Medium bluetooth_medium = startBluetoothAdvertising( @@ -118,10 +134,14 @@ Status::Value P2PClusterPCPHandler::stopAdvertisingImpl( Ptr> client_proxy) { medium_manager_->stopBleAdvertising(client_proxy->getAdvertisingServiceId()); medium_manager_->turnOffBluetoothDiscoverability(); + medium_manager_->StopWifiLanAdvertising( + client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBleConnections( client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBluetoothConnections( client_proxy->getAdvertisingServiceId()); + medium_manager_->StopListeningForIncomingWifiLanConnections( + client_proxy->getAdvertisingServiceId()); return Status::SUCCESS; } @@ -132,6 +152,14 @@ P2PClusterPCPHandler::startDiscoveryImpl( const DiscoveryOptions& options) { std::vector mediums_started_successfully; + proto::connections::Medium wifi_lan_medium = + StartWifiLanDiscovery(MakePtr(new FoundWifiLanServiceProcessor( + self_, client_proxy, service_id)), + client_proxy, service_id); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + proto::connections::Medium bluetooth_medium = startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor( self_, client_proxy, service_id)), @@ -170,6 +198,12 @@ typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::connectImpl( Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) { + Ptr wifi_lan_endpoint = + DowncastPtr(endpoint); + if (!wifi_lan_endpoint.isNull()) { + return WifiLanConnectImpl(client_proxy, wifi_lan_endpoint); + } + Ptr bluetooth_endpoint = DowncastPtr(endpoint); if (!bluetooth_endpoint.isNull()) { @@ -295,6 +329,60 @@ void P2PClusterPCPHandler::IncomingBleConnectionProcessor:: proto::connections::Medium::BLE); } +//////////// P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor ///////// +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnection(Ptr wifi_lan_socket) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnIncomingWifiLanConnectionRunnable( + pcp_handler_, client_proxy_, wifi_lan_socket))); +} + +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + wifi_lan_socket_(wifi_lan_socket) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::run() { + string remote_service_name = + wifi_lan_socket_->GetRemoteWifiLanService()->GetName(); + ScopedPtr> scoped_wifi_lan_endpoint_channel( + pcp_handler_->endpoint_channel_manager_ + ->CreateIncomingWifiLanEndpointChannel(remote_service_name, + wifi_lan_socket_)); + if (!scoped_wifi_lan_endpoint_channel.isNull()) { + // TODO(b/149806065): Add logging. + } else { + Exception::Value exception = wifi_lan_socket_->Close(); + wifi_lan_socket_.destroy(); + if (Exception::NONE != exception) { + if (Exception::IO == exception) { + // TODO(b/149806065): Add logging. + } + } + } + pcp_handler_->onIncomingConnection(client_proxy_, remote_service_name, + scoped_wifi_lan_endpoint_channel.release(), + proto::connections::Medium::WIFI_LAN); +} + ///////// P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor ////////// template P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: @@ -582,6 +670,137 @@ void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: } } +////////// P2PClusterPCPHandler::FoundWifiLanServiceProcessor /////////// +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, absl::string_view service_id) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +bool P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + IsRecognizedWifiLanEndpoint(Ptr wifi_lan_service_info) { + if (wifi_lan_service_info.isNull()) { + return false; + } + + if (wifi_lan_service_info->GetPcp() != pcp_handler_->getPCP()) { + return false; + } + + if (*(wifi_lan_service_info->GetServiceIdHash()) != + *(expected_service_id_hash_.get())) { + return false; + } + + return true; +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the discovered endpoint to the client. + pcp_handler_->onEndpointFound( + client_proxy_, + MakePtr(new WifiLanEndpoint( + wifi_lan_service_.release(), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service.operator->()) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(b/149806065): Add logging. + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the endpoint as lost to the client. + // TODO(b/149806065): Add logging. + pcp_handler_->onEndpointLost( + client_proxy_, + MakePtr(new WifiLanEndpoint( + Ptr(wifi_lan_service_.release()), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + //////////////////// END IMPLEMENTATIONS FOR NESTED CLASSES //////////////////// template @@ -710,6 +929,65 @@ proto::connections::Medium P2PClusterPCPHandler::startBleDiscovery( return proto::connections::BLE; } +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + if (!medium_manager_->IsListeningForIncomingWifiLanConnections(service_id)) { + if (!medium_manager_->StartListeningForIncomingWifiLanConnections( + service_id, MakePtr(new IncomingWifiLanConnectionProcessor( + self_, client_proxy, local_endpoint_name)))) { + // TODO(b/149806065): logger.atWarning().log("In + // StartWifiLanAdvertising(%s), client %d failed to start listening for + // incoming WifiLan connections to ServiceId %s", local_endpoint_name, + // clientProxy.getClientId(), service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(b/149806065): Add logging. + } + + // Generate a WifiLanServiceInfo. + const string wifi_lan_service_info = + WifiLanServiceInfo::AsString(kWifiLanServiceInfoVersion, + getPCP(), + local_endpoint_id, + service_id_hash); + if (wifi_lan_service_info.empty()) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(b/149806065): Add logging. + } + + // TODO(b/149806065): Add logging + + if (!medium_manager_->StartWifiLanAdvertising( + service_id, wifi_lan_service_info)) { + // TODO(b/149806065): Add logging + medium_manager_->StopWifiLanAdvertising(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + return proto::connections::WIFI_LAN; +} + +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanDiscovery( + Ptr processor, + Ptr > client_proxy, absl::string_view service_id) { + if (!medium_manager_->StartWifiLanDiscovery(service_id, processor)) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } + + return proto::connections::WIFI_LAN; +} + template typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::bluetoothConnectImpl( @@ -783,6 +1061,38 @@ string P2PClusterPCPHandler::getBlePeripheralId( #endif } +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint) { + Ptr remote_wifi_lan_service = + wifi_lan_endpoint->GetWifiLanService(); + + Ptr wifi_lan_socket = medium_manager_->ConnectToWifiLanService( + remote_wifi_lan_service, wifi_lan_endpoint->getServiceId()); + + if (wifi_lan_socket.isNull()) { + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::BLUETOOTH_ERROR); + } + + ScopedPtr> scoped_wifi_lan_endpoint_channel( + this->endpoint_channel_manager_->CreateOutgoingWifiLanEndpointChannel( + wifi_lan_endpoint->getEndpointId(), wifi_lan_socket)); + + if (scoped_wifi_lan_endpoint_channel.isNull()) { + wifi_lan_socket->Close(); + wifi_lan_socket.destroy(); // Avoid leaks. + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::ERROR); + } + + // TODO(b/149806065): Add logging. + return typename BasePCPHandler::ConnectImplResult( + scoped_wifi_lan_endpoint_channel.release()); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h index 78d5c757..26d60b6e 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -13,6 +13,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "core/internal/pcp.h" +#include "core/internal/wifi_lan_service_info.h" #include "core/options.h" #include "core/strategy.h" #include "platform/api/bluetooth_classic.h" @@ -35,11 +36,10 @@ namespace connections { template class P2PClusterPCPHandler : public BasePCPHandler { public: - P2PClusterPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PClusterPCPHandler(Ptr> medium_manager, + Ptr> endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PClusterPCPHandler() override; Strategy getStrategy() override; @@ -52,27 +52,29 @@ class P2PClusterPCPHandler : public BasePCPHandler { // @PCPHandlerThread Ptr::StartOperationResult> - startAdvertisingImpl(Ptr > client_proxy, + startAdvertisingImpl(Ptr> 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 > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread Ptr::StartOperationResult> - startDiscoveryImpl(Ptr > client_proxy, + startDiscoveryImpl(Ptr> client_proxy, const string& service_id, const DiscoveryOptions& options) override; + // @PCPHandlerThread Status::Value stopDiscoveryImpl( - Ptr > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread typename BasePCPHandler::ConnectImplResult connectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) override; @@ -82,16 +84,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { template friend class IncomingBleConnectionProcessor; template + friend class IncomingWifiLanConnectionProcessor; + template friend class FoundBluetoothAdvertisementProcessor; template friend class FoundBleAdvertisementProcessor; + template + friend class FoundWifiLanServiceProcessor; class IncomingBluetoothConnectionProcessor : public MediumManager::IncomingBluetoothConnectionProcessor { public: IncomingBluetoothConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBluetoothConnection( @@ -101,20 +107,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBluetoothConnectionRunnable : public Runnable { public: OnIncomingBluetoothConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr bluetooth_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr bluetooth_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -122,8 +128,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::IncomingBleConnectionProcessor { public: IncomingBleConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBleConnection(Ptr ble_socket, @@ -133,19 +139,51 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBleConnectionRunnable : public Runnable { public: OnIncomingBleConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, Ptr ble_socket); + Ptr> pcp_handler, + Ptr> client_proxy, Ptr ble_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr ble_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string local_endpoint_name_; + }; + + class IncomingWifiLanConnectionProcessor + : public MediumManager::IncomingWifiLanConnectionProcessor { + public: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name); + + void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) override; + + private: + class OnIncomingWifiLanConnectionRunnable : public Runnable { + public: + OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr wifi_lan_socket_; + }; + + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -153,8 +191,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBluetoothDeviceProcessor { public: FoundBluetoothAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, const string& service_id); + Ptr> pcp_handler, + Ptr> client_proxy, const string& service_id); void onFoundBluetoothDevice(Ptr bluetooth_device) override; void onLostBluetoothDevice(Ptr bluetooth_device) override; @@ -163,8 +201,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBluetoothDeviceRunnable : public Runnable { public: OnFoundBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -172,19 +210,19 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; class OnLostBluetoothDeviceRunnable : public Runnable { public: OnLostBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -192,22 +230,22 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; bool isRecognizedBluetoothEndpoint( const string& found_bluetooth_device_name, Ptr bluetooth_device_name); - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string service_id_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> expected_service_id_hash_; std::shared_ptr self_{this, [](void*) {}}; }; @@ -216,8 +254,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBlePeripheralProcessor { public: FoundBleAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy); + Ptr> pcp_handler, + Ptr> client_proxy); void onFoundBlePeripheral(Ptr ble_peripheral, const string& service_id, @@ -229,8 +267,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBlePeripheralRunnable : public Runnable { public: OnFoundBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral, ConstPtr advertisement_bytes); @@ -238,31 +276,31 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; - ScopedPtr > advertisement_bytes_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> ble_peripheral_; + ScopedPtr> advertisement_bytes_; + ScopedPtr> expected_service_id_hash_; }; class OnLostBlePeripheralRunnable : public Runnable { public: OnLostBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; }; // Holds the state required to re-create a BLEEndpoint we see on a @@ -278,14 +316,74 @@ class P2PClusterPCPHandler : public BasePCPHandler { const string endpoint_name; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + // Maps a BLEPeripheral to its corresponding BLEEndpointState. typedef std::map FoundBLEEndpointsMap; FoundBLEEndpointsMap found_ble_endpoints_; std::shared_ptr self_{this, [](void*) {}}; }; + class FoundWifiLanServiceProcessor + : public MediumManager::FoundWifiLanServiceProcessor { + public: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view service_id); + + void OnFoundWifiLanService(Ptr wifi_lan_service) override; + void OnLostWifiLanService(Ptr wifi_lan_service) override; + + private: + class OnFoundWifiLanServiceRunnable : public Runnable { + public: + OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + ScopedPtr> expected_service_id_hash_; + }; + + class OnLostWifiLanServiceRunnable : public Runnable { + public: + OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + }; + + bool IsRecognizedWifiLanEndpoint( + Ptr wifi_lan_service_info); + + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string service_id_; + ScopedPtr> expected_service_id_hash_; + std::shared_ptr self_{this, [](void*) {}}; + }; + class BluetoothEndpoint : public BasePCPHandler::DiscoveredEndpoint { public: @@ -310,7 +408,7 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBluetoothAdvertisementProcessor; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -336,7 +434,35 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBleAdvertisementProcessor; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; + const string endpoint_id_; + const string endpoint_name_; + const string service_id_; + }; + + class WifiLanEndpoint : public BasePCPHandler::DiscoveredEndpoint { + public: + Ptr GetWifiLanService() { return wifi_lan_service_.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::WIFI_LAN; + } + + private: + WifiLanEndpoint(Ptr wifi_lan_service, + absl::string_view endpoint_id, + absl::string_view endpoint_name, + absl::string_view service_id) + : wifi_lan_service_(wifi_lan_service), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + service_id_(service_id) {} + + friend class FoundWifiLanServiceProcessor; + + ScopedPtr> wifi_lan_service_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -344,32 +470,44 @@ class P2PClusterPCPHandler : public BasePCPHandler { static const BluetoothDeviceName::Version::Value kBluetoothDeviceNameVersion; static const BLEAdvertisement::Version::Value kBleAdvertisementVersion; + static const WifiLanServiceInfo::Version kWifiLanServiceInfoVersion; static ConstPtr generateHash(const string& source, size_t size); static string getBlePeripheralId(Ptr ble_peripheral); proto::connections::Medium startBluetoothAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBluetoothDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bluetoothConnectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr bluetooth_endpoint); proto::connections::Medium startBleAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBleDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bleConnectImpl( - Ptr > client_proxy, Ptr ble_endpoint); + Ptr> client_proxy, Ptr ble_endpoint); - Ptr > medium_manager_; + proto::connections::Medium StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name); + proto::connections::Medium StartWifiLanDiscovery( + Ptr processor, + Ptr> client_proxy, absl::string_view service_id); + typename BasePCPHandler::ConnectImplResult WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint); + + Ptr> medium_manager_; std::shared_ptr self_{this, [](void*) {}}; }; diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc index 4e48a42c..7623f490 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -8,8 +8,8 @@ template P2PPointToPointPCPHandler::P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PStarPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.h b/cpp/core/internal/p2p_point_to_point_pcp_handler.h index 56f7104b..0b75dbef 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -27,8 +27,8 @@ class P2PPointToPointPCPHandler : public P2PStarPCPHandler { P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); Strategy getStrategy() override; PCP::Value getPCP() override; diff --git a/cpp/core/internal/p2p_star_pcp_handler.cc b/cpp/core/internal/p2p_star_pcp_handler.cc index a3bf50d6..320bc1a0 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.cc +++ b/cpp/core/internal/p2p_star_pcp_handler.cc @@ -10,8 +10,8 @@ template P2PStarPCPHandler::P2PStarPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PClusterPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_star_pcp_handler.h b/cpp/core/internal/p2p_star_pcp_handler.h index 4a7c110f..b16a5a48 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.h +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -27,11 +27,10 @@ namespace connections { template class P2PStarPCPHandler : public P2PClusterPCPHandler { public: - P2PStarPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PStarPCPHandler(Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PStarPCPHandler() override; Strategy getStrategy() override; diff --git a/cpp/core/internal/pcp_manager.cc b/cpp/core/internal/pcp_manager.cc index 50500e2e..2411ee39 100644 --- a/cpp/core/internal/pcp_manager.cc +++ b/cpp/core/internal/pcp_manager.cc @@ -11,9 +11,9 @@ namespace connections { template PCPManager::PCPManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager) + Ptr bandwidth_upgrade_manager) : pcp_handlers_(), current_pcp_handler_() { pcp_handlers_[PCP::P2P_CLUSTER] = MakePtr(new P2PClusterPCPHandler( medium_manager, endpoint_manager, endpoint_channel_manager, diff --git a/cpp/core/internal/pcp_manager.h b/cpp/core/internal/pcp_manager.h index 8bb77a32..731f6951 100644 --- a/cpp/core/internal/pcp_manager.h +++ b/cpp/core/internal/pcp_manager.h @@ -29,9 +29,9 @@ template class PCPManager { public: PCPManager(Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager); + Ptr bandwidth_upgrade_manager); ~PCPManager(); Status::Value startAdvertising( diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.cc b/cpp/core/internal/wifi_lan_endpoint_channel.cc new file mode 100644 index 00000000..ca2589a5 --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.cc @@ -0,0 +1,49 @@ +#include "core/internal/wifi_lan_endpoint_channel.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +Ptr +WifiLanEndpointChannel::CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +Ptr +WifiLanEndpointChannel::CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +WifiLanEndpointChannel::WifiLanEndpointChannel( + absl::string_view channel_name, Ptr wifi_lan_socket) + : BaseEndpointChannel(channel_name, + wifi_lan_socket->GetInputStream(), + wifi_lan_socket->GetOutputStream()), + wifi_lan_socket_(wifi_lan_socket) {} + +WifiLanEndpointChannel::~WifiLanEndpointChannel() {} + +proto::connections::Medium WifiLanEndpointChannel::getMedium() { + return proto::connections::Medium::WIFI_LAN; +} + +void WifiLanEndpointChannel::closeImpl() { + Exception::Value exception = wifi_lan_socket_->Close(); + if (exception != Exception::NONE) { + if (exception == Exception::IO) { + // TODO(b/149806065): Add logging. + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.h b/cpp/core/internal/wifi_lan_endpoint_channel.h new file mode 100644 index 00000000..8d31df0f --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.h @@ -0,0 +1,46 @@ +#ifndef CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ + +#include "core/internal/base_endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiLanEndpointChannel : public BaseEndpointChannel { + public: + using Platform = platform::ImplementationPlatform; + + static Ptr CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + static Ptr CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + + ~WifiLanEndpointChannel() override; + + proto::connections::Medium getMedium() override; + + protected: + void closeImpl() override; + + private: + WifiLanEndpointChannel(absl::string_view channel_name, + Ptr wifi_lan_socket); + + ScopedPtr > wifi_lan_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc index 7df38ed7..00639406 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.cc +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -18,8 +18,8 @@ class OnIncomingWifiConnectionRunnable : public Runnable { template WifiLanUpgradeHandler::WifiLanUpgradeHandler( Ptr > medium_manager, - Ptr > endpoint_channel_manager) - : BaseBandwidthUpgradeHandler(endpoint_channel_manager), + Ptr endpoint_channel_manager) + : BaseBandwidthUpgradeHandler(endpoint_channel_manager), medium_manager_(medium_manager) {} template diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.h b/cpp/core/internal/wifi_lan_upgrade_handler.h index 26781c47..1b4d4d1a 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.h +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -23,36 +23,35 @@ class OnIncomingWifiConnectionRunnable; // Manages the WIFI_LAN-specific methods needed to upgrade an EndpointChannel template -class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { +class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // TODO(ahlee): Uncomment when WIFI_LAN plumbing is done. // public MediumManager::IncomingWifiConnectionProcessor { public: - WifiLanUpgradeHandler( - Ptr > medium_manager_, - Ptr > endpoint_channel_manager); - ~WifiLanUpgradeHandler(); + WifiLanUpgradeHandler(Ptr > medium_manager_, + Ptr endpoint_channel_manager); + ~WifiLanUpgradeHandler() override; void onIncomingWifiConnection(Ptr socket); protected: // @BandwidthUpgradeHandlerThread ConstPtr initializeUpgradedMediumForEndpoint( - const string& endpoint_id); + const string& endpoint_id) override; // @BandwidthUpgradeHandlerThread Ptr createUpgradedEndpointChannel( const string& endpoint_id, ConstPtr - upgrade_path_info); + upgrade_path_info) override; // TODO(ahlee): Change the java counterparts of these methods to private. - proto::connections::Medium getUpgradeMedium(); + proto::connections::Medium getUpgradeMedium() override; // @BandwidthUpgradeHandlerThread - void revertImpl(); + void revertImpl() override; private: class IncomingWifiLanSocketConnection - : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { + : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { public: - IncomingWifiLanSocketConnection(Ptr socket) + explicit IncomingWifiLanSocketConnection(Ptr socket) : new_endpoint_channel_(Ptr()), // TODO(ahlee): Uncomment when plumbing for WIFI_LAN is done. // new_endpoint_channel_(getEndpointChannelManager() @@ -61,15 +60,15 @@ class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // 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() { + string socketToString() override { return string(); } + void closeSocket() override { // 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 getEndpointChannel() { + Ptr getEndpointChannel() override { return new_endpoint_channel_.release(); } diff --git a/cpp/core_v2/BUILD b/cpp/core_v2/BUILD new file mode 100644 index 00000000..12a7a8fe --- /dev/null +++ b/cpp/core_v2/BUILD @@ -0,0 +1,73 @@ +cc_library( + name = "core_v2", + srcs = [ + "core.cc", + ], + hdrs = [ + "core.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":core_types", + "//core_v2/internal", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "core_types", + srcs = [ + "strategy.cc", + ], + hdrs = [ + "listeners.h", + "options.h", + "params.h", + "payload.h", + "status.h", + "strategy.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/types:variant", + ], +) + +cc_test( + name = "core_v2_test", + size = "small", + srcs = [ + "core_test.cc", + "listeners_test.cc", + "payload_test.cc", + "status_test.cc", + "strategy_test.cc", + ], + shard_count = 16, + deps = [ + ":core_types", + ":core_v2", + "//core_v2/internal", + "//core_v2/internal:internal_test", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/time", + "//absl/types:variant", + ], +) diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc new file mode 100644 index 00000000..c7848047 --- /dev/null +++ b/cpp/core_v2/core.cc @@ -0,0 +1,107 @@ +#include "core_v2/core.h" + +#include +#include + +#include "core_v2/options.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +Core::~Core() { + CountDownLatch latch(1); + router_.ClientDisconnecting( + &client_, { + .result_cb = [&latch](Status) { latch.CountDown(); }, + }); + if (!latch.Await(kWaitForDisconnect).result()) { + NEARBY_LOG(FATAL, "Unable to shutdown"); + } +} + +void Core::StartAdvertising(absl::string_view service_id, + ConnectionOptions options, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartAdvertising(&client_, service_id, options, info, callback); +} + +void Core::StopAdvertising(const ResultCallback callback) { + router_.StopAdvertising(&client_, callback); +} + +void Core::StartDiscovery(absl::string_view service_id, + ConnectionOptions options, DiscoveryListener listener, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartDiscovery(&client_, service_id, options, listener, callback); +} + +void Core::StopDiscovery(ResultCallback callback) { + router_.StopDiscovery(&client_, callback); +} + +void Core::RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RequestConnection(&client_, endpoint_id, info, callback); +} + +void Core::AcceptConnection(absl::string_view endpoint_id, + PayloadListener listener, ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.AcceptConnection(&client_, endpoint_id, listener, callback); +} + +void Core::RejectConnection(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RejectConnection(&client_, endpoint_id, callback); +} + +void Core::InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback) { + router_.InitiateBandwidthUpgrade(&client_, endpoint_id, callback); +} + +void Core::SendPayload(absl::Span endpoint_ids, + Payload payload, ResultCallback callback) { + assert(payload.GetType() != Payload::Type::kUnknown); + assert(!endpoint_ids.empty()); + + router_.SendPayload(&client_, endpoint_ids, std::move(payload), callback); +} + +void Core::CancelPayload(std::int64_t payload_id, ResultCallback callback) { + assert(payload_id != 0); + + router_.CancelPayload(&client_, payload_id, callback); +} + +void Core::DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.DisconnectFromEndpoint(&client_, endpoint_id, callback); +} + +void Core::StopAllEndpoints(ResultCallback callback) { + router_.StopAllEndpoints(&client_, callback); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/core.h b/cpp/core_v2/core.h new file mode 100644 index 00000000..60021671 --- /dev/null +++ b/cpp/core_v2/core.h @@ -0,0 +1,208 @@ +#ifndef CORE_V2_CORE_H_ +#define CORE_V2_CORE_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/internal/service_controller_router.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// This class defines the API of the Nearby Connections Core library. +class Core { + public: + explicit Core(std::function factory) + : router_(factory) {} + ~Core(); + Core(Core&&) = default; + Core& operator=(Core&&) = default; + + // Starts advertising an endpoint for a local app. + // + // service_id - An identifier to advertise your app to other endpoints. + // This can be an arbitrary string, so long as it uniquely + // identifies your service. A good default is to use your + // app's package name. + // options - The options for advertising. + // info - Connection parameters: + // > name - A human readable name for this endpoint, to appear on + // other devices. + // > listener - A callback notified when remote endpoints request a + // connection to this endpoint. + // callback - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if advertising started successfully. + // Status::STATUS_ALREADY_ADVERTISING if the app is already advertising. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartAdvertising(absl::string_view service_id, ConnectionOptions options, + ConnectionRequestInfo info, ResultCallback callback); + + // Stops advertising a local endpoint. Should be called after calling + // StartAdvertising, as soon as the application no longer needs to advertise + // itself or goes inactive. Payloads can still be sent to connected + // endpoints after advertising ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopAdvertising(ResultCallback callback); + + // Starts discovery for remote endpoints with the specified service ID. + // + // service_id - The ID for the service to be discovered, as specified in + // the corresponding call to StartAdvertising. + // listener - A callback notified when a remote endpoint is discovered. + // options - The options for discovery. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if discovery started successfully. + // Status::STATUS_ALREADY_DISCOVERING if the app is already + // discovering the specified service. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartDiscovery(absl::string_view service_id, ConnectionOptions options, + DiscoveryListener listener, ResultCallback callback); + + // Stops discovery for remote endpoints, after a previous call to + // StartDiscovery, when the client no longer needs to discover endpoints or + // goes inactive. Payloads can still be sent to connected endpoints after + // discovery ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopDiscovery(ResultCallback callback); + + // Sends a request to connect to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint to which a + // connection request will be sent. Should match the value + // provided in a call to + // DiscoveryListener::endpoint_found_cb() + // info - Connection parameters: + // > name - A human readable name for the local endpoint, to appear on + // the remote endpoint. + // > listener - A callback notified when the remote endpoint sends a + // response to the connection request. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was sent. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already + // has a connection to the specified endpoint. + // Status::STATUS_RADIO_ERROR if we failed to connect because of an + // issue with Bluetooth/WiFi. + // Status::STATUS_ERROR if we failed to connect for any other reason. + void RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, ResultCallback callback); + + // Accepts a connection to a remote endpoint. This method must be called + // before Payloads can be exchanged with the remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated. + // listener - A callback for payloads exchanged with the remote endpoint. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was accepted. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already. + // has a connection to the specified endpoint. + void AcceptConnection(absl::string_view endpoint_id, PayloadListener listener, + ResultCallback callback); + + // Rejects a connection to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK} if the connection request was rejected. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT} if the app already + // has a connection to the specified endpoint. + void RejectConnection(absl::string_view endpoint_id, ResultCallback callback); + + // Sends a Payload to a remote endpoint. Payloads can only be sent to remote + // endpoints once a notice of connection acceptance has been delivered via + // ConnectionListener::onConnectionResult(). + // + // endpoint_ids - Array of remote endpoint identifiers for the to which the + // payload should be sent. + // payload - The Payload to be sent. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OUT_OF_ORDER_API_CALL if the device has not first + // performed advertisement or discovery (to set the Strategy. + // Status::STATUS_ENDPOINT_UNKNOWN if there's no active (or pending) + // connection to the remote endpoint. + // Status::STATUS_OK if none of the above errors occurred. Note that this + // indicates that Nearby Connections will attempt to send the Payload, + // but not that the send has successfully completed yet. Errors might + // still occur during transmission (and at different times for + // different endpoints), and will be delivered via + // PayloadCallback#onPayloadTransferUpdate. + void SendPayload(absl::Span endpoint_ids, Payload payload, + ResultCallback callback); + + // Cancels a Payload currently in-flight to or from remote endpoint(s). + // + // payload_id - The identifier for the Payload to be canceled. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void CancelPayload(std::int64_t payload_id, ResultCallback callback); + + // Disconnects from a remote endpoint. {@link Payload}s can no longer be sent + // to or received from the endpoint after this method is called. + // + // endpoint_id - The identifier for the remote endpoint to disconnect from. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback); + + // Disconnects from, and removes all traces of, all connected and/or + // discovered endpoints. This call is expected to be preceded by a call to + // StopAdvertising or StartDiscovery as needed. After calling + // StopAllEndpoints, no further operations with remote endpoints will be + // possible until a new call to one of StartAdvertising() or StartDiscovery(). + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void StopAllEndpoints(ResultCallback callback); + + // Sends a request to initiate connection bandwidth upgrade. + // + // endpoint_id - The identifier for the remote endpoint which will be + // switching to a higher connection data rate and possibly + // different wireless protocol. On success, calls + // ConnectionListener::bandwidth_changed_cb(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback); + + private: + static constexpr absl::Duration kWaitForDisconnect = absl::Milliseconds(5000); + + ClientProxy client_; + ServiceControllerRouter router_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_CORE_H_ diff --git a/cpp/core_v2/core_test.cc b/cpp/core_v2/core_test.cc new file mode 100644 index 00000000..038383e3 --- /dev/null +++ b/cpp/core_v2/core_test.cc @@ -0,0 +1,44 @@ +#include "core_v2/core.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(CoreTest, ConstructorDestructorWorks) { + MockServiceController mock; + Core core{[&mock]() { return &mock; }}; +} + +TEST(CoreTest, DestructorReportsFatalFailure) { + MockServiceController mock; + ON_CALL(mock, StopDiscovery).WillByDefault([](ClientProxy* client) { + NEARBY_LOG(INFO, "Blocking Endpoint disconnect for 10 sec"); + absl::SleepFor(absl::Milliseconds(10000)); + }); + ASSERT_DEATH( + [&mock]() { + Core core{[&mock]() { return &mock; }}; + EXPECT_CALL(mock, StartDiscovery).Times(1); + EXPECT_CALL(mock, StopAdvertising).Times(1); + core.StartDiscovery("service_id", {.strategy = Strategy::kP2pCluster}, + {}, {.result_cb = [](Status status) { + NEARBY_LOG(INFO, "Discovery status: %d", + static_cast(status.value)); + }}); + }(), + "Unable to shutdown"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD new file mode 100644 index 00000000..e375d8a1 --- /dev/null +++ b/cpp/core_v2/internal/BUILD @@ -0,0 +1,101 @@ +cc_library( + name = "internal", + srcs = [ + "base_endpoint_channel.cc", + "base_pcp_handler.cc", + "ble_advertisement.cc", + "client_proxy.cc", + "encryption_runner.cc", + "endpoint_channel_manager.cc", + "endpoint_manager.cc", + "offline_frames.cc", + "service_controller_router.cc", + "wifi_lan_service_info.cc", + ], + hdrs = [ + "base_endpoint_channel.h", + "base_pcp_handler.h", + "ble_advertisement.h", + "client_proxy.h", + "encryption_runner.h", + "endpoint_channel.h", + "endpoint_channel_manager.h", + "endpoint_manager.h", + "offline_frames.h", + "pcp.h", + "pcp_handler.h", + "service_controller.h", + "service_controller_router.h", + "wifi_lan_service_info.h", + ], + visibility = [ + "//core_v2:__pkg__", + ], + deps = [ + "//core/internal:message_lite", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "internal_test", + testonly = True, + hdrs = [ + "mock_service_controller.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":internal", + "//testing/base/public:gunit", + ], +) + +cc_test( + name = "core_v2_internal_test", + size = "small", + srcs = [ + "base_endpoint_channel_test.cc", + "base_pcp_handler_test.cc", + "ble_advertisement_test.cc", + "client_proxy_test.cc", + "encryption_runner_test.cc", + "endpoint_channel_manager_test.cc", + "endpoint_manager_test.cc", + "offline_frames_test.cc", + "service_controller_router_test.cc", + "wifi_lan_service_info_test.cc", + ], + shard_count = 16, + deps = [ + ":internal", + ":internal_test", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//testing/base/public:gunit", + "//testing/base/public:gunit_main", + "//absl/container:flat_hash_set", + "//absl/synchronization", + "//absl/time", + "//absl/types:span", + ], +) diff --git a/cpp/core_v2/internal/base_endpoint_channel.cc b/cpp/core_v2/internal/base_endpoint_channel.cc new file mode 100644 index 00000000..078224c4 --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.cc @@ -0,0 +1,270 @@ +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +std::int32_t BytesToInt(const ByteArray& bytes) { + const char* int_bytes = bytes.data(); + + std::int32_t result = 0; + result |= (static_cast(int_bytes[0]) & 0x0FF) << 24; + result |= (static_cast(int_bytes[1]) & 0x0FF) << 16; + result |= (static_cast(int_bytes[2]) & 0x0FF) << 8; + result |= (static_cast(int_bytes[3]) & 0x0FF); + + return result; +} + +ByteArray IntToBytes(std::int32_t value) { + char int_bytes[sizeof(std::int32_t)]; + int_bytes[0] = static_cast((value >> 24) & 0x0FF); + int_bytes[1] = static_cast((value >> 16) & 0x0FF); + int_bytes[2] = static_cast((value >> 8) & 0x0FF); + int_bytes[3] = static_cast((value)&0x0FF); + + return ByteArray(int_bytes, sizeof(int_bytes)); +} + +ExceptionOr ReadExactly(InputStream* reader, std::int64_t size) { + ByteArray buffer(size); + std::int64_t current_pos = 0; + + while (current_pos < size) { + ExceptionOr read_bytes = reader->Read(size - current_pos); + if (!read_bytes.ok()) { + return read_bytes; + } + ByteArray result = read_bytes.result(); + + if (result.Empty()) { + return ExceptionOr(Exception::kIo); + } + + buffer.CopyAt(current_pos, result); + current_pos += result.size(); + } + + return ExceptionOr(std::move(buffer)); +} + +ExceptionOr ReadInt(InputStream* reader) { + ExceptionOr read_bytes = ReadExactly(reader, sizeof(std::int32_t)); + if (!read_bytes.ok()) { + return ExceptionOr(read_bytes.exception()); + } + return ExceptionOr(BytesToInt(std::move(read_bytes.result()))); +} + +Exception WriteInt(OutputStream* writer, std::int32_t value) { + return writer->Write(IntToBytes(value)); +} + +} // namespace + +BaseEndpointChannel::BaseEndpointChannel(const std::string& channel_name, + InputStream* reader, + OutputStream* writer) + : channel_name_(channel_name), reader_(reader), writer_(writer) {} + +ExceptionOr BaseEndpointChannel::Read() { + ByteArray result; + { + MutexLock lock(&reader_mutex_); + + ExceptionOr read_int = ReadInt(reader_); + if (!read_int.ok()) { + return ExceptionOr(read_int.exception()); + } + + if (read_int.result() < 0 || read_int.result() > kMaxAllowedReadBytes) { + return ExceptionOr(Exception::kIo); + } + + ExceptionOr read_bytes = ReadExactly(reader_, read_int.result()); + if (!read_bytes.ok()) { + return read_bytes; + } + result = std::move(read_bytes.result()); + } + + // If encryption is enabled, decode the message. + if (IsEncryptionEnabled()) { + MutexLock crypto_lock(&crypto_mutex_); + result = ByteArray(std::move( + *encryption_context_->DecodeMessageFromPeer(std::string(result)))); + if (result.Empty()) { + return ExceptionOr(Exception::kInvalidProtocolBuffer); + } + } + + { + MutexLock lock(&last_read_mutex_); + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + } + return ExceptionOr(result); +} + +Exception BaseEndpointChannel::Write(const ByteArray& data) { + { + MutexLock pause_lock(&is_paused_mutex_); + if (is_paused_) { + BlockUntilUnpaused(); + } + } + + ByteArray encrypted_data; + const ByteArray* data_to_write = &data; + { + MutexLock crypto_lock(&crypto_mutex_); + // If encryption is enabled, encode the message. + if (IsEncryptionEnabled()) { + encrypted_data = ByteArray(std::move( + *encryption_context_->EncodeMessageToPeer(std::string(data)))); + data_to_write = &encrypted_data; + } + } + + { + MutexLock lock(&writer_mutex_); + Exception write_exception = + WriteInt(writer_, static_cast(data_to_write->size())); + if (!write_exception.Ok()) { + return write_exception; + } + + write_exception = writer_->Write(*data_to_write); + if (write_exception.Ok()) { + return write_exception; + } + + Exception flush_exception = writer_->Flush(); + if (!flush_exception.Ok()) { + return flush_exception; + } + } + + return {Exception::kSuccess}; +} + +void BaseEndpointChannel::Close() { + { + // In case channel is paused, resume it first thing. + MutexLock lock(&is_paused_mutex_); + UnblockPausedWriter(); + } + CloseIo(); + CloseImpl(); +} + +void BaseEndpointChannel::CloseIo() { + // Keep this method dedicated to reader and writer handling an nothing else. + { + // Do not take reader_mutex_ here: read may be in progress, and it will + // deadlock. Calling Close() with Read() in progress will terminate the + // IO and Read() will proceed normally (with Exception::kIo). + Exception exception = reader_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } + { + // Do not take writer_mutex_ here: write may be in progress, and it will + // deadlock. Calling Close() with Write() in progress will terminate the + // IO and Write() will proceed normally (with Exception::kIo). + Exception exception = writer_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } +} + +void BaseEndpointChannel::Close( + proto::connections::DisconnectionReason reason) { + Close(); +} + +std::string BaseEndpointChannel::GetType() const { + std::string subtype = IsEncryptionEnabled() ? "ENCRYPTED_" : ""; + + switch (GetMedium()) { + case proto::connections::Medium::BLUETOOTH: + return absl::StrCat(subtype, "BLUETOOTH"); + case proto::connections::Medium::BLE: + return absl::StrCat(subtype, "BLE"); + case proto::connections::Medium::MDNS: + return absl::StrCat(subtype, "MDNS"); + case proto::connections::Medium::WIFI_HOTSPOT: + return absl::StrCat(subtype, "WIFI_HOTSPOT"); + case proto::connections::Medium::WIFI_LAN: + return absl::StrCat(subtype, "WIFI_LAN"); + default: + return "UNKNOWN"; + } +} + +std::string BaseEndpointChannel::GetName() const { return channel_name_; } + +void BaseEndpointChannel::EnableEncryption( + securegcm::D2DConnectionContextV1* encryption_context) { + MutexLock lock(&crypto_mutex_); + encryption_context_ = encryption_context; +} + +bool BaseEndpointChannel::IsPaused() const { + MutexLock lock(&is_paused_mutex_); + return is_paused_; +} + +void BaseEndpointChannel::Pause() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = true; +} + +void BaseEndpointChannel::Resume() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = false; + is_paused_cond_.Notify(); +} + +absl::Time BaseEndpointChannel::GetLastReadTimestamp() const { + MutexLock lock(&last_read_mutex_); + return last_read_timestamp_; +} + +bool BaseEndpointChannel::IsEncryptionEnabled() const { + return encryption_context_ != nullptr; +} + +void BaseEndpointChannel::BlockUntilUnpaused() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + while (is_paused_) { + Exception wait_succeeded = is_paused_cond_.Wait(); + if (!wait_succeeded.Ok()) { + return; + } + } +} + +void BaseEndpointChannel::UnblockPausedWriter() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + is_paused_ = false; + is_paused_cond_.Notify(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_endpoint_channel.h b/cpp/core_v2/internal/base_endpoint_channel.h new file mode 100644 index 00000000..2799e58d --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.h @@ -0,0 +1,113 @@ +#ifndef CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace connections { + +class BaseEndpointChannel : public EndpointChannel { + public: + BaseEndpointChannel(const std::string& channel_name, InputStream* reader, + OutputStream* writer); + ~BaseEndpointChannel() override = default; + + ExceptionOr Read() + ABSL_LOCKS_EXCLUDED(reader_mutex_, crypto_mutex_, + last_read_mutex_) override; + + Exception Write(const ByteArray& data) + ABSL_LOCKS_EXCLUDED(writer_mutex_, crypto_mutex_) override; + + // Closes this EndpointChannel, without tracking the closure in analytics. + void Close() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) 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. + std::string GetType() const override; + + // Returns the name of the EndpointChannel. + std::string GetName() const override; + + // Enables encryption on the EndpointChannel. + // Should be called after connection is accepted by both parties, and + // before entering data phase, where Payloads may be exchanged. + void EnableEncryption(securegcm::D2DConnectionContextV1* context) override; + + // True if the EndpointChannel is currently pausing all writes. + bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Pauses all writes on this EndpointChannel until resume() is called. + void Pause() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + void Resume() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Returns the timestamp (returned by ElapsedRealtime) of the last read from + // this endpoint, or -1 if no reads have occurred. + absl::Time GetLastReadTimestamp() const + ABSL_LOCKS_EXCLUDED(last_read_mutex_) override; + + protected: + virtual void CloseImpl() = 0; + + private: + // Used to sanity check that our frame sizes are reasonable. + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + + bool IsEncryptionEnabled() const; + void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS; + + // We need a separate mutex to pritect read timestamp, because if a read + // blocks on IO, we don't want timestamp read access to block too. + mutable Mutex last_read_mutex_; + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(last_read_mutex_) = + absl::InfinitePast(); + const std::string channel_name_; + + // The reader and writer are synchronized independently since we can't have + // writes waiting on reads that might potentially block forever. + Mutex reader_mutex_; + InputStream* reader_ ABSL_PT_GUARDED_BY(reader_mutex_); + + Mutex writer_mutex_; + OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_); + + // Used by both read and write to protect payload encryption/decryption. + Mutex crypto_mutex_; + // An encryptor/decryptor. May be null. + securegcm::D2DConnectionContextV1* encryption_context_ + ABSL_PT_GUARDED_BY(crypto_mutex_) = nullptr; + + mutable Mutex is_paused_mutex_; + ConditionVariable is_paused_cond_{&is_paused_mutex_}; + // If true, writes should block until this has been set to false. + bool is_paused_ ABSL_GUARDED_BY(is_paused_mutex_) = false; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/base_endpoint_channel_test.cc b/cpp/core_v2/internal/base_endpoint_channel_test.cc new file mode 100644 index 00000000..c96e8f4a --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel_test.cc @@ -0,0 +1,342 @@ +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "core_v2/internal/encryption_runner.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/single_thread_executor.h" +#include "proto/connections_enums.pb.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; + +class TestEndpointChannel : public BaseEndpointChannel { + public: + explicit TestEndpointChannel(InputStream* input, OutputStream* output) + : BaseEndpointChannel("channel", input, output) {} + + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, CloseImpl, (), (override)); +}; + +std::function MakeDataPump( + std::string label, InputStream* input, OutputStream* output, + std::function monitor = nullptr) { + return [label, input, output, monitor]() { + NEARBY_LOG(INFO, "streaming data thorough '%s'", label.c_str()); + while (true) { + auto read_response = input->Read(Pipe::kChunkSize); + if (!read_response.ok()) { + NEARBY_LOG(INFO, "Peer reader closed on '%s'", label.c_str()); + output->Close(); + break; + } + if (monitor) { + monitor(read_response.result()); + } + auto write_response = output->Write(read_response.result()); + if (write_response.Raised()) { + NEARBY_LOG(INFO, "Peer writer closed on '%s'", label.c_str()); + input->Close(); + break; + } + } + NEARBY_LOG(INFO, "streaming terminated on '%s'", label.c_str()); + }; +} + +std::function MakeDataMonitor(const std::string& label, + std::string* capture, + absl::Mutex* mutex) { + return [label, capture, mutex](const ByteArray& input) mutable { + std::string s = std::string(input); + { + absl::MutexLock lock(mutex); + *capture += s; + } + NEARBY_LOG(INFO, "source='%s'; message='%s'", label.c_str(), s.c_str()); + }; +} + +std::pair, + std::unique_ptr> +DoDhKeyExchange(BaseEndpointChannel* channel_a, + BaseEndpointChannel* channel_b) { + std::unique_ptr context_a; + std::unique_ptr context_b; + EncryptionRunner crypto_a; + EncryptionRunner crypto_b; + ClientProxy proxy_a; + ClientProxy proxy_b; + CountDownLatch latch(2); + crypto_a.StartClient( + &proxy_a, "endpoint_id", channel_a, + { + .on_success_cb = + [&latch, &context_a]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-A side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_a = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-A side key negotiation failed"); + latch.CountDown(); + }, + }); + crypto_b.StartServer( + &proxy_b, "endpoint_id", channel_b, + { + .on_success_cb = + [&latch, &context_b]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-B side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_b = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-B side key negotiation failed"); + latch.CountDown(); + }, + }); + EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); + return std::make_pair(std::move(context_a), std::move(context_b)); +} + +TEST(BaseEndpointChannelTest, ConstructorDestructorWorks) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); +} + +TEST(BaseEndpointChannelTest, ReadWrite) { + // Direct not-encrypted IO. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + EXPECT_EQ(rx_message, tx_message); +} + +TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) { + // Not encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { return Medium::BLE; }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { return Medium::BLE; }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + EXPECT_EQ(channel_a.GetType(), "BLE"); + EXPECT_EQ(channel_b.GetType(), "BLE"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) != std::string::npos || + capture_b.find(message) != std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) { + // Encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + // Run DH key exchange; setup encryption contexts for channels. + auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b); + ASSERT_NE(context_a, nullptr); + ASSERT_NE(context_b, nullptr); + channel_a.EnableEncryption(context_a.get()); + channel_b.EnableEncryption(context_b.get()); + + EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH"); + EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) == std::string::npos && + capture_b.find(message) == std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) { + // Setup test communication environment. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + + EXPECT_EQ(channel_a.GetType(), "WIFI_LAN"); + EXPECT_EQ(channel_b.GetType(), "WIFI_LAN"); + + // Start data transfer + ByteArray tx_message{"data message"}; + ByteArray more_message{"more data"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Pause and make sure reader blocks. + MultiThreadExecutor pause_resume_executor(2); + channel_a.Pause(); + pause_resume_executor.Execute([&channel_a, &more_message](){ + // Write will block until channel is resumed, or closed. + EXPECT_TRUE(channel_a.Write(more_message).Ok()); + }); + std::atomic_bool done = false; + ByteArray read_more; + pause_resume_executor.Execute([&channel_b, &read_more, &done](){ + // Read will block until channel is resumed, or closed. + auto response = channel_b.Read(); + EXPECT_TRUE(response.ok()); + read_more = std::move(response.result()); + done = true; + }); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(read_more.Empty()); + + // Resume; verify that data transfer comepleted. + channel_a.Resume(); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(done); + EXPECT_EQ(read_more, more_message); + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); + + // Close the output stream before trying to read from the input. + output_stream.Close(); + + // Trying to read should fail gracefully with an IO error. + ExceptionOr read_data = test_channel.Read(); + + ASSERT_FALSE(read_data.ok()); + ASSERT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc new file mode 100644 index 00000000..99482b77 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -0,0 +1,143 @@ +#include "core_v2/internal/base_pcp_handler.h" + +#include +#include +#include +#include +#include + +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager) + : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} + +BasePcpHandler::~BasePcpHandler() { + // Unregister ourselves from the FrameProcessors. + endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE, + handle_); + + // Stop all the ongoing Runnables (as gracefully as possible). + serial_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +Status BasePcpHandler::StartAdvertising(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) { + Future response; + RunOnPcpHandlerThread( + [this, client, &service_id, &info, &options, &response]() { + auto result = StartAdvertisingImpl(client, service_id, + client->GenerateLocalEndpointId(), + info.name, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartAdvertising(", info.name, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopAdvertising(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopAdvertisingImpl(client); + client->StoppedAdvertising(); + advertising_options_.Clear(); + latch.CountDown(); + }); + WaitForLatch("StopAdvertising", &latch); +} + +Status BasePcpHandler::StartDiscovery(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) { + Future response; + RunOnPcpHandlerThread( + [this, client, service_id, options, listener, &response]() { + // Ask the implementation to attempt to start discovery. + auto result = StartDiscoveryImpl(client, service_id, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as discovering and clear + // out any old endpoints we had discovered. + discovery_options_ = options; + discovered_endpoints_.clear(); + client->StartedDiscovery(service_id, GetStrategy(), listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartDiscovery(", service_id, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopDiscovery(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopDiscoveryImpl(client); + client->StoppedDiscovery(); + discovery_options_.Clear(); + latch.CountDown(); + }); + + WaitForLatch("stopDiscovery", &latch); +} + +void BasePcpHandler::WaitForLatch(const string& method_name, + CountDownLatch* latch) { + Exception await_exception = latch->Await(); + if (!await_exception.Ok()) { + if (await_exception.Raised(Exception::kTimeout)) { + NEARBY_LOG(INFO, "Blocked in %s", method_name.c_str()); + } + } +} + +Status BasePcpHandler::WaitForResult(const string& method_name, + std::int64_t client_id, + Future* future) { + if (!future) { + NEARBY_LOG(INFO, "No future to wait for; return with error"); + return {Status::kError}; + } + NEARBY_LOG(INFO, "waiting for future to complete"); + ExceptionOr result = future->Get(); + if (!result.ok()) { + NEARBY_LOG(INFO, "Future completed with exception: %d", result.exception()); + return {Status::kError}; + } + NEARBY_LOG(INFO, "Future completed with status: %d", result.result().value); + return result.result(); +} + +void BasePcpHandler::RunOnPcpHandlerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h new file mode 100644 index 00000000..e4df32f3 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -0,0 +1,323 @@ +#ifndef CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ + +#include +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/internal/pcp_handler.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Define a class that supports move operation for pointers using std::swap. +// It replicates std::unique_ptr<> behavior, but it does not own the pointer, +// so it does not attempt destroy it. +// This approach was recommended during code review, as a better alternative to +// reuse of std::unique_ptr<> with custom no-op deleter, for the sake of +// readability. +template +class Swapper { + public: + Swapper(T* pointer) : pointer_(pointer) {} // NOLINT. + Swapper(Swapper&& other) { *this = std::move(other); } + Swapper& operator=(Swapper&& other) { + std::swap(pointer_, other.pointer_); + return *this; + } + T* operator->() const { return pointer_; } + T& operator*() { return *pointer_; } + operator T*() { return pointer_; } // NOLINT. + T* get() const { return pointer_; } + void reset() { pointer_ = nullptr; } + + private: + T* pointer_ = nullptr; +}; + +template +Swapper MakeSwapper(T* value) { + return Swapper(value); +} + +// 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. +class BasePcpHandler : public PcpHandler, + public EndpointManager::FrameProcessor { + public: + using FrameProcessor = EndpointManager::FrameProcessor; + + // TODO(tracyzhou): Add SecureRandom. + BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager); + ~BasePcpHandler() override; + BasePcpHandler(BasePcpHandler&&) = delete; + BasePcpHandler& operator=(BasePcpHandler&&) = delete; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See + // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 + Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) override; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopAdvertising(ClientProxy* client_proxy) override; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) override; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopDiscovery(ClientProxy* client_proxy) override; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // If either party does call it, connection will terminate. + // Update state in ClientProxy. + Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) override { + return Status{Status::kError}; + } + + // @EndpointManagerReaderThread + void OnIncomingFrame(const OfflineFrame& frame, + const std::string& endpoint_id, ClientProxy* client, + proto::connections::Medium medium) override {} + + // Called when an endpoint disconnects while we're waiting for both sides to + // approve/reject the connection. + // @EndpointManagerThread + void OnEndpointDisconnect(ClientProxy* client_proxy, + const std::string& endpoint_id, + CountDownLatch* barrier) override {} + + protected: + // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). + struct StartOperationResult { + Status status; + // If success, the mediums on which we are now advertising/discovering, for + // analytics. + std::vector 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() = default; + + virtual std::string GetEndpointId() const = 0; + virtual std::string GetEndpointName() const = 0; + virtual std::string GetServiceId() const = 0; + virtual proto::connections::Medium GetMedium() const = 0; + }; + + struct ConnectImplResult { + proto::connections::Medium medium = + proto::connections::Medium::UNKNOWN_MEDIUM; + Status status = {Status::kError}; + std::unique_ptr endpoint_channel; + }; + + void RunOnPcpHandlerThread(Runnable runnable); + + ConnectionOptions GetConnectionOptions() const; + + // @PcpHandlerThread + void OnEndpointFound(ClientProxy* client_proxy, + std::unique_ptr endpoint); + + // @PcpHandlerThread + void OnEndpointLost(ClientProxy* client_proxy, + const DiscoveredEndpoint* endpoint); + + Exception OnIncomingConnection( + ClientProxy* client_proxy, const std::string& remote_device_name, + std::unique_ptr endpoint_channel, + proto::connections::Medium medium); // throws Exception::IO + + // @PcpHandlerThread + virtual StartOperationResult StartAdvertisingImpl( + ClientProxy* client_proxy, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopAdvertisingImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual StartOperationResult StartDiscoveryImpl( + ClientProxy* client_proxy, const std::string& service_id, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopDiscoveryImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual ConnectImplResult ConnectImpl(ClientProxy* client_proxy, + DiscoveredEndpoint* endpoint) = 0; + + virtual std::vector + GetConnectionMediumsByPriority() = 0; + virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0; + + EndpointManager* endpoint_manager_; + EndpointChannelManager* channel_manager_; + + private: + static Exception WriteConnectionRequestFrame( + EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, + const std::string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums); + + static constexpr absl::Duration kConnectionRequestReadTimeout = + absl::Seconds(2); + static constexpr absl::Duration kRejectedConnectionCloseDelay = + absl::Seconds(2); + + void OnConnectionResponse(ClientProxy* client_proxy, + const std::string& endpoint_id, + const OfflineFrame& frame); + + // Returns true if the new endpoint is preferred over the old endpoint. + bool IsPreferred(const BasePcpHandler::DiscoveredEndpoint& new_endpoint, + const BasePcpHandler::DiscoveredEndpoint& old_endpoint); + + // 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( + ClientProxy* client_proxy, const std::string& endpoint_id, + const std::vector& supported_mediums); + + // Returns the optimal medium supported by both devices. + proto::connections::Medium ChooseBestUpgradeMedium( + const std::vector& supported_mediums); + + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, + EndpointChannel* channel, + Status status, + Future* result); + void ProcessPreConnectionResultFailure(ClientProxy* client_proxy, + const std::string& endpoint_id); + DiscoveredEndpoint* GetDiscoveredEndpoint(const std::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(ClientProxy* client_proxy, + const std::string& endpoint_id, + bool can_close_immediately); + + ExceptionOr ReadConnectionRequestFrame( + EndpointChannel* channel); + + void WaitForLatch(const std::string& method_name, CountDownLatch* latch); + Status WaitForResult(const std::string& method_name, std::int64_t client_id, + Future* future); + + AtomicReference bandwidth_upgrade_medium_{ + proto::connections::Medium::UNKNOWN_MEDIUM}; + ScheduledExecutor alarm_executor_; + SingleThreadExecutor serial_executor_; + + // A map of endpoint id -> DiscoveredEndpoint. + absl::flat_hash_map> + 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. + absl::flat_hash_map pending_alarms_; + + // The active ClientProxy's advertising constraints. Empty() + // returns true if the client hasn't started advertising false otherwise. + // 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.) + ConnectionOptions advertising_options_; + // The active ClientProxy's connection lifecycle listener. Non-null while + // advertising. + ConnectionListener advertising_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.) + ConnectionOptions discovery_options_; + Prng prng_; + EncryptionRunner encryption_runner_; + EndpointManager::FrameProcessor::Handle handle_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc new file mode 100644 index 00000000..756ea76b --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -0,0 +1,287 @@ +#include "core_v2/internal/base_pcp_handler.h" + +#include + +#include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/offline_frames.h" +#include "core_v2/listeners.h" +#include "core_v2/params.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; +using ::testing::_; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public BaseEndpointChannel { + public: + explicit MockEndpointChannel(Pipe* reader, Pipe* writer) + : BaseEndpointChannel("channel", &reader->GetInputStream(), + &writer->GetOutputStream()) {} + + ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } + Exception DoWrite(const ByteArray& data) { + return BaseEndpointChannel::Write(data); + } + absl::Time DoGetLastReadTimestamp() { + return BaseEndpointChannel::GetLastReadTimestamp(); + } + + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, CloseImpl, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); +}; + +class MockPcpHandler : public BasePcpHandler { + public: + MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) + : BasePcpHandler(em, ecm) {} + + // Expose protected inner types of a base type for mocking. + using BasePcpHandler::ConnectImplResult; + using BasePcpHandler::DiscoveredEndpoint; + using BasePcpHandler::StartOperationResult; + + MOCK_METHOD(Strategy, GetStrategy, (), (override)); + MOCK_METHOD(Pcp, GetPcp, (), (override)); + + MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, + (ClientProxy * client, const string& service_id, + const string& local_endpoint_id, + const string& local_endpoint_name, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); + MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, + (ClientProxy * client, const string& service_id, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); + MOCK_METHOD(ConnectImplResult, ConnectImpl, + (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); + MOCK_METHOD(std::vector, + GetConnectionMediumsByPriority, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), + (override)); + + // Mock adapters for protected non-virtual methods of a base class. + void OnEndpointFound(ClientProxy* client, + std::unique_ptr endpoint) { + BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); + } + void OnEndpointLost(ClientProxy* client, DiscoveredEndpoint* endpoint) { + BasePcpHandler::OnEndpointLost(client, endpoint); + } +}; + +class MockDiscoveredEndpoint final : public MockPcpHandler::DiscoveredEndpoint { + public: + MOCK_METHOD(std::string, GetEndpointId, (), (const override)); + MOCK_METHOD(std::string, GetEndpointName, (), (const override)); + MOCK_METHOD(std::string, GetServiceId, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); +}; + +class BasePcpHandlerTest : public ::testing::Test { + protected: + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + StrictMock< + MockFunction> + endpoint_distance_changed_cb; + }; + + void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + ConnectionRequestInfo info{ + .name = "remote_endpoint_name", + .listener = connection_listener_, + }; + EXPECT_CALL(*pcp_handler, + StartAdvertisingImpl(client, service_id, _, info.name, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, + discovery_listener_), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsDiscovering()); + } + + std::pair, + std::unique_ptr> + SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT + auto channel_a = std::make_unique(&pipe_b, &pipe_a); + auto channel_b = std::make_unique(&pipe_a, &pipe_b); + // On initiator (A) side, we drop the first write, since this is a + // connection establishment packet, and we don't have the peer entity, just + // the peer channel. The rest of the exchange must happen for the benefit of + // DH key exchange. + EXPECT_CALL(*channel_a, Read()) + .WillRepeatedly(Invoke( + [channel = channel_a.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_a, Write(_)) + .WillOnce(Return(Exception{Exception::kSuccess})) + .WillRepeatedly( + Invoke([channel = channel_a.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_a, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_a, IsPaused) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_b, Read()) + .WillRepeatedly(Invoke( + [channel = channel_b.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_b, Write(_)) + .WillRepeatedly( + Invoke([channel = channel_b.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_b, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_b, IsPaused) + .WillRepeatedly(Return(false)); + return std::make_pair(std::move(channel_a), std::move(channel_b)); + } + + Pipe pipe_a_; + Pipe pipe_b_; + MockConnectionListener mock_connection_listener_; + MockDiscoveryListener mock_discovery_listener_; + ConnectionListener connection_listener_{ + .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_connection_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = + mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = + mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), + .endpoint_distance_changed_cb = + mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), + }; +}; + +TEST_F(BasePcpHandlerTest, ConstructorDestructorWorks) { + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + SUCCEED(); +} + +TEST_F(BasePcpHandlerTest, StartAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopAdvertisingImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsAdvertising()); + pcp_handler->StopAdvertising(client.get()); + EXPECT_FALSE(client->IsAdvertising()); +} + +TEST_F(BasePcpHandlerTest, StartDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopDiscoveryImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsDiscovering()); + pcp_handler->StopDiscovery(client.get()); + EXPECT_FALSE(client->IsDiscovering()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc new file mode 100644 index 00000000..af266605 --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -0,0 +1,222 @@ +#include "core_v2/internal/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { + +BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, + const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address) { + if (version != Version::kV1 || + service_id_hash.size() != kServiceIdHashLength || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + endpoint_name.length() > kMaxEndpointNameLength) { + return; + } + + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; + endpoint_name_ = endpoint_name; + if (!BluetoothMacAddressHexStringToBytes(bluetooth_mac_address).Empty()) { + bluetooth_mac_address_ = bluetooth_mac_address; + } +} + +BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Start reading the bytes. + auto* ble_advertisement_bytes_read_ptr = ble_advertisement_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV1) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: unsupported Version %d", + version_); + return; + } + + pcp_ = static_cast(*ble_advertisement_bytes_read_ptr & kPcpBitmask); + ble_advertisement_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: { + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength); + ble_advertisement_bytes_read_ptr += kServiceIdHashLength; + + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(ble_advertisement_bytes_read_ptr, kEndpointIdLength); + ble_advertisement_bytes_read_ptr += kEndpointIdLength; + + // The next 8 bits are the length of the endpoint name. + auto expected_endpoint_name_length = static_cast( + *ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask); + ble_advertisement_bytes_read_ptr++; + + // The next x bits are the endpoint name. (Max length is 131 bytes). + // Check that the stated endpoint_name_length is the same as what we + // received (based off of the length of ble_advertisement_bytes). + auto actual_endpoint_name_length = + ComputeEndpointNameLength(ble_advertisement_bytes); + if (actual_endpoint_name_length < expected_endpoint_name_length) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisement: expected endpointName to " + "be %d bytes, got %d bytes", + expected_endpoint_name_length, actual_endpoint_name_length); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string(ble_advertisement_bytes_read_ptr, + expected_endpoint_name_length); + ble_advertisement_bytes_read_ptr += expected_endpoint_name_length; + + // The next 48 bits are the bluetooth mac address. + auto bluetooth_mac_address_bytes = ByteArray( + ble_advertisement_bytes_read_ptr, kBluetoothMacAddressLength); + // If the Bluetooth MAC Address bytes are unset or invalid, leave the + // string empty. Otherwise, convert it to the proper colon delimited + // format. + if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) { + bluetooth_mac_address_ = + HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); + } + break; + } + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d", + pcp_); + break; + } +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_pcp_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the Pcp. + version_and_pcp_byte |= static_cast(pcp_) & kPcpBitmask; + out.reserve(1 + service_id_hash_.size() + kEndpointIdLength + 1 + + endpoint_name_.size() + kBluetoothMacAddressLength); + out.append(1, version_and_pcp_byte); + out.append(std::string(service_id_hash_)); + out.append(endpoint_id_); + out.append(1, endpoint_name_.size()); + out.append(endpoint_name_); + // The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is + // invalid or empty, we get back a null byte array. + auto bluetooth_mac_address_bytes( + BluetoothMacAddressHexStringToBytes(bluetooth_mac_address_)); + if (!bluetooth_mac_address_bytes.Empty()) { + out.append(bluetooth_mac_address_bytes.data(), kBluetoothMacAddressLength); + } + + return ByteArray(std::move(out)); +} + +std::uint32_t BleAdvertisement::ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const { + std::string bt_mac_address(bluetooth_mac_address); + + // 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 ByteArray(); + } + + // Convert to bytes. If MAC Address bytes are unset, return a null byte array. + auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address)); + auto bt_mac_address_bytes = + ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size()); + if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) { + return ByteArray(); + } + return bt_mac_address_bytes; +} + +std::string BleAdvertisement::HexBytesToColonDelimitedString( + const ByteArray& hex_bytes) const { + // Convert the hex bytes to a string. + std::string colon_delimited_string( + absl::BytesToHexString(std::string(hex_bytes.data(), 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; +} + +bool BleAdvertisement::IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const { + for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) { + if (bluetooth_mac_address_bytes.data()[i] != 0) { + return false; + } + } + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h new file mode 100644 index 00000000..2a86082e --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -0,0 +1,90 @@ +#ifndef CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Connections Ble Advertisement used in +// Advertising + Discovery. +// +//

[VERSION][PCP][SERVICE_ID_HASH][ENDPOINT_ID][ENDPOINT_NAME_SIZE] +// [ENDPOINT_NAME][BLUETOOTH_MAC] +// +//

See go/connections-ble-advertisement for more information. +class BleAdvertisement { + public: + // Versions of the BleAdvertisement. + enum class Version { + kUndefined = 0, + kV1 = 1, + // Version is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + static constexpr int kVersionAndPcpLength = 1; + // Should be defined as EndpointManager::kEndpointIdLength, but that + // involves making BleAdvertisement templatized on Platform just for + // that one little thing, so forget it (at least for now). + static constexpr int kEndpointIdLength = 4; + static constexpr int kEndpointNameSizeLength = 1; + static constexpr int kBluetoothMacAddressLength = 6; + static constexpr int kMinAdvertisementLength = + kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + + kEndpointNameSizeLength + kBluetoothMacAddressLength; + static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kEndpointNameLengthBitmask = 0x0FF; + + BleAdvertisement() = default; + BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address); + explicit BleAdvertisement(const ByteArray& ble_advertisement_bytes); + ~BleAdvertisement() = default; + + BleAdvertisement(const BleAdvertisement&) = default; + BleAdvertisement& operator=(const BleAdvertisement&) = default; + BleAdvertisement(BleAdvertisement&&) = default; + BleAdvertisement& operator=(BleAdvertisement&&) = default; + + explicit operator ByteArray() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline ByteArray GetServiceIdHash() const{ return service_id_hash_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline std::string GetBluetoothMacAddress() const { + return bluetooth_mac_address_; + } + + private: + std::uint32_t ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const; + ByteArray BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const; + std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const; + bool IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const; + + Version version_ = Version::kUndefined; + Pcp pcp_ = Pcp::kUnknown; + ByteArray service_id_hash_; + std::string endpoint_id_; + std::string endpoint_name_; + std::string bluetooth_mac_address_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc new file mode 100644 index 00000000..9ff3ffea --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -0,0 +1,258 @@ +#include "core_v2/internal/ble_advertisement.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +const char kEndPointID[] = "AB12"; +const char kEndpointName[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +const char kBluetoothMacAddress[] = "00:00:E6:88:64:13"; + +TEST(BleAdvertisementTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + empty_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(empty_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { + std::string emoji_endpoint_name("\u0001F450 \u0001F450"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + emoji_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(emoji_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { + std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, + 'x'); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + long_endpoint_name, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(bad_version, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, bad_pcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { + std::string empty_bluetooth_mac_address = ""; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, empty_bluetooth_mac_address); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { + std::string bad_bluetooth_mac_address = "022:00"; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, bad_bluetooth_mac_address); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty()); +} + +TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(org_ble_advertisement); + + auto ble_advertisement = BleAdvertisement(ble_advertisement_bytes); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +// Bytes at the end should be ignored so that they can be used as reserve bytes +// in the future. +TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Add bytes to the end of the valid Ble advertisement. + auto long_ble_advertisement_bytes = + ByteArray(BleAdvertisement::kMinAdvertisementLength + 1000); + ASSERT_LE(ble_advertisement_bytes.size(), + long_ble_advertisement_bytes.size()); + memcpy(long_ble_advertisement_bytes.data(), + ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + + auto long_ble_advertisement = BleAdvertisement(long_ble_advertisement_bytes); + auto is_valid = long_ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, long_ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, long_ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, + long_ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + auto ble_advertisement = BleAdvertisement(ByteArray()); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Shorten the valid Ble Advertisement. + auto short_ble_advertisement_bytes( + ByteArray(ble_advertisement_bytes.data(), + BleAdvertisement::kMinAdvertisementLength - 1)); + + auto short_ble_advertisement = + BleAdvertisement(short_ble_advertisement_bytes); + auto is_valid = short_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, + ConstructionFromByesWithWrongEndpointNameLengthFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Corrupt the EndpointNameLength bits. + std::string corrupt_ble_advertisement_string(ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + corrupt_ble_advertisement_string[8] ^= 0x0FF; + auto corrupt_ble_advertisement_bytes = + ByteArray(corrupt_ble_advertisement_string); + + auto corrupt_ble_advertisement = + BleAdvertisement(corrupt_ble_advertisement_bytes); + auto is_valid = corrupt_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc new file mode 100644 index 00000000..aaa67dba --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.cc @@ -0,0 +1,461 @@ +#include "core_v2/internal/client_proxy.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +ClientProxy::ClientProxy() : client_id_(Prng().NextInt64()) {} + +ClientProxy::~ClientProxy() { Reset(); } + +std::int64_t ClientProxy::GetClientId() const { return client_id_; } + +std::string ClientProxy::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. + ByteArray id_hash(Crypto::Sha256( + absl::StrCat(api::ImplementationPlatform::GetDeviceId(), GetClientId()))); + + return Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength); +} + +void ClientProxy::Reset() { + MutexLock lock(&mutex_); + + StoppedAdvertising(); + StoppedDiscovery(); + RemoveAllEndpoints(); +} + +void ClientProxy::StartedAdvertising( + const std::string& service_id, Strategy strategy, + const ConnectionListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + advertising_info_ = {service_id, listener}; +} + +void ClientProxy::StoppedAdvertising() { + MutexLock lock(&mutex_); + + if (IsAdvertising()) { + advertising_info_.Clear(); + } +} + +bool ClientProxy::IsAdvertising() const { + MutexLock lock(&mutex_); + + return !advertising_info_.IsEmpty(); +} + +std::string ClientProxy::GetAdvertisingServiceId() const { + MutexLock lock(&mutex_); + return advertising_info_.service_id; +} + +void ClientProxy::StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + discovery_info_ = DiscoveryInfo{service_id, listener}; +} + +void ClientProxy::StoppedDiscovery() { + MutexLock lock(&mutex_); + + if (IsDiscovering()) { + discovered_endpoint_ids_.clear(); + discovery_info_.Clear(); + } +} + +bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const { + MutexLock lock(&mutex_); + + return IsDiscovering() && service_id == discovery_info_.service_id; +} + +bool ClientProxy::IsDiscovering() const { + MutexLock lock(&mutex_); + + return !discovery_info_.IsEmpty(); +} + +std::string ClientProxy::GetDiscoveryServiceId() const { + MutexLock lock(&mutex_); + + return discovery_info_.service_id; +} + +void ClientProxy::OnEndpointFound(const std::string& service_id, + const std::string& endpoint_id, + const std::string& endpoint_name, + proto::connections::Medium medium) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + if (discovered_endpoint_ids_.count(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + discovered_endpoint_ids_.insert(endpoint_id); + discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_name, + service_id); +} + +void ClientProxy::OnEndpointLost(const std::string& service_id, + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + const auto it = discovered_endpoint_ids_.find(endpoint_id); + if (it == discovered_endpoint_ids_.end()) return; + discovered_endpoint_ids_.erase(it); + discovery_info_.listener.endpoint_lost_cb(endpoint_id); +} + +void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener) { + MutexLock lock(&mutex_); + + // 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. + auto result = connections_.emplace( + endpoint_id, Connection{ + .is_incoming = info.is_incoming_connection, + .connection_listener = listener, + }); + // Instead of using structured binding which is nice, but banned + // (can not use c++17 features, until chromium does) we unpack manually. + auto& pair_iter = result.first; + bool& inserted = result.second; + DCHECK(inserted); + const Connection& item = pair_iter->second; + // Notify the client. + // + // Note: we allow devices to connect to an advertiser even after it stops + // advertising, so no need to check IsAdvertising() here. + item.connection_listener.initiated_cb(endpoint_id, info); +} + +void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + // Notify the client. + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.accepted_cb(endpoint_id); + item->status = Connection::kConnected; + } +} + +void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, + const Status& status) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + NEARBY_LOG(INFO, "ClientProxy [Rejected]: no pending connection; id=%s", + endpoint_id.c_str()); + return; + } + + // Notify the client. + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.rejected_cb(endpoint_id, status); + OnDisconnected(endpoint_id, false /* notify */); + } +} + +void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id, + std::int32_t quality) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.bandwidth_changed_cb(endpoint_id, quality); + } +} + +void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + if (notify) { + item->connection_listener.disconnected_cb({endpoint_id}); + } + connections_.erase(endpoint_id); + } +} + +bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status == status; + } + return false; +} + +bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const { + return ConnectionStatusMatches(endpoint_id, Connection::kConnected); +} + +std::vector ClientProxy::GetMatchingEndpoints( + std::function pred) const { + MutexLock lock(&mutex_); + + std::vector connected_endpoints; + + for (const auto& pair : connections_) { + const auto& endpoint_id = pair.first; + const auto& connection = pair.second; + if (pred(connection)) { + connected_endpoints.push_back(endpoint_id); + } + } + return connected_endpoints; +} + +std::vector ClientProxy::GetPendingConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status != Connection::kConnected; + }); +} + +std::vector ClientProxy::GetConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected; + }); +} + +std::int32_t ClientProxy::GetNumOutgoingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + !connection.is_incoming; + }) + .size(); +} + +std::int32_t ClientProxy::GetNumIncomingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + connection.is_incoming; + }) + .size(); +} + +bool ClientProxy::HasPendingConnectionToEndpoint( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status != Connection::kConnected; + } + return false; +} + +bool ClientProxy::HasLocalEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointAccepted | + Connection::kLocalEndpointRejected)); +} + +bool ClientProxy::HasRemoteEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kRemoteEndpointAccepted | + Connection::kRemoteEndpointRejected)); +} + +void ClientProxy::LocalEndpointAcceptedConnection( + const std::string& endpoint_id, const PayloadListener& listener) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointAccepted); + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener = listener; + } +} + +void ClientProxy::LocalEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointRejected); +} + +void ClientProxy::RemoteEndpointAcceptedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::RemoteEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointRejected); +} + +bool ClientProxy::IsConnectionAccepted(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains(endpoint_id, + Connection::kLocalEndpointAccepted) && + ConnectionStatusesContains(endpoint_id, + Connection::kRemoteEndpointAccepted); +} + +bool ClientProxy::IsConnectionRejected(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointRejected | + Connection::kRemoteEndpointRejected)); +} + +bool ClientProxy::LocalConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kLocalEndpointAccepted); +} + +bool ClientProxy::RemoteConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::OnPayload(const std::string& endpoint_id, Payload payload) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_cb(endpoint_id, std::move(payload)); + } + } +} + +const ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) const { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +void ClientProxy::OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_progress_cb(endpoint_id, info); + } + } +} + +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() == rhs.GetClientId(); +} + +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() < rhs.GetClientId(); +} + +void ClientProxy::RemoveAllEndpoints() { + MutexLock lock(&mutex_); + + // 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. + connections_.clear(); +} + +bool ClientProxy::ConnectionStatusesContains( + const std::string& endpoint_id, Connection::Status status_to_match) const { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return (item->status & status_to_match) != 0; + } + return false; +} + +void ClientProxy::AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->status = + static_cast(item->status | status_to_append); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h new file mode 100644 index 00000000..a1013e0c --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.h @@ -0,0 +1,217 @@ +#ifndef CORE_V2_INTERNAL_CLIENT_PROXY_H_ +#define CORE_V2_INTERNAL_CLIENT_PROXY_H_ + +#include +#include +#include + +#include "core_v2/listeners.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "proto/connections_enums.pb.h" +// Prefer using absl:: versions of a set and a map; they tend to be more +// efficient: implementation is using open-addressing hash tables. +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// CLientProxy is tracking state of client's connection, and serves as +// a proxy for notifications sent to this client. +class ClientProxy final { + public: + static constexpr int kEndpointIdLength = 4; + + ClientProxy(); + ~ClientProxy(); + ClientProxy(ClientProxy&&) = default; + ClientProxy& operator=(ClientProxy&&) = default; + + 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, Strategy strategy, + const ConnectionListener& connection_lifecycle_listener, + absl::Span mediums); + // Marks this client as not advertising. + void StoppedAdvertising(); + bool IsAdvertising() const; + std::string GetAdvertisingServiceId() const; + + // Marks this client as discovering with the given callback. + void StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& discovery_listener, + absl::Span mediums); + // Marks this client as not discovering at all. + void StoppedDiscovery(); + bool IsDiscoveringServiceId(const std::string& service_id) const; + bool IsDiscovering() const; + std::string GetDiscoveryServiceId() const; + + // Proxies to the client's DiscoveryListener::OnEndpointFound() callback. + void OnEndpointFound(const std::string& service_id, + const std::string& endpoint_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 ConnectionListener::OnInitiated() callback. + void OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener); + + // Proxies to the client's ConnectionListener::OnAccepted() callback. + void OnConnectionAccepted(const std::string& endpoint_id); + // Proxies to the client's ConnectionListener::OnRejected() callback. + void OnConnectionRejected(const std::string& endpoint_id, + const Status& 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 + // ConnectionListener.disconnected_cb() 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) const; + // Returns all endpoints that can safely be sent payloads. + std::vector GetConnectedEndpoints() const; + // Returns all endpoints that are still awaiting acceptance. + std::vector GetPendingConnectedEndpoints() const; + // Returns the number of endpoints that are connected and outgoing. + std::int32_t GetNumOutgoingConnections() const; + // Returns the number of endpoints that are connected and incoming. + std::int32_t GetNumIncomingConnections() const; + // 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) const; + // Returns true if the local endpoint has already marked itself as + // accepted/rejected. + bool HasLocalEndpointResponded(const std::string& endpoint_id) const; + // Returns true if the remote endpoint has already marked themselves as + // accepted/rejected. + bool HasRemoteEndpointResponded(const std::string& endpoint_id) const; + // Marks the local endpoint as having accepted the connection. + void LocalEndpointAcceptedConnection(const std::string& endpoint_id, + const PayloadListener& 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) const; + // Returns true if either the local endpoint or the remote endpoint has + // rejected the connection. + bool IsConnectionRejected(const std::string& endpoint_id) const; + + // Proxies to the client's PayloadListener::OnPayload() callback. + void OnPayload(const std::string& endpoint_id, Payload payload); + // Proxies to the client's PayloadListener::OnPayloadProgress() callback. + void OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info); + bool LocalConnectionIsAccepted(std::string endpoint_id) const; + bool RemoteConnectionIsAccepted(std::string endpoint_id) const; + + private: + struct Connection { + // Status: may be either: + // Connection::PENDING, or combination of + // Connection::LOCAL_ENDPOINT_ACCEPTED: + // Connection::LOCAL_ENDPOINT_REJECTED and + // Connection::REMOTE_ENDPOINT_ACCEPTED: + // Connection::REMOTE_ENDPOINT_REJECTED, or + // Connection::CONNECTED. + // Only when this is set to CONNECTED should you allow payload transfers. + // + // We want this enum to be implicitly convertible to int, because + // we perform bit operations on it. + enum Status : uint8_t { + kPending = 0, + kLocalEndpointAccepted = 1 << 0, + kLocalEndpointRejected = 1 << 1, + kRemoteEndpointAccepted = 1 << 2, + kRemoteEndpointRejected = 1 << 3, + kConnected = 1 << 4, + }; + bool is_incoming{false}; + Status status{kPending}; + ConnectionListener connection_listener; + PayloadListener payload_listener; + }; + + struct AdvertisingInfo { + std::string service_id; + ConnectionListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + struct DiscoveryInfo { + std::string service_id; + DiscoveryListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + void RemoveAllEndpoints(); + bool ConnectionStatusesContains(const std::string& endpoint_id, + Connection::Status status_to_match) const; + void AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append); + + const Connection* LookupConnection(const std::string& endpoint_id) const; + Connection* LookupConnection(const std::string& endpoint_id); + bool ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const; + std::vector GetMatchingEndpoints( + std::function pred) const; + + mutable RecursiveMutex mutex_; + std::int64_t client_id_; + + // If not empty, we are currently advertising and accepting connection + // requests for the given service_id. + AdvertisingInfo advertising_info_; + + // If not empty, we are currently discovering for the given service_id. + DiscoveryInfo discovery_info_; + + // Maps endpoint_id to endpoint connection state. + absl::flat_hash_map connections_; + + // 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. + absl::flat_hash_set discovered_endpoint_ids_; +}; + +// Operator overloads when comparing Ptr. +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs); +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_CLIENT_PROXY_H_ diff --git a/cpp/core_v2/internal/client_proxy_test.cc b/cpp/core_v2/internal/client_proxy_test.cc new file mode 100644 index 00000000..88a3e93e --- /dev/null +++ b/cpp/core_v2/internal/client_proxy_test.cc @@ -0,0 +1,357 @@ +#include "core_v2/internal/client_proxy.h" + +#include + +#include "core_v2/listeners.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::testing::MockFunction; +using ::testing::StrictMock; + +class ClientProxyTest : public testing::Test { + protected: + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockPayloadListener { + StrictMock< + MockFunction> + payload_cb; + StrictMock> + payload_progress_cb; + }; + + struct Endpoint { + std::string name; + std::string id; + }; + + Endpoint StartAdvertising(ClientProxy* client, ConnectionListener listener) { + Endpoint endpoint{ + .name = "advertising endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedAdvertising(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + Endpoint StartDiscovery(ClientProxy* client, DiscoveryListener listener) { + Endpoint endpoint{ + .name = "discovery endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedDiscovery(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + void OnDiscoveryEndpointFound(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_found_cb, Call).Times(1); + client->OnEndpointFound(service_id_, endpoint.id, endpoint.name, medium_); + } + + void OnDiscoveryEndpointLost(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_lost_cb, Call).Times(1); + client->OnEndpointLost(service_id_, endpoint.id); + } + + void OnDiscoveryConnectionInitiated(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1); + const std::string auth_token{"auth_token"}; + const ByteArray raw_auth_token{auth_token}; + advertising_connection_info_.remote_endpoint_name = endpoint.name; + client->OnConnectionInitiated(endpoint.id, advertising_connection_info_, + discovery_connection_listener_); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + } + + void OnDiscoveryConnectionLocalAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointAcceptedConnection(endpoint.id, payload_listener_); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointAcceptedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionLocalRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.accepted_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint.id)); + client->OnConnectionAccepted(endpoint.id); + } + + void OnDiscoveryConnectionRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.rejected_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionRejected(endpoint.id)); + client->OnConnectionRejected(endpoint.id, {Status::kConnectionRejected}); + } + + void OnDiscoveryBandwidthChanged(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.bandwidth_changed_cb, Call).Times(1); + client->OnBandwidthChanged(endpoint.id, 1); + } + + void OnDiscoveryConnectionDisconnected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.disconnected_cb, Call).Times(1); + client->OnDisconnected(endpoint.id, true); + } + + void OnPayload(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_cb, Call).Times(1); + client->OnPayload(endpoint.id, Payload(payload_bytes_)); + } + + void OnPayloadProgress(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_progress_cb, Call).Times(1); + client->OnPayloadProgress(endpoint.id, {}); + } + + MockDiscoveryListener mock_discovery_; + MockConnectionListener mock_discovery_connection_; + MockPayloadListener mock_discovery_payload_; + + proto::connections::Medium medium_{proto::connections::Medium::BLUETOOTH}; + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH, + }; + Strategy strategy_{Strategy::kP2pPointToPoint}; + const std::string service_id_{"service"}; + ClientProxy client1_; + ClientProxy client2_; + std::string auth_token_ = "auth_token"; + ByteArray raw_auth_token_ = ByteArray(auth_token_); + ByteArray payload_bytes_{"bytes"}; + ConnectionResponseInfo advertising_connection_info_{ + .authentication_token = auth_token_, + .raw_authentication_token = raw_auth_token_, + .is_incoming_connection = true, + }; + ConnectionListener advertising_connection_listener_; + ConnectionListener discovery_connection_listener_{ + .initiated_cb = mock_discovery_connection_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_discovery_connection_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_discovery_connection_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_discovery_connection_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_discovery_connection_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = mock_discovery_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = mock_discovery_.endpoint_lost_cb.AsStdFunction(), + }; + PayloadListener payload_listener_{ + .payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(), + .payload_progress_cb = + mock_discovery_payload_.payload_progress_cb.AsStdFunction(), + }; +}; + +TEST_F(ClientProxyTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(ClientProxyTest, ClientIdIsUnique) { + EXPECT_NE(client1_.GetClientId(), client2_.GetClientId()); +} + +TEST_F(ClientProxyTest, GeneratedEndpointIdIsUnique) { + EXPECT_NE(client1_.GenerateLocalEndpointId(), + client2_.GenerateLocalEndpointId()); +} + +TEST_F(ClientProxyTest, ResetClearsState) { + client1_.Reset(); + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedAdvertisingChangesStateFromIdle) { + client1_.StartedAdvertising(service_id_, strategy_, {}, {}); + + EXPECT_TRUE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_EQ(client1_.GetAdvertisingServiceId(), service_id_); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedDiscoveryChangesStateFromIdle) { + client1_.StartedDiscovery(service_id_, strategy_, {}, {}); + + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_TRUE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_EQ(client1_.GetDiscoveryServiceId(), service_id_); +} + +TEST_F(ClientProxyTest, OnEndpointFoundFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnEndpointLostFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryEndpointLost(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnConnectionInitiatedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnBandwidthChangedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnDiscoveryBandwidthChanged(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnDisconnectedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionDisconnected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayload(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadProgressChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayloadProgress(&client2_, advertising_endpoint); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.cc b/cpp/core_v2/internal/encryption_runner.cc new file mode 100644 index 00000000..226c0695 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.cc @@ -0,0 +1,368 @@ +#include "core_v2/internal/encryption_runner.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/logging.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/strings/ascii.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::Duration kTimeout = absl::Seconds(15); +constexpr std::int32_t kMaxUkey2VerificationStringLength = 32; +constexpr std::int32_t kTokenLength = 5; +constexpr securegcm::UKey2Handshake::HandshakeCipher kCipher = + securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512; + +// Transforms a raw UKEY2 token (which is a random ByteArray that's +// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only +// uses [A-Z], [0-9], '_', '-' for each character. +std::string ToHumanReadableString(const ByteArray& token) { + std::string result = Base64Utils::Encode(token).substr(0, kTokenLength); + absl::AsciiStrToUpper(&result); + return result; +} + +bool HandleEncryptionSuccess(const std::string& endpoint_id, + std::unique_ptr ukey2, + const EncryptionRunner::ResultListener& listener) { + std::unique_ptr verification_string = + ukey2->GetVerificationString(kMaxUkey2VerificationStringLength); + if (verification_string == nullptr) { + return false; + } + + ByteArray raw_authentication_token(*verification_string); + + listener.on_success_cb(endpoint_id, std::move(ukey2), + ToHumanReadableString(raw_authentication_token), + raw_authentication_token); + + return true; +} + +void CancelableAlarmRunnable(ClientProxy* client_proxy, + const std::string& endpoint_id, + EndpointChannel* endpoint_channel) { + NEARBY_LOG(INFO, + "Timing out encryption for client %" PRId64 + " to endpoint %s after %" PRId64 " ms", + client_proxy->GetClientId(), endpoint_id.c_str(), + static_cast(absl::ToInt64Milliseconds(kTimeout))); + endpoint_channel->Close(); +} + +class ServerRunnable final { + public: + ServerRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startServer() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr server = + securegcm::UKey2Handshake::ForResponder(kCipher); + if (server == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + ExceptionOr client_init = channel_->Read(); + if (!client_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + server->ParseHandshakeMessage(std::string(client_init.result())); + + // 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 server_init = + server->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (server_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_exception = + channel_->Write(ByteArray(std::move(*server_init))); + if (!write_exception.Ok()) { + 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 client_finish = channel_->Read(); + + if (!client_finish.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + parse_result = + server->ParseHandshakeMessage(std::string(client_finish.result())); + + // 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(endpoint_id_, std::move(server), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startServer(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +class ClientRunnable final { + public: + ClientRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startClient() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr crypto = + securegcm::UKey2Handshake::ForInitiator(kCipher); + + // Java code throws a HandshakeException. + if (crypto == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + std::unique_ptr client_init = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_init_exception = channel_->Write(ByteArray(*client_init)); + if (!write_init_exception.Ok()) { + 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 server_init = channel_->Read(); + + if (!server_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + crypto->ParseHandshakeMessage(std::string(server_init.result())); + + // 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 client_finish = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_finish == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_finish_exception = + channel_->Write(ByteArray(*client_finish)); + if (!write_finish_exception.Ok()) { + 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(endpoint_id_, std::move(crypto), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startClient(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startClient(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +} // namespace + +EncryptionRunner::~EncryptionRunner() { + // Stop all the ongoing Runnables (as gracefully as possible). + client_executor_.Shutdown(); + server_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +void EncryptionRunner::StartServer( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + server_executor_.Execute( + [runnable{ServerRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +void EncryptionRunner::StartClient( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + client_executor_.Execute( + [runnable{ClientRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.h b/cpp/core_v2/internal/encryption_runner.h new file mode 100644 index 00000000..399fb0b5 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.h @@ -0,0 +1,72 @@ +#ifndef CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ +#define CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "securegcm/ukey2_handshake.h" + +namespace location { +namespace nearby { +namespace connections { + +// Encrypts a connection over UKEY2. +// +// NOTE: Stalled EndpointChannels will be disconnected after kTimeout. +// This is to prevent unverified endpoints from maintaining an +// indefinite connection to us. +class EncryptionRunner { + public: + EncryptionRunner() = default; + ~EncryptionRunner(); + + struct ResultListener { + // @EncryptionRunnerThread + std::function ukey2, + const std::string& auth_token, + const ByteArray& raw_auth_token)> + on_success_cb = + DefaultCallback, + const std::string&, const ByteArray&>(); + + // Encryption has failed. The remote_endpoint_id and channel are given so + // that any pending state can be cleaned up. + // + // 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 + std::function + on_failure_cb = DefaultCallback(); + }; + + // @AnyThread + void StartServer(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + // @AnyThread + void StartClient(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + + private: + ScheduledExecutor alarm_executor_; + SingleThreadExecutor server_executor_; + SingleThreadExecutor client_executor_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ diff --git a/cpp/core_v2/internal/encryption_runner_test.cc b/cpp/core_v2/internal/encryption_runner_test.cc new file mode 100644 index 00000000..cc4839db --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner_test.cc @@ -0,0 +1,128 @@ +#include "core_v2/internal/encryption_runner.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; + +class FakeEndpointChannel : public EndpointChannel { + public: + FakeEndpointChannel(InputStream* in, OutputStream* out) + : in_(in), out_(out) {} + ExceptionOr Read() override { + read_timestamp_ = SystemClock::ElapsedRealtime(); + return in_ ? in_->Read(Pipe::kChunkSize) + : ExceptionOr{Exception::kIo}; + } + Exception Write(const ByteArray& data) override { + return out_ ? out_->Write(data) : Exception{Exception::kIo}; + } + void Close() override { + if (in_) in_->Close(); + if (out_) out_->Close(); + } + void Close(proto::connections::DisconnectionReason reason) override { + Close(); + } + std::string GetType() const override { return "fake-channel-type"; } + std::string GetName() const override { return "fake-channel"; } + Medium GetMedium() const override { return Medium::BLE; } + void EnableEncryption( + securegcm::D2DConnectionContextV1* connection_context) override {} + bool IsPaused() const override { return false; } + void Pause() override {} + void Resume() override {} + absl::Time GetLastReadTimestamp() const override { return read_timestamp_; } + + private: + InputStream* in_ = nullptr; + OutputStream* out_ = nullptr; + absl::Time read_timestamp_ = absl::InfinitePast(); +}; + +struct User { + User(Pipe* reader, Pipe* writer) + : channel(&reader->GetInputStream(), &writer->GetOutputStream()) {} + + FakeEndpointChannel channel; + EncryptionRunner crypto; + ClientProxy client; +}; + +struct Response { + enum class Status { + kUnknown = 0, + kDone = 1, + kFailed = 2, + }; + + CountDownLatch latch{2}; + Status server_status = Status::kUnknown; + Status client_status = Status::kUnknown; +}; + +TEST(EncryptionRunnerTest, ConstructorDestructorWorks) { EncryptionRunner enc; } + +TEST(EncryptionRunnerTest, ReadWrite) { + Pipe from_a_to_b; + Pipe from_b_to_a; + User user_a(/*reader=*/&from_b_to_a, /*writer=*/&from_a_to_b); + User user_b(/*reader=*/&from_a_to_b, /*writer=*/&from_b_to_a); + Response response; + + user_a.crypto.StartServer( + &user_a.client, "endpoint_id", &user_a.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.server_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.server_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + user_b.crypto.StartClient( + &user_b.client, "endpoint_id", &user_b.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.client_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.client_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + EXPECT_TRUE(response.latch.Await(absl::Milliseconds(5000)).result()); + EXPECT_EQ(response.server_status, Response::Status::kDone); + EXPECT_EQ(response.client_status, Response::Status::kDone); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel.h b/cpp/core_v2/internal/endpoint_channel.h new file mode 100644 index 00000000..6c441191 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel.h @@ -0,0 +1,74 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +class EndpointChannel { + public: + virtual ~EndpointChannel() = default; + + virtual ExceptionOr + Read() = 0; // throws Exception::IO, Exception::INTERRUPTED + + virtual Exception Write(const 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 std::string GetType() const = 0; + + // Returns the name of the EndpointChannel. + virtual std::string GetName() const = 0; + + // Returns the analytics enum representing the medium of this EndpointChannel. + virtual proto::connections::Medium GetMedium() const = 0; + + // Enables encryption on the EndpointChannel. + virtual void EnableEncryption( + securegcm::D2DConnectionContextV1* context) = 0; + + // True if the EndpointChannel is currently pausing all writes. + virtual bool IsPaused() const = 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. + virtual absl::Time GetLastReadTimestamp() const = 0; +}; + +inline bool operator==(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return (lhs.GetType() == rhs.GetType()) && (lhs.GetName() == rhs.GetName()) && + (lhs.GetMedium() == rhs.GetMedium()); +} + +inline bool operator!=(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager.cc b/cpp/core_v2/internal/endpoint_channel_manager.cc new file mode 100644 index 00000000..2e0bdc41 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.cc @@ -0,0 +1,137 @@ +#include "core_v2/internal/endpoint_channel_manager.h" + +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +EndpointChannelManager::~EndpointChannelManager() { + MutexLock lock(&mutex_); + channel_state_.DestroyAll(); +} + +void EndpointChannelManager::RegisterChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); + + NEARBY_LOG(INFO, "Registered channel: id=%s", endpoint_id.c_str()); +} + +void EndpointChannelManager::ReplaceChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint != nullptr && endpoint->channel == nullptr) { + NEARBY_LOG(INFO, "Channel is missing while trying to update: id=%s", + endpoint_id.c_str()); + } + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); +} + +bool EndpointChannelManager::EncryptChannelForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + MutexLock lock(&mutex_); + + channel_state_.UpdateEncryptionContextForEndpoint(endpoint_id, + std::move(context)); + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + return channel_state_.EncryptChannel(endpoint); +} + +std::shared_ptr EndpointChannelManager::GetChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint == nullptr) { + NEARBY_LOG(INFO, "No channel info: id=%s", endpoint_id.c_str()); + return {}; + } + + return endpoint->channel; +} + +void EndpointChannelManager::SetActiveEndpointChannel( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + + // Update the channel first, then encrypt this new channel, if + // crypto context is present. + channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel)); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint); +} + +// endpoint - channel endpoint to encrypt +bool EndpointChannelManager::ChannelState::EncryptChannel( + EndpointChannelManager::ChannelState::EndpointData* endpoint) { + if (endpoint != nullptr && endpoint->channel != nullptr && + endpoint->context != nullptr) { + endpoint->channel->EnableEncryption(endpoint->context.get()); + return true; + } + return false; +} + +///////////////////////////////// ChannelState ///////////////////////////////// +EndpointChannelManager::ChannelState::EndpointData* +EndpointChannelManager::ChannelState::LookupEndpointData( + const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + return item != endpoints_.end() ? &item->second : nullptr; +} + +void EndpointChannelManager::ChannelState::UpdateChannelForEndpoint( + const std::string& endpoint_id, std::unique_ptr channel) { + // Create EndpointData instance, if necessary, and populate channel. + endpoints_[endpoint_id].channel = std::move(channel); +} + +void EndpointChannelManager::ChannelState::UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + // Create EndpointData instance, if necessary, and populate crypto context. + endpoints_[endpoint_id].context = std::move(context); +} + +bool EndpointChannelManager::ChannelState::RemoveEndpoint( + const std::string& endpoint_id, + proto::connections::DisconnectionReason reason) { + auto item = endpoints_.find(endpoint_id); + if (item == endpoints_.end()) return false; + item->second.disconnect_reason = reason; + endpoints_.erase(item); + return true; +} + +bool EndpointChannelManager::UnregisterChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!channel_state_.RemoveEndpoint( + endpoint_id, + proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) { + return false; + } + + NEARBY_LOG(INFO, "Unregistered channel: id=%s", endpoint_id.c_str()); + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel_manager.h b/cpp/core_v2/internal/endpoint_channel_manager.h new file mode 100644 index 00000000..c6e9e9c7 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.h @@ -0,0 +1,155 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +using EncryptionContext = ::securegcm::D2DConnectionContextV1; + +// NOTE(std::string): +// All the strings in internal class public interfaces should be exchanged as +// const std::string& if they are immutable, and as std::string +// it they are mutable. +// This is to keep all the internal classes compatible with each other, +// and minimize resources spent on the type conversion. +// Project-wide, strings are either passed around as reference (which has +// zero maintenance costs, and sizeof(void*) memory usage => passed around in a +// CPU register), and whenever lifetime etension is required, it must be copied +// to std::string instance (which will again propagate as a const reference +// within it's lifetime domain). + +// Manages the communication channels to all the remote endpoints with which we +// are interacting. +class EndpointChannelManager final { + public: + ~EndpointChannelManager(); + + // 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(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + // 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. + void ReplaceChannelForEndpoint(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool EncryptChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr context) + ABSL_LOCKS_EXCLUDED(mutex_); + + // NOTE(shared_ptr<> usage): + // + // EndpointChannelManager is holding an EndpointChannel instance; + // GetChannelForEndpoint() is passing ownership over to a worker thread. + // It is not a pointer passing but an ownership passing, to guarantee that + // channel instance will not disappear underneath the feet of a worker thread + // inside EndpointManager [ EndpointManager::EndpointChannelLoopRunnable() ]. + // If it is just a pointer, Channel will get destroyed while in use by a + // worker thread. shared_ptr is a simple and reliable tool to avoid that. + // + // The reason why it can not be std::unique_ptr<> is: there are other code + // paths that expect to be able to read the pointer value multiple times, from + // multiple places (each of them needs "ownership" for the duration of their + // use). EndpointManager::SendTransferFrameBytes() is another such place. + // If EndpointChannelManager replaces the current channel, and any (or both) + // EndpointManager methods that use a channel are running, it is better to + // have a shared ownership. + std::shared_ptr GetChannelForEndpoint( + const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if 'endpoint_id' actually had a registered EndpointChannel. + // IOW, a return of false signifies a no-op. + bool UnregisterChannelForEndpoint(const std::string& endpoint_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + 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: + struct EndpointData { + EndpointData() = default; + EndpointData(EndpointData&&) = default; + EndpointData& operator=(EndpointData&&) = default; + ~EndpointData() { + if (channel != nullptr) { + channel->Close(disconnect_reason); + } + } + + // True if we have a 'context' for the endpoint. + bool IsEncrypted() const { return context != nullptr; } + + std::shared_ptr channel; + std::unique_ptr context; + proto::connections::DisconnectionReason disconnect_reason = + proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON; + }; + + ChannelState() = default; + ~ChannelState() { DestroyAll(); } + ChannelState(ChannelState&&) = default; + ChannelState& operator=(ChannelState&&) = default; + + // Provides a way to destroy contents of a container, while holding a lock. + void DestroyAll() { endpoints_.clear(); } + // Return pointer to endpoint data, or nullptr, it not found. + EndpointData* LookupEndpointData(const std::string& endpoint_id); + + // Stores a new EndpointChannel for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr channel); + + // Stores a new EncryptionContext for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context); + + // Removes all knowledge of this endpoint, cleaning up as necessary. + // Returns false if the endpoint was not found. + bool RemoveEndpoint(const std::string& endpoint_id, + proto::connections::DisconnectionReason reason); + + bool EncryptChannel(EndpointData* endpoint); + + private: + // Endpoint ID -> EndpointData. Contains everything we know about the + // endpoint. + absl::flat_hash_map endpoints_; + }; + + void SetActiveEndpointChannel(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Mutex mutex_; + ChannelState channel_state_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager_test.cc b/cpp/core_v2/internal/endpoint_channel_manager_test.cc new file mode 100644 index 00000000..673ed7f1 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager_test.cc @@ -0,0 +1,17 @@ +#include "core_v2/internal/endpoint_channel_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(EndpointChannelManagerTest, ConstructorDestructorWorks) { + EndpointChannelManager mgr; + SUCCEED(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc new file mode 100644 index 00000000..501df7a3 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -0,0 +1,477 @@ +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +using ::location::nearby::proto::connections::Medium; + +// A Runnable that continuously grabs the most recent EndpointChannel available +// for an endpoint. +// +// handler - 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, +// handler(EndpointChannel) will be called again. Return false to exit +// the loop. +void EndpointManager::EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler) { + // EndpointChannelManager will not let multiple channels exist simultaneously + // for the same endpoint_id; it will be closing "old" channels as new ones + // come. (There will be a short overlap). + // Closed channel will return Exception::kIo for any Read, and loop (below) + // will retry and attempt to pick another channel. + // If channel is deleted (no mapping), or it is still the same channel + // (same Medium) on which we got the Exception::kIo, we terminate the loop. + Medium last_failed_medium = Medium::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). + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (channel == nullptr) { + // 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_medium != Medium::UNKNOWN_MEDIUM) && + (channel->GetMedium() == last_failed_medium)) { + // TODO(tracyzhou): Add logging. + break; + } + + ExceptionOr keep_using_channel = handler(channel.get()); + + if (!keep_using_channel.ok()) { + Exception exception = keep_using_channel.GetException(); + if (exception.Raised(Exception::kIo)) { + last_failed_medium = channel->GetMedium(); + // TODO(tracyzhou): Add logging. + continue; + } + if (exception.Raised(Exception::kInterrupted)) { + break; + } + } + + if (!keep_using_channel.result()) { + // TODO(tracyzhou): Add logging. + break; + } + } + // Indicate we're out of the loop and it is ok to schedule another instance + // if needed. + NEARBY_LOG(INFO, "Worker going down; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); + barrier->CountDown(); + + // Always clear out all state related to this endpoint before terminating + // this thread. + DiscardEndpoint(client, endpoint_id); + NEARBY_LOG(INFO, "Worker done; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); +} + +ExceptionOr EndpointManager::HandleData( + const std::string& endpoint_id, ClientProxy* client, + EndpointChannel* endpoint_channel) { + // 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 bytes = endpoint_channel->Read(); + if (!bytes.ok()) { + NEARBY_LOG(INFO, "Stop reading on read-time exception: %d", + bytes.exception()); + return ExceptionOr(bytes.exception()); + } + ExceptionOr wrapped_frame = parser::FromBytes(bytes.result()); + if (!wrapped_frame.ok()) { + if (wrapped_frame.GetException().Raised( + Exception::kInvalidProtocolBuffer)) { + NEARBY_LOG(INFO, "failed to decode; endpoint=%s; channel=%s; skip", + endpoint_id.c_str(), endpoint_channel->GetType().c_str()); + continue; + } else { + NEARBY_LOG(INFO, "Stop reading on parse-time exception: %d", + wrapped_frame.exception()); + return ExceptionOr(wrapped_frame.exception()); + } + } + OfflineFrame& frame = wrapped_frame.result(); + + // Route the incoming offlineFrame to its registered processor. + V1Frame::FrameType frame_type = parser::GetFrameType(frame); + EndpointManager::FrameProcessor* frame_processor = + GetFrameProcessor(frame_type); + if (frame_processor == nullptr) { + NEARBY_LOG(ERROR, "Unhandled message: type=%d", frame_type); + continue; + } + + frame_processor->OnIncomingFrame(frame, endpoint_id, client, + endpoint_channel->GetMedium()); + } +} + +ExceptionOr EndpointManager::HandleKeepAlive( + EndpointChannel* endpoint_channel) { + // Check if it has been too long since we received a frame from our + // endpoint. + if ((endpoint_channel->GetLastReadTimestamp() != kInvalidTimestamp) && + ((endpoint_channel->GetLastReadTimestamp() + + EndpointManager::kKeepAliveReadTimeout) < + SystemClock::ElapsedRealtime())) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(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 write_exception = endpoint_channel->Write(parser::ForKeepAlive()); + if (!write_exception.Ok()) { + return ExceptionOr(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 sleep_exception = + SystemClock::Sleep(EndpointManager::kKeepAliveWriteInterval); + if (!sleep_exception.Ok()) { + return ExceptionOr(sleep_exception); + } + + return ExceptionOr(true); +} + +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs == &rhs; +} + +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs < &rhs; +} + +EndpointManager::EndpointManager(EndpointChannelManager* manager) + : channel_manager_(manager) {} + +EndpointManager::~EndpointManager() { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, &latch]() { + NEARBY_LOG(INFO, "Bringing down endpoints"); + for (auto& item : endpoints_) { + const std::string& endpoint_id = item.first; + EndpointState& state = item.second; + // This will close the channel; all workers will sense that and + // terminate. + NEARBY_LOG(INFO, "Bringing down endpoint channels: id=%s", + endpoint_id.c_str()); + WaitForEndpointDisconnectionProcessing(state.client, endpoint_id); + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + } + latch.CountDown(); + }); + latch.Await(); + NEARBY_LOG(INFO, "Bringing down worker threads"); + + // Stop all the ongoing Runnables (as gracefully as possible). + // Order matters: bring worker pools down first; serial_executor_ thread + // should go last, since workers schedule jobs there even during shutdown. + handlers_executor_.Shutdown(); + keep_alive_executor_.Shutdown(); + NEARBY_LOG(INFO, "Bringing down control thread"); + serial_executor_.Shutdown(); + NEARBY_LOG(INFO, "EndpointManager is down"); +} + +const EndpointManager::FrameProcessor::Handle +EndpointManager::RegisterFrameProcessor( + V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) { + const FrameProcessor::Handle handle = processor; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &latch, processor]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + // TODO(tracyzhou): Add logging. + it->second = processor; + } else { + frame_processors_.emplace(frame_type, processor); + } + latch.CountDown(); + }); + latch.Await(); + return handle; +} + +void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle) { + RunOnEndpointManagerThread([this, frame_type, handle]() { + auto it = frame_processors_.find(frame_type); + if (it == frame_processors_.end()) return; + if (it->second != handle) { + NEARBY_LOG(INFO, + "Failed to unregister: type=%d; handle mismatch: passed=%p, " + "expected=%p", + frame_type, handle, it->second); + return; + } + + frame_processors_.erase(it); + NEARBY_LOG(INFO, "unregistered: type=%d", frame_type); + }); +} + +EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor( + V1Frame::FrameType frame_type) { + EndpointManager::FrameProcessor* processor = nullptr; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &processor, &latch]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + processor = it->second; + } + latch.CountDown(); + }); + latch.Await(); + return processor; +} + +void EndpointManager::EnsureWorkersTerminated(const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + if (item != endpoints_.end()) { + // If another instance of data and keep-alive handlers is running, it will + // terminate soon; we should block until it happens. + EndpointState& endpoint_state = item->second; + NEARBY_LOG(INFO, "Waiting for workers to terminate for endpoint_id='%s'", + endpoint_id.c_str()); + endpoint_state.barrier.Await(); + endpoints_.erase(item); + } +} + +void EndpointManager::RegisterEndpoint(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& listener) { + CountDownLatch latch(1); + + // NOTE (unique_ptr<> capture): + // std::unique_ptr<> is not copyable, so we can not pass it to + // lambda capture, because lambda eventually is converted to std::function<>. + // Instead, we release() a pointer, and pass a raw pointer, which is copyalbe. + // We ignore the risk of job not scheduled (and an associated risk of memory + // leak), because this may only happen during service shutdown. + RunOnEndpointManagerThread([this, client, channel = channel.release(), + &endpoint_id, &info, &listener, &latch]() { + // Pass ownership of channel to EndpointChannelManager + NEARBY_LOG(INFO, "Registering endpoint with channel manager: id=%s", + endpoint_id.c_str()); + channel_manager_->RegisterChannelForEndpoint( + client, endpoint_id, std::unique_ptr(channel)); + + EnsureWorkersTerminated(endpoint_id); + EndpointState& endpoint_state = + endpoints_.emplace(endpoint_id, EndpointState()).first->second; + endpoint_state.client = client; + + NEARBY_LOG(INFO, "Starting workers: id=%s", endpoint_id.c_str()); + // For every endpoint, there's normally only one Read handler instance + // running on the handlers_executor_ pool. This instance reads data from the + // endpoint and delegates incoming frames to various FrameProcessors. + // Once the frame has been properly handled, it starts reading again for + // the next frame. If the handler fails its read and no other + // EndpointChannels are available for this endpoint, a disconnection + // will be initiated. + StartEndpointReader( + [this, client, endpoint_id, barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable( + "Read", client, endpoint_id, barrier, + [this, client, endpoint_id](EndpointChannel* channel) { + return HandleData(endpoint_id, client, channel); + }); + }); + + // For every endpoint, there's only one KeepAliveManager instance + // running on the keep_alive_executor_ pool. 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. + StartEndpointKeepAliveManager([this, client, endpoint_id, + barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable("KeepAliveManager", client, endpoint_id, + barrier, [this](EndpointChannel* channel) { + return HandleKeepAlive(channel); + }); + }); + // 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->OnConnectionInitiated(endpoint_id, info, listener); + latch.CountDown(); + }); + latch.Await(); +} + +void EndpointManager::UnregisterEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, client, endpoint_id, &latch]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, /*notify=*/false); + latch.CountDown(); + }); + latch.Await(); +} + +// Designed to run asynchronously. It is called from IO thread pools, and +// jobs in these pools may be waited for from the EndpointManager thread. If we +// allow synchronous behavior here it will cause a live lock. +void EndpointManager::DiscardEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + RunOnEndpointManagerThread([this, client, endpoint_id]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, + /*notify=*/ + client->IsConnectedToEndpoint(endpoint_id)); + }); +} + +std::vector EndpointManager::SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids) { + ByteArray bytes = + parser::ForDataPayloadTransfer(payload_header, payload_chunk); + + return SendTransferFrameBytes(endpoint_ids, bytes, payload_header.id(), + /*offset=*/payload_chunk.offset(), + /*packet_type=*/"DATA"); +} + +std::vector EndpointManager::SendControlMessage( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control, + const std::vector& endpoint_ids) { + ByteArray bytes = parser::ForControlPayloadTransfer(header, control); + + return SendTransferFrameBytes(endpoint_ids, bytes, header.id(), + /*offset=*/control.offset(), + /*packet_type=*/"CONTROL"); +} + +// @EndpointManagerThread +void EndpointManager::RemoveEndpoint(ClientProxy* client, + const std::string& endpoint_id, + bool notify) { + // Unregistering from channel_manager_ will also serve to terminate + // the dedicated handler and KeepAlive threads we started when we registered + // this endpoint. + if (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, endpoint_id); + EnsureWorkersTerminated(endpoint_id); + + client->OnDisconnected(endpoint_id, notify); + // TODO(tracyzhou): Add logging. + } +} + +// @EndpointManagerThread +void EndpointManager::WaitForEndpointDisconnectionProcessing( + ClientProxy* client, const std::string& endpoint_id) { + CountDownLatch barrier(frame_processors_.size()); + + for (auto& item : frame_processors_) { + auto& processor = item.second; + processor->OnEndpointDisconnect(client, endpoint_id, &barrier); + } + + barrier.Await(kProcessEndpointDisconnectionTimeout); +} + +std::vector EndpointManager::SendTransferFrameBytes( + const std::vector& endpoint_ids, const ByteArray& bytes, + std::int64_t payload_id, std::int64_t offset, + const std::string& packet_type) { + std::vector failed_endpoint_ids; + for (const std::string& endpoint_id : endpoint_ids) { + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + + if (channel == nullptr) { + // We no longer know about this endpoint (it was either explicitly + // unregistered, or a read/write error made us unregister it internally). + NEARBY_LOG(INFO, "Channel not available; id=%s", endpoint_id.c_str()); + failed_endpoint_ids.push_back(endpoint_id); + continue; + } + + Exception write_exception = channel->Write(bytes); + if (!write_exception.Ok()) { + failed_endpoint_ids.push_back(endpoint_id); + NEARBY_LOG(INFO, "Failed to send packet; endpoint_id=%s", + endpoint_id.c_str()); + continue; + } + } + + return failed_endpoint_ids; +} + +void EndpointManager::StartEndpointReader(Runnable runnable) { + handlers_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::StartEndpointKeepAliveManager(Runnable runnable) { + keep_alive_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::RunOnEndpointManagerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h new file mode 100644 index 00000000..b9ddd5b7 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -0,0 +1,218 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/listeners.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all operations related to the remote endpoints with which we are +// interacting. +// +// All processing of incoming and outgoing payloads is spread across this and +// the PayloadManager as described below. +// +// 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 Payload::Type. +// +// 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::ProcessFrame() (still running on that +// same dedicated reader thread). + +class EndpointManager { + public: + class FrameProcessor { + public: + using Handle = void*; + + virtual ~FrameProcessor() = default; + + // @EndpointManagerReaderThread + virtual void OnIncomingFrame(const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, + ClientProxy* to_client, + proto::connections::Medium current_medium) = 0; + + // Implementations must call barrier.CountDown() once + // they're done. This parallelizes the disconnection event across all frame + // processors. + // + // @EndpointManagerThread + virtual void OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier) = 0; + }; + + explicit EndpointManager(EndpointChannelManager* manager); + ~EndpointManager(); + + // Invoked from the constructors of the various *Manager components that make + // up the OfflineServiceController implementation. + // FrameProcessor* instances are of dynamic duration and survive all sessions. + // returns unique handle to be used for unregistering. + // Blocks until registration is complete. + const FrameProcessor::Handle RegisterFrameProcessor( + V1Frame::FrameType frame_type, FrameProcessor* processor); + void UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle); + + // Invoked from the different PcpHandler implementations (of which there can + // be only one at a time). + // Blocks until registration is complete. + void RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& 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(ClientProxy* client, const std::string& endpoint_id); + + // Returns the list of endpoints to which sending this chunk failed. + // + // Invoked from the PayloadManager's sendPayload() method. + std::vector SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids); + std::vector SendControlMessage( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::ControlMessage& control_message, + const std::vector& endpoint_ids); + + // 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 FrameProcessor to + // processEndpointDisconnection() while the caller of DiscardEndpoint() is + // blocked here. + void DiscardEndpoint(ClientProxy* client, const std::string& endpoint_id); + + private: + struct EndpointState { + // ClientProxy object associated with this endpoint. + ClientProxy* client; + // Execution barrier, used to ensure that all workers associated with an + // endpoint on handlers_executor_ and keep_alive_executor_ are terminated. + CountDownLatch barrier{2}; + }; + + FrameProcessor* GetFrameProcessor(V1Frame::FrameType frame_type); + + ExceptionOr HandleData(const std::string& endpoint_id, + ClientProxy* client_proxy, + EndpointChannel* endpoint_channel); + + ExceptionOr HandleKeepAlive(EndpointChannel* endpoint_channel); + + // Waits for a given endpoint EndpointChannelLoopRunnable() workers to + // terminate. + // Is called from RegisterEndpoint to avoid races; also called from + // RemoveEndpoint as part of proper endpoint shutdown sequence. + // @EndpointManagerThread + void EnsureWorkersTerminated(const std::string& endpoint_id); + + void EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client_proxy, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler); + + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch); + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch, std::int32_t timeout_millis); + + static constexpr absl::Duration kKeepAliveWriteInterval = + absl::Milliseconds(5000); + static constexpr absl::Duration kKeepAliveReadTimeout = + absl::Milliseconds(30000); + static constexpr absl::Duration kProcessEndpointDisconnectionTimeout = + absl::Milliseconds(2000); + static constexpr std::int32_t kMaxConcurrentEndpoints = 50; + static constexpr absl::Time kInvalidTimestamp = absl::InfinitePast(); + + // 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. + // @EndpointManagerThread + void RemoveEndpoint(ClientProxy* client, const std::string& endpoint_id, + bool notify); + + void WaitForEndpointDisconnectionProcessing(ClientProxy* client, + const std::string& endpoint_id); + + std::vector SendTransferFrameBytes( + const std::vector& endpoint_ids, + const ByteArray& payload_transfer_frame_bytes, std::int64_t payload_id, + std::int64_t offset, const std::string& packet_type); + + // Executes data-handing jobs on a separate thread for each endpoint, on a + // handlers_executor_. + // If amount of concurrent connections is less the pool capacity, it is + // possible that while a channel is being replaced, two jobs are trying to + // run for the same endpoint (for a short time). + // TODO (apolyudov): do not let extra job start. + void StartEndpointReader(Runnable runnable); + + // Executes keep-alive jobs on a separate thread for each endpoint on a + // keep_alive_executor_. + void StartEndpointKeepAliveManager(Runnable runnable); + + // Executes all jobs sequentially, on a serial_executor_. + void RunOnEndpointManagerThread(Runnable runnable); + + EndpointChannelManager* channel_manager_; + + absl::flat_hash_map + frame_processors_; + + // We keep track of all registered channel endpoints here. + absl::flat_hash_map endpoints_; + + MultiThreadExecutor keep_alive_executor_{kMaxConcurrentEndpoints}; + MultiThreadExecutor handlers_executor_{kMaxConcurrentEndpoints}; + SingleThreadExecutor serial_executor_; +}; + +// Operator overloads when comparing FrameProcessor*. +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_manager_test.cc b/cpp/core_v2/internal/endpoint_manager_test.cc new file mode 100644 index 00000000..23c816c5 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager_test.cc @@ -0,0 +1,242 @@ +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/pipe.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; +using ::securegcm::D2DConnectionContextV1; +using ::testing::_; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public EndpointChannel { + public: + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, Close, (), (override)); + MOCK_METHOD(void, Close, (DisconnectionReason reason), (override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, EnableEncryption, + (D2DConnectionContextV1 * connection_context), + (override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); + + bool IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; + } + void DoClose() { + absl::MutexLock lock(&mutex_); + closed_ = true; + } + + private: + mutable absl::Mutex mutex_; + bool closed_ = false; +}; + +class MockFrameProcessor : public EndpointManager::FrameProcessor { + public: + MOCK_METHOD(void, OnIncomingFrame, + (const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, ClientProxy* to_client, + Medium current_medium), + (override)); + + MOCK_METHOD(void, OnEndpointDisconnect, + (ClientProxy * client, const std::string& endpoint_id, + CountDownLatch* barrier), + (override)); +}; + +class EndpointManagerTest : public ::testing::Test { + protected: + void RegisterEndpoint(std::unique_ptr channel, + bool should_close = true) { + CountDownLatch done(1); + if (should_close) { + ON_CALL(*channel, Close(_)) + .WillByDefault( + [&done](DisconnectionReason reason) { done.CountDown(); }); + } + EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel, GetLastReadTimestamp()) + .WillRepeatedly(Return(start_time_)); + EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1); + em_.RegisterEndpoint(&client_, endpoint_id_, info_, std::move(channel), + listener_); + if (should_close) { + EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); + } + } + + ClientProxy client_; + std::vector> processors_; + EndpointChannelManager ecm_; + EndpointManager em_{&ecm_}; + std::string endpoint_id_ = "endpoint_id"; + ConnectionResponseInfo info_ = { + .remote_endpoint_name = "name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + } mock_listener_; + ConnectionListener listener_{ + .initiated_cb = mock_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = mock_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + absl::Time start_time_{absl::Now()}; +}; + +TEST_F(EndpointManagerTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(EndpointManagerTest, RegisterEndpointCallsOnConnectionInitiated) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Close(_)).Times(1); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + RegisterEndpoint(std::make_unique()); + // NOTE: disconnect_cb is not called, because we did not reach fully connected + // state. On top of that, UnregisterEndpoint is suppressing this notification. + // (IMO, it should be called as long as any connection callback was called + // before. (in this case initiated_cb is called)). + // Test captures current protocol behavior. + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + auto connect_request = std::make_unique(); + auto read_data = parser::ForConnectionRequest("endpoint_id", "endpoint_name", + 1234, std::vector{Medium::BLE}); + EXPECT_CALL(*connect_request, OnIncomingFrame); + EXPECT_CALL(*connect_request, OnEndpointDisconnect); + EXPECT_CALL(*endpoint_channel, Read()) + .WillOnce(Return(ExceptionOr(read_data))) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + // Register frame processor, then register endpoint. + // Endpoint will read one frame, then fail to read more and terminate. + // On disconnection, it will notify frame processor and we verify that. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + // We should not receive any notifications to frame processor. + auto connect_request = std::make_unique>(); + + // Register frame processor and immediately unregister it. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + em_.UnregisterFrameProcessor(V1Frame::CONNECTION_REQUEST, handle); + // Endpoint will not send OnDisconnect notification to frame processor. + RegisterEndpoint(std::move(endpoint_channel), false); + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, SendControlMessageWorks) { + auto endpoint_channel = std::make_unique(); + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_offset(150); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + + ON_CALL(*endpoint_channel, Read()) + .WillByDefault([channel = endpoint_channel.get()]() { + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + NEARBY_LOG(INFO, "Simulate read delay: wait"); + absl::SleepFor(absl::Milliseconds(100)); + NEARBY_LOG(INFO, "Simulate read delay: done"); + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + return ExceptionOr(ByteArray{}); + }); + ON_CALL(*endpoint_channel, Close(_)) + .WillByDefault( + [channel = endpoint_channel.get()](DisconnectionReason reason) { + channel->DoClose(); + NEARBY_LOG(INFO, "Channel closed"); + }); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + RegisterEndpoint(std::move(endpoint_channel), false); + auto failed_ids = + em_.SendControlMessage(header, control, std::vector{endpoint_id_}); + EXPECT_EQ(failed_ids, std::vector{}); + NEARBY_LOG(INFO, "Will unregister endpoint now"); + em_.UnregisterEndpoint(&client_, endpoint_id_); + NEARBY_LOG(INFO, "Will call destructors now"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD new file mode 100644 index 00000000..5a33fe85 --- /dev/null +++ b/cpp/core_v2/internal/mediums/BUILD @@ -0,0 +1,70 @@ +cc_library( + name = "mediums", + srcs = [ + "advertisement_read_result.cc", + "ble_advertisement.cc", + "ble_advertisement_header.cc", + "ble_packet.cc", + "bluetooth_radio.cc", + "uuid.cc", + ], + hdrs = [ + "advertisement_read_result.h", + "ble_advertisement.h", + "ble_advertisement_header.h", + "ble_packet.h", + "ble_peripheral.h", + "bluetooth_radio.h", + "lost_entity_tracker.h", + "uuid.h", + ], + visibility = [ + "//core_v2/internal:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "utils", + srcs = ["utils.cc"], + hdrs = ["utils.h"], + visibility = [ + "//core_v2/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + ], +) + +cc_test( + name = "core_v2_internal_mediums_test", + srcs = [ + "advertisement_read_result_test.cc", + "ble_advertisement_header_test.cc", + "ble_advertisement_test.cc", + "ble_packet_test.cc", + "ble_peripheral_test.cc", + "bluetooth_radio_test.cc", + "lost_entity_tracker_test.cc", + "uuid_test.cc", + ], + shard_count = 16, + deps = [ + ":mediums", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.cc b/cpp/core_v2/internal/mediums/advertisement_read_result.cc new file mode 100644 index 00000000..fbd97e34 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.cc @@ -0,0 +1,125 @@ +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include +#include + +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{ + .backoff_multiplier = 2.0, + .base_backoff_duration = absl::Seconds(1), + .max_backoff_duration = absl::Minutes(5), +}; + +// Adds a successfully read advertisement for the specified slot to this read +// result. This is fundamentally different from RecordLastReadStatus() because +// we can report a read failure, but still manage to read some advertisements. +void AdvertisementReadResult::AddAdvertisement(std::int32_t slot, + const ByteArray& advertisement) { + MutexLock lock(&mutex_); + + // Blindly remove from the advertisements map to make sure any existing + // key-value pair is destroyed. + advertisements_.emplace(slot, advertisement); +} + +// Determines whether or not an advertisement was successfully read at the +// specified slot. +bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const { + MutexLock lock(&mutex_); + + return advertisements_.contains(slot); +} + +// Retrieves all raw advertisements that were successfully read. +std::vector AdvertisementReadResult::GetAdvertisements() + const { + MutexLock lock(&mutex_); + + std::vector all_advertisements; + all_advertisements.reserve(advertisements_.size()); + for (const auto& item : advertisements_) { + all_advertisements.emplace_back(&item.second); + } + + return all_advertisements; +} + +// Determines what stage we're in for retrying a read from an advertisement +// GATT server. +AdvertisementReadResult::RetryStatus +AdvertisementReadResult::EvaluateRetryStatus() const { + MutexLock lock(&mutex_); + + // Check if we have already succeeded reading this advertisement. + if (status_ == Status::kSuccess) { + return RetryStatus::kPreviouslySucceeded; + } + + // Check if we have recently failed to read this advertisement. + if (GetDurationSinceReadLocked() < backoff_duration_) { + return RetryStatus::kTooSoon; + } + + return RetryStatus::kRetry; +} + +// Records the status of the latest read, and updates the next backoff +// duration for subsequent reads. Be sure to also call +// AddAdvertisement() if any advertisements were read. +void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { + MutexLock lock(&mutex_); + + // Update the last read timestamp. + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + + // Update the backoff duration. + if (is_success) { + // Reset the backoff duration now that we had a successful read. + backoff_duration_ = config_.base_backoff_duration; + } else { + // Determine whether or not we were already failing before. If we were, we + // should increase the backoff duration. + if (status_ == Status::kFailure) { + // Use exponential backoff to determine the next backoff duration. This + // simply involves multiplying our current backoff duration by some + // multiplier. + absl::Duration next_backoff_duration = + config_.backoff_multiplier * backoff_duration_; + // Update the backoff duration, making sure not to blow past the + // ceiling. + backoff_duration_ = + std::min(next_backoff_duration, config_.max_backoff_duration); + } else { + // This is our first time failing, so we should only backoff for the + // initial duration. + backoff_duration_ = config_.base_backoff_duration; + } + } + + // Update the internal result. + status_ = is_success ? Status::kSuccess : Status::kFailure; +} + +// Returns how much time has passed since we last tried reading from an +// advertisement GATT server. +absl::Duration AdvertisementReadResult::GetDurationSinceRead() const { + MutexLock lock(&mutex_); + return GetDurationSinceReadLocked(); +} + +absl::Duration AdvertisementReadResult::GetDurationSinceReadLocked() const { + return SystemClock::ElapsedRealtime() - last_read_timestamp_; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.h b/cpp/core_v2/internal/mediums/advertisement_read_result.h new file mode 100644 index 00000000..c4d2c566 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.h @@ -0,0 +1,90 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.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. +class AdvertisementReadResult { + public: + // 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). + + struct Config { + // How much to multiply the backoff duration by with every failure to read + // from the advertisement GATT server. This should never be below 1! + float backoff_multiplier; + // The initial backoff duration when we fail to read from an advertisement + // GATT server. + absl::Duration base_backoff_duration; + // The maximum backoff duration allowed between advertisement GATT server + // reads. + absl::Duration max_backoff_duration; + }; + + static const Config kDefaultConfig; + explicit AdvertisementReadResult(const Config& config = kDefaultConfig) + : config_(config) {} + ~AdvertisementReadResult() = default; + + enum class RetryStatus { + kUnknown = 0, + kRetry = 1, + kPreviouslySucceeded = 2, + kTooSoon = 3, + }; + + void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement) + ABSL_LOCKS_EXCLUDED(mutex_); + bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetAdvertisements() const + ABSL_LOCKS_EXCLUDED(mutex_); + RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_); + void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_); + absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + enum class Status { + kUnknown = 0, + kSuccess = 1, + kFailure = 2, + }; + + absl::Duration GetDurationSinceReadLocked() const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + + // Maps slot numbers to the GATT advertisement found in that slot. + absl::flat_hash_map advertisements_ + ABSL_GUARDED_BY(mutex_); + + Config config_; + absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_); + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_); + Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc new file mode 100644 index 00000000..0d822274 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc @@ -0,0 +1,129 @@ +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +constexpr char kAdvertisementBytes[] = "\x0A\x0B\x0C"; + +// Default values may be too big and impractical to wait for in the test. +// For the test platform, we redefine them to some reasonable values. +const absl::Duration kAdvertisementBaseBackoffDuration = absl::Seconds(1); +const absl::Duration kAdvertisementMaxBackoffDuration = absl::Seconds(6); + +const AdvertisementReadResult::Config test_config{ + .backoff_multiplier = + AdvertisementReadResult::kDefaultConfig.backoff_multiplier, + .base_backoff_duration = kAdvertisementBaseBackoffDuration, + .max_backoff_duration = kAdvertisementMaxBackoffDuration, +}; + +TEST(AdvertisementReadResultTest, AdvertisementExists) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + advertisement_read_result.AddAdvertisement(slot, + ByteArray(kAdvertisementBytes)); + + EXPECT_TRUE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + + EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { + AdvertisementReadResult advertisement_read_result(test_config); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kPreviouslySucceeded); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep for some time, but not long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration / 2); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { + AdvertisementReadResult advertisement_read_result(test_config); + 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); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { + AdvertisementReadResult advertisement_read_result(test_config); + 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); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, GetDurationSinceRead) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + absl::Duration sleepTime = absl::Milliseconds(420); + absl::SleepFor(sleepTime); + + EXPECT_GE(advertisement_read_result.GetDurationSinceRead(), sleepTime); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_advertisement.cc new file mode 100644 index 00000000..027a3a92 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.cc @@ -0,0 +1,201 @@ +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisement::BleAdvertisement(Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, + const ByteArray &data) { + // Check that the given input is valid. + if (!IsSupportedVersion(version) || + !IsSupportedSocketVersion(socket_version) || + service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + + version_ = version; + socket_version_ = socket_version; + service_id_hash_ = service_id_hash; + data_ = data; +} + +BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Now, time to read the bytes! + const auto *read_ptr = ble_advertisement_bytes.data(); + + // 1. Version. + version_ = static_cast((*read_ptr & kVersionBitmask) >> 5); + if (!IsSupportedVersion(version_)) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: unsupported Version %u", + version_); + return; + } + + // 2. Socket Version. + socket_version_ = + static_cast((*read_ptr & kSocketVersionBitmask) >> 2); + if (!IsSupportedSocketVersion(socket_version_)) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u", + socket_version_); + version_ = Version::kUndefined; + return; + } + read_ptr += kVersionLength; + + // 3. Service ID hash. + service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength); + read_ptr += kServiceIdHashLength; + + // 4.1. Data size. + size_t expected_data_size = DeserializeDataSize(read_ptr); + if (expected_data_size < 0) { + NEARBY_LOG( + INFO, + "Cannot deserialize BleAdvertisement: negative data size %" PRIu64, + expected_data_size); + version_ = Version::kUndefined; + return; + } + read_ptr += kDataSizeLength; + + // Check that the stated data size is the same as what we received. + size_t actual_data_size = ComputeDataSize(ble_advertisement_bytes); + if (actual_data_size < expected_data_size) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: expected data to be %zu " + "bytes, got %" PRIu64 " bytes", + expected_data_size, actual_data_size); + version_ = Version::kUndefined; + return; + } + + // 4.2. Data. + data_ = ByteArray(read_ptr, expected_data_size); + read_ptr += expected_data_size; +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray{}; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_socket_version_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 3 bits are the Socket version. 2 bits left are reserved. + version_and_socket_version_byte |= + (static_cast(socket_version_) << 2) & kSocketVersionBitmask; + // Serialize Data size bytes(4). + ByteArray data_size_bytes{kDataSizeLength}; + auto *data_size_bytes_write_ptr = data_size_bytes.data(); + SerializeDataSize(data_size_bytes_write_ptr, data_.size()); + + out.reserve(1 + service_id_hash_.size() + 1 + data_.size()); + out.append(1, version_and_socket_version_byte); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_size_bytes)); + out.append(std::string(data_)); + + return ByteArray{std::move(out)}; +} + +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(); +} + +bool BleAdvertisement::IsSupportedVersion(Version version) const { + return version >= Version::kV1 && version <= Version::kV2; +} + +bool BleAdvertisement::IsSupportedSocketVersion( + SocketVersion socket_version) const { + return socket_version >= SocketVersion::kV1 && + socket_version <= SocketVersion::kV2; +} + +void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const { + // Get a raw representation of the data size bytes in memory. + char *data_size_bytes = reinterpret_cast(&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]; + } +} + +size_t BleAdvertisement::DeserializeDataSize( + const char *data_size_bytes_read_ptr) const { + // Allocate a chunk of memory to store our deserialized size. + char data_size_bytes[kDataSizeLength]; + + // Assign the bits of our size from the given raw bytes, keeping in mind that + // we need to convert from Big Endian to Little Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1]; + } + + // Interpret the char array as a single int. + return static_cast( + *(reinterpret_cast(&data_size_bytes))); +} + +size_t BleAdvertisement::ComputeDataSize( + const ByteArray &ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +size_t BleAdvertisement::ComputeAdvertisementLength( + const ByteArray &data) const { + // The advertisement length is the minimum length + the length of the data. + return kMinAdvertisementLength + data.size(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_advertisement.h new file mode 100644 index 00000000..557b93b8 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.h @@ -0,0 +1,100 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ + +#include + +#include "platform_v2/base/byte_array.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. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // Version is only allocated 3 bits in the BleAdvertisement, so this can + // never go beyond V7. + }; + + // Versions of the BLESocket. + enum class SocketVersion { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // SocketVersion is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + + BleAdvertisement() = default; + BleAdvertisement(Version version, SocketVersion socket_version, + const ByteArray &service_id_hash, const ByteArray &data); + explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes); + BleAdvertisement(const BleAdvertisement &) = default; + BleAdvertisement &operator=(const BleAdvertisement &) = default; + BleAdvertisement(BleAdvertisement &&) = default; + BleAdvertisement &operator=(BleAdvertisement &&) = default; + ~BleAdvertisement() = default; + + explicit operator ByteArray() const; + // Operator overloads when comparing BleAdvertisement. + bool operator==(const BleAdvertisement &rhs) const; + bool operator<(const BleAdvertisement &rhs) const; + + bool IsValid() const { return IsSupportedVersion(version_); } + Version GetVersion() const { return version_; } + SocketVersion GetSocketVersion() const { return socket_version_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray &GetData() & { return data_; } + const ByteArray &GetData() const & { return data_; } + ByteArray &&GetData() && { return std::move(data_); } + const ByteArray &&GetData() const && { return std::move(data_); } + + private: + bool IsSupportedVersion(Version version) const; + bool IsSupportedSocketVersion(SocketVersion socket_version) const; + void SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const; + size_t DeserializeDataSize(const char *data_size_bytes_read_ptr) const; + size_t ComputeDataSize(const ByteArray &ble_advertisement_bytes) const; + size_t ComputeAdvertisementLength(const ByteArray &data) const; + + static constexpr int kVersionLength = 1; + // Length of one int. Be sure to re-evaluate how we compute data size in this + // class if this constant ever changes! + static constexpr int kDataSizeLength = 4; + static constexpr int 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. + static constexpr int kMaxGattCharacteristicValueSize = 512; + static constexpr int kMaxDataSize = + kMaxGattCharacteristicValueSize - kMinAdvertisementLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kSocketVersionBitmask = 0x01C; + + Version version_{Version::kUndefined}; + SocketVersion socket_version_{SocketVersion::kUndefined}; + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc new file mode 100644 index 00000000..e8910194 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc @@ -0,0 +1,118 @@ +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisementHeader::BleAdvertisementHeader( + Version version, int num_slots, const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash) { + // TODO(edwinwu): Checks if num_slots needs to be >= 0 + if (version != Version::kV2 || + service_id_bloom_filter.size() != kServiceIdBloomFilterLength || + advertisement_hash.size() != kAdvertisementHashLength) { + return; + } + + version_ = version; + num_slots_ = num_slots; + service_id_bloom_filter_ = service_id_bloom_filter; + advertisement_hash_ = advertisement_hash; +} + +BleAdvertisementHeader::BleAdvertisementHeader( + const std::string &ble_advertisement_header_string) { + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + + if (ble_advertisement_header_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding"); + return; + } + + if (ble_advertisement_header_bytes.size() < kMinAdvertisementHeaderLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisementHeader: expecting min %u " + "raw bytes, got %" PRIu64 " instead", + kMinAdvertisementHeaderLength, + ble_advertisement_header_bytes.size()); + return; + } + + // Start reading the bytes. + auto *ble_advertisement_header_read_ptr = + ble_advertisement_header_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_header_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV2) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisementHeader: unsupported Version %d", + version_); + return; + } + // The last 5 bits of the first byte represent the number of slots. + num_slots_ = static_cast(*ble_advertisement_header_read_ptr & + kNumSlotsBitmask); + ble_advertisement_header_read_ptr++; + + // Service ID bloom filter. + service_id_bloom_filter_ = + ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength); + ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength; + + // Advertisement hash. + advertisement_hash_ = + ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength); + ble_advertisement_header_read_ptr += kAdvertisementHashLength; +} + +BleAdvertisementHeader::operator std::string() const { + if (!IsValid()) { + return ""; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_num_slots_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the number of slots. + version_and_num_slots_byte |= + static_cast(num_slots_) & kNumSlotsBitmask; + out.reserve(1 + service_id_bloom_filter_.size() + advertisement_hash_.size()); + out.append(1, version_and_num_slots_byte); + out.append(std::string(service_id_bloom_filter_)); + out.append(std::string(advertisement_hash_)); + + return Base64Utils::Encode(ByteArray(std::move(out))); +} + +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 diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.h b/cpp/core_v2/internal/mediums/ble_advertisement_header.h new file mode 100644 index 00000000..aa8163df --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.h @@ -0,0 +1,84 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ + +#include + +#include "platform_v2/base/byte_array.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. +// +// Note. The object constructed by default constructor or the parameterized +// constructor with invalid value(s) is treated as invalid instance. Caller +// should be responsible to call IsValid() to check the instance is invalid in +// advance before continue on. +class BleAdvertisementHeader { + public: + // Versions of the BleAdvertisementHeader. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 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. + }; + + BleAdvertisementHeader() = default; + BleAdvertisementHeader(Version version, int num_slots, + const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash); + explicit BleAdvertisementHeader( + const std::string &ble_advertisement_header_string); + ~BleAdvertisementHeader() = default; + + BleAdvertisementHeader(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader(BleAdvertisementHeader &&) = default; + BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default; + + // Produces an encoded binary string which can be decoded by the explicit + // constructor. The returned string is empty if BleAdvertisementHeader is not + // valid - false on IsValid(). + explicit operator std::string() const; + bool operator<(const BleAdvertisementHeader &rhs) const; + + bool IsValid() const { return version_ == Version::kV2; } + Version GetVersion() const { return version_; } + int GetNumSlots() const { return num_slots_; } + ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; } + ByteArray GetAdvertisementHash() const { return advertisement_hash_; } + + private: + static constexpr int kServiceIdBloomFilterLength = 10; + static constexpr int kAdvertisementHashLength = 4; + static constexpr int kMinAdvertisementHeaderLength = + 1 + kServiceIdBloomFilterLength + kAdvertisementHashLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kNumSlotsBitmask = 0x01F; + + Version version_ = Version::kUndefined; + int num_slots_; + ByteArray service_id_bloom_filter_; + ByteArray advertisement_hash_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc new file mode 100644 index 00000000..30bfe536 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc @@ -0,0 +1,176 @@ +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { +constexpr BleAdvertisementHeader::Version kVersion = + BleAdvertisementHeader::Version::kV2; +constexpr int kNumSlots = 2; +constexpr char kServiceIDBloomFilter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"; +constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d"; + +TEST(BleAdvertisementHeaderTest, ConstructionWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithShortServiceIdBloomFilter) { + char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09"; + + ByteArray short_service_id_bloom_filter_bytes(short_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, short_service_id_bloom_filter_bytes, + advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithLongServiceIdBloomFilter) { + char long_service_id_bloom_filter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"; + + ByteArray service_id_bloom_filter(long_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { + char short_advertisement_hash[] = "\x0a\x0b\x0c"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(short_advertisement_hash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { + char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\0x0e"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(long_advertisement_hash, + sizeof(long_advertisement_hash) / sizeof(char)); + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader org_ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = + std::string(org_ble_advertisement_header); + + auto ble_advertisement_header = + BleAdvertisementHeader(ble_advertisement_header_string); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, add a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray long_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() + 1); + long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes); + std::string long_ble_advertisement_header_string = + Base64Utils::Encode(long_ble_advertisement_header_bytes); + + auto long_ble_advertisement_header = + BleAdvertisementHeader(long_ble_advertisement_header_string); + + EXPECT_TRUE(long_ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, long_ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + long_ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + long_ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, remove a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray short_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() - 1); + short_ble_advertisement_header_bytes.CopyAt(0, + ble_advertisement_header_bytes); + std::string short_ble_advertisement_header_string = + Base64Utils::Encode(short_ble_advertisement_header_bytes); + + auto short_ble_advertisement_header = + BleAdvertisementHeader(short_ble_advertisement_header_string); + + EXPECT_FALSE(short_ble_advertisement_header.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc new file mode 100644 index 00000000..cefb7f7a --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc @@ -0,0 +1,223 @@ +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; +const BleAdvertisement::SocketVersion kSocketVersion = + BleAdvertisement::SocketVersion::kV2; +const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; +const char kData[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +// 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, ConstructionWorksV1) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, + BleAdvertisement::SocketVersion::kV1, + service_id_hash, data}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); + EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, + ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + BleAdvertisement::Version bad_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{bad_version, kSocketVersion, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { + BleAdvertisement::SocketVersion bad_socket_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, bad_socket_version, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = "\x0a\x0b"; + + ByteArray bad_service_id_hash{short_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; + + ByteArray bad_service_id_hash{long_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { + // 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]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray bad_data{long_data, 512}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash, + bad_data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { + char empty_data[0]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{empty_data}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // 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, org_ble_advertisement_bytes.data(), + std::min(sizeof(raw_ble_advertisement_bytes), + org_ble_advertisement_bytes.size())); + + // Re-parse the Ble advertisement using our extra long advertisement bytes. + ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kLongAdvertisementLength}; + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + + EXPECT_TRUE(long_ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size()); + EXPECT_EQ(data, long_ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + BleAdvertisement ble_advertisement{ByteArray{}}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Cut off the advertisement so that it's too short. + ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), + 7}; + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithInvalidDataLengthFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // 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, org_ble_advertisement_bytes.data(), + 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. + ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kAdvertisementLength}; + BleAdvertisement corrupted_ble_advertisement{ + corrupted_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.cc b/cpp/core_v2/internal/mediums/ble_packet.cc new file mode 100644 index 00000000..0cfb14ff --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.cc @@ -0,0 +1,59 @@ +#include "core_v2/internal/mediums/ble_packet.h" + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) { + if (service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + service_id_hash_ = service_id_hash; + data_ = data; +} + +BlePacket::BlePacket(const ByteArray& ble_packet_bytes) { + if (ble_packet_bytes.Empty()) { + NEARBY_LOG(ERROR, "Cannot deserialize BlePacket: null bytes passed in"); + return; + } + + if (ble_packet_bytes.size() < kServiceIdHashLength) { + NEARBY_LOG( + INFO, + "Cannot deserialize BlePacket: expecting min %u raw bytes, got %zu", + kServiceIdHashLength, ble_packet_bytes.size()); + return; + } + + const char *ble_packet_bytes_read_ptr = ble_packet_bytes.data(); + service_id_hash_ = + ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength); + ble_packet_bytes_read_ptr += kServiceIdHashLength; + + data_ = ByteArray(ble_packet_bytes_read_ptr, + ble_packet_bytes.size() - kServiceIdHashLength); +} + +BlePacket::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + out.reserve(service_id_hash_.size() + data_.size()); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_)); + + return ByteArray(std::move(out)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.h b/cpp/core_v2/internal/mediums/ble_packet.h new file mode 100644 index 00000000..159f6349 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.h @@ -0,0 +1,51 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ + +#include + +#include "platform_v2/base/byte_array.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 const std::uint32_t kServiceIdHashLength = 3; + + BlePacket() = default; + BlePacket(const ByteArray& service_id_hash, const ByteArray& data); + explicit BlePacket(const ByteArray& ble_packet_byte); + ~BlePacket() = default; + + BlePacket(const BlePacket&) = default; + BlePacket& operator=(const BlePacket&) = default; + BlePacket(BlePacket&&) = default; + BlePacket& operator=(BlePacket&&) = default; + + explicit operator ByteArray() const; + + bool IsValid() const { return !service_id_hash_.Empty(); } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray GetData() const { return data_; } + + private: + static const std::uint32_t kMaxDataSize = + std::numeric_limits::max() - kServiceIdHashLength; + + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_packet_test.cc new file mode 100644 index 00000000..b9a1c858 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet_test.cc @@ -0,0 +1,97 @@ +#include "core_v2/internal/mediums/ble_packet.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +constexpr char kServiceIDHash[] = "\x0a\x0b\x0c"; +constexpr char kData[] = "\x01\x02\x03\x04\x05"; + +TEST(BlePacketTest, ConstructionWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionWorksWithEmptyData) { + char empty_data[] = {}; + + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(empty_data); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash[] = "\x0a\x0b"; + + ByteArray service_id_hash(short_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash[] = "\x0a\x0b\x0c\x0d"; + + ByteArray service_id_hash(long_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray ble_packet_bytes(org_ble_packet); + + BlePacket ble_packet(ble_packet_bytes); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFromNullBytesFails) { + BlePacket ble_packet(ByteArray{}); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromShortLengthDataFails) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray org_ble_packet_bytes(org_ble_packet); + + // Cut off the packet so that it's too short + ByteArray short_ble_packet_bytes(ByteArray(org_ble_packet_bytes.data(), 2)); + + BlePacket short_ble_packet(short_ble_packet_bytes); + + EXPECT_FALSE(short_ble_packet.IsValid()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_peripheral.h b/cpp/core_v2/internal/mediums/ble_peripheral.h new file mode 100644 index 00000000..01d0b594 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral.h @@ -0,0 +1,36 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class BlePeripheral { + public: + BlePeripheral() = default; + explicit BlePeripheral(const ByteArray& id) : id_(id) {} + ~BlePeripheral() = default; + + BlePeripheral(const BlePeripheral&) = default; + BlePeripheral& operator=(const BlePeripheral&) = default; + BlePeripheral(BlePeripheral&&) = default; + BlePeripheral& operator=(BlePeripheral&&) = default; + + bool IsValid() const { return !id_.Empty(); } + ByteArray GetId() const { return id_; } + + private: + // A unique identifier for this peripheral. It can be the BLE advertisement it + // was found on, or even simply the BLE MAC address. + ByteArray id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc new file mode 100644 index 00000000..d43c375a --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc @@ -0,0 +1,33 @@ +#include "core_v2/internal/mediums/ble_peripheral.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const char kId[] = "AB12"; + +TEST(BlePeripheralTest, ConstructionWorks) { + ByteArray id(kId); + + BlePeripheral ble_peripheral(id); + + EXPECT_TRUE(ble_peripheral.IsValid()); + EXPECT_EQ(id, ble_peripheral.GetId()); +} + +TEST(BlePeripheralTest, ConstructionEmptyFails) { + BlePeripheral ble_peripheral; + + EXPECT_FALSE(ble_peripheral.IsValid()); + EXPECT_TRUE(ble_peripheral.GetId().Empty()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.cc b/cpp/core_v2/internal/mediums/bluetooth_radio.cc new file mode 100644 index 00000000..77a7ec00 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.cc @@ -0,0 +1,104 @@ +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "platform_v2/base/exception.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothRadio::BluetoothRadio() { + if (!IsAdapterValid()) { + NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported"); + } +} + +BluetoothRadio::~BluetoothRadio() { + // We never enabled Bluetooth, nothing to do. + if (!ever_saved_state_.Get()) { + NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW."); + return; + } + + // 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. + NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter."); + Toggle(); + + NEARBY_LOG(INFO, "Bring BT adapter to original state"); + if (!SetBluetoothState(originally_enabled_.Get())) { + NEARBY_LOG(INFO, "Failed to restore BT adapter original state."); + } +} + +bool BluetoothRadio::Enable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(true); +} + +bool BluetoothRadio::Disable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(false); +} + +bool BluetoothRadio::IsEnabled() const { + return IsAdapterValid() && IsInDesiredState(true); +} + +bool BluetoothRadio::Toggle() { + if (!SaveOriginalState()) { + return false; + } + + if (!SetBluetoothState(false)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off."); + return false; + } + + if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) { + NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on."); + return false; + } + + if (!SetBluetoothState(true)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on."); + return false; + } + + return true; +} + +bool BluetoothRadio::SetBluetoothState(bool enable) { + return bluetooth_adapter_.SetStatus( + enable ? BluetoothAdapter::Status::kEnabled + : BluetoothAdapter::Status::kDisabled); +} + +bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const { + return bluetooth_adapter_.IsEnabled() == should_be_enabled; +} + +bool BluetoothRadio::SaveOriginalState() { + if (!IsAdapterValid()) { + return false; + } + + // If we haven't saved the original state of the radio, save it. + if (!ever_saved_state_.Set(true)) { + originally_enabled_.Set(bluetooth_adapter_.IsEnabled()); + } + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.h b/cpp/core_v2/internal/mediums/bluetooth_radio.h new file mode 100644 index 00000000..ebec1881 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.h @@ -0,0 +1,80 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ + +#include + +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +// Provides the operations that can be performed on the Bluetooth radio. +class BluetoothRadio { + public: + BluetoothRadio(); + BluetoothRadio(BluetoothRadio&&) = default; + BluetoothRadio& operator=(BluetoothRadio&&) = default; + + // 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() const; + + // Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On. + // This will block calling thread for at least kPauseBetweenToggle duration. + bool Toggle(); + + // Returns result of BluetoothAdapter::IsValid() for private adapter instance. + bool IsAdapterValid() const { + return bluetooth_adapter_.IsValid(); + } + + BluetoothAdapter& GetBluetoothAdapter() { + return bluetooth_adapter_; + } + + private: + static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3); + + 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(); + + // BluetoothAdapter::IsValid() will return false if BT is not supported. + BluetoothAdapter bluetooth_adapter_; + + // The Bluetooth radio's original state, before we modified it. True if + // originally enabled, false if originally disabled. + // We restore the radio to its original state in the destructor. + + AtomicBoolean originally_enabled_{false}; + // false if we never modified the radio state, true otherwise. + AtomicBoolean ever_saved_state_{false}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc new file mode 100644 index 00000000..f02d19de --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc @@ -0,0 +1,45 @@ +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(BluetoothRadioTest, ConstructorDestructorWorks) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); +} + +TEST(BluetoothRadioTest, CanEnable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanDisable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); + EXPECT_TRUE(radio.Disable()); + EXPECT_FALSE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanToggle) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Toggle()); + EXPECT_TRUE(radio.IsEnabled()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker.h b/cpp/core_v2/internal/mediums/lost_entity_tracker.h new file mode 100644 index 00000000..e83b21f1 --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker.h @@ -0,0 +1,80 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ + +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.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 +class LostEntityTracker { + public: + using EntitySet = absl::flat_hash_set; + + LostEntityTracker(); + ~LostEntityTracker(); + + // Records the given entity as being recently found, whether or not this is + // our first time discovering the entity. + void RecordFoundEntity(const Entity& entity) ABSL_LOCKS_EXCLUDED(mutex_); + + // Computes and returns the set of entities considered lost since the last + // time this method was called. + EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Mutex mutex_; + EntitySet current_entities_ ABSL_GUARDED_BY(mutex_); + EntitySet previously_found_entities_ ABSL_GUARDED_BY(mutex_); +}; + +template +LostEntityTracker::LostEntityTracker() + : current_entities_{}, previously_found_entities_{} {} + +template +LostEntityTracker::~LostEntityTracker() { + previously_found_entities_.clear(); + current_entities_.clear(); +} + +template +void LostEntityTracker::RecordFoundEntity(const Entity& entity) { + MutexLock lock(&mutex_); + + current_entities_.insert(entity); +} + +template +typename LostEntityTracker::EntitySet +LostEntityTracker::ComputeLostEntities() { + MutexLock lock(&mutex_); + + // The set of lost entities is the previously found set MINUS the currently + // found set. + for (const auto& item : current_entities_) { + previously_found_entities_.erase(item); + } + auto lost_entities = std::move(previously_found_entities_); + previously_found_entities_ = std::move(current_entities_); + current_entities_ = {}; + + return lost_entities; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc new file mode 100644 index 00000000..829aaadf --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc @@ -0,0 +1,123 @@ +#include "core_v2/internal/mediums/lost_entity_tracker.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +struct TestEntity { + int id; + + template + friend H AbslHashValue(H h, const TestEntity& test_entity) { + return H::combine(std::move(h), test_entity.id); + } + + bool operator==(const TestEntity& other) const { return id == other.id; } + bool operator<(const TestEntity& other) const { return id < other.id; } +}; + +TEST(LostEntityTrackerTest, NoEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // 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); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure we still didn't lose any entities. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); +} + +TEST(LostEntityTrackerTest, AllEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through a round without rediscovering any entities. + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) != lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SomeEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + + // Make sure none are lost on the first round. + EXPECT_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); + lost_entity_tracker.RecordFoundEntity(entity_3); + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) == lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) == lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SameEntityMultipleCopies) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_1_copy{1}; + + // Discover an entity. + lost_entity_tracker.RecordFoundEntity(entity_1); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Rediscover the same entity, but through a copy of it. + lost_entity_tracker.RecordFoundEntity(entity_1_copy); + + // Make sure none are lost on the second round. + EXPECT_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::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_EQ(lost_entities.size(), 1); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_1_copy) != lost_entities.end()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc new file mode 100644 index 00000000..6785345a --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -0,0 +1,41 @@ +#include "core_v2/internal/mediums/utils.h" + +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { + +ByteArray Utils::GenerateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.NextUint32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return ByteArray(data); +} + +ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) { + ByteArray full_hash(length); + full_hash.CopyAt(0, Crypto::Sha256(std::string(source))); + return full_hash; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.h b/cpp/core_v2/internal/mediums/utils.h new file mode 100644 index 00000000..7234a897 --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.h @@ -0,0 +1,22 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +class Utils { + public: + static ByteArray GenerateRandomBytes(size_t length); + static ByteArray Sha256Hash(const ByteArray& source, size_t length); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ diff --git a/cpp/core_v2/internal/mediums/uuid.cc b/cpp/core_v2/internal/mediums/uuid.cc new file mode 100644 index 00000000..2bd8b947 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.cc @@ -0,0 +1,75 @@ +#include "core_v2/internal/mediums/uuid.h" + +#include +#include + +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { +std::ostream& write_hex(std::ostream& os, absl::string_view data) { + for (const auto b : data) { + os << std::setfill('0') + << std::setw(2) + << std::hex + << (static_cast(b) & 0x0ff); + } + return os; +} +} // namespace + +Uuid::Uuid(absl::string_view data) : data_(Crypto::Md5(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. + data_[6] &= 0x0f; // Clear version. + data_[6] |= 0x30; // Set to version 3. + data_[8] &= 0x3f; // Clear variant. + data_[8] |= 0x80; // Set to IETF variant. +} + +Uuid::Uuid(std::uint64_t most_sig_bits, std::uint64_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((most_sig_bits >> 56) & 0x0ff); + data_[1] = static_cast((most_sig_bits >> 48) & 0x0ff); + data_[2] = static_cast((most_sig_bits >> 40) & 0x0ff); + data_[3] = static_cast((most_sig_bits >> 32) & 0x0ff); + data_[4] = static_cast((most_sig_bits >> 24) & 0x0ff); + data_[5] = static_cast((most_sig_bits >> 16) & 0x0ff); + data_[6] = static_cast((most_sig_bits >> 8) & 0x0ff); + data_[7] = static_cast((most_sig_bits >> 0) & 0x0ff); + + data_[8] = static_cast((least_sig_bits >> 56) & 0x0ff); + data_[9] = static_cast((least_sig_bits >> 48) & 0x0ff); + data_[10] = static_cast((least_sig_bits >> 40) & 0x0ff); + data_[11] = static_cast((least_sig_bits >> 32) & 0x0ff); + data_[12] = static_cast((least_sig_bits >> 24) & 0x0ff); + data_[13] = static_cast((least_sig_bits >> 16) & 0x0ff); + data_[14] = static_cast((least_sig_bits >> 8) & 0x0ff); + data_[15] = static_cast((least_sig_bits >> 0) & 0x0ff); +} + +Uuid::operator std::string() const { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375. + std::ostringstream md5_hex; + write_hex(md5_hex, absl::string_view(&data_[0], 4)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[4], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[6], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[8], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[10], 6)); + + return md5_hex.str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/uuid.h b/cpp/core_v2/internal/mediums/uuid.h new file mode 100644 index 00000000..e197ff69 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.h @@ -0,0 +1,45 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_UUID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UUID_H_ + +#include +#include + +#include "absl/strings/string_view.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 +class Uuid final { + public: + Uuid() : Uuid("uuid") {} + explicit Uuid(absl::string_view data); + Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits); + Uuid(const Uuid&) = default; + Uuid& operator=(const Uuid&) = default; + Uuid(Uuid&&) = default; + Uuid& operator=(Uuid&&) = default; + ~Uuid() = default; + + // Returns the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the + // UUID. + explicit operator std::string() const; + std::string data() const { + return data_; + } + + private: + std::string data_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UUID_H_ diff --git a/cpp/core_v2/internal/mediums/uuid_test.cc b/cpp/core_v2/internal/mediums/uuid_test.cc new file mode 100644 index 00000000..f5872dfa --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid_test.cc @@ -0,0 +1,56 @@ +#include "core_v2/internal/mediums/uuid.h" + +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr char kString[] = "some string"; +constexpr std::uint64_t kNum1 = 0x123456789abcdef0; +constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f; + +TEST(UuidTest, CreateFromStringWithMd5) { + Uuid uuid(kString); + std::string uuid_str(uuid); + std::string uuid_data(uuid.data()); + std::string md5_data(Crypto::Md5(kString)); + NEARBY_LOG(INFO, "MD5-based UUID: '%s'", uuid_str.c_str()); + uuid_data[6] = 0; + uuid_data[8] = 0; + md5_data[6] = 0; + md5_data[8] = 0; + EXPECT_EQ(md5_data, uuid_data); +} + +TEST(UuidTest, CreateFromBinary) { + Uuid uuid(kNum1, kNum2); + std::string uuid_data(uuid.data()); + std::string uuid_str(uuid); + NEARBY_LOG(INFO, "UUID: '%s'", uuid_str.c_str()); + EXPECT_EQ(uuid_data[0], (kNum1 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[1], (kNum1 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[2], (kNum1 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[3], (kNum1 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[4], (kNum1 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[5], (kNum1 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[6], (kNum1 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[7], (kNum1 >> 0) & 0xFF); + EXPECT_EQ(uuid_data[8], (kNum2 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[9], (kNum2 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[10], (kNum2 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[11], (kNum2 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[12], (kNum2 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[13], (kNum2 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[14], (kNum2 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[15], (kNum2 >> 0) & 0xFF); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..9e8cc9e8 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -0,0 +1,76 @@ +cc_library( + name = "webrtc", + srcs = [ + "webrtc_socket.cc", + ], + hdrs = [ + "webrtc_socket.h", + ], + deps = [ + "//core_v2:core_types", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform_v2/base", + "//platform_v2/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform_v2/base", + "//platform_v2/impl/g3", #buildcleaner: keep + "//platform_v2/public", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform_v2/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep + ], +) + +cc_library( + name = "peer_id", + srcs = ["peer_id.cc"], + hdrs = ["peer_id.h"], + deps = [ + "//core_v2/internal/mediums:utils", + "//platform_v2/base", + "//absl/strings", + ], +) + +cc_library( + name = "signaling_frames", + srcs = ["signaling_frames.cc"], + hdrs = ["signaling_frames.h"], + deps = [ + ":peer_id", + "//platform_v2/base", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..71d2c5db --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,38 @@ +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core_v2/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(const ByteArray& bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes.data(), bytes.size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +PeerId PeerId::FromRandom() { + return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength)); +} + +PeerId PeerId::FromSeed(const ByteArray& seed) { + ByteArray full_hash(Utils::Sha256Hash(seed, kPeerIdLength)); + ByteArray hashed_seed(full_hash.data(), kPeerIdLength / 2); + return PeerId(BytesToStringUppercase(hashed_seed)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.h b/cpp/core_v2/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..e2bd1262 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,35 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static PeerId FromRandom(); + static PeerId FromSeed(const ByteArray& seed); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..37b54d04 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,42 @@ +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/crypto.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +TEST(PeerIdTest, GenerateRandomPeerId) { + PeerId peer_id = PeerId::FromRandom(); + EXPECT_EQ(64, peer_id.GetId().size()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "seed"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + ByteArray seed_bytes(seed); + PeerId peer_id = PeerId::FromSeed(seed_bytes); + + EXPECT_EQ(64, peer_id.GetId().size()); + EXPECT_EQ(expected_peer_id, peer_id.GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + EXPECT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..7bb7872b --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,120 @@ +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return ByteArray(message.c_str(), message.size()); +} + +void SetSenderId(const PeerId& sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id.GetId()); +} + +std::unique_ptr DecodeIceCandidate( + location::nearby::mediums::IceCandidate ice_candidate_proto) { + webrtc::SdpParseError error; + return std::unique_ptr( + webrtc::CreateIceCandidate(ice_candidate_proto.sdp_mid(), + ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.set_allocated_ready_for_signaling_poke( + new location::nearby::mediums::ReadyForSignalingPoke()); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +std::unique_ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()); +} + +std::unique_ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..63a92718 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,44 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "platform_v2/base/byte_array.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id); + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer); +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +std::unique_ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +std::unique_ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..54ecd527 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,182 @@ +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + PeerId sender_id("abc"); + ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + PeerId sender_id("abc"); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ByteArray encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer.data(), encoded_offer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignaingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + std::unique_ptr decoded_offer = + DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + PeerId sender_id("abc"); + std::unique_ptr answer( + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp)); + ByteArray encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer.data(), encoded_answer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + std::unique_ptr decoded_answer = + DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + PeerId sender_id("abc"); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate)); + } + ByteArray encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates.data(), encoded_candidates.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> + decoded_candidates = DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..a961ee0d --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,101 @@ +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) { + if (data.size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return {Exception::kIo}; + } + + socket_->BlockUntilSufficientSpaceInBuffer(data.size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return {Exception::kIo}; + } + + if (!socket_->SendMessage(data)) { + return {Exception::kIo}; + } + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Flush() { + // Java implementation is empty. + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Close() { + socket_->Close(); + return {Exception::kSuccess}; +} + +// WebRtcSocket +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), data_channel_(std::move(data_channel)) {} + +InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); } + +OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; } + +void WebRtcSocket::Close() { + if (IsClosed()) return; + + closed_.Set(true); + pipe_.GetInputStream().Close(); + pipe_.GetOutputStream().Close(); + data_channel_->Close(); + WakeUpWriter(); + socket_closed_listener_.socket_closed_cb(); +} + +void WebRtcSocket::NotifyDataChannelMsgReceived(const ByteArray& message) { + if (!pipe_.GetOutputStream().Write(message).Ok()) { + Close(); + return; + } + + if (!pipe_.GetOutputStream().Flush().Ok()) Close(); +} + +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { WakeUpWriter(); } + +bool WebRtcSocket::SendMessage(const ByteArray& data) { + return data_channel_->Send( + webrtc::DataBuffer(std::string(data.data(), data.size()))); +} + +bool WebRtcSocket::IsClosed() { return closed_.Get(); } + +void WebRtcSocket::WakeUpWriter() { + MutexLock lock(&backpressure_mutex_); + buffer_variable_.Notify(); +} + +void WebRtcSocket::SetOnSocketClosedListener(SocketClosedListener&& listener) { + socket_closed_listener_ = std::move(listener); +} + +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + MutexLock lock(&backpressure_mutex_); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_.Wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..e5d90939 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,101 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include + +#include "core_v2/listeners.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/base/socket.h" +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/pipe.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + InputStream& GetInputStream() override; + OutputStream& GetOutputStream() override; + void Close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(const ByteArray& message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + struct SocketClosedListener { + std::function socket_closed_cb = DefaultCallback<>(); + }; + + void SetOnSocketClosedListener(SocketClosedListener&& listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(const ByteArray& data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Pipe pipe_; + + OutputStreamImpl output_stream_{this}; + + AtomicBoolean closed_{false}; + + SocketClosedListener socket_closed_listener_; + + mutable Mutex backpressure_mutex_; + ConditionVariable buffer_variable_{&backpressure_mutex_}; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..89184569 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,154 @@ +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +// using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +TEST(WebRtcSocketTest, ReadFromSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"Me"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ssa"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ge"}); + + ExceptionOr result; + + // This behaviour is different from the Java code + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"Me"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ssa"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ge"}); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok()); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + const ByteArray kMessage{kMaxDataSize + 1}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Close()); + + int socket_closed_cb_called = 0; + + webrtc_socket.SetOnSocketClosedListener( + {.socket_closed_cb = [&]() { socket_closed_cb_called++; }}); + webrtc_socket.Close(); + + EXPECT_EQ(socket_closed_cb_called, 1); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.Close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.GetOutputStream().Write(kMessage); + webrtc_socket.Close(); + + EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mock_service_controller.h b/cpp/core_v2/internal/mock_service_controller.h new file mode 100644 index 00000000..f2668139 --- /dev/null +++ b/cpp/core_v2/internal/mock_service_controller.h @@ -0,0 +1,71 @@ +#ifndef CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ + +#include "core_v2/internal/service_controller.h" +#include "gmock/gmock.h" + +namespace location { +namespace nearby { +namespace connections { + +/* Mock implementation for ServiceController: + * All methods execute asynchronously (in a private executor thread). + * To synchronise, two approaches may be used: + * 1. For methods that have result callback, we use it to unblock main thread. + * 2. For methods that do not have callbacks, we provide a mock implementation + * that unblocks main thread. + */ +class MockServiceController : public ServiceController { + public: + MOCK_METHOD(Status, StartAdvertising, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(void, StopAdvertising, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, StartDiscovery, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener), + (override)); + + MOCK_METHOD(void, StopDiscovery, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, RequestConnection, + (ClientProxy * client, const std::string& endpoint_id, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(Status, AcceptConnection, + (ClientProxy * client, const std::string& endpoint_id, + const PayloadListener& listener), + (override)); + + MOCK_METHOD(Status, RejectConnection, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, InitiateBandwidthUpgrade, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, SendPayload, + (ClientProxy * client, + const std::vector& endpoint_ids, Payload payload), + (override)); + + MOCK_METHOD(Status, CancelPayload, + (ClientProxy * client, std::int64_t payload_id), (override)); + + MOCK_METHOD(void, DisconnectFromEndpoint, + (ClientProxy * client, const std::string& endpoint_id), + (override)); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc new file mode 100644 index 00000000..792922bb --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.cc @@ -0,0 +1,251 @@ +#include "core_v2/internal/offline_frames.h" + +#include +#include + +#include "core/internal/message_lite.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using ExceptionOrOfflineFrame = ExceptionOr; +using Medium = proto::connections::Medium; +using MessageLite = ::google3_proto_compat::MessageLite; + +ByteArray ToBytes(OfflineFrame&& frame) { + ByteArray bytes(frame.ByteSizeLong()); + frame.set_version(OfflineFrame::V1); + frame.SerializeToArray(bytes.data(), bytes.size()); + return bytes; +} + +} // namespace + +ExceptionOrOfflineFrame FromBytes(const ByteArray& bytes) { + OfflineFrame frame; + + if (frame.ParseFromString(std::string(bytes))) { + return ExceptionOrOfflineFrame(std::move(frame)); + } else { + return ExceptionOrOfflineFrame(Exception::kInvalidProtocolBuffer); + } +} + +V1Frame::FrameType GetFrameType(const OfflineFrame& frame) { + if ((frame.version() == OfflineFrame::V1) && frame.has_v1()) { + return frame.v1().type(); + } + + return V1Frame::UNKNOWN_FRAME_TYPE; +} + +ByteArray ForConnectionRequest(const std::string& endpoint_id, + const std::string& endpoint_name, + std::int32_t nonce, + const std::vector& mediums) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + auto* connection_request = v1_frame->mutable_connection_request(); + connection_request->set_endpoint_id(endpoint_id); + connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); + connection_request->set_nonce(nonce); + for (const auto& medium : mediums) { + connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); + } + + return ToBytes(std::move(frame)); +} + +ByteArray ForConnectionResponse(std::int32_t status) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_RESPONSE); + auto* sub_frame = v1_frame->mutable_connection_response(); + sub_frame->set_status(status); + + return ToBytes(std::move(frame)); +} + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::DATA); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_payload_chunk() = chunk; + + return ToBytes(std::move(frame)); +} + +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::CONTROL); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_control_message() = control; + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, + const std::string& password, + std::int32_t port) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium( + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); + auto* wifi_hotspot_credentials = + upgrade_path_info->mutable_wifi_hotspot_credentials(); + wifi_hotspot_credentials->set_ssid(ssid); + wifi_hotspot_credentials->set_password(password); + wifi_hotspot_credentials->set_port(port); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeLastWrite() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeSafeToClose() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION); + auto* client_introduction = sub_frame->mutable_client_introduction(); + client_introduction->set_endpoint_id(endpoint_id); + + return ToBytes(std::move(frame)); +} + +ByteArray ForKeepAlive() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::KEEP_ALIVE); + v1_frame->mutable_keep_alive(); + + return ToBytes(std::move(frame)); +} + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium) { + switch (medium) { + case Medium::MDNS: + return ConnectionRequestFrame::MDNS; + case Medium::BLUETOOTH: + return ConnectionRequestFrame::BLUETOOTH; + case Medium::WIFI_HOTSPOT: + return ConnectionRequestFrame::WIFI_HOTSPOT; + case Medium::BLE: + return ConnectionRequestFrame::BLE; + case Medium::WIFI_LAN: + return ConnectionRequestFrame::WIFI_LAN; + case Medium::WIFI_AWARE: + return ConnectionRequestFrame::WIFI_AWARE; + case Medium::NFC: + return ConnectionRequestFrame::NFC; + case Medium::WIFI_DIRECT: + return ConnectionRequestFrame::WIFI_DIRECT; + case Medium::WEB_RTC: + return ConnectionRequestFrame::WEB_RTC; + default: + return ConnectionRequestFrame::UNKNOWN_MEDIUM; + } +} + +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium) { + switch (medium) { + case ConnectionRequestFrame::MDNS: + return Medium::MDNS; + case ConnectionRequestFrame::BLUETOOTH: + return Medium::BLUETOOTH; + case ConnectionRequestFrame::WIFI_HOTSPOT: + return Medium::WIFI_HOTSPOT; + case ConnectionRequestFrame::BLE: + return Medium::BLE; + case ConnectionRequestFrame::WIFI_LAN: + return Medium::WIFI_LAN; + case ConnectionRequestFrame::WIFI_AWARE: + return Medium::WIFI_AWARE; + case ConnectionRequestFrame::NFC: + return Medium::NFC; + case ConnectionRequestFrame::WIFI_DIRECT: + return Medium::WIFI_DIRECT; + case ConnectionRequestFrame::WEB_RTC: + return Medium::WEB_RTC; + default: + return Medium::UNKNOWN_MEDIUM; + } +} + +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& frame) { + std::vector result; + for (const auto& int_medium : frame.mediums()) { + result.push_back(ConnectionRequestMediumToMedium( + static_cast(int_medium))); + } + return result; +} + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_frames.h b/cpp/core_v2/internal/offline_frames.h new file mode 100644 index 00000000..81bf8aca --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.h @@ -0,0 +1,61 @@ +#ifndef CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ +#define CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ + +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { + +// Serialize/Deserialize Nearby Connections Protocol messages. + +// Parses incoming message. +// Returns OfflineFrame if parser was able to understand it, or +// Exception::kInvalidProtocolBuffer, if parser failed. +ExceptionOr FromBytes(const ByteArray& offline_frame_bytes); + +// Returns FrameType of a parsed message, or +// V1Frame::UNKNOWN_FRAME_TYPE, if frame contents is not recognized. +V1Frame::FrameType GetFrameType(const OfflineFrame& offline_frame); + +// Build ConnectionRequest message. +ByteArray ForConnectionRequest( + const std::string& endpoint_id, const std::string& endpoint_name, + std::int32_t nonce, const std::vector& mediums); +ByteArray ForConnectionResponse(std::int32_t status); + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk); +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control); + +ByteArray ForBandwidthUpgradeWifiHotspot( + const std::string& ssid, const std::string& password, std::int32_t port); +ByteArray ForBandwidthUpgradeLastWrite(); +ByteArray ForBandwidthUpgradeSafeToClose(); +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id); + +ByteArray ForKeepAlive(); + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium); +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium); +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& connection_request_frame); + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc new file mode 100644 index 00000000..b0dedddd --- /dev/null +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -0,0 +1,252 @@ +#include "core_v2/internal/offline_frames.h" + +#include +#include +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using Medium = proto::connections::Medium; +using ::testing::EqualsProto; + +constexpr char kEndpointId[] = "ABC"; +constexpr char kEndpointName[] = "XYZ"; +constexpr int kNonce = 1234; +constexpr std::array kMediums = { + Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, + Medium::BLE, Medium::WIFI_LAN, Medium::WIFI_AWARE, + Medium::NFC, Medium::WIFI_DIRECT, Medium::WEB_RTC, +}; + +TEST(OfflineFramesTest, CanParseMessageFromBytes) { + OfflineFrame tx_message; + + { + tx_message.set_version(OfflineFrame::V1); + auto* v1_frame = tx_message.mutable_v1(); + auto* sub_frame = v1_frame->mutable_connection_request(); + + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + sub_frame->set_endpoint_id(kEndpointId); + sub_frame->set_endpoint_name(kEndpointName); + sub_frame->set_endpoint_info(kEndpointName); + sub_frame->set_nonce(kNonce); + for (auto& medium : kMediums) { + sub_frame->add_mediums(MediumToConnectionRequestMedium(medium)); + } + } + auto serialized_bytes = ByteArray(tx_message.SerializeAsString()); + auto ret_value = FromBytes(serialized_bytes); + ASSERT_TRUE(ret_value.ok()); + const auto& rx_message = ret_value.result(); + EXPECT_THAT(rx_message, EqualsProto(tx_message)); + EXPECT_EQ(GetFrameType(rx_message), V1Frame::CONNECTION_REQUEST); + EXPECT_EQ( + ConnectionRequestMediumsToMediums(rx_message.v1().connection_request()), + std::vector(kMediums.begin(), kMediums.end())); +} + +TEST(OfflineFramesTest, CanGenerateConnectionRequest) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_REQUEST + connection_request: < + endpoint_id: "ABC" + endpoint_name: "XYZ" + endpoint_info: "XYZ" + nonce: 1234 + mediums: MDNS + mediums: BLUETOOTH + mediums: WIFI_HOTSPOT + mediums: BLE + mediums: WIFI_LAN + mediums: WIFI_AWARE + mediums: NFC + mediums: WIFI_DIRECT + mediums: WEB_RTC + > + >)pb"; + ByteArray bytes = + ForConnectionRequest(kEndpointId, kEndpointName, kNonce, + std::vector(kMediums.begin(), kMediums.end())); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateConnectionResponse) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_RESPONSE + connection_response: < status: 1 > + >)pb"; + ByteArray bytes = ForConnectionResponse(1); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateControlPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(150); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: CONTROL, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + control_message: < event: PAYLOAD_CANCELED offset: 150 > + > + >)pb"; + ByteArray bytes = ForControlPayloadTransfer(header, control); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateDataPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: DATA, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + payload_chunk: < flags: 1 offset: 150 body: "payload data" > + > + >)pb"; + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeWifiHotspot) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_HOTSPOT + wifi_hotspot_credentials: < + ssid: "ssid" + password: "password" + port: 1234 + > + > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeWifiHotspot("ssid", "password", 1234); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeLastWrite) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: LAST_WRITE_TO_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeLastWrite(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeSafeToClose) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: SAFE_TO_CLOSE_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeSafeToClose(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: CLIENT_INTRODUCTION + client_introduction: < endpoint_id: "ABC" > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeIntroduction(kEndpointId); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateKeepAlive) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: KEEP_ALIVE + keep_alive: <> + >)pb"; + ByteArray bytes = ForKeepAlive(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +} // namespace +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/pcp.h b/cpp/core_v2/internal/pcp.h new file mode 100644 index 00000000..f2fec5ea --- /dev/null +++ b/cpp/core_v2/internal/pcp.h @@ -0,0 +1,26 @@ +#ifndef CORE_V2_INTERNAL_PCP_H_ +#define CORE_V2_INTERNAL_PCP_H_ + +namespace location { +namespace nearby { +namespace connections { + +// The PreConnectionProtocol (PCP) defines the combinations of interactions +// between the techniques (ultrasound audio, Bluetooth device names, BLE +// advertisements) used for offline Advertisement + Discovery, and identifies +// the steps to go through on each device. +// +// See go/nearby-offline-data-interchange-formats for more. +enum class Pcp { + kUnknown = 0, + kP2pStar = 1, + kP2pCluster = 2, + kP2pPointToPoint = 3, + // PCP is only allocated 5 bits in our data interchange formats, so there can + // never be more than 31 PCP values. +}; +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_H_ diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h new file mode 100644 index 00000000..3666360d --- /dev/null +++ b/cpp/core_v2/internal/pcp_handler.h @@ -0,0 +1,88 @@ +#ifndef CORE_V2_INTERNAL_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_PCP_HANDLER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/status.h" +#include "core_v2/strategy.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. +// +// 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). +// +// See details here: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +class PcpHandler { + public: + virtual ~PcpHandler() = default; + + // Return strategy supported by this protocol. + virtual Strategy GetStrategy() = 0; + + // Return concrete variant of protocol. + virtual Pcp GetPcp() = 0; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See + // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;bpv=1;bpt=1;l=71?gsn=ConnectionListener + virtual Status StartAdvertising(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopAdvertising(ClientProxy* client) = 0; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + virtual Status StartDiscovery(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopDiscovery(ClientProxy* client) = 0; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + virtual Status RequestConnection(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + virtual Status AcceptConnection(ClientProxy* clientProxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) = 0; + + // Either party may call this to reject connection on their part before + // connection reaches data phase. If either party does call it, connection + // will terminate. Update state in ClientProxy. + virtual Status RejectConnection(ClientProxy* client, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/service_controller.h b/cpp/core_v2/internal/service_controller.h new file mode 100644 index 00000000..119a633b --- /dev/null +++ b/cpp/core_v2/internal/service_controller.h @@ -0,0 +1,77 @@ +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" + +namespace location { +namespace nearby { +namespace connections { + +// Interface defines the core functionality of Nearby Connections Service. +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// ResultCallback arguments are not provided for this class, because all methods +// are called synchronously. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// See details here: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +class ServiceController { + public: + virtual ~ServiceController() = default; + ServiceController() = default; + ServiceController(const ServiceController&) = delete; + ServiceController& operator=(const ServiceController&) = delete; + + // Starts advertising an endpoint for a local app. + virtual Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + virtual void StopAdvertising(ClientProxy* client_proxy) = 0; + + virtual Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + virtual void StopDiscovery(ClientProxy* client_proxy) = 0; + + virtual Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + virtual Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& listener) = 0; + virtual Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void InitiateBandwidthUpgrade(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void SendPayload(ClientProxy* client_proxy, + const std::vector& endpoint_ids, + Payload payload) = 0; + + virtual Status CancelPayload(ClientProxy* client_proxy, + std::int64_t payload_id) = 0; + + virtual void DisconnectFromEndpoint(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc new file mode 100644 index 00000000..dd1c044b --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -0,0 +1,383 @@ +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +ServiceControllerRouter::~ServiceControllerRouter() { + // TODO(tracyzhou): Add logging. + + // And make sure that cleanup is the last thing we do. + serializer_.Shutdown(); +} + +void ServiceControllerRouter::StartAdvertising( + ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, const ConnectionRequestInfo& info, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, info, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsAdvertising()) { + callback.result_cb({Status::kAlreadyAdvertising}); + return; + } + + status = service_controller_->StartAdvertising(client, service_id, options, + info); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopAdvertising(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsAdvertising()) { + service_controller_->StopAdvertising(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::StartDiscovery(ClientProxy* client, + absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, listener, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsDiscovering()) { + callback.result_cb({Status::kAlreadyDiscovering}); + return; + } + + status = service_controller_->StartDiscovery(client, service_id, options, + listener); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopDiscovery(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsDiscovering()) { + service_controller_->StopDiscovery(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::RequestConnection( + ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), info, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->HasPendingConnectionToEndpoint(endpoint_id) || + client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + callback.result_cb( + service_controller_->RequestConnection(client, endpoint_id, info)); + }); +} + +void ServiceControllerRouter::AcceptConnection(ClientProxy* client, + absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, + endpoint_id = std::string(endpoint_id), listener, + callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->AcceptConnection(client, endpoint_id, listener)); + }); +} + +void ServiceControllerRouter::RejectConnection(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->RejectConnection(client, endpoint_id)); + }); +} + +void ServiceControllerRouter::InitiateBandwidthUpgrade( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client) || + !client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + service_controller_->InitiateBandwidthUpgrade(client, endpoint_id); + + // Operation is triggered; the caller can listen to + // ConnectionListener::OnBandwidthChanged() to determine its success. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::SendPayload( + ClientProxy* client, absl::Span endpoint_ids, + Payload payload, const ResultCallback& callback) { + // Payload is a move-only type. + // We have to capture it by value inside the lambda, and pass it over to + // the executor as an std::function instance. + // Lambda must be copyable, in order ot satisfy std::function<> requirements. + // To make it so, we need Payload wrapped by a copyable wrapper. + // std::shared_ptr<> is used, because it is copyable. + auto shared_payload = std::make_shared(std::move(payload)); + RouteToServiceController( + [this, client, shared_payload, + endpoint_ids = std::vector(endpoint_ids.begin(), endpoint_ids.end()), + &callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (!ClientHasConnectionToAtLeastOneEndpoint(client, endpoint_ids)) { + callback.result_cb({Status::kEndpointUnknown}); + return; + } + + service_controller_->SendPayload(client, endpoint_ids, + std::move(*shared_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. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::CancelPayload(ClientProxy* client, + std::uint64_t payload_id, + const ResultCallback& callback) { + RouteToServiceController([this, client, payload_id, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb(service_controller_->CancelPayload(client, payload_id)); + }); +} + +void ServiceControllerRouter::DisconnectFromEndpoint( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (ClientHasAcquiredServiceController(client)) { + if (!client->IsConnectedToEndpoint(endpoint_id) && + !client->HasPendingConnectionToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + callback.result_cb({Status::kSuccess}); + } + }); +} + +void ServiceControllerRouter::StopAllEndpoints(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::ClientDisconnecting( + ClientProxy* client, const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + // Log the completion of this client's connection. + // TODO(tracyzhou): Add logging. + } + callback.result_cb({Status::kSuccess}); + }); +} + +Status ServiceControllerRouter::AcquireServiceControllerForClient( + ClientProxy* client, Strategy strategy) { + if (current_strategy_.IsNone()) { + // Case 1: There is no existing Strategy at all. + + // Set everything up for the first time. + Status status = UpdateCurrentServiceControllerAndStrategy(strategy); + if (!status.Ok()) { + return status; + } + clients_.insert(client); + return {Status::kSuccess}; + } 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. + clients_.insert(client); + return {Status::kSuccess}; + } 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 = + clients_.size() == 1 && ClientHasAcquiredServiceController(client); + if (!is_the_only_client_of_service_controller) { + // TODO(tracyzhou): logging + return {Status::kAlreadyHaveActiveStrategy}; + } + + // If the client still has connected endpoints, they must disconnect before + // they can switch. + if (!client->GetConnectedEndpoints().empty()) { + // TODO(tracyzhou): logging + return {Status::kOutOfOrderApiCall}; + } + + // 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); + } +} + +bool ServiceControllerRouter::ClientHasAcquiredServiceController( + ClientProxy* client) const { + return clients_.contains(client); +} + +void ServiceControllerRouter::ReleaseServiceControllerForClient( + ClientProxy* client) { + clients_.erase(client); + + if (clients_.empty()) { + service_controller_.reset(); + current_strategy_ = Strategy{}; + } +} + +/** Clean up all state for this client. The client is now free to switch + * strategies. */ +void ServiceControllerRouter::DoneWithStrategySessionForClient( + ClientProxy* client) { + // Disconnect from all the connected endpoints tied to this clientProxy. + for (auto& endpoint_id : client->GetPendingConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + for (auto& endpoint_id : client->GetConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + // Stop any advertising and discovery that may be underway due to this + // clientProxy. + service_controller_->StopAdvertising(client); + service_controller_->StopDiscovery(client); + + ReleaseServiceControllerForClient(client); +} + +void ServiceControllerRouter::RouteToServiceController(Runnable runnable) { + serializer_.Execute(std::move(runnable)); +} + +bool ServiceControllerRouter::ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids) { + for (auto& endpoint_id : remote_endpoint_ids) { + if (client->IsConnectedToEndpoint(endpoint_id)) { + return true; + } + } + return false; +} + +Status ServiceControllerRouter::UpdateCurrentServiceControllerAndStrategy( + Strategy strategy) { + if (!strategy.IsValid()) { + // TODO(tracyzhou): logging + return {Status::kError}; + } + + service_controller_.reset(service_controller_factory_()); + current_strategy_ = strategy; + + return {Status::kSuccess}; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/service_controller_router.h b/cpp/core_v2/internal/service_controller_router.h new file mode 100644 index 00000000..8ccfd057 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.h @@ -0,0 +1,111 @@ +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// ServiceControllerRouter: this class is an implementation detail of a +// location::nearby::Core class. The latter delegates all of its activities to +// the former. +// +// All the activities are documented in the public API class: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// Every activity is handled the same way: +// 1) all the arguments to the call are captured by value; +// 2) the actual processing is scheduled on a private single-threaded executor, +// which makes locking unnecessary, when internal data is being manipulated. +// 3) activity handlers are delegating much of their work to an implementation +// of a ServiceController interface, which does the actual job. +class ServiceControllerRouter { + public: + explicit ServiceControllerRouter(std::function factory) + : service_controller_factory_(std::move(factory)) {} + ~ServiceControllerRouter(); + ServiceControllerRouter(ServiceControllerRouter&&) = default; + ServiceControllerRouter& operator=(ServiceControllerRouter&&) = default; + + void StartAdvertising(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void StopAdvertising(ClientProxy* client, const ResultCallback& callback); + + void StartDiscovery(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback); + void StopDiscovery(ClientProxy* client, const ResultCallback& callback); + + void RequestConnection(ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void AcceptConnection(ClientProxy* client, absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback); + void RejectConnection(ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback); + + void InitiateBandwidthUpgrade(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + + void SendPayload(ClientProxy* client, + absl::Span endpoint_ids, Payload payload, + const ResultCallback& callback); + void CancelPayload(ClientProxy* client, std::uint64_t payload_id, + const ResultCallback& callback); + + void DisconnectFromEndpoint(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + void StopAllEndpoints(ClientProxy* client, const ResultCallback& callback); + + void ClientDisconnecting(ClientProxy* client, const ResultCallback& callback); + + private: + friend class ServiceControllerRouterTest; + static bool ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids); + + void RouteToServiceController(Runnable runnable); + + Status AcquireServiceControllerForClient(ClientProxy* client, + Strategy strategy); + bool ClientHasAcquiredServiceController(ClientProxy* client) const; + void ReleaseServiceControllerForClient(ClientProxy* client); + void DoneWithStrategySessionForClient(ClientProxy* client); + Status UpdateCurrentServiceControllerAndStrategy(Strategy strategy); + + absl::flat_hash_set clients_; + std::function service_controller_factory_; + std::unique_ptr service_controller_; + Strategy current_strategy_; + SingleThreadExecutor serializer_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ diff --git a/cpp/core_v2/internal/service_controller_router_test.cc b/cpp/core_v2/internal/service_controller_router_test.cc new file mode 100644 index 00000000..2fc45d00 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router_test.cc @@ -0,0 +1,376 @@ +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { +using ::testing::Return; +} // namespace + +// This class must be in the same namespace as ServiceControllerRouter for +// friend class to work. +class ServiceControllerRouterTest : public testing::Test { + public: + ServiceControllerRouterTest() = default; + ~ServiceControllerRouterTest() override { + router_.service_controller_.release(); + } + + void StartAdvertising(ClientProxy* client, std::string service_id, + ConnectionOptions options, ConnectionRequestInfo info, + ResultCallback callback) { + EXPECT_CALL(mock_, StartAdvertising) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartAdvertising(client, service_id, options, info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedAdvertising(kServiceId, options.strategy, info.listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StopAdvertising(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopAdvertising).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopAdvertising(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedAdvertising(); + EXPECT_FALSE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, std::string service_id, + ConnectionOptions options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, StartDiscovery) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartDiscovery(client, kServiceId, options, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedDiscovery(service_id, options.strategy, listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsDiscovering()); + } + + void StopDiscovery(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopDiscovery).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopDiscovery(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedDiscovery(); + EXPECT_FALSE(client->IsDiscovering()); + } + + void RequestConnection(ClientProxy* client, const std::string& endpoint_id, + const ConnectionRequestInfo& request_info, + ResultCallback callback) { + EXPECT_CALL(mock_, RequestConnection) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RequestConnection(client, endpoint_id, request_info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + ConnectionResponseInfo response_info{ + .remote_endpoint_name = "endpoint_name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + client->OnConnectionInitiated(endpoint_id, response_info, + request_info.listener); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + } + + void AcceptConnection(ClientProxy* client, const std::string endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, AcceptConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.AcceptConnection(client, endpoint_id, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointAcceptedConnection(endpoint_id, listener); + client->RemoteEndpointAcceptedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint_id)); + client->OnConnectionAccepted(endpoint_id); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + } + + void RejectConnection(ClientProxy* client, const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, RejectConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RejectConnection(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointRejectedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionRejected(endpoint_id)); + } + + void InitiateBandwidthUpgrade(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, InitiateBandwidthUpgrade).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.InitiateBandwidthUpgrade(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void SendPayload(ClientProxy* client, + const std::vector& endpoint_ids, + Payload payload, ResultCallback callback) { + EXPECT_CALL(mock_, SendPayload).Times(1); + + bool connected = false; + for (const auto& endpoint_id : endpoint_ids) { + connected = connected || client->IsConnectedToEndpoint(endpoint_id); + } + EXPECT_TRUE(connected); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.SendPayload(client, absl::MakeSpan(endpoint_ids), + std::move(payload), callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void CancelPayload(ClientProxy* client, std::int64_t payload_id, + ResultCallback callback) { + EXPECT_CALL(mock_, CancelPayload) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.CancelPayload(client, payload_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void DisconnectFromEndpoint(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, DisconnectFromEndpoint).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.DisconnectFromEndpoint(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + } + client->OnDisconnected(endpoint_id, false); + EXPECT_FALSE(client->IsConnectedToEndpoint(endpoint_id)); + } + + protected: + const ResultCallback kCallback{ + .result_cb = + [this](Status status) { + MutexLock lock(&mutex_); + result_ = status; + complete_ = true; + cond_.Notify(); + }, + }; + const std::string kServiceId = "service id"; + const std::string kRequestorName = "requestor name"; + const std::string kRemoteEndpointId = "remote endpoint id"; + const std::int64_t kPayloadId = UINT64_C(0x123456789ABCDEF0); + const ConnectionOptions kConnectionOptions{ + .strategy = Strategy::kP2pPointToPoint, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH}; + const ConnectionRequestInfo kConnectionRequestInfo{ + .name = kRequestorName, + .listener = ConnectionListener(), + }; + + DiscoveryListener discovery_listener_; + PayloadListener payload_listener_; + + Mutex mutex_; + ConditionVariable cond_{&mutex_}; + Status result_ ABSL_GUARDED_BY(mutex_) = {Status::kError}; + bool complete_ ABSL_GUARDED_BY(mutex_) = false; + MockServiceController mock_; + ClientProxy client_; + + ServiceControllerRouter router_{ + [this]() -> ServiceController* { return &mock_; }}; +}; + +namespace { +TEST_F(ServiceControllerRouterTest, CostructorDestructorWorks) { SUCCEED(); } + +TEST_F(ServiceControllerRouterTest, StartAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); + StopAdvertising(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StartDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + StopDiscovery(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RequestConnectionCalled) { + // Either Advertising, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, AcceptConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RejectConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can reject connection. + RejectConnection(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, InitiateBandwidthUpgradeCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can change connection bandwidth. + InitiateBandwidthUpgrade(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, SendPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can send payload. + SendPayload(&client_, std::vector{kRemoteEndpointId}, + Payload{ByteArray("data")}, kCallback); +} + +TEST_F(ServiceControllerRouterTest, CancelPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We have to know payload id, before we can cancel payload transfer. + // It is either after a call to SendPayload, or after receiving + // PayloadProgress callback. Let's assume we have it, and proceed. + CancelPayload(&client_, kPayloadId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, DisconnectFromEndpointCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We can disconnect at any time after RequestConnection. + DisconnectFromEndpoint(&client_, kRemoteEndpointId, kCallback); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc new file mode 100644 index 00000000..f034eeea --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -0,0 +1,180 @@ +#include "core_v2/internal/wifi_lan_service_info.h" + +#include + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, + absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name) { + if (version != Version::kV1 || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + service_id_hash.size() != kServiceIdHashLength) { + return; + } + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; +} + +WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { + ByteArray service_info_bytes = Base64Utils::Decode(service_info_string); + + if (service_info_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: failed Base64 decoding of %s", + std::string(service_info_string).c_str()); + return; + } + + if (service_info_bytes.size() > kMaxLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " + "bytes, got %" PRIu64, + kMaxLanServiceNameLength, service_info_bytes.size()); + return; + } + + if (service_info_bytes.size() < kMinLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting min %d raw " + "bytes, got %" PRIu64, + kMinLanServiceNameLength, service_info_bytes.size()); + return; + } + + // The upper 3 bits are supposed to be the version. + version_ = static_cast( + (service_info_bytes.data()[0] & kVersionBitmask) >> kVersionShift); + const char* service_info_bytes_read_ptr = service_info_bytes.data(); + switch (version_) { + case Version::kV1: + // The lower 5 bits of the V1 payload are supposed to be the Pcp. + pcp_ = static_cast(*service_info_bytes_read_ptr & kPcpBitmask); + service_info_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(service_info_bytes_read_ptr, kEndpointIdLength); + service_info_bytes_read_ptr += kEndpointIdLength; + + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(service_info_bytes_read_ptr, kServiceIdHashLength); + service_info_bytes_read_ptr += kServiceIdHashLength; + + // The next bits are supposed to be endpoint_name. + // TODO(edwinwu): Implements it. Temp to set "found_device". + endpoint_name_ = "found_device"; + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + break; + } + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", + version_); + break; + } +} + +WifiLanServiceInfo::operator std::string() const { + if (!IsValid()) { + return ""; + } + + ByteArray wifi_lan_service_info_name_bytes(kMinLanServiceNameLength); + auto* wifi_lan_service_info_name_bytes_write_ptr = + wifi_lan_service_info_name_bytes.data(); + + // The upper 3 bits are the Version. + auto version_and_pcp_byte = static_cast( + (static_cast(Version::kV1) << 5) & kVersionBitmask); + // The lower 5 bits are the PCP. + version_and_pcp_byte |= + static_cast(static_cast(pcp_) & kPcpBitmask); + *wifi_lan_service_info_name_bytes_write_ptr = version_and_pcp_byte; + wifi_lan_service_info_name_bytes_write_ptr++; + + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are the endpoint_id. + if (endpoint_id_.size() != kEndpointIdLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 Endpoint ID %s (%" PRIu64 + " bytes) should be exactly %d bytes", + endpoint_id_.c_str(), endpoint_id_.size(), kEndpointIdLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, endpoint_id_.data(), + kEndpointIdLength); + wifi_lan_service_info_name_bytes_write_ptr += kEndpointIdLength; + + // The next 24 bits are the service_id_hash. + if (service_id_hash_.size() != kServiceIdHashLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 ServiceID hash (%" PRIu64 + " bytes) should be exactly %d bytes", + service_id_hash_.size(), kServiceIdHashLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, + service_id_hash_.data(), kServiceIdHashLength); + wifi_lan_service_info_name_bytes_write_ptr += kServiceIdHashLength; + + // The next bits are the endpoint_name. + // TODO(edwinwu): Implements to parse endpoint_name. + break; + default: + NEARBY_LOG(ERROR, + "Cannot serialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + return ""; + } + + return Base64Utils::Encode(wifi_lan_service_info_name_bytes); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.h b/cpp/core_v2/internal/wifi_lan_service_info.h new file mode 100644 index 00000000..21f1f1bb --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -0,0 +1,81 @@ +#ifndef CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ +#define CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ + +#include + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the WifiLan service info used in Advertising + +// Discovery. +// +// See go/nearby-offline-data-interchange-formats for the specification. +class WifiLanServiceInfo { + public: + // Versions of the WifiLanServiceInfo. + enum class Version { + kUndefined = 0, + kV1 = 1, + }; + + static constexpr std::uint32_t kServiceIdHashLength = 3; + + WifiLanServiceInfo() = default; + WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name); + explicit WifiLanServiceInfo(absl::string_view service_info_string); + ~WifiLanServiceInfo() = default; + + WifiLanServiceInfo(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo(WifiLanServiceInfo&&) = default; + WifiLanServiceInfo& operator=(WifiLanServiceInfo&&) = default; + + explicit operator std::string() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline ByteArray GetServiceIdHash() const { return service_id_hash_; } + + private: + // The maximum length of encrypted WifiLanServiceInfo string. + static constexpr int kMaxLanServiceNameLength = 47; + // The minimum length of encrypted WifiLanServiceInfo string. + static constexpr int kMinLanServiceNameLength = 9; + // The length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kEndpointIdLength = 4; + // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kMaxEndpointNameLength = 131; + + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kVersionShift = 5; + + // WifiLanServiceInfo version. + Version version_ = Version::kUndefined; + // Pre-Connection Protocols version. + Pcp pcp_ = Pcp::kUnknown; + // Connected endpoint id. + std::string endpoint_id_; + // Connected hash service id. + ByteArray service_id_hash_; + // TODO(edwinwu): Replaces endpointName as endPointInfo eventually; + // it is not in this version yet for endpointName. + // Connected endpoint name. + std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ diff --git a/cpp/core_v2/internal/wifi_lan_service_info_test.cc b/cpp/core_v2/internal/wifi_lan_service_info_test.cc new file mode 100644 index 00000000..b5aee9aa --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -0,0 +1,143 @@ +#include "core_v2/internal/wifi_lan_service_info.h" + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kEndPointID[] = "AB12"; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +// TODO(edwinwu): Temp to set empty string for endpoint_name. +const char kEndPointName[] = ""; + +TEST(WifiLanServiceInfoTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto wifi_lan_service_info_string = std::string(org_wifi_lan_service_info); + + auto wifi_lan_service_info = WifiLanServiceInfo(wifi_lan_service_info_string); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName); + + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + auto short_service_id_hash = + ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + auto long_service_id_hash = + ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortStringLength) { + char wifi_lan_service_info_string[] = {'X'}; + + auto wifi_lan_service_info_bytes = + ByteArray(wifi_lan_service_info_string, + sizeof(wifi_lan_service_info_string) / sizeof(char)); + auto wifi_lan_service_info = + WifiLanServiceInfo(Base64Utils::Encode(wifi_lan_service_info_bytes)); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h new file mode 100644 index 00000000..4f375344 --- /dev/null +++ b/cpp/core_v2/listeners.h @@ -0,0 +1,180 @@ +#ifndef CORE_V2_LISTENERS_H_ +#define CORE_V2_LISTENERS_H_ + +#include +#include +#include +#include + +// This file defines all the protocol listeners and their parameter structures. +// Listeners are defined as collections of std::function instances, which is +// more flexible than a virtual function: +// - a subset of listener callbacks may be overridden, while others may remain +// default-initialized. +// - callbacks may be initialized with lambdas; lambda definitions are concize. + +#include "core_v2/payload.h" +#include "core_v2/status.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Common callback for asynchronously invoked methods. +// Called after a job scheduled for execution is completed. +// This is not the same as completion of the associated process, +// which may have many states, and multiple async jobs, and be still ongoing. +// Progress on the overall process is reported by the associated listener. +struct ResultCallback { + // Callback to access the status of the operation when available. + // status - result of job execution; + // Status::kSuccess, if successful; anything else indicates failure. + std::function result_cb = DefaultCallback(); +}; + +struct ConnectionResponseInfo { + std::string remote_endpoint_name; + std::string authentication_token; + ByteArray raw_authentication_token; + ByteArray endpoint_info; + bool is_incoming_connection; + bool is_connection_verified; +}; + +struct PayloadProgressInfo { + std::int64_t payload_id; + enum class Status { + kSuccess, + kFailure, + kInProgress, + kCanceled, + } status; + std::int64_t total_bytes; + std::int64_t bytes_transferred; +}; + +enum class DistanceInfo { + kUnknown = 1, + kVeryClose = 2, + kClose = 3, + kFar = 4, +}; + +struct ConnectionListener { + // A basic encrypted channel has been created between you and the endpoint. + // Both sides are now asked if they wish to accept or reject the connection + // before any data can be sent over this channel. + // + // This is your chance, before you accept the connection, to confirm that you + // connected to the correct device. Both devices are given an identical token; + // it's up to you to decide how to verify it before proceeding. Typically this + // involves showing the token on both devices and having the users manually + // compare and confirm; however, this is only required if you desire a secure + // connection between the devices. + // + // Whichever route you decide to take (including not authenticating the other + // device), call Core::AcceptConnection() when you're ready to talk, or + // Core::RejectConnection() to close the connection. + // + // endpoint_id - The identifier for the remote endpoint. + // info - Other relevant information about the connection. + std::function + initiated_cb = + DefaultCallback(); + + // Called after both sides have accepted the connection. + // Both sides may now send Payloads to each other. + // Call Core::SendPayload() or wait for incoming PayloadListener::OnPayload(). + // + // endpoint_id - The identifier for the remote endpoint. + std::function accepted_cb = + DefaultCallback(); + + // Called when either side rejected the connection. + // Payloads can not be exchaged. Call Core::DisconnectFromEndpoint() + // to terminate connection. + // + // endpoint_id - The identifier for the remote endpoint. + std::function + rejected_cb = DefaultCallback(); + + // Called when a remote endpoint is disconnected or has become unreachable. + // At this point service (re-)discovery may start again. + // + // endpoint_id - The identifier for the remote endpoint. + std::function disconnected_cb = + DefaultCallback(); + + // Called when the connection's available bandwidth has changed. + // + // endpoint_id - The identifier for the remote endpoint. + // quality - TODO(apolyudov): document. + std::function + bandwidth_changed_cb = + DefaultCallback(); +}; + +struct DiscoveryListener { + // Called when a remote endpoint is discovered. + // + // endpoint_id - The ID of the remote endpoint that was discovered. + // endpoint_name - The human readable name of the remote endpoint. + // service_id - The ID of the service advertised by the remote endpoint. + std::function + endpoint_found_cb = + DefaultCallback(); + + // Called when a remote endpoint is no longer discoverable; only called for + // endpoints that previously had been passed to {@link + // #onEndpointFound(String, DiscoveredEndpointInfo)}. + // + // endpoint_id - The ID of the remote endpoint that was lost. + std::function endpoint_lost_cb = + DefaultCallback(); + + // Called when a remote endpoint is found with an updated distance. + // + // arguments: + // endpoint_id - The ID of the remote endpoint that was lost. + // info - The distance info, encoded as enum value. + std::function + endpoint_distance_changed_cb = + DefaultCallback(); +}; + +struct PayloadListener { + // Called when a Payload is received from a remote endpoint. Depending + // on the type of the Payload, all of the data may or may not have been + // received at the time of this call. Use OnPayloadProgress() to + // get updates on the status of the data received. + // + // endpoint_id - The identifier for the remote endpoint that sent the + // payload. + // payload - The Payload object received. + std::function + payload_cb = DefaultCallback(); + + // Called with progress information about an active Payload transfer, either + // incoming or outgoing. + // + // endpoint_id - The identifier for the remote endpoint that is sending or + // receiving this payload. + // info - The PayloadProgressInfo structure describing the status of + // the transfer. + std::function + payload_progress_cb = + DefaultCallback(); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_LISTENERS_H_ diff --git a/cpp/core_v2/listeners_test.cc b/cpp/core_v2/listeners_test.cc new file mode 100644 index 00000000..8f73b1c0 --- /dev/null +++ b/cpp/core_v2/listeners_test.cc @@ -0,0 +1,45 @@ +#include "core_v2/listeners.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(ListenersTest, EnsureDefaultInitializedIsCallable) { + ConnectionListener listener; + std::string endpoint_id("endpoint_id"); + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + SUCCEED(); +} + +TEST(ListenersTest, EnsurePartiallyInitializedIsCallable) { + std::string endpoint_id = {"endpoint_id"}; + bool initiated_cb_called = false; + ConnectionListener listener{ + .initiated_cb = + [&](std::string, ConnectionResponseInfo) { + initiated_cb_called = true; + }, + }; + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + EXPECT_TRUE(initiated_cb_called); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h new file mode 100644 index 00000000..d55e41d5 --- /dev/null +++ b/cpp/core_v2/options.h @@ -0,0 +1,30 @@ +#ifndef CORE_V2_OPTIONS_H_ +#define CORE_V2_OPTIONS_H_ + +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +// Connection Options: used for both Advertising and Discovery. +// All fields are mutable, to make the type copy-assignable. +struct ConnectionOptions { + Strategy strategy; + bool auto_upgrade_bandwidth; + bool enforce_topology_constraints; + // Verify if ConnectionOptions is in a not-initialized (Empty) state. + bool Empty() const { + return strategy.IsNone(); + } + // Bring ConnectionOptions to a not-initialized (Empty) state. + void Clear() { + strategy.Clear(); + } +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_OPTIONS_H_ diff --git a/cpp/core_v2/params.h b/cpp/core_v2/params.h new file mode 100644 index 00000000..b0ddde22 --- /dev/null +++ b/cpp/core_v2/params.h @@ -0,0 +1,27 @@ +#ifndef CORE_V2_PARAMS_H_ +#define CORE_V2_PARAMS_H_ + +#include + +#include "core_v2/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Used by Discovery in Core::RequestConnection(). +// Used by Advertising in Core::StartAdvertising(). +struct ConnectionRequestInfo { + // name - A human readable name for this endpoint, to appear on + // other devices. + // listener - A set of callbacks notified when remote endpoints request a + // connection to this endpoint. + std::string name; + ConnectionListener listener; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PARAMS_H_ diff --git a/cpp/core_v2/payload.h b/cpp/core_v2/payload.h new file mode 100644 index 00000000..c1e81633 --- /dev/null +++ b/cpp/core_v2/payload.h @@ -0,0 +1,85 @@ +#ifndef CORE_V2_PAYLOAD_H_ +#define CORE_V2_PAYLOAD_H_ + +#include +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/file.h" +#include "absl/types/variant.h" + +namespace location { +namespace nearby { +namespace connections { + +// Payload is default-constructible, and moveable, but not copyable container +// that holds at most one instance of one of: +// ByteArray, InputStream, or InputFile. +class Payload { + public: + // Order of types in variant, and values in Type enum is important. + // Enum values must match respective variant types. + using Content = + absl::variant, + std::unique_ptr>; + enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; + + Payload(Payload&& other) = default; + ~Payload() = default; + Payload& operator=(Payload&& other) = default; + + // Create Payload from bytes, steam, or file. Payload is immutable. + Payload() : content_(absl::monostate()) {} + explicit Payload(ByteArray&& bytes) : content_(std::move(bytes)) {} + explicit Payload(const ByteArray& bytes) : content_(bytes) {} + explicit Payload(std::unique_ptr stream) + : content_(std::move(stream)) {} + explicit Payload(std::unique_ptr file) + : content_(std::move(file)) {} + + // Returns ByteArray payload, if it has been defined, or empty ByteArray. + const ByteArray& AsBytes() const & { + static const ByteArray empty; // NOLINT: function-level static is OK. + auto* result = absl::get_if(&content_); + return result ? *result : empty; + } + ByteArray&& AsBytes() && { + auto* result = absl::get_if(&content_); + return result ? std::move(*result) : std::move(ByteArray()); + } + // Returns InputStream* payload, if it has been defined, or nullptr. + InputStream* AsStream() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + // Returns InputFile* payload, if it has been defined, or nullptr. + InputFile* AsFile() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + + // Returns Payload unique ID. + std::int64_t GetId() const { return id_; } + + // Returns Payload type. + Type GetType() const { return type_; } + + private: + static std::int64_t GenerateId() { return Prng().NextInt64(); } + Type FindType(const Content& content) const { + return static_cast(content_.index()); + } + + Content content_; + std::int64_t id_{GenerateId()}; + Type type_{FindType(content_)}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PAYLOAD_H_ diff --git a/cpp/core_v2/payload_test.cc b/cpp/core_v2/payload_test.cc new file mode 100644 index 00000000..498efb7b --- /dev/null +++ b/cpp/core_v2/payload_test.cc @@ -0,0 +1,76 @@ +#include "core_v2/payload.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/public/file.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(PayloadTest, DefaultPayloadHasUnknownType) { + Payload payload; + EXPECT_EQ(payload.GetType(), Payload::Type::kUnknown); +} + +TEST(PayloadTest, SupportsByteArrayType) { + const ByteArray bytes("bytes"); + Payload payload(bytes); + EXPECT_EQ(payload.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), bytes); +} + +TEST(PayloadTest, SupportsFileType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr file(raw_file); + Payload payload(std::move(file)); + EXPECT_EQ(payload.GetType(), Payload::Type::kFile); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), raw_file); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, SupportsStreamType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr stream(raw_file); + Payload payload(std::move(stream)); + EXPECT_EQ(payload.GetType(), Payload::Type::kStream); + EXPECT_EQ(payload.AsStream(), raw_file); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, PayloadIsMoveable) { + Payload payload1; + Payload payload2(ByteArray("bytes")); + auto id = payload2.GetId(); + ByteArray bytes = payload2.AsBytes(); + EXPECT_EQ(payload1.GetType(), Payload::Type::kUnknown); + EXPECT_EQ(payload2.GetType(), Payload::Type::kBytes); + payload1 = std::move(payload2); + EXPECT_EQ(payload1.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload1.AsBytes(), bytes); + EXPECT_EQ(payload1.GetId(), id); +} + +TEST(PayloadTest, PayloadHasUniqueId) { + Payload payload1; + Payload payload2; + EXPECT_NE(payload1.GetId(), payload2.GetId()); +} + +TEST(PayloadTest, PayloadIsNotCopyable) { + EXPECT_FALSE(std::is_copy_constructible_v); + EXPECT_FALSE(std::is_copy_assignable_v); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/status.h b/cpp/core_v2/status.h new file mode 100644 index 00000000..c4ff633c --- /dev/null +++ b/cpp/core_v2/status.h @@ -0,0 +1,45 @@ +#ifndef CORE_V2_STATUS_H_ +#define CORE_V2_STATUS_H_ + +namespace location { +namespace nearby { +namespace connections { + +// Protocol operation result: kSuccess, if operation was successful; +// descriptive error code otherwise. +struct Status { + // Status is a struct, so it is possible to pass some context about failure, + // by adding extra fields to it when necessary, and not change any of the + // method signatures. + enum Value { + kSuccess, + kError, + kOutOfOrderApiCall, + kAlreadyHaveActiveStrategy, + kAlreadyAdvertising, + kAlreadyDiscovering, + kEndpointIoError, + kEndpointUnknown, + kConnectionRejected, + kAlreadyConnectedToEndpoint, + kNotConnectedToEndpoint, + kBluetoothError, + kPayloadUnknown, + }; + Value value {kError}; + bool Ok() const { return value == kSuccess; } +}; + +inline bool operator==(const Status& a, const Status& b) { + return a.value == b.value; +} + +inline bool operator!=(const Status& a, const Status& b) { + return !(a == b); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STATUS_H_ diff --git a/cpp/core_v2/status_test.cc b/cpp/core_v2/status_test.cc new file mode 100644 index 00000000..86f37b4f --- /dev/null +++ b/cpp/core_v2/status_test.cc @@ -0,0 +1,44 @@ +#include "core_v2/status.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StatusTest, DefaultIsError) { + Status status; + EXPECT_FALSE(status.Ok()); + EXPECT_EQ(status, Status{Status::kError}); +} + +TEST(StatusTest, DefaultEquals) { + Status status1; + Status status2; + EXPECT_EQ(status1, status2); +} + +TEST(StatusTest, ExplicitInitEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kSuccess}; + EXPECT_EQ(status1, status2); + EXPECT_TRUE(status1.Ok()); +} + +TEST(StatusTest, ExplicitInitNotEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kAlreadyAdvertising}; + EXPECT_NE(status1, status2); +} + +TEST(StatusTest, CopyInitEquals) { + Status status1 = {Status::kAlreadyAdvertising}; + Status status2 = {status1}; + + EXPECT_EQ(status1, status2); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.cc b/cpp/core_v2/strategy.cc new file mode 100644 index 00000000..d17a9090 --- /dev/null +++ b/cpp/core_v2/strategy.cc @@ -0,0 +1,47 @@ +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +const Strategy Strategy::kNone = {Strategy::ConnectionType::kNone, + Strategy::TopologyType::kUnknown}; +const Strategy Strategy::kP2pCluster{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kManyToMany}; +const Strategy Strategy::kP2pStar{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kOneToMany}; +const Strategy Strategy::kP2pPointToPoint{ + Strategy::ConnectionType::kPointToPoint, Strategy::TopologyType::kOneToOne}; + +bool Strategy::IsNone() const { + return *this == kNone; +} + +bool Strategy::IsValid() const { + return *this == kP2pStar || *this == kP2pCluster || *this ==kP2pPointToPoint; +} + +std::string Strategy::GetName() const { + if (*this == Strategy::kP2pCluster) { + return "P2P_CLUSTER"; + } else if (*this == Strategy::kP2pStar) { + return "P2P_STAR"; + } else if (*this == Strategy::kP2pPointToPoint) { + return "P2P_POINT_TO_POINT"; + } else { + return "UNKNOWN"; + } +} + +bool operator==(const Strategy& lhs, const Strategy& rhs) { + return lhs.connection_type_ == rhs.connection_type_ && + lhs.topology_type_ == rhs.topology_type_; +} + +bool operator!=(const Strategy& lhs, const Strategy& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.h b/cpp/core_v2/strategy.h new file mode 100644 index 00000000..de134f78 --- /dev/null +++ b/cpp/core_v2/strategy.h @@ -0,0 +1,62 @@ +#ifndef CORE_V2_STRATEGY_H_ +#define CORE_V2_STRATEGY_H_ + +#include + +namespace location { +namespace nearby { +namespace connections { + +// Defines a copyable, comparable connection strategy type. +// It is one of: kP2pCluster, kP2pStar, kP2pPointToPoint. +class Strategy { + public: + static const Strategy kNone; + static const Strategy kP2pCluster; + static const Strategy kP2pStar; + static const Strategy kP2pPointToPoint; + + Strategy() : Strategy(kNone) {} + + constexpr Strategy(const Strategy& other) + : connection_type_(other.connection_type_), + topology_type_(other.topology_type_) {} + + // Returns true, if strategy is kNone, false otherwise. + bool IsNone() const; + // Returns true, if a strategy is one of the supported strategies, + // false otherwise. + bool IsValid() const; + // Returns a string representing given strategy, for every valid strategy. + std::string GetName() const; + // Undefine strategy. + void Clear() { + *this = kNone; + } + + friend bool operator==(const Strategy& lhs, const Strategy& rhs); + friend bool operator!=(const Strategy& lhs, const Strategy& rhs); + + private: + enum class ConnectionType { + kNone = 0, + kPointToPoint = 1, + }; + enum class TopologyType { + kUnknown = 0, + kOneToOne = 1, + kOneToMany = 2, + kManyToMany = 3, + }; + Strategy(ConnectionType connection_type, TopologyType topology_type) + : connection_type_(connection_type), topology_type_(topology_type) {} + + ConnectionType connection_type_; + TopologyType topology_type_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STRATEGY_H_ diff --git a/cpp/core_v2/strategy_test.cc b/cpp/core_v2/strategy_test.cc new file mode 100644 index 00000000..6b1565e3 --- /dev/null +++ b/cpp/core_v2/strategy_test.cc @@ -0,0 +1,41 @@ +#include "core_v2/strategy.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StrategyTest, IsValidWorks) { + EXPECT_FALSE(Strategy().IsValid()); + EXPECT_TRUE(Strategy::kP2pCluster.IsValid()); + EXPECT_TRUE(Strategy::kP2pStar.IsValid()); + EXPECT_TRUE(Strategy::kP2pPointToPoint.IsValid()); +} + +TEST(StrategyTest, IsNoneWorks) { + EXPECT_TRUE(Strategy().IsNone()); + EXPECT_FALSE(Strategy::kP2pCluster.IsNone()); + EXPECT_FALSE(Strategy::kP2pStar.IsNone()); + EXPECT_FALSE(Strategy::kP2pPointToPoint.IsNone()); +} + +TEST(StrategyTest, CompareWorks) { + EXPECT_EQ(Strategy::kP2pCluster, Strategy::kP2pCluster); + EXPECT_EQ(Strategy::kP2pStar, Strategy::kP2pStar); + EXPECT_EQ(Strategy::kP2pPointToPoint, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pStar); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pStar, Strategy::kP2pPointToPoint); +} + +TEST(StrategyTest, GetNameWorks) { + EXPECT_EQ(Strategy().GetName(), "UNKNOWN"); + EXPECT_EQ(Strategy::kP2pCluster.GetName(), "P2P_CLUSTER"); + EXPECT_EQ(Strategy::kP2pStar.GetName(), "P2P_STAR"); + EXPECT_EQ(Strategy::kP2pPointToPoint.GetName(), "P2P_POINT_TO_POINT"); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 72d19bc6..a0d279b6 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -2,16 +2,16 @@ cc_library( name = "utils", srcs = [ "base64_utils.cc", + "cancelable_alarm.cc", "file_impl.cc", + "pipe.cc", "prng.cc", "reliability_utils.cc", ], hdrs = [ "base64_utils.h", - "cancelable_alarm.cc", "cancelable_alarm.h", "file_impl.h", - "pipe.cc", "pipe.h", "prng.h", "reliability_utils.h", @@ -50,7 +50,6 @@ cc_library( ], deps = [ ":logging", - "//platform/impl/default:lock", "//platform/port:down_cast", "//platform/port:string", ], @@ -64,6 +63,7 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", + "//platform_v2/public:__pkg__", ], deps = [ "//absl/base", @@ -72,75 +72,24 @@ cc_library( ) cc_test( - name = "container_of_test", - srcs = ["container_of_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", + name = "platform_test", + timeout = "short", + srcs = [ + "atomic_reference_test.cc", + "byte_array_test.cc", + "container_of_test.cc", + "file_impl_test.cc", + "pipe_test.cc", + "prng_test.cc", + "ptr_test.cc", + "settable_future_test.cc", ], -) - -cc_test( - name = "ptr_test", - srcs = ["ptr_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "prng_test", - srcs = ["prng_test.cc"], - deps = [ - ":utils", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "file_test", - srcs = ["file_impl_test.cc"], deps = [ ":utils", "//file/util:temp_path", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "exception_test", - srcs = ["exception_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "pipe_test", - timeout = "short", - srcs = ["pipe_test.cc"], - deps = [ - ":utils", "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", - "//platform/port:string", - "//testing/base/public:gunit_main", - "//absl/time", - ], -) - -cc_test( - name = "byte_array_test", - timeout = "short", - srcs = ["byte_array_test.cc"], - deps = [ - ":utils", - "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", "//absl/time", diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index f1c769b7..1b155f0c 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -9,6 +9,7 @@ cc_library( hdrs = [ "atomic_boolean.h", "atomic_reference.h", + "atomic_reference_def.h", "ble.h", "ble_v2.h", "bluetooth_adapter.h", @@ -25,12 +26,15 @@ cc_library( "multi_thread_executor.h", "output_file.h", "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", + "settable_future_def.h", "single_thread_executor.h", "socket.h", "submittable_executor.h", + "submittable_executor_def.h", "system_clock.h", "thread_utils.h", "webrtc.h", @@ -41,6 +45,8 @@ cc_library( "//platform:types", "//platform/port:down_cast", "//platform/port:string", + "//absl/strings", + "//absl/types:any", "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/atomic_reference.h b/cpp/platform/api/atomic_reference.h index 52a8b14e..f06a5e06 100644 --- a/cpp/platform/api/atomic_reference.h +++ b/cpp/platform/api/atomic_reference.h @@ -1,21 +1,48 @@ #ifndef PLATFORM_API_ATOMIC_REFERENCE_H_ #define PLATFORM_API_ATOMIC_REFERENCE_H_ +#include "platform/api/atomic_reference_def.h" +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + namespace location { namespace nearby { -// An object reference that may be updated atomically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { template -class AtomicReference { +class AtomicReferenceImpl : public AtomicReference { public: - virtual ~AtomicReference() {} + explicit AtomicReferenceImpl(T initial_value) { + atomic_ = platform::ImplementationPlatform::createAtomicReferenceAny( + absl::any(initial_value)); + } - virtual T get() = 0; - virtual void set(T value) = 0; + ~AtomicReferenceImpl() override = default; + + void set(T new_value) override { atomic_->set(absl::any(new_value)); } + + T get() override { return absl::any_cast(atomic_->get()); } + + private: + Ptr> atomic_; }; +} // namespace impl + +template +Ptr> ImplementationPlatform::createAtomicReference( + T initial_value) { + return Ptr>( + new impl::AtomicReferenceImpl{initial_value}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/atomic_reference_def.h b/cpp/platform/api/atomic_reference_def.h new file mode 100644 index 00000000..7133caf8 --- /dev/null +++ b/cpp/platform/api/atomic_reference_def.h @@ -0,0 +1,27 @@ +#ifndef PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ +#define PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateAtomicReferenceSizeT() +// Ptr>> CreateAtomicReferencePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class AtomicReference { + public: + virtual ~AtomicReference() = default; + + virtual T get() = 0; + virtual void set(T value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h index 06a88288..b4353076 100644 --- a/cpp/platform/api/ble_v2.h +++ b/cpp/platform/api/ble_v2.h @@ -24,7 +24,7 @@ namespace nearby { struct BLEAdvertisementData { typedef std::int8_t TXPowerLevel; - static const TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = + static constexpr TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = std::numeric_limits::min(); bool is_connectable; diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h index 3770fda4..f9aa8b9c 100644 --- a/cpp/platform/api/multi_thread_executor.h +++ b/cpp/platform/api/multi_thread_executor.h @@ -10,11 +10,9 @@ namespace nearby { // unbounded queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { +class MultiThreadExecutor : public SubmittableExecutor { public: - ~MultiThreadExecutor() override {} + ~MultiThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/platform.h b/cpp/platform/api/platform.h new file mode 100644 index 00000000..70260c7f --- /dev/null +++ b/cpp/platform/api/platform.h @@ -0,0 +1,106 @@ +#ifndef PLATFORM_API_PLATFORM_H_ +#define PLATFORM_API_PLATFORM_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/api/wifi_lan.h" + +// Project-specific basic types, that are not part of API. +// TODO(apolyudov): replace with c++ standard types. +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // Class Templates in platform code. + // + // Platform interface does not support templates directly. + // This is a design decision. The purpose is to have type isolation + // between platform library (or simply platform) and core library. + // Another goal is to make a platform implementation a black box, + // which does not leak implementation details in any form, be that types, + // methods, or variables. + // + // Core library code does provide platform-specific class templates + // on top of (a non-templated) platform support. + // + // For every common library template that needs platform support, + // platform must provide an absl::any specialization of class template: + template + static Ptr> createAtomicReference(T initial_value = T{}); + template + static Ptr> createSettableFuture(); + + // AtomicReference + static Ptr> createAtomicReferenceAny( + absl::any initial_value); + + // SettableFuture + static Ptr> createSettableFutureAny(); + + // Non-template methods: general platform support. + static Ptr createAtomicBoolean(bool initial_value); + static Ptr createCountDownLatch(std::int32_t count); + static Ptr createLock(); + static Ptr createConditionVariable(Ptr lock); + static Ptr createHashUtils(); + static Ptr createThreadUtils(); + static Ptr createSystemClock(); + + // Java-like Executors + // Type aliases used to API 1.0 compatibility. + // They will be retired soon. + // TODO(apolyudov): cleanup. + using SingleThreadExecutorType = SubmittableExecutor; + using MultiThreadExecutorType = SubmittableExecutor; + using ScheduledExecutorType = ScheduledExecutor; + + static Ptr createSingleThreadExecutor(); + static Ptr createMultiThreadExecutor( + std::int32_t max_concurrency); + static Ptr createScheduledExecutor(); + + // Protocol implementations, domain-specific support + static Ptr createBluetoothAdapter(); + static Ptr createWifiMedium(); + static Ptr createBluetoothClassicMedium(); + static Ptr createBLEMedium(); + static Ptr createBLEMediumV2(); + static Ptr createServerSyncMedium(); + static Ptr createWifiLanMedium(); + static Ptr createWebRtcSignalingMessenger( + const std::string& self_id); + static std::string getDeviceId(); + static std::string getPayloadPath(int64_t payload_id); +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_PLATFORM_H_ diff --git a/cpp/platform/api/scheduled_executor.h b/cpp/platform/api/scheduled_executor.h index 2100058b..f4877450 100644 --- a/cpp/platform/api/scheduled_executor.h +++ b/cpp/platform/api/scheduled_executor.h @@ -3,7 +3,7 @@ #include -#include "platform/api/executor.h" +#include "platform/api/submittable_executor_def.h" #include "platform/cancelable.h" #include "platform/ptr.h" #include "platform/runnable.h" @@ -15,9 +15,9 @@ namespace nearby { // execute periodically. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html -class ScheduledExecutor : public Executor { +class ScheduledExecutor : public SubmittableExecutor { public: - virtual ~ScheduledExecutor() {} + ~ScheduledExecutor() override = default; virtual Ptr schedule(Ptr runnable, std::int64_t delay_millis) = 0; diff --git a/cpp/platform/api/server_sync.h b/cpp/platform/api/server_sync.h index e6b01aa9..1be12149 100644 --- a/cpp/platform/api/server_sync.h +++ b/cpp/platform/api/server_sync.h @@ -22,7 +22,7 @@ class ServerSyncDevice { virtual std::string getOwnGuid() = 0; }; -// Container of operations that can be performed over the Chrome Sync medium. +// Container of operations that can be performed over the Server Sync medium. class ServerSyncMedium { public: virtual ~ServerSyncMedium() {} diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h index f9a5e35c..4fd69616 100644 --- a/cpp/platform/api/settable_future.h +++ b/cpp/platform/api/settable_future.h @@ -1,24 +1,65 @@ #ifndef PLATFORM_API_SETTABLE_FUTURE_H_ #define PLATFORM_API_SETTABLE_FUTURE_H_ -#include "platform/api/listenable_future.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future_def.h" +#include "platform/exception.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" namespace location { namespace nearby { -// A SettableFuture is a type of Future whose result can be set. -// -// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { + template -class SettableFuture : public ListenableFuture { +class SettableFutureImpl : public SettableFuture { public: - ~SettableFuture() override {} + SettableFutureImpl() { + future_ = platform::ImplementationPlatform::createSettableFutureAny(); + } - virtual bool set(T value) = 0; + ~SettableFutureImpl() override = default; - virtual bool setException(Exception exception) = 0; + bool set(T value) override { return future_->set(absl::any(value)); } + + bool setException(Exception exception) override { + return future_->setException(exception); + } + + void addListener(Ptr runnable, Executor* executor) override { + future_->addListener(runnable, executor); + } + + ExceptionOr get() override { return CommonGet(future_->get()); } + ExceptionOr get(std::int64_t timeout_ms) override { + return CommonGet(future_->get(timeout_ms)); + } + + private: + ExceptionOr CommonGet(ExceptionOr ret_val) { + if (ret_val.exception() != Exception::kSuccess) { + return ExceptionOr{ret_val.exception()}; + } + return ExceptionOr{absl::any_cast(ret_val.result())}; + } + + Ptr> future_; }; +} // namespace impl +template +Ptr> ImplementationPlatform::createSettableFuture() { + return Ptr>(new impl::SettableFutureImpl{}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/settable_future_def.h b/cpp/platform/api/settable_future_def.h new file mode 100644 index 00000000..e1a27c20 --- /dev/null +++ b/cpp/platform/api/settable_future_def.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_API_SETTABLE_FUTURE_DEF_H_ +#define PLATFORM_API_SETTABLE_FUTURE_DEF_H_ + +#include "platform/api/listenable_future.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// A SettableFuture is a type of Future whose result can be set. +// +// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateSettableFutureSizeT() +// Ptr>> CreateSettableFuturePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class SettableFuture : public ListenableFuture { + public: + ~SettableFuture() override = default; + + virtual bool set(T value) = 0; + + virtual bool setException(Exception exception) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SETTABLE_FUTURE_DEF_H_ diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h index e3338648..51dd02a2 100644 --- a/cpp/platform/api/single_thread_executor.h +++ b/cpp/platform/api/single_thread_executor.h @@ -10,11 +10,9 @@ namespace nearby { // queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { +class SingleThreadExecutor : public SubmittableExecutor { public: - ~SingleThreadExecutor() override {} + ~SingleThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h index 3d7bd625..b84ae602 100644 --- a/cpp/platform/api/submittable_executor.h +++ b/cpp/platform/api/submittable_executor.h @@ -1,37 +1,40 @@ #ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ #define PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ +#include + #include "platform/api/executor.h" #include "platform/api/future.h" -#include "platform/callable.h" -#include "platform/port/down_cast.h" -#include "platform/ptr.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/exception.h" namespace location { namespace nearby { -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class IOSSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// Ptr > submit(Ptr > callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - Ptr> submit(Ptr> callable) { - return DOWN_CAST(this)->submit(callable); +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +template +Ptr> SubmittableExecutor::submit(Ptr> callable) { + using Platform = platform::ImplementationPlatform; + Ptr> future{Platform::createSettableFuture()}; + bool submitted = DoSubmit([callable, future]() { + ExceptionOr result = callable->call(); + if (result.ok()) { + future->set(std::move(result.result())); + } else { + future->setException({result.exception()}); + } + }); + if (!submitted) { + // Raise Exception::kExecution if we are shutting down. + future->setException({Exception::kExecution}); } -}; + return future; +} } // namespace nearby } // namespace location diff --git a/cpp/platform/api/submittable_executor_def.h b/cpp/platform/api/submittable_executor_def.h new file mode 100644 index 00000000..0f7cd99c --- /dev/null +++ b/cpp/platform/api/submittable_executor_def.h @@ -0,0 +1,35 @@ +#ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ +#define PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ + +#include + +#include "platform/api/executor.h" +#include "platform/api/future.h" +#include "platform/callable.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + template + Ptr> submit(Ptr> callable); + + protected: + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(std::function wrapped_callable) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index 35f53e60..c428c0cb 100644 --- a/cpp/platform/api/webrtc.h +++ b/cpp/platform/api/webrtc.h @@ -33,7 +33,7 @@ class WebRtcSignalingMessenger { virtual bool registerSignaling() = 0; virtual bool unregisterSignaling() = 0; - virtual bool sendMessage(const string& peer_id, + virtual bool sendMessage(const std::string& peer_id, ConstPtr message) = 0; virtual bool startReceivingMessages( Ptr listener) = 0; diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h index 1b13b393..f282ba45 100644 --- a/cpp/platform/api/wifi_lan.h +++ b/cpp/platform/api/wifi_lan.h @@ -7,6 +7,7 @@ #include "platform/exception.h" #include "platform/port/string.h" #include "platform/ptr.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -50,9 +51,10 @@ class WifiLanMedium { public: virtual ~WifiLanMedium() = default; - virtual bool StartAdvertising(const std::string& service_id, - const string& wifi_lan_service_info_name) = 0; - virtual void StopAdvertising(const std::string& service_id) = 0; + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; // Callback for WifiLan discover results. class DiscoveredServiceCallback { @@ -64,9 +66,9 @@ class WifiLanMedium { }; virtual bool StartDiscovery( - const std::string& service_id, + absl::string_view service_id, Ptr discovered_service_callback) = 0; - virtual void StopDiscovery(const std::string& service_id) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; class AcceptedConnectionCallback { public: @@ -76,16 +78,16 @@ class WifiLanMedium { // destroyed) by the recipient of the callback methods (i.e. the creator of // the concrete AcceptedConnectionCallback object). virtual void OnConnectionAccepted(Ptr socket, - const string& service_id) = 0; + absl::string_view service_id) = 0; }; virtual bool StartAcceptingConnections( - const std::string& service_id, + absl::string_view service_id, Ptr accepted_connection_callback) = 0; - virtual void StopAcceptingConnections(const std::string& service_id) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; virtual Ptr Connect(Ptr wifi_lan_service, - const std::string& service_id) = 0; + absl::string_view service_id) = 0; }; } // namespace nearby diff --git a/cpp/platform/api2/atomic_boolean.h b/cpp/platform/api2/atomic_boolean.h deleted file mode 100644 index b5e729fa..00000000 --- a/cpp/platform/api2/atomic_boolean.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef PLATFORM_API2_ATOMIC_BOOLEAN_H_ -#define PLATFORM_API2_ATOMIC_BOOLEAN_H_ - -namespace location { -namespace nearby { - -// A boolean value that may be updated atomically. -// -// https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html -class AtomicBoolean { - public: - virtual ~AtomicBoolean() {} - - virtual bool Get() = 0; - virtual void Set(bool value) = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/input_file.h b/cpp/platform/api2/input_file.h deleted file mode 100644 index 0191aff8..00000000 --- a/cpp/platform/api2/input_file.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PLATFORM_API2_INPUT_FILE_H_ -#define PLATFORM_API2_INPUT_FILE_H_ - -#include - -#include "platform/api2/input_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An InputFile represents a readable file on the system. -class InputFile : public InputStream { - public: - ~InputFile() override = default; - virtual std::string GetFilePath() const = 0; - virtual size_t GetTotalSize() const = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_INPUT_FILE_H_ diff --git a/cpp/platform/api2/input_stream.h b/cpp/platform/api2/input_stream.h deleted file mode 100644 index f91a5466..00000000 --- a/cpp/platform/api2/input_stream.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef PLATFORM_API2_INPUT_STREAM_H_ -#define PLATFORM_API2_INPUT_STREAM_H_ - -#include - -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An InputStream represents an input stream of bytes. -// -// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html -class InputStream { - public: - virtual ~InputStream() {} - - virtual ExceptionOr Read( - size_t size) = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_INPUT_STREAM_H_ diff --git a/cpp/platform/api2/multi_thread_executor.h b/cpp/platform/api2/multi_thread_executor.h deleted file mode 100644 index f910bbc4..00000000 --- a/cpp/platform/api2/multi_thread_executor.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ - -#include "platform/api2/submittable_executor.h" - -namespace location { -namespace nearby { - -// An Executor that reuses a fixed number of threads operating off a shared -// unbounded queue. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { - public: - ~MultiThreadExecutor() override {} -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/mutex.h b/cpp/platform/api2/mutex.h deleted file mode 100644 index d4dbaf61..00000000 --- a/cpp/platform/api2/mutex.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_MUTEX_H_ -#define PLATFORM_API2_MUTEX_H_ - -namespace location { -namespace nearby { - -// A lock is a tool for controlling access to a shared resource by multiple -// threads. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html -class Mutex { - public: - virtual ~Mutex() {} - - virtual void Lock() = 0; - virtual void Unlock() = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_MUTEX_H_ diff --git a/cpp/platform/api2/output_file.h b/cpp/platform/api2/output_file.h deleted file mode 100644 index 4ac962e8..00000000 --- a/cpp/platform/api2/output_file.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef PLATFORM_API2_OUTPUT_FILE_H_ -#define PLATFORM_API2_OUTPUT_FILE_H_ - -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An OutputFile represents a writable file on the system. -class OutputFile : public OutputStream { - public: - ~OutputFile() override = default; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_OUTPUT_FILE_H_ diff --git a/cpp/platform/api2/output_stream.h b/cpp/platform/api2/output_stream.h deleted file mode 100644 index b9336ad1..00000000 --- a/cpp/platform/api2/output_stream.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PLATFORM_API2_OUTPUT_STREAM_H_ -#define PLATFORM_API2_OUTPUT_STREAM_H_ - -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An OutputStream represents an output stream of bytes. -// -// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html -class OutputStream { - public: - virtual ~OutputStream() {} - - virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo - virtual Exception Flush() = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api2/scheduled_executor.h b/cpp/platform/api2/scheduled_executor.h deleted file mode 100644 index ae773ee1..00000000 --- a/cpp/platform/api2/scheduled_executor.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef PLATFORM_API2_SCHEDULED_EXECUTOR_H_ -#define PLATFORM_API2_SCHEDULED_EXECUTOR_H_ - -#include -#include - -#include "platform/api2/executor.h" -#include "platform/cancelable.h" -#include "platform/runnable.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -// An Executor that can schedule commands to run after a given delay, or to -// execute periodically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html -class ScheduledExecutor : public Executor { - public: - ~ScheduledExecutor() override = default; - virtual std::unique_ptr Schedule( - std::unique_ptr runnable, absl::Duration duration) = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/single_thread_executor.h b/cpp/platform/api2/single_thread_executor.h deleted file mode 100644 index 990f2fe7..00000000 --- a/cpp/platform/api2/single_thread_executor.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ - -#include "platform/api2/submittable_executor.h" - -namespace location { -namespace nearby { - -// An Executor that uses a single worker thread operating off an unbounded -// queue. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { - public: - ~SingleThreadExecutor() override {} -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/submittable_executor.h b/cpp/platform/api2/submittable_executor.h deleted file mode 100644 index 43f16f56..00000000 --- a/cpp/platform/api2/submittable_executor.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ -#define PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ - -#include - -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/callable.h" - -namespace location { -namespace nearby { - -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class XyzSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// std::unique_ptr> submit(std::unique_ptr> callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - std::unique_ptr> Submit(std::unique_ptr> callable) { - static_assert( - std::is_base_of_v, - "Class template type is not derived from SubmittableExecutor"); - return static_cast(this)->submit(callable); - } -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api2/system_clock.h b/cpp/platform/api2/system_clock.h deleted file mode 100644 index 3b0b8090..00000000 --- a/cpp/platform/api2/system_clock.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_SYSTEM_CLOCK_H_ -#define PLATFORM_API2_SYSTEM_CLOCK_H_ - -#include - -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -class SystemClock final { - public: - // Returns the time (in milliseconds) since the system was booted, and - // includes deep sleep. This clock should be guaranteed to be monotonic, and - // should continue to tick even when the CPU is in power saving modes. - static absl::Time ElapsedRealtime(); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/thread_utils.h b/cpp/platform/api2/thread_utils.h deleted file mode 100644 index 990c0ec2..00000000 --- a/cpp/platform/api2/thread_utils.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_THREAD_UTILS_H_ -#define PLATFORM_API2_THREAD_UTILS_H_ - -#include - -#include "platform/exception.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -class ThreadUtils final { - public: - // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long) - // throws Exception::kInterrupted - static Exception Sleep(absl::Duration timeout); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_THREAD_UTILS_H_ diff --git a/cpp/platform/atomic_reference_test.cc b/cpp/platform/atomic_reference_test.cc new file mode 100644 index 00000000..58df5fc3 --- /dev/null +++ b/cpp/platform/atomic_reference_test.cc @@ -0,0 +1,80 @@ +#include "platform/api/atomic_reference.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(5); + ASSERT_EQ(p->get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createAtomicReference(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createAtomicReference< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = p->get(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + auto ref = + platform::ImplementationPlatform::createAtomicReference(s); + ASSERT_EQ(s, ref->get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h index a3ea830e..fba6ad08 100644 --- a/cpp/platform/byte_array.h +++ b/cpp/platform/byte_array.h @@ -37,7 +37,7 @@ class ByteArray { data_.assign(size, value); } - char* getData() { return data_.data(); } + char* getData() { return &data_[0]; } const char* getData() const { return data_.data(); } size_t size() const { return data_.size(); } diff --git a/cpp/platform/cancelable_alarm.cc b/cpp/platform/cancelable_alarm.cc index 326a893e..bb5fd90d 100644 --- a/cpp/platform/cancelable_alarm.cc +++ b/cpp/platform/cancelable_alarm.cc @@ -1,25 +1,27 @@ #include "platform/cancelable_alarm.h" +#include "platform/api/platform.h" +#include "platform/api/scheduled_executor.h" #include "platform/synchronized.h" namespace location { namespace nearby { -template -CancelableAlarm::CancelableAlarm( - const string &name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor) +namespace { +using Platform = platform::ImplementationPlatform; +} + +CancelableAlarm::CancelableAlarm(const std::string &name, + Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor) : name_(name), lock_(Platform::createLock()), cancelable_(scheduled_executor->schedule(runnable, delay_millis)) {} -template -CancelableAlarm::~CancelableAlarm() { - cancelable_.destroy(); -} +CancelableAlarm::~CancelableAlarm() { cancelable_.destroy(); } -template -bool CancelableAlarm::cancel() { +bool CancelableAlarm::cancel() { Synchronized s(lock_.get()); if (cancelable_.isNull()) { diff --git a/cpp/platform/cancelable_alarm.h b/cpp/platform/cancelable_alarm.h index d5549cf6..e8e317c1 100644 --- a/cpp/platform/cancelable_alarm.h +++ b/cpp/platform/cancelable_alarm.h @@ -4,6 +4,7 @@ #include #include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" #include "platform/cancelable.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -17,18 +18,17 @@ namespace nearby { * for posting a Runnable on a ScheduledExecutor and (possibly) later * canceling it. */ -template class CancelableAlarm { public: - CancelableAlarm( - const string& name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor); + CancelableAlarm(const std::string& name, Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor); ~CancelableAlarm(); bool cancel(); private: - string name_; + std::string name_; ScopedPtr > lock_; Ptr cancelable_; }; @@ -36,6 +36,4 @@ class CancelableAlarm { } // namespace nearby } // namespace location -#include "platform/cancelable_alarm.cc" - #endif // PLATFORM_CANCELABLE_ALARM_H_ diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h index 485f03a3..01b333e8 100644 --- a/cpp/platform/exception.h +++ b/cpp/platform/exception.h @@ -15,13 +15,13 @@ struct Exception { EXECUTION, // New code should use the kConstants. // Old CONSTANTS are deprecated, and should not be used. - kFailed = -1, // Initial value of Exception; any unknown error. + kFailed = -1, // Initial value of Exception; any unknown error. kSuccess = NONE, // No exception. - kIo = IO, // IO Error happened. + kIo = IO, // IO Error happened. kInterrupted = INTERRUPTED, // Operation was interrupted. kInvalidProtocolBuffer = INVALID_PROTOCOL_BUFFER, // Couldn't parse. - kExecution = EXECUTION, // Couldn't execute. - kTimeout, // Operarion did not finish within specified time. + kExecution = EXECUTION, // Couldn't execute. + kTimeout, // Operation did not finish within specified time. }; Value value {kFailed}; }; diff --git a/cpp/platform/file_impl.h b/cpp/platform/file_impl.h index db522c23..702cf7d0 100644 --- a/cpp/platform/file_impl.h +++ b/cpp/platform/file_impl.h @@ -14,7 +14,7 @@ namespace nearby { class InputFileImpl final : public InputFile { public: - explicit InputFileImpl(const std::string& path, std::int64_t size); + InputFileImpl(const std::string& path, std::int64_t size); ~InputFileImpl() override {} ExceptionOr> read(std::int64_t size) override; diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc index f1397d5c..d4a5b339 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/file_impl_test.cc @@ -41,7 +41,7 @@ class FileImplTest : public ::testing::Test { ASSERT_TRUE(bytes.result().isNull()); } - static const int64_t kMaxSize = 3; + static constexpr int64_t kMaxSize = 3; std::unique_ptr temp_path_; std::string path_; diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/default/BUILD deleted file mode 100644 index 87f28f9c..00000000 --- a/cpp/platform/impl/default/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -cc_library( - name = "default", - srcs = [ - "default_platform.cc", - ], - hdrs = [ - "default_condition_variable.h", - "default_lock.h", - "default_platform.h", - ], - visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//core:__subpackages__", - ], - deps = [ - ":condition_variable", - ":lock", - "//platform:types", - "//platform/api", - ], -) - -cc_library( - name = "lock", - srcs = ["default_lock.cc"], - hdrs = ["default_lock.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = ["//platform/api:lock"], -) - -cc_library( - name = "condition_variable", - srcs = ["default_condition_variable.cc"], - hdrs = ["default_condition_variable.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = [ - ":lock", - "//platform:types", - "//platform/api:condition_variable", - ], -) diff --git a/cpp/platform/impl/default/default_condition_variable.cc b/cpp/platform/impl/default/default_condition_variable.cc deleted file mode 100644 index d7e3811f..00000000 --- a/cpp/platform/impl/default/default_condition_variable.cc +++ /dev/null @@ -1,28 +0,0 @@ -#include "platform/impl/default/default_condition_variable.h" - -namespace location { -namespace nearby { - -DefaultConditionVariable::DefaultConditionVariable(Ptr lock) - : lock_(lock), attr_(), cond_() { - pthread_condattr_init(&attr_); - - pthread_cond_init(&cond_, &attr_); -} - -DefaultConditionVariable::~DefaultConditionVariable() { - pthread_cond_destroy(&cond_); - - pthread_condattr_destroy(&attr_); -} - -void DefaultConditionVariable::notify() { pthread_cond_broadcast(&cond_); } - -Exception::Value DefaultConditionVariable::wait() { - pthread_cond_wait(&cond_, &(lock_->mutex_)); - - return Exception::NONE; -} - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/impl/default/default_condition_variable.h b/cpp/platform/impl/default/default_condition_variable.h deleted file mode 100644 index 4aa1343f..00000000 --- a/cpp/platform/impl/default/default_condition_variable.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ - -#include - -#include "platform/api/condition_variable.h" -#include "platform/impl/default/default_lock.h" -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -class DefaultConditionVariable : public ConditionVariable { - public: - explicit DefaultConditionVariable(Ptr lock); - ~DefaultConditionVariable() override; - - void notify() override; - Exception::Value wait() override; - - private: - Ptr lock_; - pthread_condattr_t attr_; - pthread_cond_t cond_; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_platform.cc b/cpp/platform/impl/default/default_platform.cc deleted file mode 100644 index 3d41d42b..00000000 --- a/cpp/platform/impl/default/default_platform.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "platform/impl/default/default_platform.h" - -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" - -namespace location { -namespace nearby { - -Ptr DefaultPlatform::createLock() { return MakePtr(new DefaultLock()); } - -Ptr DefaultPlatform::createConditionVariable( - Ptr lock) { - return MakePtr(new DefaultConditionVariable(DowncastPtr(lock))); -} - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/impl/default/default_platform.h b/cpp/platform/impl/default/default_platform.h deleted file mode 100644 index 0d001825..00000000 --- a/cpp/platform/impl/default/default_platform.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ - -#include "platform/api/condition_variable.h" -#include "platform/api/lock.h" -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -// Provides obvious portable implementations of a subset of the hooks specified -// within //platform/api/. -// -// It's highly recommended that custom Platform implementations delegate to -// these methods unless there's a very good reason not to. -class DefaultPlatform { - public: - static Ptr createLock(); - - static Ptr createConditionVariable(Ptr lock); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ diff --git a/cpp/platform/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD index e69de29b..e043b58e 100644 --- a/cpp/platform/impl/g3/BUILD +++ b/cpp/platform/impl/g3/BUILD @@ -0,0 +1,26 @@ +cc_library( + name = "g3", + srcs = [ + "atomic_reference_impl.h", + "platform.cc", + "settable_future_impl.h", + "system_clock_impl.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform/api", + "//platform/impl/shared:atomic_boolean", + "//platform/impl/shared:posix_condition_variable", + "//platform/impl/shared:posix_lock", + "//platform/port:string", + "//absl/base:core_headers", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + ], +) diff --git a/cpp/platform/impl/g3/atomic_reference_impl.h b/cpp/platform/impl/g3/atomic_reference_impl.h new file mode 100644 index 00000000..b5f94c60 --- /dev/null +++ b/cpp/platform/impl/g3/atomic_reference_impl.h @@ -0,0 +1,39 @@ +#ifndef PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference.h" +#include "platform/ptr.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { + absl::MutexLock lock(&mutex_); + return value_; + } + void set(absl::any value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/g3/platform.cc b/cpp/platform/impl/g3/platform.cc new file mode 100644 index 00000000..b261cbe0 --- /dev/null +++ b/cpp/platform/impl/g3/platform.cc @@ -0,0 +1,137 @@ +#include "platform/api/platform.h" + +#include +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/impl/g3/atomic_reference_impl.h" +#include "platform/impl/g3/settable_future_impl.h" +#include "platform/impl/g3/system_clock_impl.h" +#include "platform/impl/shared/atomic_boolean_impl.h" +#include "platform/impl/shared/posix_condition_variable.h" +#include "platform/impl/shared/posix_lock.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr(/*new SingleThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr(/*new MultiThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr(/*new ScheduledExecutorImpl()*/); +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr(/*new CountDownLatchImpl(count)*/); +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr(/*new ThreadUtilsImpl()*/); +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr(new SystemClockImpl()); +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr(new AtomicBooleanImpl(initial_value)); +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr(/*new ServerSyncMediumImpl()*/); +} + +Ptr +ImplementationPlatform::createWebRtcSignalingMessenger( + const std::string& self_id) { + return Ptr(/*new FCMSignalingMessenger()*/); +} + +Ptr ImplementationPlatform::createLock() { + return Ptr(new PosixLock()); +} + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr(new PosixConditionVariable(lock)); +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr(/*new HashUtilsImpl()*/); +} + +std::string ImplementationPlatform::getDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/g3/settable_future_impl.h b/cpp/platform/impl/g3/settable_future_impl.h new file mode 100644 index 00000000..36e5aebf --- /dev/null +++ b/cpp/platform/impl/g3/settable_future_impl.h @@ -0,0 +1,94 @@ +#ifndef PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ + +#include + +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool setException(Exception exception) override { + absl::MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + absl::MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(&mutex_); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + absl::MutexLock lock(&mutex_); + absl::Duration timeout = absl::Milliseconds(timeout_ms); + while (!done_) { + absl::Time start_time = absl::Now(); + if (completed_.WaitWithTimeout(&mutex_, timeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = absl::Now() - start_time; + if (spent < timeout) { + timeout -= spent; + } else if (!done_) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + private: + bool SetExceptionLocked(Exception exception) { + if (!done_) { + exception_ = exception.value != Exception::kSuccess + ? exception + : Exception{Exception::kFailed}; + done_ = true; + completed_.SignalAll(); + } + return true; + } + + absl::Mutex mutex_; + absl::CondVar completed_; + bool done_{false}; + absl::any value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/impl/g3/system_clock_impl.h b/cpp/platform/impl/g3/system_clock_impl.h new file mode 100644 index 00000000..5f7d22ee --- /dev/null +++ b/cpp/platform/impl/g3/system_clock_impl.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ +#define PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ + +#include + +#include "platform/api/system_clock.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +class SystemClockImpl : public SystemClock { + public: + std::int64_t elapsedRealtime() override { + return absl::ToUnixMillis(absl::Now()); + } +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index 892ccd51..1ace2e42 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -1,10 +1,10 @@ cc_library( - name = "sample", + name = "sample_platform", srcs = [ - "sample_wifi_medium.cc", - "sample_wifi_medium.h", + "atomic_reference_impl.h", + "sample_platform.cc", + "settable_future_impl.h", ], - hdrs = ["sample_platform.h"], visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", @@ -14,7 +14,9 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", "//absl/time", + "//absl/types:any", ], ) diff --git a/cpp/platform/impl/sample/atomic_reference_impl.h b/cpp/platform/impl/sample/atomic_reference_impl.h new file mode 100644 index 00000000..8479cc52 --- /dev/null +++ b/cpp/platform/impl/sample/atomic_reference_impl.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { return {}; } + void set(absl::any value) override {} +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/sample/sample_platform.cc b/cpp/platform/impl/sample/sample_platform.cc new file mode 100644 index 00000000..dc660c46 --- /dev/null +++ b/cpp/platform/impl/sample/sample_platform.cc @@ -0,0 +1,125 @@ +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/wifi.h" +#include "platform/cancelable.h" +#include "platform/impl/sample/atomic_reference_impl.h" +#include "platform/impl/sample/settable_future_impl.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr{}; +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createWebRtcSignalingMessenger( + const std::string& self_id) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createLock() { return Ptr{}; } + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr{}; +} + +std::string ImplementationPlatform::getDeviceId() { return "sample"; } + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/sample-" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h deleted file mode 100644 index 113f4636..00000000 --- a/cpp/platform/impl/sample/sample_platform.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ - -#include - -#include "platform/api/atomic_boolean.h" -#include "platform/api/atomic_reference.h" -#include "platform/api/ble.h" -#include "platform/api/ble_v2.h" -#include "platform/api/bluetooth_adapter.h" -#include "platform/api/bluetooth_classic.h" -#include "platform/api/condition_variable.h" -#include "platform/api/count_down_latch.h" -#include "platform/api/hash_utils.h" -#include "platform/api/lock.h" -#include "platform/api/multi_thread_executor.h" -#include "platform/api/settable_future.h" -#include "platform/api/single_thread_executor.h" -#include "platform/api/system_clock.h" -#include "platform/api/thread_utils.h" -#include "platform/api/wifi.h" -#include "platform/cancelable.h" -#include "platform/impl/sample/sample_wifi_medium.h" -#include "platform/port/string.h" -#include "platform/ptr.h" -#include "platform/runnable.h" - -namespace location { -namespace nearby { -namespace sample { - -// The SamplePlatform class below shows an example of the factory functions -// and typedefs. -class SamplePlatform { - public: - class SampleSubmittableExecutor - : public SubmittableExecutor { - public: - template - Ptr > submit(Ptr > callable) { - return Ptr >(); - } - }; - - class SampleSingleThreadExecutor - : public SingleThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleMultiThreadExecutor - : public MultiThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleScheduledExecutor { - public: - Ptr schedule(Ptr runnable, - std::int64_t delay_millis) { - return Ptr(); - } - void shutdown() {} - }; - - typedef SampleSingleThreadExecutor SingleThreadExecutorType; - static Ptr createSingleThreadExecutor() { - return MakePtr(new SingleThreadExecutorType()); - } - - typedef SampleMultiThreadExecutor MultiThreadExecutorType; - static Ptr createMultiThreadExecutor( - std::int32_t max_concurrency) { - return MakePtr(new MultiThreadExecutorType()); - } - - typedef SampleScheduledExecutor ScheduledExecutorType; - static Ptr createScheduledExecutor() { - return MakePtr(new ScheduledExecutorType()); - } - - static Ptr createBluetoothAdapter() { - return Ptr(); - } - - static Ptr createWifiMedium() { - return MakePtr(new SampleWifiMedium()); - } - - static Ptr createCountDownLatch(std::int32_t count) { - return Ptr(); - } - - template - static Ptr > createSettableFuture() { - return Ptr >(); - } - - static Ptr createThreadUtils() { return Ptr(); } - - static Ptr createSystemClock() { return Ptr(); } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr > createAtomicReference(T initial_value) { - return Ptr >(); - } - - static Ptr createBluetoothClassicMedium() { - return Ptr(); - } - - static Ptr createBLEMedium() { return Ptr(); } - - static Ptr createBLEMediumV2() { return Ptr(); } - - static Ptr createLock() { return Ptr(); } - - static Ptr createConditionVariable(Ptr lock) { - return Ptr(); - } - - static Ptr createHashUtils() { return Ptr(); } - - static std::string getDeviceId() { return ""; } - - static std::string getPayloadPath(int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); - } -}; - -} // namespace sample -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ diff --git a/cpp/platform/impl/sample/settable_future_impl.h b/cpp/platform/impl/sample/settable_future_impl.h new file mode 100644 index 00000000..16f82672 --- /dev/null +++ b/cpp/platform/impl/sample/settable_future_impl.h @@ -0,0 +1,35 @@ +#ifndef PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ + +#include "platform/api/settable_future_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { return true; } + + bool setException(Exception exception) override { return true; } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + return ExceptionOr{Exception{Exception::kFailed}}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + return ExceptionOr{Exception{Exception::kFailed}}; + } +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/impl/shared/BUILD b/cpp/platform/impl/shared/BUILD new file mode 100644 index 00000000..fb1850b2 --- /dev/null +++ b/cpp/platform/impl/shared/BUILD @@ -0,0 +1,45 @@ +cc_library( + name = "posix_lock", + srcs = [ + "posix_lock.cc", + ], + hdrs = [ + "posix_lock.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + "//platform/api", + ], +) + +cc_library( + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + ":posix_lock", + "//platform:types", + "//platform/api:condition_variable", + ], +) + +cc_library( + name = "atomic_boolean", + hdrs = ["atomic_boolean_impl.h"], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = ["//platform/api"], +) diff --git a/cpp/platform/impl/shared/atomic_boolean_impl.h b/cpp/platform/impl/shared/atomic_boolean_impl.h new file mode 100644 index 00000000..8f28e64a --- /dev/null +++ b/cpp/platform/impl/shared/atomic_boolean_impl.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ +#define PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ + +#include + +#include "platform/api/atomic_boolean.h" + +namespace location { +namespace nearby { + +class AtomicBooleanImpl : public AtomicBoolean { + public: + explicit AtomicBooleanImpl(bool initial_value) : value_(initial_value) {} + ~AtomicBooleanImpl() override = default; + + // AtomicBoolean: + bool get() override { + return value_.load(); + } + + // AtomicBoolean: + void set(bool value) override { + value_.store(value); + } + + private: + std::atomic_bool value_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ diff --git a/cpp/platform/impl/shared/posix_condition_variable.cc b/cpp/platform/impl/shared/posix_condition_variable.cc new file mode 100644 index 00000000..72b4450e --- /dev/null +++ b/cpp/platform/impl/shared/posix_condition_variable.cc @@ -0,0 +1,28 @@ +#include "platform/impl/shared/posix_condition_variable.h" + +namespace location { +namespace nearby { + +PosixConditionVariable::PosixConditionVariable(Ptr lock) + : lock_(lock), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +PosixConditionVariable::~PosixConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void PosixConditionVariable::notify() { pthread_cond_broadcast(&cond_); } + +Exception::Value PosixConditionVariable::wait() { + pthread_cond_wait(&cond_, &(lock_->mutex_)); + + return Exception::kSuccess; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/shared/posix_condition_variable.h b/cpp/platform/impl/shared/posix_condition_variable.h new file mode 100644 index 00000000..ea558558 --- /dev/null +++ b/cpp/platform/impl/shared/posix_condition_variable.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ + +#include + +#include "platform/api/condition_variable.h" +#include "platform/impl/shared/posix_lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +class PosixConditionVariable : public ConditionVariable { + public: + explicit PosixConditionVariable(Ptr lock); + ~PosixConditionVariable() override; + + void notify() override; + Exception::Value wait() override; + + private: + Ptr lock_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_lock.cc b/cpp/platform/impl/shared/posix_lock.cc similarity index 55% rename from cpp/platform/impl/default/default_lock.cc rename to cpp/platform/impl/shared/posix_lock.cc index bfd3cf0b..3bb154b6 100644 --- a/cpp/platform/impl/default/default_lock.cc +++ b/cpp/platform/impl/shared/posix_lock.cc @@ -1,24 +1,24 @@ -#include "platform/impl/default/default_lock.h" +#include "platform/impl/shared/posix_lock.h" namespace location { namespace nearby { -DefaultLock::DefaultLock() : attr_(), mutex_() { +PosixLock::PosixLock() : attr_(), mutex_() { pthread_mutexattr_init(&attr_); pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex_, &attr_); } -DefaultLock::~DefaultLock() { +PosixLock::~PosixLock() { pthread_mutex_destroy(&mutex_); pthread_mutexattr_destroy(&attr_); } -void DefaultLock::lock() { pthread_mutex_lock(&mutex_); } +void PosixLock::lock() { pthread_mutex_lock(&mutex_); } -void DefaultLock::unlock() { pthread_mutex_unlock(&mutex_); } +void PosixLock::unlock() { pthread_mutex_unlock(&mutex_); } } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/default/default_lock.h b/cpp/platform/impl/shared/posix_lock.h similarity index 51% rename from cpp/platform/impl/default/default_lock.h rename to cpp/platform/impl/shared/posix_lock.h index 18d50e44..b972e7e4 100644 --- a/cpp/platform/impl/default/default_lock.h +++ b/cpp/platform/impl/shared/posix_lock.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#ifndef PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ +#define PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ #include @@ -8,16 +8,16 @@ namespace location { namespace nearby { -class DefaultLock : public Lock { +class PosixLock : public Lock { public: - DefaultLock(); - ~DefaultLock() override; + PosixLock(); + ~PosixLock() override; void lock() override; void unlock() override; private: - friend class DefaultConditionVariable; + friend class PosixConditionVariable; pthread_mutexattr_t attr_; pthread_mutex_t mutex_; @@ -26,4 +26,4 @@ class DefaultLock : public Lock { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#endif // PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ diff --git a/cpp/platform/impl/shared/sample/BUILD b/cpp/platform/impl/shared/sample/BUILD new file mode 100644 index 00000000..a1d0605f --- /dev/null +++ b/cpp/platform/impl/shared/sample/BUILD @@ -0,0 +1,22 @@ +cc_library( + name = "sample_wifi_medium", + srcs = [ + "sample_wifi_medium.cc", + ], + hdrs = [ + "sample_wifi_medium.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform/impl:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/time", + ], +) diff --git a/cpp/platform/impl/sample/sample_wifi_medium.cc b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc similarity index 98% rename from cpp/platform/impl/sample/sample_wifi_medium.cc rename to cpp/platform/impl/shared/sample/sample_wifi_medium.cc index 89b68391..fed3d1fc 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.cc +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc @@ -1,4 +1,4 @@ -#include "platform/impl/sample/sample_wifi_medium.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include diff --git a/cpp/platform/impl/sample/sample_wifi_medium.h b/cpp/platform/impl/shared/sample/sample_wifi_medium.h similarity index 90% rename from cpp/platform/impl/sample/sample_wifi_medium.h rename to cpp/platform/impl/shared/sample/sample_wifi_medium.h index e64f1b8e..688ea2d2 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.h +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#ifndef PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#define PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ #include "platform/api/wifi.h" @@ -56,4 +56,4 @@ class SampleWifiMedium : public WifiMedium { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#endif // PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ diff --git a/cpp/platform/pipe.cc b/cpp/platform/pipe.cc index e5572127..0cb6818c 100644 --- a/cpp/platform/pipe.cc +++ b/cpp/platform/pipe.cc @@ -1,19 +1,21 @@ #include "platform/pipe.h" +#include "platform/api/platform.h" #include "platform/synchronized.h" namespace location { namespace nearby { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace pipe { -template class PipeInputStream : public InputStream { public: - explicit PipeInputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeInputStream() override { - close(); - } + explicit PipeInputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeInputStream() override { close(); } ExceptionOr> read() override { return read(kChunkSize); } ExceptionOr> read(std::int64_t size) override { @@ -27,18 +29,15 @@ class PipeInputStream : public InputStream { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; - Ptr> pipe_; + Ptr pipe_; }; -template class PipeOutputStream : public OutputStream { public: - explicit PipeOutputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeOutputStream() override { - close(); - } + explicit PipeOutputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeOutputStream() override { close(); } Exception::Value write(ConstPtr data) override { // Avoid leaks. @@ -59,13 +58,12 @@ class PipeOutputStream : public OutputStream { } private: - Ptr> pipe_; + Ptr pipe_; }; } // namespace pipe -template -Pipe::Pipe() +Pipe::Pipe() : lock_(Platform::createLock()), cond_(Platform::createConditionVariable(lock_.get())), buffer_(), @@ -73,8 +71,7 @@ Pipe::Pipe() output_stream_closed_(false), read_all_chunks_(false) {} -template -Pipe::~Pipe() { +Pipe::~Pipe() { // Deallocate all the chunks still left in buffer_. for (BufferType::iterator chunk_iter = buffer_.begin(); chunk_iter != buffer_.end(); ++chunk_iter) { @@ -82,20 +79,17 @@ Pipe::~Pipe() { } } -template -Ptr Pipe::createInputStream(Ptr self) { +Ptr Pipe::createInputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeInputStream(self)); + return MakeRefCountedPtr(new pipe::PipeInputStream(self)); } -template -Ptr Pipe::createOutputStream(Ptr self) { +Ptr Pipe::createOutputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); + return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); } -template -ExceptionOr> Pipe::read(std::int64_t size) { +ExceptionOr> Pipe::read(std::int64_t size) { Synchronized s(lock_.get()); // We're done reading all the chunks that were written before the OutputStream @@ -148,15 +142,13 @@ ExceptionOr> Pipe::read(std::int64_t size) { } } -template -Exception::Value Pipe::write(ConstPtr data) { +Exception::Value Pipe::write(ConstPtr data) { Synchronized s(lock_.get()); return writeLocked(data); } -template -void Pipe::markInputStreamClosed() { +void Pipe::markInputStreamClosed() { Synchronized s(lock_.get()); input_stream_closed_ = true; @@ -165,8 +157,7 @@ void Pipe::markInputStreamClosed() { cond_->notify(); } -template -void Pipe::markOutputStreamClosed() { +void Pipe::markOutputStreamClosed() { Synchronized s(lock_.get()); // Write a sentinel null chunk before marking output_stream_closed as true. @@ -174,8 +165,7 @@ void Pipe::markOutputStreamClosed() { output_stream_closed_ = true; } -template -Exception::Value Pipe::writeLocked(ConstPtr data) { +Exception::Value Pipe::writeLocked(ConstPtr data) { // Avoid leaks. ScopedPtr> scoped_data(data); @@ -190,8 +180,7 @@ Exception::Value Pipe::writeLocked(ConstPtr data) { return Exception::NONE; } -template -bool Pipe::eitherStreamClosed() const { +bool Pipe::eitherStreamClosed() const { return input_stream_closed_ || output_stream_closed_; } diff --git a/cpp/platform/pipe.h b/cpp/platform/pipe.h index 29e06d11..4242f845 100644 --- a/cpp/platform/pipe.h +++ b/cpp/platform/pipe.h @@ -17,14 +17,11 @@ namespace nearby { namespace pipe { -template class PipeInputStream; -template class PipeOutputStream; } // namespace pipe -template class Pipe { public: Pipe(); @@ -42,9 +39,7 @@ class Pipe { // classes. ////////////////////////////////////////////////////////////////////////////// - template friend class pipe::PipeInputStream; - template friend class pipe::PipeOutputStream; ExceptionOr > read(std::int64_t size); @@ -70,6 +65,4 @@ class Pipe { } // namespace nearby } // namespace location -#include "platform/pipe.cc" - #endif // PLATFORM_PIPE_H_ diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc index 35b97a2d..8657b1c5 100644 --- a/cpp/platform/pipe_test.cc +++ b/cpp/platform/pipe_test.cc @@ -4,8 +4,7 @@ #include -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/prng.h" #include "platform/ptr.h" @@ -17,16 +16,7 @@ namespace location { namespace nearby { namespace { -class SamplePlatform { - public: - static Ptr createLock() { return MakePtr(new DefaultLock()); } - static Ptr createConditionVariable(Ptr lock) { - return MakePtr( - new DefaultConditionVariable(DowncastPtr(lock))); - } -}; - -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(PipeTest, SimpleWriteRead) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h index 6527db19..e675cac6 100644 --- a/cpp/platform/ptr.h +++ b/cpp/platform/ptr.h @@ -46,6 +46,7 @@ class Ptr { Ptr() = default; explicit Ptr(T* pointee) : ptr_(pointee) {} Ptr(const Ptr& that) = default; + Ptr(Ptr&& that) = default; Ptr(std::shared_ptr ptr) : ptr_(ptr) {} // NOLINT @@ -81,18 +82,23 @@ class Ptr { return *(this->ptr_) < *(other.ptr_); } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void destroy(bool = true) {} + void destroy(bool = true) { + // Legacy code expects isNull() to return true after destroy(). + ptr_.reset(); + } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void clear() {} + void clear() { + // Legacy code expects isNull() to return true after clear(). + ptr_.reset(); + } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_.get(); } T* get() { return ptr_.get(); } + T* get() const { return ptr_.get(); } void reset() { return ptr_.reset(); } ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") @@ -180,11 +186,12 @@ class ScopedPtr { // Accessor for the underlying Ptr. PtrType get() const { return this->ptr_; } - // Does nothing; - // this is to avoid unintended destruction of a managed pointer. // TODO(b/149938110): remove this completely. PtrType release() { - return ptr_; + // Legacy code expects isNull() to return true after release(). + PtrType ptr = std::move(ptr_); + ptr_.clear(); + return ptr; } private: @@ -252,14 +259,16 @@ ConstPtr ConstifyPtr(Ptr ptr) { // Ptr my_child_ptr = DowncastPtr(my_base_ptr); template Ptr DowncastPtr(Ptr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return Ptr(std::static_pointer_cast(base_ptr.ptr_)); } // ConstPtr counterpart to DowncastPtr(). template ConstPtr DowncastConstPtr(ConstPtr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return ConstPtr( std::static_pointer_cast(base_ptr.ptr_)); } diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc index adc73c08..622c4a60 100644 --- a/cpp/platform/ptr_test.cc +++ b/cpp/platform/ptr_test.cc @@ -116,7 +116,8 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted) { Ptr ref_counted_2 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_2); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); + ASSERT_EQ(1234, *ref_counted_1); ASSERT_EQ(1234, *ref_counted_2); } @@ -127,7 +128,7 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted_Stay_Valid) { Ptr ref_counted_3 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_3); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); ASSERT_EQ(1234, *ref_counted_2); ASSERT_EQ(1234, *ref_counted_3); } diff --git a/cpp/platform/settable_future_test.cc b/cpp/platform/settable_future_test.cc new file mode 100644 index 00000000..2de63970 --- /dev/null +++ b/cpp/platform/settable_future_test.cc @@ -0,0 +1,84 @@ +#include "platform/api/settable_future.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(SettableFutureTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(5); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), 5); +} + +TEST(SettableFutureTest, SetExceptionIsPropagated) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->setException({Exception::kIo}); + ASSERT_EQ(p->get().exception(), Exception::kIo); +} + +TEST(SettableFutureTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), TestEnum::kValue1); +} + +TEST(SettableFutureTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createSettableFuture(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), ScopedTestEnum::kValue1); +} + +TEST(SettableFutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createSettableFuture< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + BigSizedStruct v2 = p->get().result(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/api2/BUILD b/cpp/platform_v2/api/BUILD similarity index 51% rename from cpp/platform/api2/BUILD rename to cpp/platform_v2/api/BUILD index 5313b366..c9b0e5a4 100644 --- a/cpp/platform/api2/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -1,11 +1,5 @@ -package(default_visibility = [ - "//core:__subpackages__", - "//platform:__subpackages__", - "//location/nearby/setup/core:__subpackages__", -]) - cc_library( - name = "api2", + name = "api", hdrs = [ "atomic_boolean.h", "atomic_reference.h", @@ -13,53 +7,37 @@ cc_library( "ble_v2.h", "bluetooth_adapter.h", "bluetooth_classic.h", + "cancelable.h", "condition_variable.h", "count_down_latch.h", + "crypto.h", "executor.h", "future.h", - "hash_utils.h", "input_file.h", - "input_stream.h", "listenable_future.h", - "multi_thread_executor.h", "mutex.h", "output_file.h", - "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", - "single_thread_executor.h", - "socket.h", "submittable_executor.h", "system_clock.h", - "thread_utils.h", "webrtc.h", "wifi.h", + "wifi_lan.h", + ], + visibility = [ + "//platform_v2/base:__pkg__", + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__subpackages__", ], deps = [ - "//platform:types", + "//platform_v2/base", + "//absl/base:core_headers", "//absl/strings", "//absl/time", + "//absl/types:any", "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) - -cc_library( - name = "mutex", - hdrs = ["mutex.h"], - visibility = [ - "//platform:__subpackages__", - ], -) - -cc_library( - name = "condition_variable", - hdrs = ["condition_variable.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = [ - "//platform:types", - "//absl/time", - ], -) diff --git a/cpp/platform_v2/api/atomic_boolean.h b/cpp/platform_v2/api/atomic_boolean.h new file mode 100644 index 00000000..fff1bfa8 --- /dev/null +++ b/cpp/platform_v2/api/atomic_boolean.h @@ -0,0 +1,24 @@ +#ifndef PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ + +namespace location { +namespace nearby { +namespace api { + +// A boolean value that may be updated atomically. +class AtomicBoolean { + public: + virtual ~AtomicBoolean() = default; + + // Atomically read and return current value. + virtual bool Get() const = 0; + + // Atomically exchange original value with a new one. Return previous value. + virtual bool Set(bool value) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/atomic_reference.h b/cpp/platform_v2/api/atomic_reference.h similarity index 53% rename from cpp/platform/api2/atomic_reference.h rename to cpp/platform_v2/api/atomic_reference.h index 8740be0d..c6e6a3e4 100644 --- a/cpp/platform/api2/atomic_reference.h +++ b/cpp/platform_v2/api/atomic_reference.h @@ -1,8 +1,9 @@ -#ifndef PLATFORM_API2_ATOMIC_REFERENCE_H_ -#define PLATFORM_API2_ATOMIC_REFERENCE_H_ +#ifndef PLATFORM_V2_API_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_API_ATOMIC_REFERENCE_H_ namespace location { namespace nearby { +namespace api { // An object reference that may be updated atomically. // @@ -10,13 +11,16 @@ namespace nearby { template class AtomicReference { public: - virtual ~AtomicReference() {} + virtual ~AtomicReference() = default; - virtual T Get() = 0; + virtual T Get() const & = 0; + virtual T Get() && = 0; virtual void Set(const T& value) = 0; + virtual void Set(T&& value) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_ATOMIC_REFERENCE_H_ +#endif // PLATFORM_V2_API_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api2/ble.h b/cpp/platform_v2/api/ble.h similarity index 90% rename from cpp/platform/api2/ble.h rename to cpp/platform_v2/api/ble.h index 337f0717..26883dec 100644 --- a/cpp/platform/api2/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -1,14 +1,15 @@ -#ifndef PLATFORM_API2_BLE_H_ -#define PLATFORM_API2_BLE_H_ +#ifndef PLATFORM_V2_API_BLE_H_ +#define PLATFORM_V2_API_BLE_H_ -#include "platform/api2/bluetooth_classic.h" -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Opaque wrapper over a BLE peripheral. Must contain enough data about a // particular BLE device to connect to its GATT server. @@ -16,8 +17,7 @@ class BlePeripheral { public: virtual ~BlePeripheral() {} - // The returned Ptr is not owned by the caller, and can be invalidated once - // the corresponding BLEPeripheral object is destroyed. + // The returned reference lifetime matches BlePeripheral object. virtual BluetoothDevice& GetBluetoothDevice() = 0; }; @@ -105,7 +105,8 @@ class BleMedium { absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_H_ +#endif // PLATFORM_V2_API_BLE_H_ diff --git a/cpp/platform/api2/ble_v2.h b/cpp/platform_v2/api/ble_v2.h similarity index 98% rename from cpp/platform/api2/ble_v2.h rename to cpp/platform_v2/api/ble_v2.h index e0573c55..8858037e 100644 --- a/cpp/platform/api2/ble_v2.h +++ b/cpp/platform_v2/api/ble_v2.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_API2_BLE_V2_H_ -#define PLATFORM_API2_BLE_V2_H_ +#ifndef PLATFORM_V2_API_BLE_V2_H_ +#define PLATFORM_V2_API_BLE_V2_H_ #include #include @@ -9,13 +9,14 @@ #include #include -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { -namespace v2 { +namespace api { +namespace ble_v2 { // https://developer.android.com/reference/android/bluetooth/le/AdvertiseData // @@ -383,8 +384,9 @@ class BleMedium { const BleSocketLifeCycleCallback& callback) = 0; }; -} // namespace v2 +} // namespace ble_v2 +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_V2_H_ +#endif // PLATFORM_V2_API_BLE_V2_H_ diff --git a/cpp/platform/api2/bluetooth_adapter.h b/cpp/platform_v2/api/bluetooth_adapter.h similarity index 82% rename from cpp/platform/api2/bluetooth_adapter.h rename to cpp/platform_v2/api/bluetooth_adapter.h index 21171a01..a18bbef3 100644 --- a/cpp/platform/api2/bluetooth_adapter.h +++ b/cpp/platform_v2/api/bluetooth_adapter.h @@ -1,18 +1,18 @@ -#ifndef PLATFORM_API2_BLUETOOTH_ADAPTER_H_ -#define PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ -#include #include #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html class BluetoothAdapter { public: - virtual ~BluetoothAdapter() {} + virtual ~BluetoothAdapter() = default; // Eligible statuses of the BluetoothAdapter. enum class Status { @@ -25,19 +25,21 @@ class BluetoothAdapter { virtual bool SetStatus(Status status) = 0; // Returns true if the BluetoothAdapter's current status is // Status::Value::kEnabled. - virtual bool IsEnabled() = 0; + virtual bool IsEnabled() const = 0; // Scan modes of a BluetoothAdapter, as described at // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). enum class ScanMode { kUnknown, + kNone, + kConnectable, kConnectableDiscoverable, }; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() // // Returns ScanMode::kUnknown on error. - virtual ScanMode GetScanMode() = 0; + virtual ScanMode GetScanMode() const = 0; // Synchronously sets the scan mode of the adapter, and returns true if the // operation was a success. virtual bool SetScanMode(ScanMode scan_mode) = 0; @@ -49,7 +51,8 @@ class BluetoothAdapter { virtual bool SetName(absl::string_view name) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api2/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h similarity index 91% rename from cpp/platform/api2/bluetooth_classic.h rename to cpp/platform_v2/api/bluetooth_classic.h index 57de4ddc..8919dc8b 100644 --- a/cpp/platform/api2/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -1,17 +1,18 @@ -#ifndef PLATFORM_API2_BLUETOOTH_CLASSIC_H_ -#define PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ #include #include -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice { @@ -19,7 +20,7 @@ class BluetoothDevice { virtual ~BluetoothDevice() {} // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() - virtual std::string GetName() = 0; + virtual std::string GetName() const = 0; }; // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. @@ -118,7 +119,8 @@ class BluetoothClassicMedium { absl::string_view service_name, absl::string_view service_uuid) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/api/cancelable.h b/cpp/platform_v2/api/cancelable.h new file mode 100644 index 00000000..56eb5699 --- /dev/null +++ b/cpp/platform_v2/api/cancelable.h @@ -0,0 +1,21 @@ +#ifndef PLATFORM_V2_API_CANCELABLE_H_ +#define PLATFORM_V2_API_CANCELABLE_H_ + +namespace location { +namespace nearby { +namespace api { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable { + public: + virtual ~Cancelable() = default; + + virtual bool Cancel() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_CANCELABLE_H_ diff --git a/cpp/platform/api2/condition_variable.h b/cpp/platform_v2/api/condition_variable.h similarity index 75% rename from cpp/platform/api2/condition_variable.h rename to cpp/platform_v2/api/condition_variable.h index 936a3c36..d1d34c98 100644 --- a/cpp/platform/api2/condition_variable.h +++ b/cpp/platform_v2/api/condition_variable.h @@ -1,10 +1,11 @@ -#ifndef PLATFORM_API2_CONDITION_VARIABLE_H_ -#define PLATFORM_API2_CONDITION_VARIABLE_H_ +#ifndef PLATFORM_V2_API_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_API_CONDITION_VARIABLE_H_ -#include "platform/exception.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // The ConditionVariable class is a synchronization primitive that can be used // to block a thread, or multiple threads at the same time, until another thread @@ -20,7 +21,8 @@ class ConditionVariable { virtual Exception Wait() = 0; // throws Exception::kInterrupted }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_CONDITION_VARIABLE_H_ +#endif // PLATFORM_V2_API_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api2/count_down_latch.h b/cpp/platform_v2/api/count_down_latch.h similarity index 70% rename from cpp/platform/api2/count_down_latch.h rename to cpp/platform_v2/api/count_down_latch.h index ae0dfc86..7e0d407f 100644 --- a/cpp/platform/api2/count_down_latch.h +++ b/cpp/platform_v2/api/count_down_latch.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_COUNT_DOWN_LATCH_H_ -#define PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#ifndef PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ #include -#include "platform/exception.h" +#include "platform_v2/base/exception.h" #include "absl/time/time.h" namespace location { namespace nearby { +namespace api { // A synchronization aid that allows one or more threads to wait until a set of // operations being performed in other threads completes. @@ -15,7 +16,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html class CountDownLatch { public: - virtual ~CountDownLatch() {} + virtual ~CountDownLatch() = default; virtual Exception Await() = 0; // throws Exception::kInterrupted virtual ExceptionOr Await( @@ -23,7 +24,8 @@ class CountDownLatch { virtual void CountDown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#endif // PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api2/hash_utils.h b/cpp/platform_v2/api/crypto.h similarity index 50% rename from cpp/platform/api2/hash_utils.h rename to cpp/platform_v2/api/crypto.h index fab68f32..c43279b3 100644 --- a/cpp/platform/api2/hash_utils.h +++ b/cpp/platform_v2/api/crypto.h @@ -1,20 +1,24 @@ -#ifndef PLATFORM_API2_HASH_UTILS_H_ -#define PLATFORM_API2_HASH_UTILS_H_ +#ifndef PLATFORM_V2_API_CRYPTO_H_ +#define PLATFORM_V2_API_CRYPTO_H_ -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { // A provider of standard hashing algorithms. -class HashUtils { +class Crypto { public: + // Initialize global crypto state. + static void Init(); + // Return MD5 hash of input. static ByteArray Md5(absl::string_view input); + // Return SHA256 hash of input. static ByteArray Sha256(absl::string_view input); }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_HASH_UTILS_H_ +#endif // PLATFORM_V2_API_CRYPTO_H_ diff --git a/cpp/platform/api2/executor.h b/cpp/platform_v2/api/executor.h similarity index 59% rename from cpp/platform/api2/executor.h rename to cpp/platform_v2/api/executor.h index ee561894..1b390124 100644 --- a/cpp/platform/api2/executor.h +++ b/cpp/platform_v2/api/executor.h @@ -1,26 +1,28 @@ -#ifndef PLATFORM_API2_EXECUTOR_H_ -#define PLATFORM_API2_EXECUTOR_H_ +#ifndef PLATFORM_V2_API_EXECUTOR_H_ +#define PLATFORM_V2_API_EXECUTOR_H_ -#include - -#include "platform/runnable.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // This abstract class is the superclass of all classes representing an // Executor. class Executor { public: + // Before returning from destructor, executor must wait for all pending + // jobs to finish. virtual ~Executor() = default; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- - virtual void Execute(std::unique_ptr runnable) = 0; + virtual void Execute(Runnable&& runnable) = 0; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- virtual void Shutdown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_EXECUTOR_H_ +#endif // PLATFORM_V2_API_EXECUTOR_H_ diff --git a/cpp/platform/api2/future.h b/cpp/platform_v2/api/future.h similarity index 74% rename from cpp/platform/api2/future.h rename to cpp/platform_v2/api/future.h index 7f46c484..b3ec2f0f 100644 --- a/cpp/platform/api2/future.h +++ b/cpp/platform_v2/api/future.h @@ -1,11 +1,12 @@ -#ifndef PLATFORM_API2_FUTURE_H_ -#define PLATFORM_API2_FUTURE_H_ +#ifndef PLATFORM_V2_API_FUTURE_H_ +#define PLATFORM_V2_API_FUTURE_H_ -#include "platform/exception.h" -#include "absl/time/time.h" +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" namespace location { namespace nearby { +namespace api { // A Future represents the result of an asynchronous computation. // @@ -24,7 +25,8 @@ class Future { virtual ExceptionOr Get(absl::Duration timeout) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_FUTURE_H_ +#endif // PLATFORM_V2_API_FUTURE_H_ diff --git a/cpp/platform_v2/api/input_file.h b/cpp/platform_v2/api/input_file.h new file mode 100644 index 00000000..cc8730ee --- /dev/null +++ b/cpp/platform_v2/api/input_file.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_API_INPUT_FILE_H_ +#define PLATFORM_V2_API_INPUT_FILE_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" + +namespace location { +namespace nearby { +namespace api { + +// An InputFile represents a readable file on the system. +class InputFile : public InputStream { + public: + ~InputFile() override = default; + virtual std::string GetFilePath() const = 0; + virtual std::int64_t GetTotalSize() const = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_INPUT_FILE_H_ diff --git a/cpp/platform/api2/listenable_future.h b/cpp/platform_v2/api/listenable_future.h similarity index 52% rename from cpp/platform/api2/listenable_future.h rename to cpp/platform_v2/api/listenable_future.h index 2993bc88..af38e8a5 100644 --- a/cpp/platform/api2/listenable_future.h +++ b/cpp/platform_v2/api/listenable_future.h @@ -1,15 +1,17 @@ -#ifndef PLATFORM_API2_LISTENABLE_FUTURE_H_ -#define PLATFORM_API2_LISTENABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#define PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#include #include -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/exception.h" -#include "platform/runnable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // A Future that accepts completion listeners. // @@ -19,11 +21,12 @@ class ListenableFuture : public Future { public: ~ListenableFuture() override = default; - virtual void AddListener(std::unique_ptr runnable, + virtual void AddListener(Runnable runnable, Executor* executor) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_LISTENABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/mutex.h b/cpp/platform_v2/api/mutex.h new file mode 100644 index 00000000..b7ed29d6 --- /dev/null +++ b/cpp/platform_v2/api/mutex.h @@ -0,0 +1,41 @@ +#ifndef PLATFORM_V2_API_MUTEX_H_ +#define PLATFORM_V2_API_MUTEX_H_ + +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace api { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class ABSL_LOCKABLE Mutex { + public: + // Mode to pass to implementation constructor. + // kRegular - produces a regular mutex: disallows multiple locks from + // the same thread; optionally, detects double locks in + // debug mode. + // This is the default option. + // kRecursive - produces recursive mutex: allows multiple locks from the + // same thread. + // kRegularNoCheck - produces a regular mutex: disallows double locks, + // but does not check for deadlocks. + enum class Mode { + kRegular = 0, + kRecursive = 1, + kRegularNoCheck = 2, + }; + + virtual ~Mutex() {} + + virtual void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() = 0; + virtual void Unlock() ABSL_UNLOCK_FUNCTION() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_MUTEX_H_ diff --git a/cpp/platform_v2/api/output_file.h b/cpp/platform_v2/api/output_file.h new file mode 100644 index 00000000..2e694b05 --- /dev/null +++ b/cpp/platform_v2/api/output_file.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_API_OUTPUT_FILE_H_ +#define PLATFORM_V2_API_OUTPUT_FILE_H_ + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/output_stream.h" + +namespace location { +namespace nearby { +namespace api { + +// An OutputFile represents a writable file on the system. +class OutputFile : public OutputStream { + public: + ~OutputFile() override = default; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_OUTPUT_FILE_H_ diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h new file mode 100644 index 00000000..ef217692 --- /dev/null +++ b/cpp/platform_v2/api/platform.h @@ -0,0 +1,78 @@ +#ifndef PLATFORM_V2_API_PLATFORM_H_ +#define PLATFORM_V2_API_PLATFORM_H_ + +#include +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/crypto.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/system_clock.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/api/wifi_lan.h" +#include "absl/strings/string_view.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace api { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // General platform support: + // - atomic variables (boolean, and any other copyable type) + // - synchronization primitives: + // - mutex (regular, and recursive) + // - condition variable (must work with regular mutex only) + // - Future : to synchronize on Callable schduled to execute. + // - CountDownLatch : to ensure at least N threads are waiting. + static std::unique_ptr> CreateAtomicReferenceAny( + absl::any initial_value); + static std::unique_ptr> CreateSettableFutureAny(); + static std::unique_ptr CreateAtomicBoolean(bool initial_value); + static std::unique_ptr CreateCountDownLatch( + std::int32_t count); + static std::unique_ptr CreateMutex(Mutex::Mode mode); + static std::unique_ptr CreateConditionVariable( + Mutex* mutex); + + // Java-like Executors + static std::unique_ptr CreateSingleThreadExecutor(); + static std::unique_ptr CreateMultiThreadExecutor( + std::int32_t max_concurrency); + static std::unique_ptr CreateScheduledExecutor(); + + // Protocol implementations, domain-specific support + static std::unique_ptr CreateBluetoothAdapter(); + static std::unique_ptr CreateBluetoothClassicMedium(); + static std::unique_ptr CreateBleMedium(); + static std::unique_ptr CreateBleV2Medium(); + static std::unique_ptr CreateServerSyncMedium(); + static std::unique_ptr CreateWifiMedium(); + static std::unique_ptr CreateWifiLanMedium(); + static std::unique_ptr + CreateWebRtcSignalingMessenger(absl::string_view self_id); + static std::string GetDeviceId(); + static std::string GetPayloadPath(std::int64_t payload_id); +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_PLATFORM_H_ diff --git a/cpp/platform_v2/api/scheduled_executor.h b/cpp/platform_v2/api/scheduled_executor.h new file mode 100644 index 00000000..a19369e4 --- /dev/null +++ b/cpp/platform_v2/api/scheduled_executor.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace api { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public Executor { + public: + ~ScheduledExecutor() override = default; + // Cancelable is kept both in the executor context, and in the caller context. + // We want Cancelable to live until both caller and executor are done with it. + // Exclusive ownership model does not work for this case; + // using std:shared_ptr<> instead if std::unique_ptr<>. + virtual std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration duration) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/server_sync.h b/cpp/platform_v2/api/server_sync.h similarity index 90% rename from cpp/platform/api2/server_sync.h rename to cpp/platform_v2/api/server_sync.h index 47bc3aa5..4e9f1b90 100644 --- a/cpp/platform/api2/server_sync.h +++ b/cpp/platform_v2/api/server_sync.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_SERVER_SYNC_H_ -#define PLATFORM_API2_SERVER_SYNC_H_ +#ifndef PLATFORM_V2_API_SERVER_SYNC_H_ +#define PLATFORM_V2_API_SERVER_SYNC_H_ #include -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Abstraction that represents a Nearby endpoint exchanging data through // ServerSync Medium. @@ -54,7 +55,8 @@ class ServerSyncMedium { virtual void StopDiscovery(absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SERVER_SYNC_H_ +#endif // PLATFORM_V2_API_SERVER_SYNC_H_ diff --git a/cpp/platform/api2/settable_future.h b/cpp/platform_v2/api/settable_future.h similarity index 62% rename from cpp/platform/api2/settable_future.h rename to cpp/platform_v2/api/settable_future.h index 2089173c..8298bbfd 100644 --- a/cpp/platform/api2/settable_future.h +++ b/cpp/platform_v2/api/settable_future.h @@ -1,10 +1,12 @@ -#ifndef PLATFORM_API2_SETTABLE_FUTURE_H_ -#define PLATFORM_API2_SETTABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_SETTABLE_FUTURE_H_ +#define PLATFORM_V2_API_SETTABLE_FUTURE_H_ -#include "platform/api2/listenable_future.h" +#include "platform_v2/api/listenable_future.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // A SettableFuture is a type of Future whose result can be set. // @@ -15,10 +17,12 @@ class SettableFuture : public ListenableFuture { ~SettableFuture() override = default; virtual bool Set(const T& value) = 0; + virtual bool Set(T&& value) = 0; virtual bool SetException(Exception exception) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SETTABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_SETTABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/submittable_executor.h b/cpp/platform_v2/api/submittable_executor.h new file mode 100644 index 00000000..542e7fd1 --- /dev/null +++ b/cpp/platform_v2/api/submittable_executor.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/runnable.h" + +namespace location { +namespace nearby { +namespace api { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(Runnable&& wrapped_callable) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform_v2/api/system_clock.h b/cpp/platform_v2/api/system_clock.h new file mode 100644 index 00000000..c805a915 --- /dev/null +++ b/cpp/platform_v2/api/system_clock.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_API_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_API_SYSTEM_CLOCK_H_ + +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +class SystemClock final { + public: + // Initialize global system state. + static void Init(); + // Returns current absolute time. It is guaranteed to be monotonic. + static absl::Time ElapsedRealtime(); + // Pauses current thread for the specified duration. + static Exception Sleep(absl::Duration duration); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/webrtc.h b/cpp/platform_v2/api/webrtc.h similarity index 85% rename from cpp/platform/api2/webrtc.h rename to cpp/platform_v2/api/webrtc.h index e1dbde9e..ee507e9d 100644 --- a/cpp/platform/api2/webrtc.h +++ b/cpp/platform_v2/api/webrtc.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_WEBRTC_H_ -#define PLATFORM_API2_WEBRTC_H_ +#ifndef PLATFORM_V2_API_WEBRTC_H_ +#define PLATFORM_V2_API_WEBRTC_H_ #include -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { +namespace api { class WebRtcSignalingMessenger { public: @@ -40,7 +41,8 @@ class WebRtcSignalingMessenger { const IceServersListener& ice_servers_listener) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WEBRTC_H_ +#endif // PLATFORM_V2_API_WEBRTC_H_ diff --git a/cpp/platform/api2/wifi.h b/cpp/platform_v2/api/wifi.h similarity index 92% rename from cpp/platform/api2/wifi.h rename to cpp/platform_v2/api/wifi.h index 74f0e5c9..74ddb6f9 100644 --- a/cpp/platform/api2/wifi.h +++ b/cpp/platform_v2/api/wifi.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_API2_WIFI_H_ -#define PLATFORM_API2_WIFI_H_ +#ifndef PLATFORM_V2_API_WIFI_H_ +#define PLATFORM_V2_API_WIFI_H_ #include #include @@ -9,6 +9,7 @@ namespace location { namespace nearby { +namespace api { // Possible authentication types for a WiFi network. enum class WifiAuthType { @@ -34,7 +35,7 @@ enum class WifiConnectionStatus { // Represents a WiFi network found during a call to WifiMedium#scan(). class WifiScanResult { public: - virtual ~WifiScanResult() {} + virtual ~WifiScanResult() = default; // Gets the SSID of this WiFi network. virtual std::string GetSsid() const = 0; @@ -53,7 +54,7 @@ class WifiMedium { class ScanResultCallback { public: - virtual ~ScanResultCallback() {} + virtual ~ScanResultCallback() = default; virtual void OnScanResults( const std::vector& scan_results) = 0; @@ -82,7 +83,8 @@ class WifiMedium { virtual std::string GetIpAddress() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WIFI_H_ +#endif // PLATFORM_V2_API_WIFI_H_ diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h new file mode 100644 index 00000000..3b95420b --- /dev/null +++ b/cpp/platform_v2/api/wifi_lan.h @@ -0,0 +1,87 @@ +#ifndef PLATFORM_V2_API_WIFI_LAN_H_ +#define PLATFORM_V2_API_WIFI_LAN_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace api { + +// Opaque wrapper over a WifiLan service which contains encoded service name. +class WifiLanService { + public: + virtual ~WifiLanService() = default; + + virtual std::string GetName() = 0; +}; + +class WifiLanSocket { + public: + virtual ~WifiLanSocket() = default; + + // Returns the InputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetInputStream() = 0; + + // Returns the OutputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetOutputStream() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception::Value Close() = 0; + + virtual WifiLanService& GetRemoteWifiLanService() = 0; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium { + public: + virtual ~WifiLanMedium() = default; + + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; + + // Callback for WifiLan discover results. + class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(WifiLanService* wifi_lan_service) = 0; + virtual void OnServiceLost(WifiLanService* wifi_lan_service) = 0; + }; + + virtual bool StartDiscovery( + absl::string_view service_id, + DiscoveredServiceCallback* discovered_service_callback) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(WifiLanSocket* socket, + absl::string_view service_id) = 0; + }; + + virtual bool StartAcceptingConnections( + absl::string_view service_id, + AcceptedConnectionCallback* accepted_connection_callback) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; + + virtual WifiLanSocket* Connect(WifiLanService* wifi_lan_service, + absl::string_view service_id) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_WIFI_LAN_H_ diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD new file mode 100644 index 00000000..d11245eb --- /dev/null +++ b/cpp/platform_v2/base/BUILD @@ -0,0 +1,73 @@ +load("//ads/util/non_compile:non_compile.bzl", "cc_with_non_compile_test") + +cc_library( + name = "base", + srcs = [ + "base64_utils.cc", + "prng.cc", + ], + hdrs = [ + "base64_utils.h", + "byte_array.h", + "callable.h", + "exception.h", + "input_stream.h", + "listeners.h", + "output_stream.h", + "prng.h", + "runnable.h", + "socket.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + "//platform_v2/api:__subpackages__", + ], + deps = [ + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "util", + srcs = [ + "base_pipe.cc", + ], + hdrs = [ + "base_mutex_lock.h", + "base_pipe.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + ":base", + "//platform_v2/api", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "platform_base_test", + srcs = [ + "byte_array_test.cc", + "prng_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) + +cc_with_non_compile_test( + name = "exception_test", + srcs = [ + "exception_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/platform_v2/base/base64_utils.cc b/cpp/platform_v2/base/base64_utils.cc new file mode 100644 index 00000000..dfedf417 --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.cc @@ -0,0 +1,27 @@ +#include "platform_v2/base/base64_utils.h" + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { + +std::string Base64Utils::Encode(const ByteArray& bytes) { + std::string base64_string; + + absl::WebSafeBase64Escape(std::string(bytes), &base64_string); + + return base64_string; +} + +ByteArray Base64Utils::Decode(absl::string_view base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return ByteArray(); + } + + return ByteArray(decoded_string.data(), decoded_string.size()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base64_utils.h b/cpp/platform_v2/base/base64_utils.h new file mode 100644 index 00000000..a5398c4d --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_V2_BASE_BASE64_UTILS_H_ +#define PLATFORM_V2_BASE_BASE64_UTILS_H_ + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class Base64Utils { + public: + static std::string Encode(const ByteArray& bytes); + static ByteArray Decode(absl::string_view base64_string); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE64_UTILS_H_ diff --git a/cpp/platform_v2/base/base_mutex_lock.h b/cpp/platform_v2/base/base_mutex_lock.h new file mode 100644 index 00000000..e48c45cc --- /dev/null +++ b/cpp/platform_v2/base/base_mutex_lock.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ +#define PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE BaseMutexLock final { + public: + explicit BaseMutexLock(api::Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex) { + mutex_->Lock(); + } + ~BaseMutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/base/base_pipe.cc b/cpp/platform_v2/base/base_pipe.cc new file mode 100644 index 00000000..e97ace56 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.cc @@ -0,0 +1,96 @@ +#include "platform_v2/base/base_pipe.h" + +#include "platform_v2/api/platform.h" +#include "platform_v2/base/base_mutex_lock.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" + +namespace location { +namespace nearby { + +ExceptionOr BasePipe::Read(size_t size) { + BaseMutexLock lock(mutex_.get()); + + // We're done reading all the chunks that were written before the OutputStream + // was closed, so there's nothing to do here other than return an empty chunk + // to serve as an EOF indication to callers. + if (read_all_chunks_) { + return ExceptionOr{ByteArray{}}; + } + + while (buffer_.empty() && !input_stream_closed_) { + Exception wait_exception = cond_->Wait(); + + if (wait_exception.Raised()) { + return ExceptionOr{wait_exception}; + } + } + + if (input_stream_closed_) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray first_chunk{buffer_.front()}; + buffer_.pop_front(); + + // If we received our sentinel chunk, mark the fact that there cannot + // possibly be any more chunks to read here on in, and return an empty chunk + // to serve as an EOF indication to callers. + if (first_chunk.Empty()) { + read_all_chunks_ = true; + return ExceptionOr{ByteArray{}}; + } + + // If first_chunk is small enough to not overshoot the requested 'size', just + // return that. + if (first_chunk.size() <= size) { + return ExceptionOr{first_chunk}; + } else { + // Break first_chunk into 2 parts -- the first one of which (next_chunk) + // will be 'size' bytes long, and will be returned, and the second one of + // which (overflow_chunk) will be re-inserted into buffer_, at the head of + // the queue, to be served up in the next call to read(). + ByteArray next_chunk(first_chunk.data(), size); + buffer_.push_front( + ByteArray(first_chunk.data() + size, first_chunk.size() - size)); + return ExceptionOr{next_chunk}; + } +} + +Exception BasePipe::Write(const ByteArray& data) { + BaseMutexLock lock(mutex_.get()); + + return WriteLocked(data); +} + +void BasePipe::MarkInputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + input_stream_closed_ = true; + // Trigger cond_ to unblock a potentially-blocked call to read(), and to let + // it know to return Exception::IO. + cond_->Notify(); +} + +void BasePipe::MarkOutputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + // Write a sentinel null chunk before marking output_stream_closed as true. + WriteLocked(ByteArray{}); + output_stream_closed_ = true; +} + +Exception BasePipe::WriteLocked(const ByteArray& data) { + if (input_stream_closed_ || output_stream_closed_) { + return {Exception::kIo}; + } + + buffer_.push_back(data); + // Trigger cond_ to unblock a potentially-blocked call to read(), now that + // there's more data for it to consume. + cond_->Notify(); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base_pipe.h b/cpp/platform_v2/base/base_pipe.h new file mode 100644 index 00000000..f74b3646 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.h @@ -0,0 +1,128 @@ +#ifndef PLATFORM_V2_BASE_BASE_PIPE_H_ +#define PLATFORM_V2_BASE_BASE_PIPE_H_ + +#include +#include +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// Common Pipe implenentation. +// It does not depend on platform implementation, and this allows it to +// be used in the platform implementation itself. +// Concrete class must be derived from it, as follows: +// +// class DerivedPipe : public BasePipe { +// public: +// DerivedPipe() { +// auto mutex = /* construct platform-dependent mutex */; +// auto cond = /* construct platform-dependent condition variable */; +// Setup(std::move(mutex), std::move(cond)); +// } +// ~DerivedPipe() override = default; +// DerivedPipe(DerivedPipe&&) = default; +// DerivedPipe& operator=(DerivedPipe&&) = default; +// }; +class BasePipe { + public: + static constexpr const size_t kChunkSize = 64 * 1024; + virtual ~BasePipe() = default; + + // Pipe is not copyable or movable, because copy/move will invalidate + // references to input and output streams. + // If move is required, Pipe could be wrapped with std::unique_ptr<>. + BasePipe(BasePipe&&) = delete; + BasePipe& operator=(BasePipe&&) = delete; + + // Get...() methods return references to input and output steam facades. + // It is safe to call Get...() methods multiple times. + InputStream& GetInputStream() { return input_stream_; } + OutputStream& GetOutputStream() { return output_stream_; } + + protected: + BasePipe() = default; + + void Setup(std::unique_ptr mutex, + std::unique_ptr cond) { + mutex_ = std::move(mutex); + cond_ = std::move(cond); + } + + private: + class BasePipeInputStream : public InputStream { + public: + explicit BasePipeInputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeInputStream() override { DoClose(); } + + ExceptionOr Read(std::int64_t size) override { + return pipe_->Read(size); + } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkInputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + class BasePipeOutputStream : public OutputStream { + public: + explicit BasePipeOutputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeOutputStream() override { DoClose(); } + + Exception Write(const ByteArray& data) override { + return pipe_->Write(data); + } + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkOutputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + + ExceptionOr Read(size_t size) ABSL_LOCKS_EXCLUDED(mutex_); + Exception Write(const ByteArray& data) ABSL_LOCKS_EXCLUDED(mutex_); + + void MarkInputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + void MarkOutputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + + Exception WriteLocked(const ByteArray& data) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Order of declaration matters: + // - mutex must be defined before condvar; + // - input & output streams must be after both mutex and condvar. + bool input_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool output_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool read_all_chunks_ ABSL_GUARDED_BY(mutex_) = false; + + std::deque ABSL_GUARDED_BY(mutex_) buffer_; + std::unique_ptr mutex_; + std::unique_ptr cond_; + + BasePipeInputStream input_stream_{this}; + BasePipeOutputStream output_stream_{this}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_PIPE_H_ diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h new file mode 100644 index 00000000..81036f24 --- /dev/null +++ b/cpp/platform_v2/base/byte_array.h @@ -0,0 +1,81 @@ +#ifndef PLATFORM_V2_BASE_BYTE_ARRAY_H_ +#define PLATFORM_V2_BASE_BYTE_ARRAY_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class ByteArray { + public: + // Create an empty ByteArray + ByteArray() = default; + ByteArray(const ByteArray&) = default; + ByteArray& operator=(const ByteArray&) = default; + ByteArray(ByteArray&&) = default; + ByteArray& operator=(ByteArray&&) = default; + + // Create ByteArray from string. + explicit ByteArray(absl::string_view source) { data_ = source; } + + // Create default-initialized ByteArray of a given size. + explicit ByteArray(size_t size) { SetData(size); } + + // Create value-initialized ByteArray of a given size. + ByteArray(const char* data, size_t size) { SetData(data, size); } + + // Assign a new value to this ByteArray, as a copy of data, with a given size. + void SetData(const char* data, size_t size) { + if (data == nullptr) { + size = 0; + } + data_.assign(data, size); + } + + // Assign a new value of a given size to this ByteArray + // (as a repeated char value). + void SetData(size_t size, char value = 0) { data_.assign(size, value); } + + // Returns true, if changes were performed to container, false otherwise. + bool CopyAt(size_t offset, const ByteArray& from, size_t source_offset = 0) { + if (offset >= size()) return false; + if (source_offset >= from.size()) return false; + memcpy(data() + offset, from.data() + source_offset, + std::min(size() - offset, from.size() - source_offset)); + return true; + } + + char* data() { return &data_[0]; } + const char* data() const { return data_.data(); } + size_t size() const { return data_.size(); } + bool Empty() const { return data_.empty(); } + + friend bool operator==(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator!=(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator<(const ByteArray& lhs, const ByteArray& rhs); + + explicit operator std::string() const { return data_; } + + private: + std::string data_; +}; + +inline bool operator==(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ == rhs.data_; +} + +inline bool operator!=(const ByteArray& lhs, const ByteArray& rhs) { + return !(lhs == rhs); +} + +inline bool operator<(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ < rhs.data_; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BYTE_ARRAY_H_ diff --git a/cpp/platform_v2/base/byte_array_test.cc b/cpp/platform_v2/base/byte_array_test.cc new file mode 100644 index 00000000..1cc7bb37 --- /dev/null +++ b/cpp/platform_v2/base/byte_array_test.cc @@ -0,0 +1,68 @@ +#include "platform_v2/base/byte_array.h" + +#include + +#include "gtest/gtest.h" + +namespace { + +using location::nearby::ByteArray; + +TEST(ByteArrayTest, DefaultSizeIsZero) { + ByteArray bytes; + EXPECT_EQ(0, bytes.size()); +} + +TEST(ByteArrayTest, DefaultIsEmpty) { + ByteArray bytes; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, NullArrayIsEmpty) { + ByteArray bytes{nullptr, 5}; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, CopyAtDoesNotExtendArray) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + EXPECT_TRUE(v2.CopyAt(/*offset=*/5, v1)); + EXPECT_TRUE(v2.CopyAt(/*offset=*/1, v1, /*source_offset=*/3)); + EXPECT_EQ(v2, ByteArray("A45DE123")); +} + +TEST(ByteArrayTest, CopyAtOutOfBoundsIsIgnored) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + // Try to do an out-of-bounds read. + EXPECT_FALSE(v2.CopyAt(/* offset=*/5, v1, /*source_offset=*/10)); + // Try to do an out-of-bounds write. + EXPECT_FALSE(v2.CopyAt(/* offset=*/9, v1)); + EXPECT_EQ(v2, ByteArray("ABCDEFGH")); +} + +TEST(ByteArrayTest, SetFromString) { + std::string setup("setup_test"); + ByteArray bytes{setup}; // array initialized with a copy of string. + EXPECT_EQ(setup.size(), bytes.size()); + EXPECT_EQ(std::string(bytes), setup); +} + +TEST(ByteArrayTest, SetExplicitSize) { + constexpr size_t kArraySize = 10; + char reference[kArraySize]{}; + ByteArray bytes{kArraySize}; // array of size 10, zero-initialized. + EXPECT_EQ(kArraySize, bytes.size()); + EXPECT_EQ(0, memcmp(bytes.data(), reference, kArraySize)); +} + +TEST(ByteArrayTest, SetExplicitData) { + constexpr static const char message[]{"test_message"}; + constexpr size_t kMessageSize = sizeof(message); + ByteArray bytes{message, kMessageSize}; + EXPECT_EQ(kMessageSize, bytes.size()); + EXPECT_NE(message, bytes.data()); + EXPECT_EQ(0, memcmp(message, bytes.data(), kMessageSize)); +} + +} // namespace diff --git a/cpp/platform_v2/base/callable.h b/cpp/platform_v2/base/callable.h new file mode 100644 index 00000000..294c7244 --- /dev/null +++ b/cpp/platform_v2/base/callable.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_BASE_CALLABLE_H_ +#define PLATFORM_V2_BASE_CALLABLE_H_ + +#include + +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// The Callable is and object intended to be executed by a thread, that is able +// to return a value of specified type T. +// It must be invokable without arguments. It must return a value implicitly +// convertible to ExceptionOr. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html +template +using Callable = std::function()>; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_CALLABLE_H_ diff --git a/cpp/platform_v2/base/exception.h b/cpp/platform_v2/base/exception.h new file mode 100644 index 00000000..c9e73425 --- /dev/null +++ b/cpp/platform_v2/base/exception.h @@ -0,0 +1,97 @@ +#ifndef PLATFORM_V2_BASE_EXCEPTION_H_ +#define PLATFORM_V2_BASE_EXCEPTION_H_ + +#include +#include + +namespace location { +namespace nearby { + +struct Exception { + enum Value : int { + kFailed = -1, // Initial value of Exception; any unknown error. + kSuccess = 0, // No exception. + kIo = 1, // IO Error happened. + kInterrupted = 2, // Operation was interrupted. + kInvalidProtocolBuffer = 3, // Couldn't parse. + kExecution = 4, // Couldn't execute. + kTimeout = 5, // Operarion did not finish within specified time. + }; + bool Ok() const { return value == kSuccess; } + bool Raised() const { return !Ok(); } + bool Raised(Value value) const { return this->value == value; } + Value value{kFailed}; +}; + +constexpr inline bool operator==(const Exception& a, const Exception& b) { + return a.value == b.value; +} + +constexpr inline bool operator!=(const Exception& a, const Exception& b) { + return !(a == b); +} + +// ExceptionOr provides experience similar to StatusOr used in +// Google Cloud API, see: +// https://googleapis.github.io/google-cloud-cpp/0.7.0/common/status__or_8h_source.html +// +// If ok() returns true, result() is a usable return value. Otherwise, +// exception() explains why such a value is not present. +// +// A typical pattern of usage is as follows: +// +// if (!e.ok()) { +// if (Exception::EXCEPTION_TYPE_1 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_1. +// } else if (Exception::EXCEPTION_TYPE_2 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_2. +// } +// +// return; +// } +// +// // Use e.result(). +template +class ExceptionOr { + public: + ExceptionOr() = default; + explicit ExceptionOr(T&& result) + : result_{std::move(result)}, + exception_{Exception::kSuccess} {} // NOLINT + explicit ExceptionOr(const T& result) + : result_{result}, exception_{Exception::kSuccess} {} // NOLINT + ExceptionOr(Exception::Value exception) : exception_{exception} {} // NOLINT + ExceptionOr(Exception exception) : exception_{exception} {} // NOLINT + // If there exists explicit conversion from from U to T, + // then allow explicit conversion from ExceptionOr to ExceptionOr. + template ()})>> + explicit ExceptionOr(ExceptionOr value) { + if (!value.ok()) { + exception_ = value.GetException(); + } else { + result_ = T{std::move(value.result())}; + exception_ = Exception{Exception::kSuccess}; + } + } + + bool ok() const { return exception_.value == Exception::kSuccess; } + + T& result() & { return result_; } + const T& result() const& { return result_; } + T&& result() && { return std::move(result_); } + const T&& result() const&& { return std::move(result_); } + + Exception::Value exception() const { return exception_.value; } + + T GetResult() const { return result_; } + Exception GetException() const { return exception_; } + + private: + T result_{}; + Exception exception_{Exception::kFailed}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_EXCEPTION_H_ diff --git a/cpp/platform_v2/base/exception_test.cc b/cpp/platform_v2/base/exception_test.cc new file mode 100644 index 00000000..92a6cdea --- /dev/null +++ b/cpp/platform_v2/base/exception_test.cc @@ -0,0 +1,106 @@ +#include "platform_v2/base/exception.h" + +#include + +#include "platform_v2/base/exception_test.nc.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby { + +TEST(ExceptionOr, Result_Copy_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should not affect the copy. + exception_or_vector.result().clear(); + EXPECT_FALSE(copy.empty()); +} + +TEST(ExceptionOr, Result_Copy_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Reference_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should reflect in the reference. + exception_or_vector.result().clear(); + EXPECT_TRUE(reference.empty()); +} + +TEST(ExceptionOr, Result_Reference_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + const std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Move_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving the result should clear the source. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, Result_Move_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving const rvalue reference will result in a copy. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, ExplicitConversionWorks) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + explicit B(A) {} + }; + ExceptionOr a(A{}); + ExceptionOr b(a); + EXPECT_TRUE(a.ok()); + EXPECT_TRUE(b.ok()); +} + +TEST(ExceptionOr, ExplicitConversionFailsToCompile) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + }; + ExceptionOr a(A{}); + EXPECT_NON_COMPILE("no matching constructor", { ExceptionOr b(a); }); +} + +} // namespace location::nearby diff --git a/cpp/platform_v2/base/input_stream.h b/cpp/platform_v2/base/input_stream.h new file mode 100644 index 00000000..a29786f1 --- /dev/null +++ b/cpp/platform_v2/base/input_stream.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_BASE_INPUT_STREAM_H_ +#define PLATFORM_V2_BASE_INPUT_STREAM_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// An InputStream represents an input stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html +class InputStream { + public: + virtual ~InputStream() = default; + + // throws Exception::kIo + virtual ExceptionOr Read(std::int64_t size) = 0; + // throws Exception::kIo + virtual Exception Close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_INPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/listeners.h b/cpp/platform_v2/base/listeners.h new file mode 100644 index 00000000..8be7193e --- /dev/null +++ b/cpp/platform_v2/base/listeners.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_V2_BASE_LISTENERS_H_ +#define PLATFORM_V2_BASE_LISTENERS_H_ + +#include + +namespace location { +namespace nearby { + +// Provides default-initialization with a valid empty method, +// instead of nullptr. This allows partial initialization +// of a set of listeners. +template +constexpr std::function DefaultCallback() { + return std::function{[](Args...) {}}; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_LISTENERS_H_ diff --git a/cpp/platform_v2/base/output_stream.h b/cpp/platform_v2/base/output_stream.h new file mode 100644 index 00000000..f126e444 --- /dev/null +++ b/cpp/platform_v2/base/output_stream.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_V2_BASE_OUTPUT_STREAM_H_ +#define PLATFORM_V2_BASE_OUTPUT_STREAM_H_ + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// An OutputStream represents an output stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html +class OutputStream { + public: + virtual ~OutputStream() = default; + + virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo + virtual Exception Flush() = 0; // throws Exception::kIo + virtual Exception Close() = 0; // throws Exception::kIo +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_OUTPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/prng.cc b/cpp/platform_v2/base/prng.cc new file mode 100644 index 00000000..ab5c1f75 --- /dev/null +++ b/cpp/platform_v2/base/prng.cc @@ -0,0 +1,45 @@ +#include "platform_v2/base/prng.h" + +#include + +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +#define UNSIGNED_INT_BITMASK (std::numeric_limits::max()) + +Prng::Prng() { + // absl::GetCurrentTimeNanos() returns 64 bits, but srand() wants an unsigned + // int, so we may have to lose some of those 64 bits. + // + // The lower bits of the current-time-in-nanos are likely to have more entropy + // than the upper bits, so choose the former. + srand(static_cast(absl::GetCurrentTimeNanos() & + UNSIGNED_INT_BITMASK)); +} + +Prng::~Prng() { + // Nothing to do. +} + +#define RANDOM_BYTE (rand() & 0x0FF) // NOLINT + +std::int32_t Prng::NextInt32() { + return (static_cast(RANDOM_BYTE) << 24) | + (static_cast(RANDOM_BYTE) << 16) | + (static_cast(RANDOM_BYTE) << 8) | + (static_cast(RANDOM_BYTE)); +} + +std::uint32_t Prng::NextUint32() { + return static_cast(NextInt32()); +} + +std::int64_t Prng::NextInt64() { + return (static_cast(NextInt32()) << 32) | + (static_cast(NextInt32())); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/prng.h b/cpp/platform_v2/base/prng.h new file mode 100644 index 00000000..8c915b89 --- /dev/null +++ b/cpp/platform_v2/base/prng.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_BASE_PRNG_H_ +#define PLATFORM_V2_BASE_PRNG_H_ + +#include + +namespace location { +namespace nearby { + +// A (non-cryptographic) pseudo-random number generator. +class Prng { + public: + Prng(); + ~Prng(); + + std::int32_t NextInt32(); + std::uint32_t NextUint32(); + std::int64_t NextInt64(); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_PRNG_H_ diff --git a/cpp/platform_v2/base/prng_test.cc b/cpp/platform_v2/base/prng_test.cc new file mode 100644 index 00000000..c8a52065 --- /dev/null +++ b/cpp/platform_v2/base/prng_test.cc @@ -0,0 +1,27 @@ +#include "platform_v2/base/prng.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PrngTest, NextInt32) { + std::int32_t i = Prng().NextInt32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextUInt32) { + std::uint32_t i = Prng().NextUint32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextInt64) { + std::int64_t i = Prng().NextInt64(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/runnable.h b/cpp/platform_v2/base/runnable.h new file mode 100644 index 00000000..4b7a6898 --- /dev/null +++ b/cpp/platform_v2/base/runnable.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_V2_BASE_RUNNABLE_H_ +#define PLATFORM_V2_BASE_RUNNABLE_H_ + +#include + +namespace location { +namespace nearby { + +// The Runnable is an object intended to be executed by a thread. +// It must be invokable without arguments. It must return void. +// +// https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html + +using Runnable = std::function; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_RUNNABLE_H_ diff --git a/cpp/platform/api2/socket.h b/cpp/platform_v2/base/socket.h similarity index 62% rename from cpp/platform/api2/socket.h rename to cpp/platform_v2/base/socket.h index 0f855609..41415083 100644 --- a/cpp/platform/api2/socket.h +++ b/cpp/platform_v2/base/socket.h @@ -1,8 +1,8 @@ -#ifndef PLATFORM_API2_SOCKET_H_ -#define PLATFORM_API2_SOCKET_H_ +#ifndef PLATFORM_V2_BASE_SOCKET_H_ +#define PLATFORM_V2_BASE_SOCKET_H_ -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" namespace location { namespace nearby { @@ -12,7 +12,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html class Socket { public: - virtual ~Socket() {} + virtual ~Socket() = default; virtual InputStream& GetInputStream() = 0; virtual OutputStream& GetOutputStream() = 0; @@ -22,4 +22,4 @@ class Socket { } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SOCKET_H_ +#endif // PLATFORM_V2_BASE_SOCKET_H_ diff --git a/cpp/platform_v2/config/BUILD b/cpp/platform_v2/config/BUILD new file mode 100644 index 00000000..1963f538 --- /dev/null +++ b/cpp/platform_v2/config/BUILD @@ -0,0 +1,21 @@ +cc_library( + name = "config", + hdrs = [ + "config.h", + ], + visibility = [ + "//visibility:private", + ], +) + +cc_library( + name = "string", + hdrs = [ + "string.h", + ], + visibility = [ + ], + deps = [ + ":config", + ], +) diff --git a/cpp/platform_v2/config/config.h b/cpp/platform_v2/config/config.h new file mode 100644 index 00000000..2efef96b --- /dev/null +++ b/cpp/platform_v2/config/config.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_CONFIG_CONFIG_H_ +#define PLATFORM_V2_CONFIG_CONFIG_H_ + +// Clients can modify this file to customize the Nearby C++ codebase as per +// their particular constraints and environments. + +// Note: Every entry in this file should conform to the following format, to +// give precedence to command-line options (-D) that set these symbols: +// +// #ifndef XXX +// #define XXX 0/1 +// #endif + +#ifndef NEARBY_USE_STD_STRING +#define NEARBY_USE_STD_STRING 0 +#endif + +#ifndef NEARBY_USE_RTTI +#define NEARBY_USE_RTTI 1 +#endif + +#endif // PLATFORM_V2_CONFIG_CONFIG_H_ diff --git a/cpp/platform_v2/config/string.h b/cpp/platform_v2/config/string.h new file mode 100644 index 00000000..10db45ef --- /dev/null +++ b/cpp/platform_v2/config/string.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_V2_CONFIG_STRING_H_ +#define PLATFORM_V2_CONFIG_STRING_H_ + +#include + +#include "platform_v2/config/config.h" + +#if NEARBY_USE_STD_STRING +using std::string; +#endif + +#endif // PLATFORM_V2_CONFIG_STRING_H_ diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD new file mode 100644 index 00000000..8f99b81f --- /dev/null +++ b/cpp/platform_v2/impl/g3/BUILD @@ -0,0 +1,57 @@ +cc_library( + name = "g3", + srcs = [ + "atomic_boolean.h", + "atomic_reference_any.h", + "bluetooth_adapter.cc", + "bluetooth_adapter.h", + "condition_variable.h", + "count_down_latch.h", + "medium_environment.cc", + "medium_environment.h", + "multi_thread_executor.h", + "mutex.h", + "platform.cc", + "scheduled_executor.cc", + "scheduled_executor.h", + "settable_future_any.h", + "single_thread_executor.h", + "system_clock.cc", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":crypto", # build_cleaner: keep + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/impl/shared:posix_mutex", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + "//thread", + ], +) + +cc_library( + name = "crypto", + srcs = [ + "crypto.cc", + ], + visibility = [ + "//platform_v2/g3:__pkg__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//absl/strings", + "//openssl:crypto", + ], +) diff --git a/cpp/platform_v2/impl/g3/atomic_boolean.h b/cpp/platform_v2/impl/g3/atomic_boolean.h new file mode 100644 index 00000000..f43a2bcf --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_boolean.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace g3 { + +// See documentation in +// https://source.corp.google.com/piper///depot/google3/platform_v2/api/atomic_boolean.h +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/g3/atomic_reference_any.h b/cpp/platform_v2/impl/g3/atomic_reference_any.h new file mode 100644 index 00000000..c59e23c3 --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_reference_any.h @@ -0,0 +1,46 @@ +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ + +#include "platform_v2/api/atomic_reference.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace g3 { + +// Provide implementation for absl::any. +class AtomicReferenceAny : public api::AtomicReference { + public: + explicit AtomicReferenceAny(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceAny() override = default; + + absl::any Get() const & override { + absl::MutexLock lock(&mutex_); + return value_; + } + absl::any Get() && override { + absl::MutexLock lock(&mutex_); + return std::move(value_); + } + void Set(const absl::any& value) override { + absl::MutexLock lock(&mutex_); + value_ = value; + } + void Set(absl::any&& value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + mutable absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc new file mode 100644 index 00000000..16059f53 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -0,0 +1,65 @@ +#include "platform_v2/impl/g3/bluetooth_adapter.h" + +#include + +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) + : adapter_(*adapter) {} + +std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } + +bool BluetoothAdapter::SetStatus(Status status) ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + enabled_ = (status == Status::kEnabled); + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +bool BluetoothAdapter::IsEnabled() const { + absl::MutexLock lock(&mutex_); + return enabled_; +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + absl::MutexLock lock(&mutex_); + return mode_; +} + +bool BluetoothAdapter::SetScanMode(BluetoothAdapter::ScanMode mode) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + mode_ = mode; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +std::string BluetoothAdapter::GetName() const { + absl::MutexLock lock(&mutex_); + return name_; +} + +bool BluetoothAdapter::SetName(absl::string_view name) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + name_ = name; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h new file mode 100644 index 00000000..2654df4b --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -0,0 +1,90 @@ +#ifndef PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// BluetoothDevice and BluetoothAdapter have a mutual dependency. +class BluetoothAdapter; + +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice : public api::BluetoothDevice { + public: + ~BluetoothDevice() override = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const override; + BluetoothAdapter& GetAdapter(); + + private: + // Only BluetoothAdapter may instantiate BluetoothDevice. + friend class BluetoothAdapter; + + explicit BluetoothDevice(BluetoothAdapter* adapter); + + BluetoothAdapter& adapter_; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() = default; + ~BluetoothAdapter() override = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode mode) override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothDevice& GetDevice() { return device_; } + + private: + void RunOnCallbackThread(std::function runnable) { + serial_executor_.Execute(std::move(runnable)); + } + + mutable absl::Mutex mutex_; + BluetoothDevice device_{this}; + ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; + std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; + bool enabled_ ABSL_GUARDED_BY(mutex_) = false; + SingleThreadExecutor serial_executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/impl/g3/condition_variable.h b/cpp/platform_v2/impl/g3/condition_variable.h new file mode 100644 index 00000000..74ef47ed --- /dev/null +++ b/cpp/platform_v2/impl/g3/condition_variable.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/impl/g3/mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(g3::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/g3/count_down_latch.h b/cpp/platform_v2/impl/g3/count_down_latch.h new file mode 100644 index 00000000..d5b423ab --- /dev/null +++ b/cpp/platform_v2/impl/g3/count_down_latch.h @@ -0,0 +1,59 @@ +#ifndef PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ + +#include "platform_v2/api/count_down_latch.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + CountDownLatch(CountDownLatch&&) = delete; + CountDownLatch& operator=(CountDownLatch&&) = delete; + ExceptionOr Await(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + absl::Time deadline = absl::Now() + timeout; + while (count_ > 0) { + if (cond_.WaitWithDeadline(&mutex_, deadline)) { + return ExceptionOr(false); + } + } + return ExceptionOr(true); + } + Exception Await() override { + absl::MutexLock lock(&mutex_); + while (count_ > 0) { + cond_.Wait(&mutex_); + } + return {Exception::kSuccess}; + } + void CountDown() override { + absl::MutexLock lock(&mutex_); + if (count_ > 0 && --count_ == 0) { + cond_.SignalAll(); + } + } + + private: + absl::Mutex mutex_; // Mutex to be used with cond_.Wait...() method family. + absl::CondVar cond_; // Condition to synchronize up to N waiting threads. + int count_ + ABSL_GUARDED_BY(mutex_); // When zero, latch should release all waiters. +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/g3/crypto.cc b/cpp/platform_v2/impl/g3/crypto.cc new file mode 100644 index 00000000..52912f13 --- /dev/null +++ b/cpp/platform_v2/impl/g3/crypto.cc @@ -0,0 +1,39 @@ +#include "platform_v2/api/crypto.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" +#include "openssl/digest.h" + +namespace location { +namespace nearby { + +// Initialize global crypto state. +void Crypto::Init() {} + +static ByteArray Hash(absl::string_view input, const EVP_MD* algo) { + unsigned int md_out_size = EVP_MAX_MD_SIZE; + uint8_t digest_buffer[EVP_MAX_MD_SIZE]; + if (input.empty()) return {}; + + if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo, + nullptr)) + return {}; + + return ByteArray{reinterpret_cast(digest_buffer), md_out_size}; +} + +// Return MD5 hash of input. +ByteArray Crypto::Md5(absl::string_view input) { + return Hash(input, EVP_md5()); +} + +// Return SHA256 hash of input. +ByteArray Crypto::Sha256(absl::string_view input) { + return Hash(input, EVP_sha256()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.cc b/cpp/platform_v2/impl/g3/medium_environment.cc new file mode 100644 index 00000000..5512a4fc --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.cc @@ -0,0 +1,32 @@ +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +MediumEnvironment& MediumEnvironment::Instance() { + static std::aligned_storage_t + storage; + static MediumEnvironment* env = new (&storage) MediumEnvironment(); + return *env; +} + +void MediumEnvironment::Reset() { + absl::MutexLock lock(&mutex_); + bluetooth_adapters_.clear(); +} + +void MediumEnvironment::OnBluetoothAdapterChangedState( + BluetoothAdapter& adapter) { + absl::MutexLock lock(&mutex_); + // We don't care if there is an adapter already since all we store is a + // pointer. + bluetooth_adapters_.emplace(&adapter); + // TODO(apolyudov): Add event propagation code when Medium registration is + // implemented. +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.h b/cpp/platform_v2/impl/g3/medium_environment.h new file mode 100644 index 00000000..3f3f73c6 --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ +#define PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ + +#include +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// MediumEnvironment is a simulated environment which allowes multiple instances +// of simulated HW devices to "work" together as if they are physical. +// For each medium type it provides necessary methods to implement +// advertising, discovery and establishment of a data link. +class MediumEnvironment { + public: + ~MediumEnvironment() = default; + // Singleton constructor/accessor. + static MediumEnvironment& Instance(); + + // Clear state. No notifications are sent. + void Reset() ABSL_LOCKS_EXCLUDED(mutex_); + + // Add an adapter to internal container. + // Notify BluetoothClassicMediums if any that adapter state has changed. + void OnBluetoothAdapterChangedState(BluetoothAdapter& adapter) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + MediumEnvironment() = default; + absl::Mutex mutex_; + absl::flat_hash_set bluetooth_adapters_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ diff --git a/cpp/platform_v2/impl/g3/multi_thread_executor.h b/cpp/platform_v2/impl/g3/multi_thread_executor.h new file mode 100644 index 00000000..c8a32233 --- /dev/null +++ b/cpp/platform_v2/impl/g3/multi_thread_executor.h @@ -0,0 +1,54 @@ +#ifndef PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ + +#include + +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_parallelism) + : thread_pool_(max_parallelism) { + thread_pool_.StartWorkers(); + } + void Execute(Runnable&& runnable) override { + if (!shutdown_) { + thread_pool_.Schedule(std::move(runnable)); + } + } + bool DoSubmit(Runnable&& runnable) override { + if (shutdown_) return false; + thread_pool_.Schedule(std::move(runnable)); + return true; + } + void Shutdown() override { DoShutdown(); } + ~MultiThreadExecutor() override { DoShutdown(); } + + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { + if (shutdown_) return; + thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); + } + bool InShutdown() const { return shutdown_; } + + private: + void DoShutdown() { + shutdown_ = true; + } + std::atomic_bool shutdown_ = false; + ThreadPool thread_pool_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/mutex.h b/cpp/platform_v2/impl/g3/mutex.h new file mode 100644 index 00000000..a70a2f2a --- /dev/null +++ b/cpp/platform_v2/impl/g3/mutex.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_G3_MUTEX_H_ +#define PLATFORM_V2_IMPL_G3_MUTEX_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/impl/shared/posix_mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(bool check) : check_(check) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + if (!check_) mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + bool check_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex { + public: + ~RecursiveMutex() override = default; + RecursiveMutex() = default; + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MUTEX_H_ diff --git a/cpp/platform_v2/impl/g3/pipe.h b/cpp/platform_v2/impl/g3/pipe.h new file mode 100644 index 00000000..9c1c1a8b --- /dev/null +++ b/cpp/platform_v2/impl/g3/pipe.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_V2_IMPL_G3_PIPE_H_ +#define PLATFORM_V2_IMPL_G3_PIPE_H_ + +#include + +#include "platform_v2/base/base_pipe.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class Pipe : public BasePipe { + public: + Pipe() { + auto mutex = std::make_unique(/*check=*/true); + auto cond = std::make_unique(mutex.get()); + Setup(std::move(mutex), std::move(cond)); + } + ~Pipe() override = default; + Pipe(Pipe &&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_PIPE_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc new file mode 100644 index 00000000..412dbacb --- /dev/null +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -0,0 +1,136 @@ +#include "platform_v2/api/platform.h" + +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/impl/g3/atomic_boolean.h" +#include "platform_v2/impl/g3/atomic_reference_any.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "platform_v2/impl/g3/multi_thread_executor.h" +#include "platform_v2/impl/g3/mutex.h" +#include "platform_v2/impl/g3/scheduled_executor.h" +#include "platform_v2/impl/g3/settable_future_any.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/integral_types.h" +#include "absl/memory/memory.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace api { + +std::unique_ptr +ImplementationPlatform::CreateSingleThreadExecutor() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateMultiThreadExecutor(int max_concurrency) { + return absl::make_unique(max_concurrency); +} + +std::unique_ptr +ImplementationPlatform::CreateScheduledExecutor() { + return absl::make_unique(); +} + +std::unique_ptr> +ImplementationPlatform::CreateAtomicReferenceAny(absl::any initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr> +ImplementationPlatform::CreateSettableFutureAny() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothAdapter() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch( + std::int32_t count) { + return absl::make_unique(count); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( + bool initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothClassicMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateServerSyncMedium() { + return std::unique_ptr(/*new ServerSyncMediumImpl()*/); +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateWebRtcSignalingMessenger( + absl::string_view self_id) { + return std::unique_ptr( + /*new FCMSignalingMessenger()*/); +} + +std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + if (mode == Mutex::Mode::kRecursive) + return absl::make_unique(); + else + return absl::make_unique(mode == Mutex::Mode::kRegular); +} + +std::unique_ptr +ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::unique_ptr( + new g3::ConditionVariable(static_cast(mutex))); +} + +std::string ImplementationPlatform::GetDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::GetPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.cc b/cpp/platform_v2/impl/g3/scheduled_executor.cc new file mode 100644 index 00000000..1f8a3290 --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.cc @@ -0,0 +1,65 @@ +#include "platform_v2/impl/g3/scheduled_executor.h" + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +namespace { + +class ScheduledCancelable : public api::Cancelable { + public: + bool Cancel() override { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kCanceled)) { + return true; + } + } + return false; + } + bool MarkExecuted() { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kExecuted)) { + return true; + } + } + return false; + } + + private: + enum Status { + kNotRun, + kExecuted, + kCanceled, + }; + std::atomic status_ = kNotRun; +}; + +} // namespace + +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration delay) { + auto scheduled_cancelable = std::make_shared(); + if (executor_.InShutdown()) { + return scheduled_cancelable; + } + executor_.ScheduleAfter( + delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() { + if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) { + runnable(); + } + }); + return scheduled_cancelable; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.h b/cpp/platform_v2/impl/g3/scheduled_executor.h new file mode 100644 index 00000000..6c65b009 --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.h @@ -0,0 +1,42 @@ +#ifndef PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class ScheduledExecutor final : public api::ScheduledExecutor { + public: + ScheduledExecutor() = default; + ~ScheduledExecutor() override { + executor_.Shutdown(); + } + + void Execute(Runnable&& runnable) override { + executor_.Execute(std::move(runnable)); + } + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration delay) override; + void Shutdown() override { executor_.Shutdown(); } + + private: + SingleThreadExecutor executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/settable_future_any.h b/cpp/platform_v2/impl/g3/settable_future_any.h new file mode 100644 index 00000000..acb1810d --- /dev/null +++ b/cpp/platform_v2/impl/g3/settable_future_any.h @@ -0,0 +1,104 @@ +#ifndef PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ +#define PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ + +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/settable_future.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace g3 { + +class SettableFutureAny : public api::SettableFuture { + public: + SettableFutureAny() = default; + ~SettableFutureAny() override = default; + + bool Set(const absl::any& value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = value; + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool Set(absl::any&& value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool SetException(Exception exception) override { + absl::MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + void AddListener(Runnable runnable, api::Executor* executor) override {} + + ExceptionOr Get() override { + absl::MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(&mutex_); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr Get(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + while (!done_) { + absl::Time start_time = absl::Now(); + if (completed_.WaitWithTimeout(&mutex_, timeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = absl::Now() - start_time; + if (spent < timeout) { + timeout -= spent; + } else if (!done_) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + private: + bool SetExceptionLocked(Exception exception) { + if (!done_) { + exception_ = exception.value != Exception::kSuccess + ? exception + : Exception{Exception::kFailed}; + done_ = true; + completed_.SignalAll(); + } + return true; + } + + absl::Mutex mutex_; + absl::CondVar completed_; + bool done_{false}; + absl::any value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ diff --git a/cpp/platform_v2/impl/g3/single_thread_executor.h b/cpp/platform_v2/impl/g3/single_thread_executor.h new file mode 100644 index 00000000..384206d7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/single_thread_executor.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/impl/g3/multi_thread_executor.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +class SingleThreadExecutor final : public MultiThreadExecutor { + public: + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/system_clock.cc b/cpp/platform_v2/impl/g3/system_clock.cc new file mode 100644 index 00000000..2f613dd7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/system_clock.cc @@ -0,0 +1,16 @@ +#include "platform_v2/api/system_clock.h" + +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +absl::Time SystemClock::ElapsedRealtime() { return absl::Now(); } +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD new file mode 100644 index 00000000..013a0192 --- /dev/null +++ b/cpp/platform_v2/impl/shared/BUILD @@ -0,0 +1,34 @@ +cc_library( + name = "posix_mutex", + srcs = [ + "posix_mutex.cc", + ], + hdrs = [ + "posix_mutex.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + ], +) + +cc_library( + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + ":posix_mutex", + "//platform_v2/api", + "//platform_v2/base", + ], +) diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.cc b/cpp/platform_v2/impl/shared/posix_condition_variable.cc new file mode 100644 index 00000000..6d734b0f --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.cc @@ -0,0 +1,30 @@ +#include "platform_v2/impl/shared/posix_condition_variable.h" + +namespace location { +namespace nearby { +namespace posix { + +ConditionVariable::ConditionVariable(Mutex* mutex) + : mutex_(mutex), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +ConditionVariable::~ConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void ConditionVariable::Notify() { pthread_cond_broadcast(&cond_); } + +Exception ConditionVariable::Wait() { + pthread_cond_wait(&cond_, &(mutex_->mutex_)); + + return {Exception::kSuccess}; +} + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.h b/cpp/platform_v2/impl/shared/posix_condition_variable.h new file mode 100644 index 00000000..25e3e756 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ + +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(Mutex* mutex); + ~ConditionVariable() override; + + void Notify() override; + Exception Wait() override; + + private: + Mutex* mutex_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/shared/posix_mutex.cc b/cpp/platform_v2/impl/shared/posix_mutex.cc new file mode 100644 index 00000000..65cdc917 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.cc @@ -0,0 +1,26 @@ +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +Mutex::Mutex() : attr_(), mutex_() { + pthread_mutexattr_init(&attr_); + pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&mutex_, &attr_); +} + +Mutex::~Mutex() { + pthread_mutex_destroy(&mutex_); + + pthread_mutexattr_destroy(&attr_); +} + +void Mutex::Lock() { pthread_mutex_lock(&mutex_); } + +void Mutex::Unlock() { pthread_mutex_unlock(&mutex_); } + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_mutex.h b/cpp/platform_v2/impl/shared/posix_mutex.h new file mode 100644 index 00000000..01b2e1f2 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + Mutex(); + ~Mutex() override; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override; + void Unlock() ABSL_UNLOCK_FUNCTION() override; + + private: + friend class ConditionVariable; + + pthread_mutexattr_t attr_; + pthread_mutex_t mutex_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD new file mode 100644 index 00000000..5e260224 --- /dev/null +++ b/cpp/platform_v2/public/BUILD @@ -0,0 +1,86 @@ +cc_library( + name = "public", + srcs = [ + "file.cc", + "pipe.cc", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "bluetooth_adapter.h", + "cancelable.h", + "cancelable_alarm.h", + "condition_variable.h", + "count_down_latch.h", + "crypto.h", + "file.h", + "future.h", + "multi_thread_executor.h", + "mutex.h", + "mutex_lock.h", + "pipe.h", + "scheduled_executor.h", + "single_thread_executor.h", + "submittable_executor.h", + "system_clock.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/base:util", + "//absl/base:core_headers", + "//absl/strings", + "//absl/time", + "//absl/types:any", + ], +) + +cc_library( + name = "logging", + hdrs = [ + "logging.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + "//platform:logging", + ], +) + +cc_test( + name = "public_test", + srcs = [ + "atomic_boolean_test.cc", + "atomic_reference_test.cc", + "bluetooth_adapter_test.cc", + "count_down_latch_test.cc", + "crypto_test.cc", + "file_test.cc", + "future_test.cc", + "logging_test.cc", + "multi_thread_executor_test.cc", + "mutex_test.cc", + "pipe_test.cc", + "scheduled_executor_test.cc", + "single_thread_executor_test.cc", + ], + shard_count = 16, + deps = [ + ":logging", + ":public", + "//file/util:temp_path", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/public/atomic_boolean.h b/cpp/platform_v2/public/atomic_boolean.h new file mode 100644 index 00000000..08c5e833 --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean.h @@ -0,0 +1,34 @@ +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +// A boolean value that may be updated atomically. +// See documentation in +// https://source.corp.google.com/piper///depot/google3/platform_v2/api/atomic_boolean.h +class AtomicBoolean final : public api::AtomicBoolean { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicBoolean(bool value = false) + : impl_(Platform::CreateAtomicBoolean(value)) {} + ~AtomicBoolean() override = default; + AtomicBoolean(AtomicBoolean&&) = default; + AtomicBoolean& operator=(AtomicBoolean&&) = default; + + bool Get() const override { return impl_->Get(); } + bool Set(bool value) override { return impl_->Set(value); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/public/atomic_boolean_test.cc b/cpp/platform_v2/public/atomic_boolean_test.cc new file mode 100644 index 00000000..00d92d0d --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean_test.cc @@ -0,0 +1,24 @@ +#include "platform_v2/public/atomic_boolean.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(AtomicBooleanTest, SetReturnsPrevoiusValue) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Set(true)); +} + +TEST(AtomicBooleanTest, GetReturnsWhatWasSet) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Get()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/atomic_reference.h b/cpp/platform_v2/public/atomic_reference.h new file mode 100644 index 00000000..1fc02fac --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference.h @@ -0,0 +1,40 @@ +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ + +#include + +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/platform.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +template +class AtomicReference final : public api::AtomicReference { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicReference(const T& value) + : impl_(Platform::CreateAtomicReferenceAny(value)) {} + explicit AtomicReference(T&& value) + : impl_(Platform::CreateAtomicReferenceAny(std::move(value))) {} + ~AtomicReference() override = default; + AtomicReference(AtomicReference&&) = default; + AtomicReference& operator=(AtomicReference&&) = default; + + T Get() const& override { return absl::any_cast(impl_->Get()); } + T Get() && override { return absl::any_cast(std::move(impl_->Get())); } + void Set(const T& value) override { impl_->Set(absl::any(value)); } + void Set(T&& value) override { impl_->Set(absl::any(value)); } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/public/atomic_reference_test.cc b/cpp/platform_v2/public/atomic_reference_test.cc new file mode 100644 index 00000000..bd79b198 --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference_test.cc @@ -0,0 +1,75 @@ +#include "platform_v2/public/atomic_reference.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + AtomicReference atomic_ref({}); + atomic_ref.Set(5); + EXPECT_EQ(atomic_ref.Get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(TestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + AtomicReference atomic_ref({}); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + atomic_ref.Set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = atomic_ref.Get(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + AtomicReference atomic_ref({}); + atomic_ref.Set(s); + EXPECT_EQ(s, atomic_ref.Get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h new file mode 100644 index 00000000..f3b9df4e --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -0,0 +1,63 @@ +#ifndef PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/platform.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() + : impl_(api::ImplementationPlatform::CreateBluetoothAdapter()) {} + ~BluetoothAdapter() override = default; + BluetoothAdapter(BluetoothAdapter&&) = default; + BluetoothAdapter& operator=(BluetoothAdapter&&) = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override { return impl_->SetStatus(status); } + Status GetStatus() const { + return IsEnabled() ? Status::kEnabled : Status::kDisabled; + } + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override { return impl_->IsEnabled(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override { return impl_->GetScanMode(); } + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode scan_mode) override { + return impl_->SetScanMode(scan_mode); + } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override { return impl_->GetName(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override { return impl_->SetName(name); } + + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/public/bluetooth_adapter_test.cc b/cpp/platform_v2/public/bluetooth_adapter_test.cc new file mode 100644 index 00000000..3914b624 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter_test.cc @@ -0,0 +1,44 @@ +#include "platform_v2/public/bluetooth_adapter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(BluetoothAdapterTest, ConstructorDestructorWorks) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.IsValid()); +} + +TEST(BluetoothAdapterTest, CanSetName) { + constexpr char kAdapterName[] = "MyBtAdapter"; + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetName(kAdapterName)); + EXPECT_EQ(adapter.GetName(), std::string(kAdapterName)); +} + +TEST(BluetoothAdapterTest, CanSetStatus) { + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetStatus(BluetoothAdapter::Status::kEnabled)); + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kEnabled); +} + +TEST(BluetoothAdapterTest, CanSetMode) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kConnectable)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kConnectable); + EXPECT_TRUE(adapter.SetScanMode( + BluetoothAdapter::ScanMode::kConnectableDiscoverable)); + EXPECT_EQ(adapter.GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kNone)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kNone); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/cancelable.h b/cpp/platform_v2/public/cancelable.h new file mode 100644 index 00000000..3105648b --- /dev/null +++ b/cpp/platform_v2/public/cancelable.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" + +namespace location { +namespace nearby { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable final { + public: + Cancelable() = default; + Cancelable(const Cancelable&) = default; + Cancelable& operator=(const Cancelable& other) = default; + + ~Cancelable() = default; + + // This constructor is used internally only, + // by other classes in "//platform_v2/public/". + explicit Cancelable(std::shared_ptr impl) + : impl_(std::move(impl)) {} + + bool Cancel() { return impl_->Cancel(); } + + private: + std::shared_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_H_ diff --git a/cpp/platform_v2/public/cancelable_alarm.h b/cpp/platform_v2/public/cancelable_alarm.h new file mode 100644 index 00000000..1fc26788 --- /dev/null +++ b/cpp/platform_v2/public/cancelable_alarm.h @@ -0,0 +1,56 @@ +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ + +#include +#include +#include +#include + +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/scheduled_executor.h" + +namespace location { +namespace nearby { + +/** + * A cancelable alarm with a name. This is a simple wrapper around the logic + * for posting a Runnable on a ScheduledExecutor and (possibly) later + * canceling it. + */ +class CancelableAlarm { + public: + CancelableAlarm(absl::string_view name, std::function&& runnable, + absl::Duration delay, ScheduledExecutor* scheduled_executor) + : name_(name), + cancelable_(scheduled_executor->Schedule(std::move(runnable), delay)) {} + ~CancelableAlarm() = default; + CancelableAlarm(CancelableAlarm&& other) { + *this = std::move(other); + } + CancelableAlarm& operator=(CancelableAlarm&& other) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + name_ = std::move(other.name_); + cancelable_ = std::move(other.cancelable_); + } + return *this; + } + + bool Cancel() { + MutexLock lock(&mutex_); + return cancelable_.Cancel(); + } + + private: + Mutex mutex_; + std::string name_; + Cancelable cancelable_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ diff --git a/cpp/platform_v2/public/condition_variable.h b/cpp/platform_v2/public/condition_variable.h new file mode 100644 index 00000000..54f83cfe --- /dev/null +++ b/cpp/platform_v2/public/condition_variable.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" + +namespace location { +namespace nearby { + +// The ConditionVariable class is a synchronization primitive that can be used +// to block a thread, or multiple threads at the same time, until another thread +// both modifies a shared variable (the condition), and notifies the +// ConditionVariable. +class ConditionVariable final { + public: + using Platform = api::ImplementationPlatform; + explicit ConditionVariable(Mutex* mutex) + : impl_(Platform::CreateConditionVariable(mutex->impl_.get())) {} + ConditionVariable(ConditionVariable&&) = default; + ConditionVariable& operator=(ConditionVariable&&) = default; + + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + void Notify() { impl_->Notify(); } + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- + Exception Wait() { return impl_->Wait(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/public/count_down_latch.h b/cpp/platform_v2/public/count_down_latch.h new file mode 100644 index 00000000..37a76901 --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch.h @@ -0,0 +1,40 @@ +#ifndef PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ + +#include + +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final { + public: + using Platform = api::ImplementationPlatform; + explicit CountDownLatch(int count) + : impl_(Platform::CreateCountDownLatch(count)) {} + CountDownLatch(CountDownLatch&&) = default; + CountDownLatch& operator=(CountDownLatch&&) = default; + ~CountDownLatch() = default; + + Exception Await() { return impl_->Await(); } + ExceptionOr Await(absl::Duration timeout) { + return impl_->Await(timeout); + } + void CountDown() { impl_->CountDown(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/public/count_down_latch_test.cc b/cpp/platform_v2/public/count_down_latch_test.cc new file mode 100644 index 00000000..52aed3fd --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch_test.cc @@ -0,0 +1,48 @@ +#include "platform_v2/public/count_down_latch.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(CountDownLatch, ConstructorDestructorWorks) { CountDownLatch latch(1); } + +TEST(CountDownLatch, LatchAwaitCanWait) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchExtraCountDownIgnored) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + latch.CountDown(); + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchAwaitWithTimeoutCanExpire) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + auto response = latch.Await(absl::Milliseconds(100)); + EXPECT_TRUE(response.ok()); + EXPECT_FALSE(response.result()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/crypto.h b/cpp/platform_v2/public/crypto.h new file mode 100644 index 00000000..f12dc177 --- /dev/null +++ b/cpp/platform_v2/public/crypto.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_CRYPTO_H_ +#define PLATFORM_V2_PUBLIC_CRYPTO_H_ + +#include "platform_v2/api/crypto.h" + +#endif // PLATFORM_V2_PUBLIC_CRYPTO_H_ diff --git a/cpp/platform_v2/public/crypto_test.cc b/cpp/platform_v2/public/crypto_test.cc new file mode 100644 index 00000000..3499831b --- /dev/null +++ b/cpp/platform_v2/public/crypto_test.cc @@ -0,0 +1,34 @@ +#include "platform_v2/public/crypto.h" + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(CryptoTest, Md5GeneratesHash) { + const ByteArray expected_md5( + "\xb4\x5c\xff\xe0\x84\xdd\x3d\x20\xd9\x28\xbe\xe8\x5e\x7b\x0f\x21"); + ByteArray md5_hash = Crypto::Md5("string"); + EXPECT_EQ(md5_hash, expected_md5); +} + +TEST(CryptoTest, Md5ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Md5(""), ByteArray{}); +} + +TEST(CryptoTest, Sha256GeneratesHash) { + const ByteArray expected_sha256( + "\x47\x32\x87\xf8\x29\x8d\xba\x71\x63\xa8\x97\x90\x89\x58\xf7\xc0" + "\xea\xe7\x33\xe2\x5d\x2e\x02\x79\x92\xea\x2e\xdc\x9b\xed\x2f\xa8"); + ByteArray sha256_hash = Crypto::Sha256("string"); + EXPECT_EQ(sha256_hash, expected_sha256); +} + +TEST(CryptoTest, Sha256ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Sha256(""), ByteArray{}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.cc b/cpp/platform_v2/public/file.cc new file mode 100644 index 00000000..63e5bc8c --- /dev/null +++ b/cpp/platform_v2/public/file.cc @@ -0,0 +1,79 @@ +#include "platform_v2/public/file.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// InputFile + +InputFile::InputFile(const std::string& path, std::int64_t size) + : file_(path), path_(path), total_size_(size) {} + +ExceptionOr InputFile::Read(std::int64_t size) { + if (!file_.is_open()) { + return ExceptionOr{Exception::kIo}; + } + + if (file_.peek() == EOF) { + return ExceptionOr{ByteArray{}}; + } + + if (!file_.good()) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray bytes(size); + std::unique_ptr read_bytes{new char[size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr{Exception::kIo}; + } + + return ExceptionOr(ByteArray(read_bytes.get(), num_bytes_read)); +} + +Exception InputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +// OutputFile + +OutputFile::OutputFile(absl::string_view path) : file_(path) {} + +Exception OutputFile::Write(const ByteArray& data) { + if (!file_.is_open()) { + return {Exception::kIo}; + } + + if (!file_.good()) { + return {Exception::kIo}; + } + + file_.write(data.data(), data.size()); + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Flush() { + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.h b/cpp/platform_v2/public/file.h new file mode 100644 index 00000000..1f8dbce3 --- /dev/null +++ b/cpp/platform_v2/public/file.h @@ -0,0 +1,51 @@ +#ifndef PLATFORM_V2_PUBLIC_FILE_H_ +#define PLATFORM_V2_PUBLIC_FILE_H_ + +#include +#include + +#include "platform_v2/api/input_file.h" +#include "platform_v2/api/output_file.h" +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class InputFile final : public api::InputFile { + public: + explicit InputFile(const std::string& path, std::int64_t size); + ~InputFile() override = default; + InputFile(InputFile&&) = default; + InputFile& operator=(InputFile&&) = default; + + ExceptionOr Read(std::int64_t size) override; + std::string GetFilePath() const override { return path_; } + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + private: + std::ifstream file_; + std::string path_; + std::int64_t total_size_; +}; + +class OutputFile final : public api::OutputFile { + public: + explicit OutputFile(absl::string_view path); + ~OutputFile() override = default; + OutputFile(OutputFile&&) = default; + OutputFile& operator=(OutputFile&&) = default; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + std::ofstream file_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FILE_H_ diff --git a/cpp/platform_v2/public/file_test.cc b/cpp/platform_v2/public/file_test.cc new file mode 100644 index 00000000..d7d0a77d --- /dev/null +++ b/cpp/platform_v2/public/file_test.cc @@ -0,0 +1,131 @@ +#include "platform_v2/public/file.h" + +#include +#include +#include +#include + +#include "file/util/temp_path.h" +#include "platform_v2/base/byte_array.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +class FileTest : public ::testing::Test { + protected: + void SetUp() override { + temp_path_ = std::make_unique(TempPath::Local); + path_ = temp_path_->path() + "/file.txt"; + std::ofstream output_file(path_); + file_ = std::fstream(path_, std::fstream::in | std::fstream::out); + } + + void WriteToFile(const std::string& text) { + file_ << text; + file_.flush(); + size_ += text.size(); + } + + size_t GetSize() const { return size_; } + + void AssertEquals(const ExceptionOr& bytes, + const std::string& expected) { + EXPECT_TRUE(bytes.ok()); + EXPECT_EQ(std::string(bytes.result()), expected); + } + + void AssertEmpty(const ExceptionOr& bytes) { + EXPECT_TRUE(bytes.ok()); + EXPECT_TRUE(bytes.result().Empty()); + } + + static constexpr int64_t kMaxSize = 3; + + std::unique_ptr temp_path_; + std::string path_; + std::fstream file_; + size_t size_ = 0; +}; + +TEST_F(FileTest, InputFile_NonExistentPath) { + InputFile input_file("/not/a/valid/path.txt", GetSize()); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, InputFile_GetFilePath) { + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetFilePath(), path_); +} + +TEST_F(FileTest, InputFile_EmptyFileEOF) { + InputFile input_file(path_, GetSize()); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWorks) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Read(kMaxSize); + SUCCEED(); +} + +TEST_F(FileTest, InputFile_ReadUntilEOF) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWithSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(2), "ab"); + AssertEquals(input_file.Read(1), "c"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_GetTotalSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetTotalSize(), 3); + AssertEquals(input_file.Read(1), "a"); + EXPECT_EQ(input_file.GetTotalSize(), 3); +} + +TEST_F(FileTest, InputFile_Close) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Close(); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_NonExistentPath) { + OutputFile output_file("/not/a/valid/path.txt"); + ByteArray bytes("a", 1); + EXPECT_TRUE(output_file.Write(bytes).Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_Write) { + OutputFile output_file(path_); + ByteArray bytes1("a"); + ByteArray bytes2("bc"); + EXPECT_EQ(output_file.Write(bytes1), Exception{Exception::kSuccess}); + EXPECT_EQ(output_file.Write(bytes2), Exception{Exception::kSuccess}); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); +} + +TEST_F(FileTest, OutputFile_Close) { + OutputFile output_file(path_); + output_file.Close(); + ByteArray bytes("a"); + EXPECT_EQ(output_file.Write(bytes), Exception{Exception::kIo}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h new file mode 100644 index 00000000..aca9975f --- /dev/null +++ b/cpp/platform_v2/public/future.h @@ -0,0 +1,63 @@ +#ifndef PLATFORM_V2_PUBLIC_FUTURE_H_ +#define PLATFORM_V2_PUBLIC_FUTURE_H_ + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/time.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +template +class Future final : public api::SettableFuture { + public: + using Platform = api::ImplementationPlatform; + ~Future() override = default; + Future() : impl_(Platform::CreateSettableFutureAny().release()) {} + Future(Future&& other) = default; + Future& operator=(Future&& other) = default; + + void AddListener(Runnable runnable, api::Executor* executor) override { + impl_->AddListener(runnable, executor); + } + bool Set(const T& value) override { return impl_->Set(absl::any(value)); } + bool Set(T&& value) override { return impl_->Set(absl::any(value)); } + bool SetException(Exception exception) override { + return impl_->SetException(exception); + } + // throws Exception::kInterrupted, Exception::kExecution + ExceptionOr Get() override { + auto ret_val = impl_->Get(); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + ExceptionOr Get(absl::Duration timeout) override { + auto ret_val = impl_->Get(timeout); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FUTURE_H_ diff --git a/cpp/platform_v2/public/future_test.cc b/cpp/platform_v2/public/future_test.cc new file mode 100644 index 00000000..60515e36 --- /dev/null +++ b/cpp/platform_v2/public/future_test.cc @@ -0,0 +1,102 @@ +#include "platform_v2/public/future.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(FutureTest, SupportIntegralTypes) { + Future future; + future.Set(5); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), 5); +} + +TEST(FutureTest, SetExceptionIsPropagated) { + Future future; + future.SetException({Exception::kIo}); + EXPECT_EQ(future.Get().exception(), Exception::kIo); +} + +TEST(FutureTest, SupportEnum) { + Future future; + future.Set(TestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), TestEnum::kValue1); +} + +TEST(FutureTest, SupportScopedEnum) { + Future future; + future.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), ScopedTestEnum::kValue1); +} + +TEST(FutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + Future future; + v1.data[0] = 5; // Changing value before calling Set() will affect stored + v1.data[7] = 3; // value. + future.Set(v1); + v1.data[1] = 6; // Changing value after calling Set() will not affect stored + v1.data[5] = 4; // value. + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + BigSizedStruct v2 = future.Get().result(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(FutureTest, SetsExceptionOnTimeout) { + Future future; + EXPECT_EQ(future.Get(absl::Milliseconds(100)).exception(), + Exception::kTimeout); +} + +TEST(FutureTest, GetBlocksWhenNotReady) { + Future future; + SingleThreadExecutor executor; + absl::Time start = absl::Now(); + executor.Execute([&future](){ + absl::SleepFor(absl::Milliseconds(500)); + future.Set(10); + }); + auto response = future.Get(); + absl::Duration blocked_duration = absl::Now() - start; + EXPECT_EQ(response.result(), 10); + EXPECT_GE(blocked_duration, absl::Milliseconds(500)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/logging.h b/cpp/platform_v2/public/logging.h new file mode 100644 index 00000000..5a9b4767 --- /dev/null +++ b/cpp/platform_v2/public/logging.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_LOGGING_H_ +#define PLATFORM_V2_PUBLIC_LOGGING_H_ + +#include "platform/logging.h" + +#endif // PLATFORM_V2_PUBLIC_LOGGING_H_ diff --git a/cpp/platform_v2/public/logging_test.cc b/cpp/platform_v2/public/logging_test.cc new file mode 100644 index 00000000..fc010372 --- /dev/null +++ b/cpp/platform_v2/public/logging_test.cc @@ -0,0 +1,12 @@ +#include "platform_v2/public/logging.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +TEST(LoggingTest, CanLog) { + NEARBY_LOG(INFO, "message"); +} + +} diff --git a/cpp/platform_v2/public/multi_thread_executor.h b/cpp/platform_v2/public/multi_thread_executor.h new file mode 100644 index 00000000..f43ffc98 --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ + +#include "platform_v2/api/platform.h" +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- +class MultiThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + explicit MultiThreadExecutor(int max_parallelism) + : SubmittableExecutor( + Platform::CreateMultiThreadExecutor(max_parallelism)) {} + MultiThreadExecutor(MultiThreadExecutor&&) = default; + MultiThreadExecutor& operator=(MultiThreadExecutor&&) = default; + ~MultiThreadExecutor() override = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/multi_thread_executor_test.cc b/cpp/platform_v2/public/multi_thread_executor_test.cc new file mode 100644 index 00000000..914aa363 --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor_test.cc @@ -0,0 +1,94 @@ +#include "platform_v2/public/multi_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { +const int kMaxThreads = 5; +} + +TEST(MultiThreadExecutorTest, ConsructorDestructorWorks) { + MultiThreadExecutor executor(kMaxThreads); +} + +TEST(MultiThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + MultiThreadExecutor executor(kMaxThreads); + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(MultiThreadExecutorTest, JobsExecuteInParallel) { + absl::Mutex mutex; + absl::CondVar thread_cond; + absl::CondVar test_cond; + MultiThreadExecutor executor(kMaxThreads); + int count = 0; + + for (int i = 0; i < kMaxThreads; ++i) { + executor.Execute([&count, &mutex, &test_cond, &thread_cond]() { + absl::MutexLock lock(&mutex); + count++; + test_cond.Signal(); + thread_cond.Wait(&mutex); + count--; + test_cond.Signal(); + }); + } + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count < kMaxThreads) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + + EXPECT_EQ(count, kMaxThreads); + thread_cond.SignalAll(); + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count > 0) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + EXPECT_EQ(count, 0); +} + +TEST(MultiThreadExecutorTest, CanSubmit) { + MultiThreadExecutor executor(kMaxThreads); + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/mutex.h b/cpp/platform_v2/public/mutex.h new file mode 100644 index 00000000..99333a27 --- /dev/null +++ b/cpp/platform_v2/public/mutex.h @@ -0,0 +1,64 @@ +#ifndef PLATFORM_V2_PUBLIC_MUTEX_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// This is a classic mutex can be acquired at most once. +// Atttempt to acuire mutex from the same thread that is holding it will likely +// cause a deadlock. +class ABSL_LOCKABLE Mutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + explicit Mutex(bool check = true) + : impl_(Platform::CreateMutex(check ? Mode::kRegular + : Mode::kRegularNoCheck)) {} + Mutex(Mutex&&) = default; + Mutex& operator=(Mutex&&) = default; + ~Mutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class ConditionVariable; + friend class MutexLock; + std::unique_ptr impl_; +}; + +// This mutex is compatible with Java definition: +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +// This mutex may be acuired multiple times by a thread that is already holding +// it without blocking. +// It needs to be released equal number of times before any other thread could +// successfully acquire it. +class ABSL_LOCKABLE RecursiveMutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + RecursiveMutex() : impl_(Platform::CreateMutex(Mode::kRecursive)) {} + RecursiveMutex(RecursiveMutex&&) = default; + RecursiveMutex& operator=(RecursiveMutex&&) = default; + ~RecursiveMutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class MutexLock; + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_H_ diff --git a/cpp/platform_v2/public/mutex_lock.h b/cpp/platform_v2/public/mutex_lock.h new file mode 100644 index 00000000..2275ee56 --- /dev/null +++ b/cpp/platform_v2/public/mutex_lock.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/public/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE MutexLock final { + public: + explicit MutexLock(Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + explicit MutexLock(RecursiveMutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + ~MutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/public/mutex_test.cc b/cpp/platform_v2/public/mutex_test.cc new file mode 100644 index 00000000..9928f01d --- /dev/null +++ b/cpp/platform_v2/public/mutex_test.cc @@ -0,0 +1,103 @@ +#include "platform_v2/public/mutex.h" + +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +class MutexTest : public testing::Test { + public: + void VerifyStepReached(int expected) { + absl::MutexLock lock(&step_mutex_); + absl::Time deadline = absl::Now() + kTimeToWait; + while (step_ != expected) { + if (step_cond_.WaitWithDeadline(&step_mutex_, deadline)) break; + } + EXPECT_EQ(step_, expected); + // Make sure we are not progressing further. + absl::SleepFor(kTimeToWait); + EXPECT_EQ(step_, expected); + } + + protected: + SingleThreadExecutor executor_; + const absl::Duration kTimeToWait = absl::Milliseconds(200); + std::atomic_int step_ = 0; + absl::Mutex step_mutex_; + absl::CondVar step_cond_; +}; + +TEST_F(MutexTest, ConstructorDestructorWorks) { + Mutex test_mutex; + SUCCEED(); +} + +TEST_F(MutexTest, BasicLockingWorks) { + Mutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() { + step_ = 1; + step_cond_.Signal(); + test_mutex.Lock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); + }); + VerifyStepReached(1); + test_mutex.Unlock(); + VerifyStepReached(2); +} + +#ifdef THREAD_SANITIZER +TEST_F(MutexTest, DISABLED_DoubleLockIsDeadlock) +ABSL_NO_THREAD_SAFETY_ANALYSIS { +#else +TEST_F(MutexTest, DoubleLockIsDeadlock) ABSL_NO_THREAD_SAFETY_ANALYSIS { +#endif + Mutex test_mutex{/*check=*/false}; // Disable run-time deadlock detection. + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + step_ = 2; + step_cond_.Signal(); // We acquired the test lock. + test_mutex.Lock(); // Deadlock. (Main thread should save us). + step_ = 3; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor proceed to step 2. + VerifyStepReached(2); + test_mutex.Unlock(); // Bring executor out of deadlock. + VerifyStepReached(3); + test_mutex.Unlock(); // Unlock before shutdown. +} + +TEST_F(MutexTest, DoubleLockIsNotDeadlock) { + RecursiveMutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + test_mutex.Lock(); + test_mutex.Unlock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor continue. + VerifyStepReached(2); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/pipe.cc b/cpp/platform_v2/public/pipe.cc new file mode 100644 index 00000000..f9ae3b11 --- /dev/null +++ b/cpp/platform_v2/public/pipe.cc @@ -0,0 +1,21 @@ +#include "platform_v2/public/pipe.h" + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +namespace { +using Platform = api::ImplementationPlatform; +} + +Pipe::Pipe() { + auto mutex = Platform::CreateMutex(api::Mutex::Mode::kRegular); + auto cond = Platform::CreateConditionVariable(mutex.get()); + Setup(std::move(mutex), std::move(cond)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/pipe.h b/cpp/platform_v2/public/pipe.h new file mode 100644 index 00000000..a80eda19 --- /dev/null +++ b/cpp/platform_v2/public/pipe.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_PUBLIC_PIPE_H_ +#define PLATFORM_V2_PUBLIC_PIPE_H_ + +#include "platform_v2/base/base_pipe.h" + +namespace location { +namespace nearby { + +// See for details: +// TODO(apolyudov): replace with cs/ link once it becomes available. +// https://critique-ng.corp.google.com/cl/310492721/depot/google3/platform_v2/base/base_pipe.h +class Pipe final : public BasePipe { + public: + Pipe(); + ~Pipe() override = default; + Pipe(Pipe&&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_PIPE_H_ diff --git a/cpp/platform_v2/public/pipe_test.cc b/cpp/platform_v2/public/pipe_test.cc new file mode 100644 index 00000000..c8a7af89 --- /dev/null +++ b/cpp/platform_v2/public/pipe_test.cc @@ -0,0 +1,332 @@ +#include "platform_v2/public/pipe.h" + +#include + +#include +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/base/runnable.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PipeTest, ConstructorDestructorWorks) { + Pipe pipe; + SUCCEED(); +} + +TEST(PipeTest, SimpleWriteRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, WriteEndClosedBeforeRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Close the write end before the read end has even begun reading. + EXPECT_TRUE(output_stream.Close().Ok()); + + // We should still be able to read what was written. + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); + + // And after that, we should get our indication that all the data that could + // ever be read, has already been read. + read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_TRUE(read_data.result().Empty()); +} + +TEST(PipeTest, ReadEndClosedBeforeWrite) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // Close the read end before the write end has even begun writing. + EXPECT_TRUE(input_stream.Close().Ok()); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, SizedReadMoreThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Even though we ask for double of what's there in the first chunk, we should + // get back only what's there in that first chunk, and that's alright. + ExceptionOr read_data = input_stream.Read(data.size() * 2); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, SizedReadLessThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data_first_part("ABCD"); + std::string data_second_part("EFGHIJ"); + std::string data = data_first_part + data_second_part; + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // When we ask for less than what's there in the first chunk, we should get + // back exactly what we asked for, with the remainder still being available + // for the next read. + std::int64_t desired_size = data_first_part.size(); + ExceptionOr first_read_data = input_stream.Read(desired_size); + EXPECT_TRUE(first_read_data.ok()); + EXPECT_EQ(data_first_part, std::string(first_read_data.result())); + + // Now read the remainder, and get everything that ought to have been left. + ExceptionOr second_read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(second_read_data.ok()); + EXPECT_EQ(data_second_part, std::string(second_read_data.result())); +} + +TEST(PipeTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + + input_stream.Close(); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(!read_data.ok()); + EXPECT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +TEST(PipeTest, WriteAfterOutputStreamClosed) { + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + output_stream.Close(); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, RepeatedClose) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); +} + +class Thread { + public: + Thread() : thread_(), attr_(), runnable_() { + pthread_attr_init(&attr_); + pthread_attr_setdetachstate(&attr_, PTHREAD_CREATE_JOINABLE); + } + ~Thread() { pthread_attr_destroy(&attr_); } + + void Start(Runnable runnable) { + runnable_ = runnable; + + pthread_create(&thread_, &attr_, Thread::Body, this); + } + + void Join() { pthread_join(thread_, nullptr); } + + private: + static void* Body(void* args) { + reinterpret_cast(args)->runnable_(); + return nullptr; + } + + pthread_t thread_; + pthread_attr_t attr_; + Runnable runnable_; +}; + +TEST(PipeTest, ReadBlockedUntilWrite) { + using CrossThreadBool = std::atomic_bool; + + class ReaderRunnable { + public: + ReaderRunnable(InputStream* input_stream, + absl::string_view expected_read_data, + CrossThreadBool* ok_for_read_to_unblock) + : input_stream_(input_stream), + expected_read_data_(expected_read_data), + ok_for_read_to_unblock_(ok_for_read_to_unblock) {} + ~ReaderRunnable() = default; + + // Signature "void()" satisfies Runnable. + void operator()() { + ExceptionOr read_data = input_stream_->Read(Pipe::kChunkSize); + + // Make sure read() doesn't return before it's appropriate. + if (!*ok_for_read_to_unblock_) { + FAIL() << "read() unblocked before it was supposed to."; + } + + // And then run our normal set of checks to make sure the read() was + // successful. + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(expected_read_data_, std::string(read_data.result())); + } + + private: + InputStream* input_stream_; + const std::string expected_read_data_; + CrossThreadBool* ok_for_read_to_unblock_; + }; + + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // State shared between this thread (the writer) and reader_thread. + CrossThreadBool ok_for_read_to_unblock = false; + std::string data("ABCD"); + + // Kick off reader_thread. + Thread reader_thread; + reader_thread.Start( + ReaderRunnable(&pipe.GetInputStream(), data, &ok_for_read_to_unblock)); + + // Introduce a delay before we actually write anything. + absl::SleepFor(absl::Seconds(5)); + // Mark that we're done with the delay, and that the write is about to occur + // (this is slightly earlier than it ought to be, but there's no way to + // atomically set this from within the implementation of write(), and doing it + // after is too late for the purposes of this test). + ok_for_read_to_unblock = true; + + // Perform the actual write. + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // And wait for reader_thread to finish. + reader_thread.Join(); +} + +TEST(PipeTest, ConcurrentWriteAndRead) { + class BaseRunnable { + protected: + explicit BaseRunnable(const std::vector& chunks) + : chunks_(chunks), prng_() {} + virtual ~BaseRunnable() = default; + + void RandomSleep() { + // Generate a random sleep between 100 and 1000 milliseconds. + absl::SleepFor(absl::Milliseconds(BoundedUint32(100, 1000))); + } + + const std::vector& chunks_; + + private: + // Both ends of the bounds are inclusive. + std::uint32_t BoundedUint32(std::uint32_t lower_bound, + std::uint32_t upper_bound) { + return (prng_.NextUint32() % (upper_bound - lower_bound + 1)) + + lower_bound; + } + + Prng prng_; + }; + + class WriterRunnable : public BaseRunnable { + public: + WriterRunnable(OutputStream* output_stream, + const std::vector& chunks) + : BaseRunnable(chunks), output_stream_(output_stream) {} + ~WriterRunnable() override = default; + + void operator()() { + for (auto& chunk : chunks_) { + RandomSleep(); // Random pauses before each write. + EXPECT_TRUE(output_stream_->Write(ByteArray(chunk)).Ok()); + } + + RandomSleep(); // A random pause before closing the writer end. + EXPECT_TRUE(output_stream_->Close().Ok()); + } + + private: + OutputStream* output_stream_; + }; + + class ReaderRunnable : public BaseRunnable { + public: + ReaderRunnable(InputStream* input_stream, + const std::vector& chunks) + : BaseRunnable(chunks), input_stream_(input_stream) {} + ~ReaderRunnable() override = default; + + void operator()() { + // First, calculate what we expect to receive, in total. + std::string expected_data; + for (auto& chunk : chunks_) { + expected_data += chunk; + } + + // Then, start actually receiving. + std::string actual_data; + while (true) { + RandomSleep(); // Random pauses before each read. + ExceptionOr read_data = + input_stream_->Read(Pipe::kChunkSize); + if (read_data.ok()) { + ByteArray result = read_data.result(); + if (result.Empty()) { + break; // Normal exit from the read loop. + } + actual_data += std::string(result); + } else { + break; // Erroneous exit from the read loop. + } + } + + // And once we're done, check that we got everything we expected. + EXPECT_EQ(expected_data, actual_data); + } + + private: + InputStream* input_stream_; + }; + + Pipe pipe; + + std::vector chunks; + chunks.push_back("ABCD"); + chunks.push_back("EFGH"); + chunks.push_back("IJKL"); + + Thread writer_thread; + Thread reader_thread; + writer_thread.Start(WriterRunnable(&pipe.GetOutputStream(), chunks)); + reader_thread.Start(ReaderRunnable(&pipe.GetInputStream(), chunks)); + writer_thread.Join(); + reader_thread.Join(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/scheduled_executor.h b/cpp/platform_v2/public/scheduled_executor.h new file mode 100644 index 00000000..3048f16e --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor.h @@ -0,0 +1,75 @@ +#ifndef PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor final { + public: + using Platform = api::ImplementationPlatform; + + ScheduledExecutor() : impl_(Platform::CreateScheduledExecutor()) {} + ScheduledExecutor(ScheduledExecutor&& other) { *this = std::move(other); } + ~ScheduledExecutor() { + MutexLock lock(&mutex_); + DoShutdown(); + } + + ScheduledExecutor& operator=(ScheduledExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + DoShutdown(); + } + + Cancelable Schedule(Runnable&& runnable, absl::Duration duration) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return impl_ ? Cancelable(impl_->Schedule(std::move(runnable), duration)) + : Cancelable(); + } + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/scheduled_executor_test.cc b/cpp/platform_v2/public/scheduled_executor_test.cc new file mode 100644 index 00000000..9efb844a --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor_test.cc @@ -0,0 +1,100 @@ +#include "platform_v2/public/scheduled_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +TEST(ScheduledExecutorTest, ConsructorDestructorWorks) { + ScheduledExecutor executor; +} + +TEST(ScheduledExecutorTest, CanExecute) { + absl::Mutex mutex; + absl::CondVar cond; + std::atomic_bool done = false; + ScheduledExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(ScheduledExecutorTest, CanSchedule) { + ScheduledExecutor executor; + std::atomic_int value = 0; + absl::Mutex mutex; + absl::CondVar cond; + // schedule job due in 100 ms. + executor.Schedule( + [&value, &cond]() { + EXPECT_EQ(value, 1); + value = 5; + cond.Signal(); + }, + absl::Milliseconds(100)); + // schedule job due in 10 ms; must fire before the first one. + executor.Schedule( + [&value]() { + EXPECT_EQ(value, 0); + value = 1; + }, + absl::Milliseconds(10)); + { + // wait for the final job to unblock us. + absl::MutexLock lock(&mutex); + cond.WaitWithTimeout(&mutex, absl::Milliseconds(1000)); + } + EXPECT_EQ(value, 5); +} + +TEST(ScheduledExecutorTest, CanCancel) { + ScheduledExecutor executor; + std::atomic_int value = 0; + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(cancelable.Cancel()); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_EQ(value, 0); +} + +TEST(ScheduledExecutorTest, FailToCancel) { + absl::Mutex mutex; + absl::CondVar cond; + ScheduledExecutor executor; + std::atomic_int value = 0; + // Schedule job in 10ms, which will we will attempt to cancel later. + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + // schedule another job to test results of the first one, in 50ms from now. + executor.Schedule( + [&cancelable, &cond]() { + EXPECT_FALSE(cancelable.Cancel()); + // Wake up main thread. + cond.Signal(); + }, + absl::Milliseconds(50)); + { + absl::MutexLock lock(&mutex); + cond.Wait(&mutex); + } + EXPECT_EQ(value, 1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/single_thread_executor.h b/cpp/platform_v2/public/single_thread_executor.h new file mode 100644 index 00000000..d9f4e0f9 --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- +class SingleThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + SingleThreadExecutor() + : SubmittableExecutor(Platform::CreateSingleThreadExecutor()) {} + ~SingleThreadExecutor() override = default; + SingleThreadExecutor(SingleThreadExecutor&&) = default; + SingleThreadExecutor& operator=(SingleThreadExecutor&&) = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/single_thread_executor_test.cc b/cpp/platform_v2/public/single_thread_executor_test.cc new file mode 100644 index 00000000..eedbe576 --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor_test.cc @@ -0,0 +1,71 @@ +#include "platform_v2/public/single_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +TEST(SingleThreadExecutorTest, ConsructorDestructorWorks) { + SingleThreadExecutor executor; +} + +TEST(SingleThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + SingleThreadExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(SingleThreadExecutorTest, JobsExecuteInOrder) { + std::vector results; + SingleThreadExecutor executor; + + for (int i = 0; i < 10; ++i) { + executor.Execute([i, &results]() { results.push_back(i); }); + } + + absl::CondVar cond; + std::atomic_bool done = false; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); + EXPECT_EQ(results, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); +} + +TEST(SingleThreadExecutorTest, CanSubmit) { + SingleThreadExecutor executor; + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/submittable_executor.h b/cpp/platform_v2/public/submittable_executor.h new file mode 100644 index 00000000..04a0c085 --- /dev/null +++ b/cpp/platform_v2/public/submittable_executor.h @@ -0,0 +1,96 @@ +#ifndef PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ + +#include +#include +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/base/callable.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutor +// - SingleThreadExecutor +class SubmittableExecutor : public api::SubmittableExecutor { + public: + ~SubmittableExecutor() override { + MutexLock lock(&mutex_); + DoShutdown(); + } + SubmittableExecutor(SubmittableExecutor&& other) { *this = std::move(other); } + SubmittableExecutor& operator=(SubmittableExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + DoShutdown(); + } + + // Submits a callable for execution. + // When execution completes, return value is assigned to the passed future. + // Future must outlive the whole execution chain. + template + bool Submit(Callable&& callable, Future* future) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + bool submitted = DoSubmit([callable{std::move(callable)}, future]() { + ExceptionOr result = callable(); + if (result.ok()) { + future->Set(result.result()); + } else { + future->SetException({result.exception()}); + } + }); + if (!submitted) { + // complete immediately with kExecution exception value. + future->SetException({Exception::kExecution}); + } + return submitted; + } + + protected: + explicit SubmittableExecutor(std::unique_ptr impl) + : impl_(std::move(impl)) {} + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + bool DoSubmit(Runnable&& wrapped_callable) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) override { + return impl_ ? impl_->DoSubmit(std::move(wrapped_callable)) : false; + } + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/system_clock.h b/cpp/platform_v2/public/system_clock.h new file mode 100644 index 00000000..f1b95bad --- /dev/null +++ b/cpp/platform_v2/public/system_clock.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ + +#include "platform_v2/api/system_clock.h" + +#endif // PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ diff --git a/proto/BUILD b/proto/BUILD index 6446d8f9..b5d3eadb 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -40,6 +40,21 @@ java_proto_library( deps = [":discovery_enums_proto"], ) +proto_library( + name = "error_code_enums_proto", + srcs = ["error_code_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +java_lite_proto_library( + name = "error_code_enums_java_proto_lite", + deps = [":error_code_enums_proto"], +) + proto_library( name = "connections_enums_proto", srcs = ["connections_enums.proto"], @@ -156,6 +171,11 @@ java_lite_proto_library( deps = [":sharing_enums_proto"], ) +java_proto_library( + name = "sharing_enums_java_proto", + deps = [":sharing_enums_proto"], +) + proto_library( name = "nearby_event_codes_proto", srcs = ["nearby_event_codes.proto"], diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto index 9c378983..9f942b8e 100644 --- a/proto/bootstrap_enums.proto +++ b/proto/bootstrap_enums.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "BootstrapEnums"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Medium used for offline socket. enum SocketMedium { diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index b5d2901e..04f99cb7 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -236,4 +236,6 @@ message PairedKeyEncryptionFrame { message MediumMetadata { // True if local device supports 5GHz. optional bool supports_5_ghz = 1; + // WiFi Lan BSSID + optional string bssid = 2; } diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 461d312c..a7f1ff1c 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -20,6 +20,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "ConnectionsEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The type of event being logged. // Lightweight START_* and STOP_* events track instances of potential crashes diff --git a/proto/connections_enums_proto_config.asciipb b/proto/connections_enums_proto_config.asciipb index b5ea0aa5..702328c1 100644 --- a/proto/connections_enums_proto_config.asciipb +++ b/proto/connections_enums_proto_config.asciipb @@ -1,5 +1,7 @@ optimize_mode: LITE_RUNTIME allowed_enum: "location.nearby.proto.connections.Medium" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeResult" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeErrorStage" allowed_enum: "location.nearby.proto.connections.DisconnectionReason" allowed_enum: "location.nearby.proto.connections.PayloadStatus" diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index 08423b4e..7d229dce 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // NEXT ID: 132 enum DiscoveryEvent { diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto new file mode 100644 index 00000000..5e45283b --- /dev/null +++ b/proto/error_code_enums.proto @@ -0,0 +1,133 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "ErrorCodeEnums"; +option objc_class_prefix = "GNCP"; + +// The type of the error. +// It help to sort error codes to different types to analyze and also impact the +// logcat print it as Warning or Severe. +enum ErrorType { + UNKNOWN_TYPE = 0; + + // The error should not happen on production, it's like the input is null or + // not invalid or it's a unexpected API call. For example, start advertising + // with empty service ID or start advertising with the same service ID twice. + DEVELOPING = 1; + + // It’s about the device's capabilities, some devices may not support the + // feature Nearby used. E.g. The device not support BLE advertising + DEVICE = 2; + + // The failure return from the system or library API we used to communicate + // with the medium. E.g. get null OS objects or call the API but get a + // negative return value which indicates that the system does not allow to do + // that now. + SYSTEM = 3; + + // The network related failure. E.g. get an EOF exception while reading pipe + // or fail to create connection. + NETWORK = 4; + + // This may not be a failure, it can be the things we are interested in, like + // to count how many BLE advertisements the device received in a specified + // period and how many different advertisements in it, it can help us to know + // the user under a clean or dirty environment. + OTHERS = 5; +} + +// The event which the error occurs on. +enum Event { + UNKNOWN_EVENT = 0; + START_ADVERTISING = 1; + STOP_ADVERTISING = 2; + START_LISTENING_INCOMING_CONNECTION = 3; + STOP_LISTENING_INCOMING_CONNECTION = 4; + START_DISCOVERING = 5; + STOP_DISCOVERING = 6; + CONNECT = 7; + DISCONNECT = 8; + ACCEPT_CONNECTION = 9; + REJECT_CONNECTION = 10; + SEND_PAYLOAD = 11; + CANCEL_PAYLOAD = 12; + RECEIVE_PAYLOAD = 13; +} + +// The error to identify the common failure for all mediums. The range between 0 +// and 30. +enum CommonError { + UNKNOWN_ERROR = 0; + + // The common error for all mediums, the range between 0 and 30. + + // Developing error, the input with invalid format or empty. + INVALID_PARAMETER = 1; + // Device error, the BLE not available on this device. + BLE_NOT_AVAILABLE = 2; + // System error, the medium in the unexpected state, e.g. we have check the + // medium is on, after then it suddently off and cause Nearby + // Connection failed. + UNEXPECTED_MEDIUM_STATE = 3; + + // Reserved 4 to 30 +} + +// The error for event START_ADVERTISING. The range between 31 and 99. +enum StartAdvertisingError { + // Developing error, not allow to advertising fast pair model id and sharing + // fast advertisement at the same time, they are both use fast + // advertisement, and only allow 1 fast advertisement at the same time. + MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED = 31; + // System error, there's already someone advertising fast advertisement, not + // allow to start another one. + FAST_ADVERTISEMENT_ALREADY_ADVERTISED = 32; + // Developing error, this service ID already requested, should not request + // it again without stop advertising. + DUPLICATE_ADVERTISING_REQUESTED = 33; + // System error, failed to start GATT server + START_GATT_SERVER_FAILED = 34; + // System error, all advertising slot ran out, can't available for new + // regular advertisement. + BLE_MAX_GATT_ADVERTISEMENT_SLOT_REACHED = 35; + // System error, failed to start advertising for legacy advertisements + START_LEGACY_ADVERTISING_FAILED = 36; + // System error, start advertising for legacy advertisements but timed out + START_LEGACY_ADVERTISING_TIMEOUT = 37; + // System error, failed to start advertising for extended advertisements + START_EXTENDED_ADVERTISING_FAILED = 38; + // System error, start advertising for extended advertisements but timed out + START_EXTENDED_ADVERTISING_TIMEOUT = 39; + + // Next ID :40 +} + +enum Description { + UNKNOWN = 0; + NULL_SERVICE_ID = 1; + NULL_ADVERTISEMENT_BYTES = 2; + CONNECTIONS_FEATURE_DISABLED = 3; + STALE_SDK_VERSION = 4; + FEATURE_BLUETOOTH_NOT_SUPPORTED = 5; + FEATURE_BLUETOOTH_LE_NOT_SUPPORTED = 6; + NULL_BLUETOOTH_MANAGER = 7; + NULL_BLUETOOTH_ADAPTER = 8; + INVALID_FAST_PAIR_MODEL_ID = 9; + INVALID_FAST_ADVERTISEMENT_DATA = 10; + INVALID_ADVERTISEMENT_HEADER_DATA = 11; + INVALID_REGULAR_ADVERTISEMENT_DATA = 12; + NULL_BLUETOOTH_LE_ADVERTISER_COMPAT = 13; + ADVERTISE_FAILED_ALREADY_STARTED = 14; + ADVERTISE_FAILED_DATA_TOO_LARGE = 15; + ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 16; + ADVERTISE_FAILED_INTERNAL_ERROR = 17; + ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 18; + INTERRUPTED_EXCEPTION = 19; + EXECUTION_EXCEPTION = 20; +} diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 51eef973..63045db8 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "MagicPairEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Enums related to logged events. For event codes, see NearbyEventCodes. message MagicPairEvent { diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 59dc9116..36bda7dc 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyClientEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The user type that is logging. enum UserType { diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto index 91610ae5..0c6f78d0 100644 --- a/proto/nearby_event_codes.proto +++ b/proto/nearby_event_codes.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyEventCodes"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Event codes for the NEARBY log source. See: // http://google3/wireless/android/play/playlog/proto/event_code_enums.proto diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto index 2bb334ca..d821ce49 100644 --- a/proto/setup_enums.proto +++ b/proto/setup_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SetupEnums"; option objc_class_prefix = "GNSP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The type of event being logged. // Lightweight START_* and STOP_* events track instances of potential crashes diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index fd517938..98e5df0d 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SharingEnums"; option objc_class_prefix = "GNSHP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. /* We use event based logging (an event object can be constructed and logged @@ -108,6 +109,21 @@ enum EventType { // Set data usage preference. SET_DATA_USAGE = 28; + + // Receiver dismisses a fast initialization + DISMISS_FAST_INITIALIZATION = 29; + + // Cancel connection. + CANCEL_CONNECTION = 30; +} + +// Event category to differentiate whether this comes from sender or receiver, +// whether this is for communication flow, or for settings. +enum EventCategory { + UNKNOWN_EVENT_CATEGORY = 0; + SENDING_EVENT = 1; + RECEIVING_EVENT = 2; + SETTINGS_EVENT = 3; } // Status of nearby sharing. @@ -234,6 +250,7 @@ enum ServerResponseState { SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5; SERVER_RESPONSE_STATUS_UNAVAILABLE = 6; SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7; + SERVER_RESPONSE_STATUS_INVALID_ARGUMENT = 9; // For GoogleAuthException. SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8;