From 6b27a508edbce839ee1b61bda58d7a60ffc62d66 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 23 Jun 2020 19:52:26 -0700 Subject: [PATCH] nearby: snapshot of cl/317978613 Signed-off-by: Alexey Polyudov Change-Id: I4bf367877a986910bb03e1c54aa972b4bcd59942 --- cpp/core/internal/mediums/webrtc/BUILD | 8 +- cpp/core/internal/mediums/webrtc/peer_id.h | 6 +- .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.cc | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 6 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- cpp/core_v2/internal/BUILD | 40 +- cpp/core_v2/internal/base_endpoint_channel.cc | 66 +- cpp/core_v2/internal/base_endpoint_channel.h | 13 +- .../internal/base_endpoint_channel_test.cc | 30 +- cpp/core_v2/internal/base_pcp_handler.cc | 30 +- cpp/core_v2/internal/base_pcp_handler.h | 34 +- cpp/core_v2/internal/base_pcp_handler_test.cc | 64 +- cpp/core_v2/internal/ble_advertisement.cc | 142 +-- cpp/core_v2/internal/ble_advertisement.h | 2 - .../internal/ble_advertisement_test.cc | 142 ++- cpp/core_v2/internal/bluetooth_device_name.cc | 162 ++- .../internal/bluetooth_device_name_test.cc | 44 +- .../internal/bluetooth_endpoint_channel.cc | 45 + .../internal/bluetooth_endpoint_channel.h | 32 + cpp/core_v2/internal/client_proxy.cc | 63 +- cpp/core_v2/internal/client_proxy.h | 2 + .../internal/encryption_runner_test.cc | 3 +- cpp/core_v2/internal/endpoint_channel.h | 6 +- .../internal/endpoint_channel_manager.cc | 6 +- .../internal/endpoint_channel_manager.h | 10 +- cpp/core_v2/internal/endpoint_manager.cc | 62 +- cpp/core_v2/internal/endpoint_manager.h | 11 +- cpp/core_v2/internal/endpoint_manager_test.cc | 5 +- cpp/core_v2/internal/internal_payload.cc | 18 + cpp/core_v2/internal/internal_payload.h | 81 ++ .../internal/internal_payload_factory.cc | 279 +++++ .../internal/internal_payload_factory.h | 24 + .../internal/internal_payload_factory_test.cc | 116 ++ cpp/core_v2/internal/mediums/BUILD | 12 + .../internal/mediums/ble_advertisement.cc | 104 +- .../internal/mediums/ble_advertisement.h | 3 - .../mediums/ble_advertisement_header.cc | 51 +- .../mediums/ble_advertisement_header_test.cc | 47 +- .../mediums/ble_advertisement_test.cc | 58 +- cpp/core_v2/internal/mediums/ble_packet.cc | 22 +- .../internal/mediums/ble_packet_test.cc | 22 +- .../internal/mediums/ble_peripheral_test.cc | 4 +- .../internal/mediums/bloom_filter_test.cc | 2 +- .../mediums/bluetooth_classic_test.cc | 2 + cpp/core_v2/internal/mediums/mediums.cc | 4 + cpp/core_v2/internal/mediums/mediums.h | 6 + cpp/core_v2/internal/mediums/uuid_test.cc | 2 +- cpp/core_v2/internal/mediums/webrtc.cc | 448 +++++++ cpp/core_v2/internal/mediums/webrtc.h | 155 +++ cpp/core_v2/internal/mediums/webrtc/BUILD | 71 +- .../mediums/webrtc/connection_flow.cc | 298 ++++- .../internal/mediums/webrtc/connection_flow.h | 81 +- .../mediums/webrtc/connection_flow_test.cc | 166 ++- .../mediums/webrtc/data_channel_listener.h | 4 +- .../webrtc/data_channel_observer_impl.cc | 28 + .../webrtc/data_channel_observer_impl.h | 35 + .../webrtc/local_ice_candidate_listener.h | 2 +- .../webrtc/peer_connection_observer_impl.cc | 8 +- .../webrtc/peer_connection_observer_impl.h | 7 +- .../internal/mediums/webrtc/peer_id.cc | 2 + cpp/core_v2/internal/mediums/webrtc/peer_id.h | 11 +- .../webrtc/session_description_wrapper.h | 50 + .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.cc | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 6 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- .../mediums/webrtc/webrtc_socket_wrapper.h | 49 + cpp/core_v2/internal/mediums/webrtc_test.cc | 121 ++ cpp/core_v2/internal/mediums/wifi_lan.cc | 230 ++++ cpp/core_v2/internal/mediums/wifi_lan.h | 118 ++ cpp/core_v2/internal/mediums/wifi_lan_test.cc | 50 + cpp/core_v2/internal/offline_frames.cc | 2 +- cpp/core_v2/internal/offline_frames_test.cc | 12 +- .../internal/p2p_cluster_pcp_handler.cc | 659 ++++++++++ .../internal/p2p_cluster_pcp_handler.h | 136 +++ .../internal/p2p_cluster_pcp_handler_test.cc | 184 +++ .../p2p_point_to_point_pcp_handler.cc | 40 + .../internal/p2p_point_to_point_pcp_handler.h | 43 + cpp/core_v2/internal/p2p_star_pcp_handler.cc | 45 + cpp/core_v2/internal/p2p_star_pcp_handler.h | 47 + cpp/core_v2/internal/payload_manager.cc | 1062 +++++++++++++++++ cpp/core_v2/internal/payload_manager.h | 282 +++++ cpp/core_v2/internal/payload_manager_test.cc | 278 +++++ cpp/core_v2/internal/pcp_handler.h | 14 + cpp/core_v2/internal/pcp_manager.cc | 105 ++ cpp/core_v2/internal/pcp_manager.h | 64 + cpp/core_v2/internal/pcp_manager_test.cc | 122 ++ .../internal/service_controller_router.cc | 28 +- cpp/core_v2/internal/simulation_user.cc | 158 +++ cpp/core_v2/internal/simulation_user.h | 129 ++ .../internal/webrtc_endpoint_channel.cc | 23 + .../internal/webrtc_endpoint_channel.h | 29 + .../internal/wifi_lan_endpoint_channel.cc | 48 + .../internal/wifi_lan_endpoint_channel.h | 30 + cpp/core_v2/internal/wifi_lan_service_info.cc | 133 ++- cpp/core_v2/internal/wifi_lan_service_info.h | 2 - .../internal/wifi_lan_service_info_test.cc | 26 +- cpp/core_v2/listeners.h | 12 +- cpp/core_v2/payload.h | 48 +- cpp/core_v2/payload_test.cc | 24 +- cpp/core_v2/status.h | 1 + cpp/core_v2/strategy.h | 4 +- cpp/platform/api/BUILD | 2 +- cpp/platform/api/webrtc.h | 2 +- cpp/platform_v2/api/BUILD | 5 +- cpp/platform_v2/api/atomic_reference.h | 20 +- cpp/platform_v2/api/condition_variable.h | 16 +- cpp/platform_v2/api/log_message.h | 41 + cpp/platform_v2/api/platform.h | 28 +- cpp/platform_v2/api/settable_future.h | 11 +- cpp/platform_v2/api/webrtc.h | 2 +- cpp/platform_v2/api/wifi_lan.h | 88 +- cpp/platform_v2/base/BUILD | 7 +- cpp/platform_v2/base/base_input_stream.h | 3 +- cpp/platform_v2/base/byte_array.h | 25 +- cpp/platform_v2/base/byte_array_test.cc | 8 + cpp/platform_v2/base/logging.h | 56 +- cpp/platform_v2/base/medium_environment.cc | 159 ++- cpp/platform_v2/base/medium_environment.h | 60 +- cpp/platform_v2/base/payload_id.h | 14 + cpp/platform_v2/base/prng.cc | 2 +- cpp/platform_v2/base/prng_test.cc | 50 + cpp/platform_v2/base/types.h | 29 + cpp/platform_v2/impl/g3/BUILD | 15 +- cpp/platform_v2/impl/g3/atomic_reference.h | 33 + .../impl/g3/atomic_reference_any.h | 46 - cpp/platform_v2/impl/g3/bluetooth_classic.cc | 38 +- cpp/platform_v2/impl/g3/bluetooth_classic.h | 7 +- cpp/platform_v2/impl/g3/condition_variable.h | 5 + cpp/platform_v2/impl/g3/log_message.cc | 56 + cpp/platform_v2/impl/g3/log_message.h | 30 + cpp/platform_v2/impl/g3/platform.cc | 39 +- cpp/platform_v2/impl/g3/settable_future_any.h | 104 -- cpp/platform_v2/impl/g3/webrtc.cc | 38 +- cpp/platform_v2/impl/g3/webrtc.h | 21 +- cpp/platform_v2/impl/g3/wifi_lan.cc | 114 ++ cpp/platform_v2/impl/g3/wifi_lan.h | 109 ++ cpp/platform_v2/public/BUILD | 9 +- cpp/platform_v2/public/atomic_reference.h | 61 +- .../public/bluetooth_classic_test.cc | 2 + cpp/platform_v2/public/condition_variable.h | 3 +- .../public/condition_variable_test.cc | 62 + cpp/platform_v2/public/file.h | 78 +- cpp/platform_v2/public/future.h | 62 +- cpp/platform_v2/public/logging_test.cc | 28 +- cpp/platform_v2/public/mutex_test.cc | 2 +- cpp/platform_v2/public/pipe.h | 3 +- .../public/scheduled_executor_test.cc | 34 +- cpp/platform_v2/public/settable_future.h | 108 ++ cpp/platform_v2/public/webrtc.h | 35 +- cpp/platform_v2/public/wifi_lan.cc | 120 ++ cpp/platform_v2/public/wifi_lan.h | 160 +++ cpp/platform_v2/public/wifi_lan_test.cc | 102 ++ proto/bootstrap_enums.proto | 1 + proto/connections/offline_wire_formats.proto | 1 + proto/connections_enums.proto | 1 + proto/discovery_enums.proto | 1 + proto/error_code_enums.proto | 57 +- proto/magic_pair_enums.proto | 1 + proto/nearby_client_enums.proto | 1 + proto/nearby_event_codes.proto | 1 + proto/setup_enums.proto | 1 + proto/sharing_enums.proto | 25 +- 164 files changed, 8663 insertions(+), 1212 deletions(-) create mode 100644 cpp/core_v2/internal/bluetooth_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/bluetooth_endpoint_channel.h create mode 100644 cpp/core_v2/internal/internal_payload.cc create mode 100644 cpp/core_v2/internal/internal_payload.h create mode 100644 cpp/core_v2/internal/internal_payload_factory.cc create mode 100644 cpp/core_v2/internal/internal_payload_factory.h create mode 100644 cpp/core_v2/internal/internal_payload_factory_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h create mode 100644 cpp/core_v2/internal/mediums/webrtc_test.cc create mode 100644 cpp/core_v2/internal/mediums/wifi_lan.cc create mode 100644 cpp/core_v2/internal/mediums/wifi_lan.h create mode 100644 cpp/core_v2/internal/mediums/wifi_lan_test.cc create mode 100644 cpp/core_v2/internal/p2p_cluster_pcp_handler.cc create mode 100644 cpp/core_v2/internal/p2p_cluster_pcp_handler.h create mode 100644 cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc create mode 100644 cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc create mode 100644 cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h create mode 100644 cpp/core_v2/internal/p2p_star_pcp_handler.cc create mode 100644 cpp/core_v2/internal/p2p_star_pcp_handler.h create mode 100644 cpp/core_v2/internal/payload_manager.cc create mode 100644 cpp/core_v2/internal/payload_manager.h create mode 100644 cpp/core_v2/internal/payload_manager_test.cc create mode 100644 cpp/core_v2/internal/pcp_manager.cc create mode 100644 cpp/core_v2/internal/pcp_manager.h create mode 100644 cpp/core_v2/internal/pcp_manager_test.cc create mode 100644 cpp/core_v2/internal/simulation_user.cc create mode 100644 cpp/core_v2/internal/simulation_user.h create mode 100644 cpp/core_v2/internal/webrtc_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/webrtc_endpoint_channel.h create mode 100644 cpp/core_v2/internal/wifi_lan_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/wifi_lan_endpoint_channel.h create mode 100644 cpp/platform_v2/api/log_message.h create mode 100644 cpp/platform_v2/base/payload_id.h create mode 100644 cpp/platform_v2/base/types.h create mode 100644 cpp/platform_v2/impl/g3/atomic_reference.h delete mode 100644 cpp/platform_v2/impl/g3/atomic_reference_any.h create mode 100644 cpp/platform_v2/impl/g3/log_message.cc create mode 100644 cpp/platform_v2/impl/g3/log_message.h delete mode 100644 cpp/platform_v2/impl/g3/settable_future_any.h create mode 100644 cpp/platform_v2/impl/g3/wifi_lan.cc create mode 100644 cpp/platform_v2/impl/g3/wifi_lan.h create mode 100644 cpp/platform_v2/public/condition_variable_test.cc create mode 100644 cpp/platform_v2/public/settable_future.h create mode 100644 cpp/platform_v2/public/wifi_lan.cc create mode 100644 cpp/platform_v2/public/wifi_lan.h create mode 100644 cpp/platform_v2/public/wifi_lan_test.cc diff --git a/cpp/core/internal/mediums/webrtc/BUILD b/cpp/core/internal/mediums/webrtc/BUILD index 5ab6e446..56cf5608 100644 --- a/cpp/core/internal/mediums/webrtc/BUILD +++ b/cpp/core/internal/mediums/webrtc/BUILD @@ -7,7 +7,7 @@ cc_library( deps = [ "//platform:utils", "//platform/api", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -20,7 +20,7 @@ cc_test( "//platform/api", "//platform/impl/g3", # buildcleaner: keep "//testing/base/public:gunit_main", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -45,7 +45,7 @@ cc_library( ":peer_id", "//platform:types", "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -72,6 +72,6 @@ cc_test( "//platform/impl/g3", # buildcleaner: keep "//net/proto2/public:proto2", "//testing/base/public:gunit_main", - "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep + "//webrtc/pc:peerconnection", # buildcleaner: keep ], ) diff --git a/cpp/core/internal/mediums/webrtc/peer_id.h b/cpp/core/internal/mediums/webrtc/peer_id.h index 984ed34c..1f559c5a 100644 --- a/cpp/core/internal/mediums/webrtc/peer_id.h +++ b/cpp/core/internal/mediums/webrtc/peer_id.h @@ -15,17 +15,17 @@ namespace mediums { // p2p connection. class PeerId { public: - explicit PeerId(const string& id) : id_(id) {} + explicit PeerId(const std::string& id) : id_(id) {} ~PeerId() = default; static ConstPtr FromRandom(Ptr hash_utils); static ConstPtr FromSeed(ConstPtr seed, Ptr hash_utils); - const string& GetId() const { return id_; } + const std::string& GetId() const { return id_; } private: - const string id_; + const std::string id_; }; } // namespace mediums diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.h b/cpp/core/internal/mediums/webrtc/signaling_frames.h index fec7046c..fb885a58 100644 --- a/cpp/core/internal/mediums/webrtc/signaling_frames.h +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.h @@ -7,7 +7,7 @@ #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" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc index 49d76110..80dd1ce6 100644 --- a/cpp/core/internal/mediums/webrtc/webrtc_socket.cc +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc @@ -46,7 +46,7 @@ Exception::Value WebRtcSocket::OutputStreamImpl::close() { // WebRtcSocket template WebRtcSocket::WebRtcSocket( - const string& name, + const std::string& name, rtc::scoped_refptr data_channel) : name_(name), data_channel_(std::move(data_channel)), diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h index 5a55e9d9..4351cadf 100644 --- a/cpp/core/internal/mediums/webrtc/webrtc_socket.h +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.h @@ -6,7 +6,7 @@ #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" +#include "webrtc/api/data_channel_interface.h" namespace location { namespace nearby { @@ -24,7 +24,7 @@ constexpr int kMaxDataSize = 1 * 1024 * 1024; template class WebRtcSocket : public Socket { public: - WebRtcSocket(const string& name, + WebRtcSocket(const std::string& name, rtc::scoped_refptr data_channel); ~WebRtcSocket() override = default; @@ -77,7 +77,7 @@ class WebRtcSocket : public Socket { bool SendMessage(ConstPtr data); void BlockUntilSufficientSpaceInBuffer(int length); - string name_; + std::string name_; rtc::scoped_refptr data_channel_; Ptr pipe_; diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc index 503b8cd8..be83d9f1 100644 --- a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc @@ -5,7 +5,7 @@ #include "platform/ptr.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" +#include "webrtc/api/data_channel_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index 3a1f78d5..2df5423b 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -5,12 +5,22 @@ cc_library( "base_pcp_handler.cc", "ble_advertisement.cc", "bluetooth_device_name.cc", + "bluetooth_endpoint_channel.cc", "client_proxy.cc", "encryption_runner.cc", "endpoint_channel_manager.cc", "endpoint_manager.cc", + "internal_payload.cc", + "internal_payload_factory.cc", "offline_frames.cc", + "p2p_cluster_pcp_handler.cc", + "p2p_point_to_point_pcp_handler.cc", + "p2p_star_pcp_handler.cc", + "payload_manager.cc", + "pcp_manager.cc", "service_controller_router.cc", + "webrtc_endpoint_channel.cc", + "wifi_lan_endpoint_channel.cc", "wifi_lan_service_info.cc", ], hdrs = [ @@ -18,16 +28,26 @@ cc_library( "base_pcp_handler.h", "ble_advertisement.h", "bluetooth_device_name.h", + "bluetooth_endpoint_channel.h", "client_proxy.h", "encryption_runner.h", "endpoint_channel.h", "endpoint_channel_manager.h", "endpoint_manager.h", + "internal_payload.h", + "internal_payload_factory.h", "offline_frames.h", + "p2p_cluster_pcp_handler.h", + "p2p_point_to_point_pcp_handler.h", + "p2p_star_pcp_handler.h", + "payload_manager.h", "pcp.h", "pcp_handler.h", + "pcp_manager.h", "service_controller.h", "service_controller_router.h", + "webrtc_endpoint_channel.h", + "wifi_lan_endpoint_channel.h", "wifi_lan_service_info.h", ], visibility = [ @@ -36,8 +56,11 @@ cc_library( deps = [ "//core/internal:message_lite", "//core_v2:core_types", + "//core_v2/internal/mediums", + "//core_v2/internal/mediums/webrtc", "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", + "//platform_v2/base:util", "//platform_v2/public:comm", "//platform_v2/public:logging", "//platform_v2/public:types", @@ -46,6 +69,7 @@ cc_library( "//absl/base:core_headers", "//absl/container:flat_hash_map", "//absl/container:flat_hash_set", + "//absl/memory", "//absl/strings", "//absl/time", "//absl/types:span", @@ -55,15 +79,23 @@ cc_library( cc_library( name = "internal_test", testonly = True, + srcs = [ + "simulation_user.cc", + ], hdrs = [ "mock_service_controller.h", + "simulation_user.h", ], visibility = [ "//core_v2:__subpackages__", ], deps = [ ":internal", + "//core_v2:core_types", + "//platform_v2/base:test_util", + "//platform_v2/public:types", "//testing/base/public:gunit", + "//absl/functional:bind_front", ], ) @@ -79,7 +111,11 @@ cc_test( "encryption_runner_test.cc", "endpoint_channel_manager_test.cc", "endpoint_manager_test.cc", + "internal_payload_factory_test.cc", "offline_frames_test.cc", + "p2p_cluster_pcp_handler_test.cc", + "payload_manager_test.cc", + "pcp_manager_test.cc", "service_controller_router_test.cc", "wifi_lan_service_info_test.cc", ], @@ -90,8 +126,8 @@ cc_test( "//core_v2:core_types", "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep - "//platform_v2/public:comm", "//platform_v2/public:logging", "//platform_v2/public:types", "//proto:connections_enums_portable_proto", @@ -99,6 +135,8 @@ cc_test( "//testing/base/public:gunit", "//testing/base/public:gunit_main", "//absl/container:flat_hash_set", + "//absl/functional:bind_front", + "//absl/strings", "//absl/synchronization", "//absl/time", "//absl/types:span", diff --git a/cpp/core_v2/internal/base_endpoint_channel.cc b/cpp/core_v2/internal/base_endpoint_channel.cc index 078224c4..569135f5 100644 --- a/cpp/core_v2/internal/base_endpoint_channel.cc +++ b/cpp/core_v2/internal/base_endpoint_channel.cc @@ -2,11 +2,14 @@ #include +#include "core_v2/internal/offline_frames.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" +#include "platform_v2/public/logging.h" #include "platform_v2/public/mutex.h" #include "platform_v2/public/mutex_lock.h" #include "proto/connections_enums.pb.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" namespace location { @@ -99,13 +102,33 @@ ExceptionOr BaseEndpointChannel::Read() { result = std::move(read_bytes.result()); } - // If encryption is enabled, decode the message. - if (IsEncryptionEnabled()) { + { MutexLock crypto_lock(&crypto_mutex_); - result = ByteArray(std::move( - *encryption_context_->DecodeMessageFromPeer(std::string(result)))); - if (result.Empty()) { - return ExceptionOr(Exception::kInvalidProtocolBuffer); + if (IsEncryptionEnabledLocked()) { + // If encryption is enabled, decode the message. + std::string input(std::move(result)); + std::unique_ptr decrypted_data = + crypto_context_->DecodeMessageFromPeer( + std::string(std::move(result))); + if (decrypted_data) { + result = ByteArray(std::move(*decrypted_data)); + } else { + // It could be a protocol race, where remote party sends a KEEP_ALIVE + // before encryption is setup on their side, and we receive it after + // we switched to encryption mode. + // In this case, we verify that message is indeed a valid KEEP_ALIVE, + // and let it through if it is, otherwise message is erased. + // TODO(apolyudov): verify this happens at most once per session. + result = {}; + auto parsed = parser::FromBytes(ByteArray(input)); + if (parsed.ok() && + parser::GetFrameType(parsed.result()) == V1Frame::KEEP_ALIVE) { + result = ByteArray(input); + } + } + if (result.Empty()) { + return ExceptionOr(Exception::kInvalidProtocolBuffer); + } } } @@ -128,10 +151,12 @@ Exception BaseEndpointChannel::Write(const ByteArray& data) { const ByteArray* data_to_write = &data; { MutexLock crypto_lock(&crypto_mutex_); - // If encryption is enabled, encode the message. - if (IsEncryptionEnabled()) { - encrypted_data = ByteArray(std::move( - *encryption_context_->EncodeMessageToPeer(std::string(data)))); + if (IsEncryptionEnabledLocked()) { + // If encryption is enabled, encode the message. + std::unique_ptr encrypted = + crypto_context_->EncodeMessageToPeer(std::string(data)); + if (!encrypted) return {Exception::kIo}; + encrypted_data = ByteArray(std::move(*encrypted)); data_to_write = &encrypted_data; } } @@ -140,17 +165,15 @@ Exception BaseEndpointChannel::Write(const ByteArray& data) { MutexLock lock(&writer_mutex_); Exception write_exception = WriteInt(writer_, static_cast(data_to_write->size())); - if (!write_exception.Ok()) { + if (write_exception.Raised()) { return write_exception; } - write_exception = writer_->Write(*data_to_write); - if (write_exception.Ok()) { + if (write_exception.Raised()) { return write_exception; } - Exception flush_exception = writer_->Flush(); - if (!flush_exception.Ok()) { + if (flush_exception.Raised()) { return flush_exception; } } @@ -196,7 +219,8 @@ void BaseEndpointChannel::Close( } std::string BaseEndpointChannel::GetType() const { - std::string subtype = IsEncryptionEnabled() ? "ENCRYPTED_" : ""; + MutexLock crypto_lock(&crypto_mutex_); + std::string subtype = IsEncryptionEnabledLocked() ? "ENCRYPTED_" : ""; switch (GetMedium()) { case proto::connections::Medium::BLUETOOTH: @@ -217,9 +241,9 @@ std::string BaseEndpointChannel::GetType() const { std::string BaseEndpointChannel::GetName() const { return channel_name_; } void BaseEndpointChannel::EnableEncryption( - securegcm::D2DConnectionContextV1* encryption_context) { - MutexLock lock(&crypto_mutex_); - encryption_context_ = encryption_context; + std::shared_ptr context) { + MutexLock crypto_lock(&crypto_mutex_); + crypto_context_ = context; } bool BaseEndpointChannel::IsPaused() const { @@ -243,8 +267,8 @@ absl::Time BaseEndpointChannel::GetLastReadTimestamp() const { return last_read_timestamp_; } -bool BaseEndpointChannel::IsEncryptionEnabled() const { - return encryption_context_ != nullptr; +bool BaseEndpointChannel::IsEncryptionEnabledLocked() const { + return crypto_context_ != nullptr; } void BaseEndpointChannel::BlockUntilUnpaused() { diff --git a/cpp/core_v2/internal/base_endpoint_channel.h b/cpp/core_v2/internal/base_endpoint_channel.h index 2799e58d..347dfe2c 100644 --- a/cpp/core_v2/internal/base_endpoint_channel.h +++ b/cpp/core_v2/internal/base_endpoint_channel.h @@ -2,6 +2,7 @@ #define CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ #include +#include #include #include "core_v2/internal/endpoint_channel.h" @@ -50,7 +51,7 @@ class BaseEndpointChannel : public EndpointChannel { // Enables encryption on the EndpointChannel. // Should be called after connection is accepted by both parties, and // before entering data phase, where Payloads may be exchanged. - void EnableEncryption(securegcm::D2DConnectionContextV1* context) override; + void EnableEncryption(std::shared_ptr context) override; // True if the EndpointChannel is currently pausing all writes. bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; @@ -74,7 +75,8 @@ class BaseEndpointChannel : public EndpointChannel { // Used to sanity check that our frame sizes are reasonable. static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB - bool IsEncryptionEnabled() const; + bool IsEncryptionEnabledLocked() const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(crypto_mutex_); void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS; @@ -94,11 +96,10 @@ class BaseEndpointChannel : public EndpointChannel { Mutex writer_mutex_; OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_); - // Used by both read and write to protect payload encryption/decryption. - Mutex crypto_mutex_; // An encryptor/decryptor. May be null. - securegcm::D2DConnectionContextV1* encryption_context_ - ABSL_PT_GUARDED_BY(crypto_mutex_) = nullptr; + mutable Mutex crypto_mutex_; + std::shared_ptr crypto_context_ + ABSL_GUARDED_BY(crypto_mutex_) ABSL_PT_GUARDED_BY(crypto_mutex_); mutable Mutex is_paused_mutex_; ConditionVariable is_paused_cond_{&is_paused_mutex_}; diff --git a/cpp/core_v2/internal/base_endpoint_channel_test.cc b/cpp/core_v2/internal/base_endpoint_channel_test.cc index c96e8f4a..7a2869fc 100644 --- a/cpp/core_v2/internal/base_endpoint_channel_test.cc +++ b/cpp/core_v2/internal/base_endpoint_channel_test.cc @@ -27,6 +27,7 @@ namespace { using ::location::nearby::proto::connections::DisconnectionReason; using ::location::nearby::proto::connections::Medium; +using EncryptionContext = BaseEndpointChannel::EncryptionContext; class TestEndpointChannel : public BaseEndpointChannel { public: @@ -76,12 +77,12 @@ std::function MakeDataMonitor(const std::string& label, }; } -std::pair, - std::unique_ptr> +std::pair, + std::shared_ptr> DoDhKeyExchange(BaseEndpointChannel* channel_a, BaseEndpointChannel* channel_b) { - std::unique_ptr context_a; - std::unique_ptr context_b; + std::shared_ptr context_a; + std::shared_ptr context_b; EncryptionRunner crypto_a; EncryptionRunner crypto_b; ClientProxy proxy_a; @@ -98,7 +99,7 @@ DoDhKeyExchange(BaseEndpointChannel* channel_a, NEARBY_LOG(INFO, "client-A side key negotiation done"); EXPECT_TRUE(ukey2->VerifyHandshake()); auto context = ukey2->ToConnectionContext(); - EXPECT_NE (context, nullptr); + EXPECT_NE(context, nullptr); context_a = std::move(context); latch.CountDown(); }, @@ -119,7 +120,7 @@ DoDhKeyExchange(BaseEndpointChannel* channel_a, NEARBY_LOG(INFO, "client-B side key negotiation done"); EXPECT_TRUE(ukey2->VerifyHandshake()); auto context = ukey2->ToConnectionContext(); - EXPECT_NE (context, nullptr); + EXPECT_NE(context, nullptr); context_b = std::move(context); latch.CountDown(); }, @@ -196,7 +197,7 @@ TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) { absl::MutexLock lock(&mutex); std::string message{tx_message}; EXPECT_TRUE(capture_a.find(message) != std::string::npos || - capture_b.find(message) != std::string::npos); + capture_b.find(message) != std::string::npos); } // Shutdown test environment. @@ -239,8 +240,8 @@ TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) { auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b); ASSERT_NE(context_a, nullptr); ASSERT_NE(context_b, nullptr); - channel_a.EnableEncryption(context_a.get()); - channel_b.EnableEncryption(context_b.get()); + channel_a.EnableEncryption(context_a); + channel_b.EnableEncryption(context_b); EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH"); EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH"); @@ -292,26 +293,25 @@ TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) { // Pause and make sure reader blocks. MultiThreadExecutor pause_resume_executor(2); channel_a.Pause(); - pause_resume_executor.Execute([&channel_a, &more_message](){ + pause_resume_executor.Execute([&channel_a, &more_message]() { // Write will block until channel is resumed, or closed. EXPECT_TRUE(channel_a.Write(more_message).Ok()); }); - std::atomic_bool done = false; + CountDownLatch latch(1); ByteArray read_more; - pause_resume_executor.Execute([&channel_b, &read_more, &done](){ + pause_resume_executor.Execute([&channel_b, &read_more, &latch]() { // Read will block until channel is resumed, or closed. auto response = channel_b.Read(); EXPECT_TRUE(response.ok()); read_more = std::move(response.result()); - done = true; + latch.CountDown(); }); absl::SleepFor(absl::Milliseconds(500)); EXPECT_TRUE(read_more.Empty()); // Resume; verify that data transfer comepleted. channel_a.Resume(); - absl::SleepFor(absl::Milliseconds(500)); - EXPECT_TRUE(done); + EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result()); EXPECT_EQ(read_more, more_message); // Shutdown test environment. diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 38459a40..ec402d7a 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -7,6 +7,7 @@ #include #include "core_v2/internal/offline_frames.h" +#include "core_v2/internal/pcp_handler.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/system_clock.h" #include "securegcm/d2d_connection_context_v1.h" @@ -25,17 +26,25 @@ constexpr absl::Duration BasePcpHandler::kConnectionRequestReadTimeout; constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager) - : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} + EndpointChannelManager* channel_manager, Pcp pcp) + : endpoint_manager_(endpoint_manager), + channel_manager_(channel_manager), + pcp_(pcp) {} BasePcpHandler::~BasePcpHandler() { // Unregister ourselves from the FrameProcessors. + NEARBY_LOGS(INFO) << "BasePcpHandler: going down; strategy=" + << strategy_.GetName(); endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE, handle_); // Stop all the ongoing Runnables (as gracefully as possible). + NEARBY_LOGS(INFO) << "BasePcpHandler: bringing down executors; strategy=" + << strategy_.GetName(); serial_executor_.Shutdown(); alarm_executor_.Shutdown(); + NEARBY_LOGS(INFO) << "BasePcpHandler: is down; strategy=" + << strategy_.GetName(); } Status BasePcpHandler::StartAdvertising(ClientProxy* client, @@ -549,7 +558,7 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, // return bandwidth_upgrade_medium_.Get(); //} -void BasePcpHandler::OnIncomingFrame(const OfflineFrame& frame, +void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, const string& endpoint_id, ClientProxy* client, proto::connections::Medium medium) { @@ -606,7 +615,7 @@ ConnectionOptions BasePcpHandler::GetConnectionOptions() const { void BasePcpHandler::OnEndpointFound( ClientProxy* client, - std::unique_ptr endpoint) { + std::shared_ptr endpoint) { // Check if we've seen this endpoint ID before. std::string& endpoint_id = endpoint->endpoint_id; BasePcpHandler::DiscoveredEndpoint* previously_discovered_endpoint = @@ -617,8 +626,7 @@ void BasePcpHandler::OnEndpointFound( // If this is the first medium we've discovered this endpoint over, then add // it to the map. const auto& owned_endpoint = - discovered_endpoints_ - .emplace(endpoint_id, std::move(endpoint)) + discovered_endpoints_.emplace(endpoint_id, std::move(endpoint)) .first->second; NEARBY_LOG(INFO, "Adding new endpoint: id=%s", endpoint_id.c_str()); @@ -641,8 +649,7 @@ void BasePcpHandler::OnEndpointFound( NEARBY_LOG(INFO, "Rediscovered endpoint on new media: id=%s", endpoint_id.c_str()); if (IsPreferred(*endpoint, *previously_discovered_endpoint)) { - discovered_endpoints_.insert_or_assign(endpoint_id, - std::move(endpoint)); + discovered_endpoints_.insert_or_assign(endpoint_id, std::move(endpoint)); } } } @@ -650,8 +657,7 @@ void BasePcpHandler::OnEndpointFound( void BasePcpHandler::OnEndpointLost( ClientProxy* client, const BasePcpHandler::DiscoveredEndpoint& endpoint) { // Look up the DiscoveredEndpoint we have in our cache. - const auto* discovered_endpoint = - GetDiscoveredEndpoint(endpoint.endpoint_id); + const auto* discovered_endpoint = GetDiscoveredEndpoint(endpoint.endpoint_id); if (discovered_endpoint == nullptr) { NEARBY_LOG(INFO, "No previous endpoint (nothing to lose): id=%s", endpoint.endpoint_id.c_str()); @@ -733,7 +739,7 @@ Exception BasePcpHandler::OnIncomingConnection( OfflineFrame& frame = wrapped_frame.result(); const ConnectionRequestFrame& connection_request = frame.v1().connection_request(); - NEARBY_LOG(ERROR, + NEARBY_LOG(INFO, "Incoming connection request; client_id=0x%" PRIX64 "; device=%s; id=%s", client->GetClientId(), remote_device_name.c_str(), @@ -930,7 +936,7 @@ void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, bool succeeded = ukey2->VerifyHandshake(); CHECK(succeeded); // If this fails, it's a UKEY2 protocol bug. auto context = ukey2->ToConnectionContext(); - assert(context); // there is no way how this can fail, if Verify succeeded. + CHECK(context); // there is no way how this can fail, if Verify succeeded. // If it did, it's a UKEY2 protocol bug. channel_manager_->EncryptChannelForEndpoint(endpoint_id, diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index 1d9dd32b..a5411612 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -74,9 +74,9 @@ class BasePcpHandler : public PcpHandler, public: using FrameProcessor = EndpointManager::FrameProcessor; - // TODO(tracyzhou): Add SecureRandom. + // TODO(apolyudov): Add SecureRandom. BasePcpHandler(EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager); + EndpointChannelManager* channel_manager, Pcp pcp); ~BasePcpHandler() override; BasePcpHandler(BasePcpHandler&&) = delete; BasePcpHandler& operator=(BasePcpHandler&&) = delete; @@ -106,7 +106,7 @@ class BasePcpHandler : public PcpHandler, // otherwise does nothing. void StopDiscovery(ClientProxy* client_proxy) override; - // Requests a newly discoveered remote endpoint it to form a connection. + // Requests a newly discovered remote endpoint it to form a connection. // Updates state on ClientProxy. Status RequestConnection(ClientProxy* client_proxy, const std::string& endpoint_id, @@ -126,8 +126,8 @@ class BasePcpHandler : public PcpHandler, const std::string& endpoint_id) override; // @EndpointManagerReaderThread - void OnIncomingFrame(const OfflineFrame& frame, - const std::string& endpoint_id, ClientProxy* client, + void OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, + ClientProxy* client, proto::connections::Medium medium) override; // Called when an endpoint disconnects while we're waiting for both sides to @@ -137,6 +137,9 @@ class BasePcpHandler : public PcpHandler, const std::string& endpoint_id, CountDownLatch* barrier) override; + Pcp GetPcp() const override { return pcp_; } + Strategy GetStrategy() const override { return strategy_; } + protected: // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). struct StartOperationResult { @@ -149,6 +152,17 @@ class BasePcpHandler : public PcpHandler, // Represents an endpoint that we've discovered. Typically, the implementation // will know how to connect to this endpoint if asked. (eg. It holds on to a // BluetoothDevice) + // + // NOTE(DiscoveredEndpoint): + // Specific protocol is expected to derive from it, as follows: + // struct ProtocolEndpoint : public DiscoveredEndpoint { + // ProtocolContext context; + // }; + // Protocol then allocates instance with std::make_shared(), + // and passes this instance to OnEndpointFound() method. + // When calling OnEndpointLost(), protocol does not need to pass the same + // instance (but it can if implementation desires to do so). + // BasePcpHandler will hold on to the shared_ptr. struct DiscoveredEndpoint { std::string endpoint_id; std::string endpoint_name; @@ -169,7 +183,7 @@ class BasePcpHandler : public PcpHandler, // @PcpHandlerThread void OnEndpointFound(ClientProxy* client_proxy, - std::unique_ptr endpoint); + std::shared_ptr endpoint); // @PcpHandlerThread void OnEndpointLost(ClientProxy* client_proxy, @@ -238,7 +252,7 @@ class BasePcpHandler : public PcpHandler, std::string remote_endpoint_name; std::int32_t nonce = 0; bool is_incoming = false; - absl::Time start_time {absl::InfinitePast()}; + absl::Time start_time{absl::InfinitePast()}; // Client callbacks. Always valid. ConnectionListener listener; @@ -375,7 +389,7 @@ class BasePcpHandler : public PcpHandler, // removed from this map. absl::flat_hash_map pending_connections_; // A map of endpoint id -> DiscoveredEndpoint. - absl::flat_hash_map> + 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 @@ -400,9 +414,11 @@ class BasePcpHandler : public PcpHandler, // stops discovering because it might still be useful downstream of // discovery (eg: connection speed, etc.) ConnectionOptions discovery_options_; + Pcp pcp_; + Strategy strategy_{PcpToStrategy(pcp_)}; Prng prng_; EncryptionRunner encryption_runner_; - EndpointManager::FrameProcessor::Handle handle_; + EndpointManager::FrameProcessor::Handle handle_ = nullptr; }; } // namespace connections diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 8da33159..a5d9f8b6 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -1,5 +1,6 @@ #include "core_v2/internal/base_pcp_handler.h" +#include #include #include "core_v2/internal/base_endpoint_channel.h" @@ -57,7 +58,7 @@ class MockEndpointChannel : public BaseEndpointChannel { class MockPcpHandler : public BasePcpHandler { public: MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(em, ecm) {} + : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; @@ -98,7 +99,7 @@ class MockPcpHandler : public BasePcpHandler { // Mock adapters for protected non-virtual methods of a base class. void OnEndpointFound(ClientProxy* client, - std::unique_ptr endpoint) { + std::shared_ptr endpoint) { BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); } void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { @@ -106,7 +107,25 @@ class MockPcpHandler : public BasePcpHandler { } }; -using MockDiscoveredEndpoint = MockPcpHandler::DiscoveredEndpoint; +class MockContext { + public: + explicit MockContext(std::atomic_bool* destroyed = nullptr) { + destroyed_ = destroyed; + } + MockContext(MockContext&&) = default; + MockContext& operator=(MockContext&&) = default; + + ~MockContext() { + if (destroyed_) *destroyed_ = true; + } + + private: + Swapper destroyed_{nullptr}; +}; + +struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { + MockContext context; +}; class BasePcpHandlerTest : public ::testing::Test { protected: @@ -216,7 +235,8 @@ class BasePcpHandlerTest : public ::testing::Test { void RequestConnection(const std::string& endpoint_id, std::unique_ptr channel_a, MockEndpointChannel* channel_b, ClientProxy* client, - MockPcpHandler* pcp_handler) { + MockPcpHandler* pcp_handler, + std::atomic_bool* flag = nullptr) { ConnectionRequestInfo info{ .name = "ABCD", .listener = connection_listener_, @@ -240,11 +260,14 @@ class BasePcpHandlerTest : public ::testing::Test { // Simulate successful discovery. auto encryption_runner = std::make_unique(); pcp_handler->OnEndpointFound( - client, std::make_unique(MockDiscoveredEndpoint{ - .endpoint_id = endpoint_id, - .endpoint_name = info.name, - .service_id = "service", - .medium = Medium::BLE, + client, std::make_shared(MockDiscoveredEndpoint{ + { + .endpoint_id = endpoint_id, + .endpoint_name = info.name, + .service_id = "service", + .medium = Medium::BLE, + }, + MockContext{flag}, })); auto other_client = std::make_unique(); @@ -427,6 +450,29 @@ TEST_F(BasePcpHandlerTest, OnEndpointDisconnectChangesState) { EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); } +TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { + std::atomic_bool destroyed_flag = false; + { + std::string endpoint_id{"1234"}; + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartDiscovery(&client, &pcp_handler); + auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto& channel_b = channel_pair.second; + RequestConnection(endpoint_id, std::move(channel_pair.first), + channel_b.get(), &client, &pcp_handler, &destroyed_flag); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); + } + EXPECT_TRUE(destroyed_flag.load()); +} + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc index af266605..0443a03f 100644 --- a/cpp/core_v2/internal/ble_advertisement.cc +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -2,6 +2,7 @@ #include +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" #include "absl/strings/escaping.h" @@ -55,82 +56,70 @@ BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { return; } - // Start reading the bytes. - auto* ble_advertisement_bytes_read_ptr = ble_advertisement_bytes.data(); - - // The first 3 bits are supposed to be the version. - version_ = static_cast( - (*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5); + ByteArray advertisement_bytes{ble_advertisement_bytes}; + BaseInputStream base_input_stream{advertisement_bytes}; + // The first 1 byte is supposed to be the version and pcp. + auto version_and_pcp_byte = static_cast(base_input_stream.ReadUint8()); + // The upper 3 bits are supposed to be the version. + version_ = + static_cast((version_and_pcp_byte & kVersionBitmask) >> 5); if (version_ != Version::kV1) { - NEARBY_LOG(ERROR, + NEARBY_LOG(INFO, "Cannot deserialize BleAdvertisement: unsupported Version %d", version_); return; } - - pcp_ = static_cast(*ble_advertisement_bytes_read_ptr & kPcpBitmask); - ble_advertisement_bytes_read_ptr++; + // The lower 5 bits are supposed to be the Pcp. + pcp_ = static_cast(version_and_pcp_byte & kPcpBitmask); switch (pcp_) { case Pcp::kP2pCluster: // Fall through case Pcp::kP2pStar: // Fall through - case Pcp::kP2pPointToPoint: { - // The next 24 bits are supposed to be the service_id_hash. - service_id_hash_ = - ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength); - ble_advertisement_bytes_read_ptr += kServiceIdHashLength; - - // The next 32 bits are supposed to be the endpoint_id. - endpoint_id_ = - std::string(ble_advertisement_bytes_read_ptr, kEndpointIdLength); - ble_advertisement_bytes_read_ptr += kEndpointIdLength; - - // The next 8 bits are the length of the endpoint name. - auto expected_endpoint_name_length = static_cast( - *ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask); - ble_advertisement_bytes_read_ptr++; - - // The next x bits are the endpoint name. (Max length is 131 bytes). - // Check that the stated endpoint_name_length is the same as what we - // received (based off of the length of ble_advertisement_bytes). - auto actual_endpoint_name_length = - ComputeEndpointNameLength(ble_advertisement_bytes); - if (actual_endpoint_name_length < expected_endpoint_name_length) { - NEARBY_LOG( - ERROR, - "Cannot deserialize BleAdvertisement: expected endpointName to " - "be %d bytes, got %d bytes", - expected_endpoint_name_length, actual_endpoint_name_length); - - // Clear enpoint_id for validadity. - endpoint_id_.clear(); - return; - } - endpoint_name_ = std::string(ble_advertisement_bytes_read_ptr, - expected_endpoint_name_length); - ble_advertisement_bytes_read_ptr += expected_endpoint_name_length; - - // The next 48 bits are the bluetooth mac address. - auto bluetooth_mac_address_bytes = ByteArray( - ble_advertisement_bytes_read_ptr, kBluetoothMacAddressLength); - // If the Bluetooth MAC Address bytes are unset or invalid, leave the - // string empty. Otherwise, convert it to the proper colon delimited - // format. - if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) { - bluetooth_mac_address_ = - HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); - } + case Pcp::kP2pPointToPoint: break; - } - default: - // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over - // the air, or older versions of GmsCore intermingling with newer - // ones. - NEARBY_LOG(ERROR, + NEARBY_LOG(INFO, "Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d", pcp_); - break; } + + // The next 3 bytes are supposed to be the service_id_hash. + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); + + // The next 4 bytes are supposed to be the endpoint_id. + endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)}; + + // The next 1 byte are supposed to be the length of the endpoint_name. + std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8(); + + // The next x bytes are the endpoint name. (Max length is 131 bytes). + // Check that the stated endpoint_name_length is the same as what we + // received. + auto endpoint_name_bytes = + base_input_stream.ReadBytes(expected_endpoint_name_length); + if (endpoint_name_bytes.Empty() || + endpoint_name_bytes.size() != expected_endpoint_name_length) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: expected " + "endpointName to be %d bytes, got %" PRIu64, + expected_endpoint_name_length, endpoint_name_bytes.size()); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string{endpoint_name_bytes}; + + // The next 6 bytes are the bluetooth mac address. + auto bluetooth_mac_address_bytes = + base_input_stream.ReadBytes(kBluetoothMacAddressLength); + // If the Bluetooth MAC Address bytes are unset or invalid, leave the + // string empty. Otherwise, convert it to the proper colon delimited + // format. + if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) { + bluetooth_mac_address_ = + HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); + } + base_input_stream.Close(); } BleAdvertisement::operator ByteArray() const { @@ -138,36 +127,31 @@ BleAdvertisement::operator ByteArray() const { 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 + + // clang-format off + std::string out = absl::StrCat(std::string(1, version_and_pcp_byte), + std::string(service_id_hash_), + endpoint_id_, + std::string(1, endpoint_name_.size()), + endpoint_name_); + // clang-format on + + // The next 6 bytes are the bluetooth mac address. If bluetooth_mac_address is // invalid or empty, we get back a null byte array. auto bluetooth_mac_address_bytes( BluetoothMacAddressHexStringToBytes(bluetooth_mac_address_)); if (!bluetooth_mac_address_bytes.Empty()) { - out.append(bluetooth_mac_address_bytes.data(), kBluetoothMacAddressLength); + absl::StrAppend(&out, std::string(bluetooth_mac_address_bytes)); } return ByteArray(std::move(out)); } -std::uint32_t BleAdvertisement::ComputeEndpointNameLength( - const ByteArray& ble_advertisement_bytes) const { - return ble_advertisement_bytes.size() - kMinAdvertisementLength; -} - ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes( const std::string& bluetooth_mac_address) const { std::string bt_mac_address(bluetooth_mac_address); diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h index 885261bc..5523e17d 100644 --- a/cpp/core_v2/internal/ble_advertisement.h +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -64,8 +64,6 @@ class BleAdvertisement { std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; } private: - std::uint32_t ComputeEndpointNameLength( - const ByteArray& ble_advertisement_bytes) const; ByteArray BluetoothMacAddressHexStringToBytes( const std::string& bluetooth_mac_address) const; std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const; diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc index d2fd5228..b0621d68 100644 --- a/cpp/core_v2/internal/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -7,19 +7,22 @@ namespace nearby { namespace connections { namespace { -const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; -const Pcp kPcp = Pcp::kP2pCluster; -const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; -const char kEndPointID[] = "AB12"; -const char kEndpointName[] = - "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; -const char kBluetoothMacAddress[] = "00:00:E6:88:64:13"; +constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; +constexpr Pcp kPcp = Pcp::kP2pCluster; +constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"}; +constexpr absl::string_view kEndPointID{"AB12"}; +constexpr absl::string_view kEndpointName{ + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"}; +constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"}; TEST(BleAdvertisementTest, ConstructionWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); @@ -33,10 +36,13 @@ TEST(BleAdvertisementTest, ConstructionWorks) { TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { std::string empty_endpoint_name; - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, empty_endpoint_name, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + empty_endpoint_name, + std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); @@ -50,10 +56,13 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { std::string emoji_endpoint_name{"\u0001F450 \u0001F450"}; - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, emoji_endpoint_name, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + emoji_endpoint_name, + std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); @@ -68,10 +77,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, 'x'); - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, long_endpoint_name, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + long_endpoint_name, + std::string(kBluetoothMacAddress)}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -79,10 +91,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{bad_version, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{bad_version, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -90,10 +105,13 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, bad_pcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + bad_pcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -101,9 +119,12 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { std::string empty_bluetooth_mac_address = ""; - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), empty_bluetooth_mac_address}; EXPECT_TRUE(ble_advertisement.IsValid()); @@ -112,9 +133,12 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { std::string bad_bluetooth_mac_address = "022:00"; - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), bad_bluetooth_mac_address}; EXPECT_TRUE(ble_advertisement.IsValid()); @@ -128,10 +152,13 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // Serialize good data into a good Ble Advertisement. - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement org_ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement org_ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; auto ble_advertisement_bytes = ByteArray(org_ble_advertisement); BleAdvertisement ble_advertisement{ble_advertisement_bytes}; @@ -149,10 +176,13 @@ TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // in the future. TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { // Serialize good data into a good Ble Advertisement. - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Add bytes to the end of the valid Ble advertisement. @@ -184,10 +214,13 @@ TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { // Serialize good data into a good Ble Advertisement. - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Shorten the valid Ble Advertisement. @@ -203,10 +236,13 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { TEST(BleAdvertisementTest, ConstructionFromByesWithWrongEndpointNameLengthFails) { // Serialize good data into a good Ble Advertisement. - ByteArray service_id_hash{kServiceIDHashBytes}; - BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - kEndPointID, kEndpointName, - kBluetoothMacAddress}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndPointID), + std::string(kEndpointName), + std::string(kBluetoothMacAddress)}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Corrupt the EndpointNameLength bits. diff --git a/cpp/core_v2/internal/bluetooth_device_name.cc b/cpp/core_v2/internal/bluetooth_device_name.cc index 857c9cf4..724723db 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.cc +++ b/cpp/core_v2/internal/bluetooth_device_name.cc @@ -6,15 +6,14 @@ #include #include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/str_cat.h" namespace location { namespace nearby { namespace connections { -// TODO(edwinwu): Define bitfield struct to replace pointer arithmetic for -// those bit parsing. - BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, @@ -71,78 +70,60 @@ BluetoothDeviceName::BluetoothDeviceName( return; } + BaseInputStream base_input_stream{bluetooth_device_name_bytes}; + // The first 1 byte is supposed to be the version and pcp. + auto version_and_pcp_byte = static_cast(base_input_stream.ReadUint8()); // The upper 3 bits are supposed to be the version. - version_ = static_cast( - (bluetooth_device_name_bytes.data()[0] & kVersionBitmask) >> 5); - const char* read_ptr = bluetooth_device_name_bytes.data(); - switch (version_) { - case Version::kV1: - // The lower 5 bits of the V1 payload are supposed to be the Pcp. - pcp_ = static_cast(*read_ptr & kPcpBitmask); - read_ptr++; - switch (pcp_) { - case Pcp::kP2pCluster: // Fall through - case Pcp::kP2pStar: // Fall through - case Pcp::kP2pPointToPoint: { - // The next 32 bits are supposed to be the endpoint_id. - endpoint_id_ = std::string(read_ptr, kEndpointIdLength); - read_ptr += kEndpointIdLength; - - // The next 24 bits are supposed to be the service_id_hash. - service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength); - read_ptr += kServiceIdHashLength; - - // The next 56 bits are supposed to be reserved, and can be left - // untouched. - read_ptr += kReservedLength; - - // The next 8 bits are supposed to be the length of the endpoint_name. - std::uint32_t expected_endpoint_name_length = - static_cast(*read_ptr & - kEndpointNameLengthBitmask); - read_ptr++; - - // Check that the stated endpoint_name_length is the same as what we - // received (based off of the length of bluetooth_device_name_bytes). - std::uint32_t actual_endpoint_name_length = - kMaxBluetoothDeviceNameLength - - bluetooth_device_name_bytes.size(); - if (actual_endpoint_name_length != expected_endpoint_name_length) { - NEARBY_LOG(INFO, - "Cannot deserialize BluetoothDeviceName: expected " - "endpointName to be %d bytes, got %d bytes", - expected_endpoint_name_length, - actual_endpoint_name_length); - - endpoint_id_.empty(); - return; - } - - endpoint_name_ = std::string{read_ptr, actual_endpoint_name_length}; - read_ptr += actual_endpoint_name_length; - } break; - - default: - // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over - // the air, or older versions of GmsCore intermingling with newer - // ones. - NEARBY_LOG( - INFO, - "Cannot deserialize BluetoothDeviceName: unsupported V1 PCP %d", - pcp_); - break; - } - break; - - default: - // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over - // the air, or older versions of GmsCore intermingling with newer ones. - NEARBY_LOG( - INFO, - "Cannot deserialize BluetoothDeviceName: unsupported Version %d", - version_); - break; + version_ = + static_cast((version_and_pcp_byte & kVersionBitmask) >> 5); + if (version_ != Version::kV1) { + NEARBY_LOG(INFO, + "Cannot deserialize BluetoothDeviceName: unsupported version=%d", + version_); + return; } + // The lower 5 bits are supposed to be the Pcp. + pcp_ = static_cast(version_and_pcp_byte & kPcpBitmask); + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + NEARBY_LOG( + INFO, "Cannot deserialize BluetoothDeviceName: unsupported V1 PCP %d", + pcp_); + return; + } + + // The next 4 bytes are supposed to be the endpoint_id. + endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)}; + + // The next 3 bytes are supposed to be the service_id_hash. + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); + + // The next 7 bytes are supposed to be reserved, and can be left + // untouched. + base_input_stream.ReadBytes(kReservedLength); + + // The next 1 byte are supposed to be the length of the endpoint_name. + std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8(); + + // The rest bytes are supposed to be the endpoint_name + auto endpoint_name_bytes = + base_input_stream.ReadBytes(expected_endpoint_name_length); + if (endpoint_name_bytes.Empty() || + endpoint_name_bytes.size() != expected_endpoint_name_length) { + NEARBY_LOG(INFO, + "Cannot deserialize BluetoothDeviceName: expected " + "endpointName to be %d bytes, got %" PRIu64, + expected_endpoint_name_length, endpoint_name_bytes.size()); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string{endpoint_name_bytes}; } BluetoothDeviceName::operator std::string() const { @@ -150,6 +131,15 @@ BluetoothDeviceName::operator std::string() const { return ""; } + // 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); + + ByteArray reserved_bytes{kReservedLength}; + std::string usable_endpoint_name(endpoint_name_); if (endpoint_name_.size() > kMaxEndpointNameLength) { NEARBY_LOG(INFO, @@ -160,24 +150,14 @@ BluetoothDeviceName::operator std::string() const { usable_endpoint_name.erase(kMaxEndpointNameLength); } - std::string out; - - // The upper 3 bits are the Version. - auto version_and_pcp_byte = static_cast( - (static_cast(Version::kV1) << 5) & kVersionBitmask); - // The lower 5 bits are the PCP. - version_and_pcp_byte |= - static_cast(static_cast(pcp_) & kPcpBitmask); - // TODO(edwinwu): Change to StrCat to gain performance. - out.reserve(kMaxBluetoothDeviceNameLength - - (kMaxEndpointNameLength - usable_endpoint_name.length())); - out.append(1, version_and_pcp_byte); - out.append(endpoint_id_); - out.append(std::string(service_id_hash_)); - ByteArray reserverdBytes{kReservedLength}; - out.append(std::string(reserverdBytes)); - out.append(1, usable_endpoint_name.size()); - out.append(usable_endpoint_name); + // clang-format off + std::string out = absl::StrCat(std::string(1, version_and_pcp_byte), + endpoint_id_, + std::string(service_id_hash_), + std::string(reserved_bytes), + std::string(1, usable_endpoint_name.size()), + usable_endpoint_name); + // clang-format on return Base64Utils::Encode(ByteArray{std::move(out)}); } diff --git a/cpp/core_v2/internal/bluetooth_device_name_test.cc b/cpp/core_v2/internal/bluetooth_device_name_test.cc index 69196b46..f92c5468 100644 --- a/cpp/core_v2/internal/bluetooth_device_name_test.cc +++ b/cpp/core_v2/internal/bluetooth_device_name_test.cc @@ -11,15 +11,15 @@ namespace nearby { namespace connections { namespace { -const BluetoothDeviceName::Version kVersion = BluetoothDeviceName::Version::kV1; -const Pcp kPcp = Pcp::kP2pCluster; -// TODO(edwinwu): Replace absl::string_view in other medium tests, too. -inline constexpr absl::string_view kEndPointID = "AB12"; -inline constexpr absl::string_view kServiceIDHashBytes = "\x0a\x0b\x0c"; -inline constexpr absl::string_view kEndPointName = "RAWK + ROWL!"; +constexpr BluetoothDeviceName::Version kVersion = + BluetoothDeviceName::Version::kV1; +constexpr Pcp kPcp = Pcp::kP2pCluster; +constexpr absl::string_view kEndPointID{"AB12"}; +constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"}; +constexpr absl::string_view kEndPointName{"RAWK + ROWL!"}; TEST(BluetoothDeviceNameTest, ConstructionWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName}; @@ -34,7 +34,7 @@ TEST(BluetoothDeviceNameTest, ConstructionWorks) { TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { std::string empty_endpoint_name; - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{ kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_name}; @@ -49,7 +49,7 @@ TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName}; @@ -59,7 +59,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) { auto bad_pcp = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName}; @@ -69,7 +69,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) { std::string short_endpoint_id("AB1"); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName}; @@ -79,7 +79,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) { std::string long_endpoint_id("AB12X"); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName}; @@ -118,7 +118,7 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortStringLength) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { // Serialize good data into a good Bluetooth Device Name. - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName}; auto bluetooth_device_name_string = std::string(bluetooth_device_name); @@ -140,7 +140,23 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { BluetoothDeviceName corrupt_bluetooth_device_name( corrupt_bluetooth_device_name_string); - EXPECT_TRUE(corrupt_bluetooth_device_name.IsValid()); + EXPECT_FALSE(corrupt_bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, CanParseGeneratedName) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + // Build name1 from scratch. + BluetoothDeviceName name1{kVersion, kPcp, kEndPointID, service_id_hash, + kEndPointName}; + // Build name2 from string composed from name1. + BluetoothDeviceName name2{std::string(name1)}; + EXPECT_TRUE(name1.IsValid()); + EXPECT_TRUE(name2.IsValid()); + EXPECT_EQ(name1.GetVersion(), name2.GetVersion()); + EXPECT_EQ(name1.GetPcp(), name2.GetPcp()); + EXPECT_EQ(name1.GetEndpointId(), name2.GetEndpointId()); + EXPECT_EQ(name1.GetServiceIdHash(), name2.GetServiceIdHash()); + EXPECT_EQ(name1.GetEndpointName(), name2.GetEndpointName()); } } // namespace diff --git a/cpp/core_v2/internal/bluetooth_endpoint_channel.cc b/cpp/core_v2/internal/bluetooth_endpoint_channel.cc new file mode 100644 index 00000000..1ae5337f --- /dev/null +++ b/cpp/core_v2/internal/bluetooth_endpoint_channel.cc @@ -0,0 +1,45 @@ +#include "core_v2/internal/bluetooth_endpoint_channel.h" + +#include + +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +OutputStream* GetOutputStreamOrNull(BluetoothSocket& socket) { + if (socket.GetRemoteDevice().IsValid()) return &socket.GetOutputStream(); + return nullptr; +} + +InputStream* GetInputStreamOrNull(BluetoothSocket& socket) { + if (socket.GetRemoteDevice().IsValid()) return &socket.GetInputStream(); + return nullptr; +} + +} // namespace + +BluetoothEndpointChannel::BluetoothEndpointChannel( + const std::string& channel_name, BluetoothSocket socket) + : BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket), + GetOutputStreamOrNull(socket)), + bluetooth_socket_(std::move(socket)) {} + +proto::connections::Medium BluetoothEndpointChannel::GetMedium() const { + return proto::connections::Medium::BLUETOOTH; +} + +void BluetoothEndpointChannel::CloseImpl() { + auto status = bluetooth_socket_.Close(); + if (!status.Ok()) { + NEARBY_LOG(INFO, "Failed to close BT socket: exception=%d", status.value); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/bluetooth_endpoint_channel.h b/cpp/core_v2/internal/bluetooth_endpoint_channel.h new file mode 100644 index 00000000..64fc0cc0 --- /dev/null +++ b/cpp/core_v2/internal/bluetooth_endpoint_channel.h @@ -0,0 +1,32 @@ +#ifndef CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ + +#include + +#include "core_v2/internal/base_endpoint_channel.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +class BluetoothEndpointChannel final : public BaseEndpointChannel { + public: + // Creates both outgoing and incoming BT channels. + BluetoothEndpointChannel(const std::string& channel_name, + BluetoothSocket bluetooth_socket); + + proto::connections::Medium GetMedium() const override; + + private: + void CloseImpl() override; + + BluetoothSocket bluetooth_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc index aaa67dba..3ee2d6c3 100644 --- a/cpp/core_v2/internal/client_proxy.cc +++ b/cpp/core_v2/internal/client_proxy.cc @@ -25,14 +25,20 @@ ClientProxy::~ClientProxy() { Reset(); } std::int64_t ClientProxy::GetClientId() const { return client_id_; } std::string ClientProxy::GenerateLocalEndpointId() { - // 1) Concatenate the DeviceID with this ClientID. + // 1) Concatenate the Random 64-bit value with "client" string. // 2) Compute a hash of that concatenation. // 3) Base64-encode that hash, to make it human-readable. - // 4) Use only the first 4 bytes of that Base64 encoding. - ByteArray id_hash(Crypto::Sha256( - absl::StrCat(api::ImplementationPlatform::GetDeviceId(), GetClientId()))); + // 4) Use only the first kEndpointIdLength bytes to make ID. + ByteArray id_hash = Crypto::Sha256( + absl::StrCat("client", prng_.NextInt64())); - return Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength); + std::string id = Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength); + + NEARBY_LOG( + INFO, "ClientProxy [Local Endpoint Generated]: client=%p; endpoint_id=%s", + this, id.c_str()); + + return id; } void ClientProxy::Reset() { @@ -113,9 +119,17 @@ void ClientProxy::OnEndpointFound(const std::string& service_id, proto::connections::Medium medium) { MutexLock lock(&mutex_); - if (!IsDiscoveringServiceId(service_id)) return; + NEARBY_LOG(INFO, + "ClientProxy [Endpoint Found]: [enter] id=%s; service=%s; name=%s", + endpoint_id.c_str(), service_id.c_str(), endpoint_name.c_str()); + if (!IsDiscoveringServiceId(service_id)) { + NEARBY_LOG(INFO, "ClientProxy [Endpoint Found]: [no discovery] id=%s", + endpoint_id.c_str()); + return; + } if (discovered_endpoint_ids_.count(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "ClientProxy [Endpoint Found]: [duplicate] id=%s", + endpoint_id.c_str()); return; } discovered_endpoint_ids_.insert(endpoint_id); @@ -150,7 +164,11 @@ void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, // Instead of using structured binding which is nice, but banned // (can not use c++17 features, until chromium does) we unpack manually. auto& pair_iter = result.first; - bool& inserted = result.second; + bool inserted = result.second; + NEARBY_LOG(INFO, + "ClientProxy [Connection Initiated]: add Connection: client=%p, " + "id=%s; inserted=%d", + this, endpoint_id.c_str(), inserted); DCHECK(inserted); const Connection& item = pair_iter->second; // Notify the client. @@ -164,7 +182,9 @@ void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) { MutexLock lock(&mutex_); if (!HasPendingConnectionToEndpoint(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, "ClientProxy [Connection Accepted]: no pending connection; id=%s", + endpoint_id.c_str()); return; } @@ -181,8 +201,9 @@ void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, MutexLock lock(&mutex_); if (!HasPendingConnectionToEndpoint(endpoint_id)) { - NEARBY_LOG(INFO, "ClientProxy [Rejected]: no pending connection; id=%s", - endpoint_id.c_str()); + NEARBY_LOG( + INFO, "ClientProxy [Connection Rejected]: no pending connection; id=%s", + endpoint_id.c_str()); return; } @@ -311,7 +332,10 @@ void ClientProxy::LocalEndpointAcceptedConnection( MutexLock lock(&mutex_); if (HasLocalEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, + "ClientProxy [Local Accepted]: local endpoint has responded; id=%s", + endpoint_id.c_str()); return; } @@ -327,7 +351,10 @@ void ClientProxy::LocalEndpointRejectedConnection( MutexLock lock(&mutex_); if (HasLocalEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, + "ClientProxy [Local Rejected]: local endpoint has responded; id=%s", + endpoint_id.c_str()); return; } @@ -339,7 +366,10 @@ void ClientProxy::RemoteEndpointAcceptedConnection( MutexLock lock(&mutex_); if (HasRemoteEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, + "ClientProxy [Remote Accepted]: remote endpoint has responded; id=%s", + endpoint_id.c_str()); return; } @@ -351,7 +381,10 @@ void ClientProxy::RemoteEndpointRejectedConnection( MutexLock lock(&mutex_); if (HasRemoteEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, + "ClientProxy [Remote Rejected]: remote endpoint has responded; id=%s", + endpoint_id.c_str()); return; } diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h index a1013e0c..67ada3ef 100644 --- a/cpp/core_v2/internal/client_proxy.h +++ b/cpp/core_v2/internal/client_proxy.h @@ -9,6 +9,7 @@ #include "core_v2/status.h" #include "core_v2/strategy.h" #include "platform_v2/base/byte_array.h" +#include "platform_v2/base/prng.h" #include "platform_v2/public/mutex.h" #include "proto/connections_enums.pb.h" // Prefer using absl:: versions of a set and a map; they tend to be more @@ -187,6 +188,7 @@ class ClientProxy final { mutable RecursiveMutex mutex_; std::int64_t client_id_; + Prng prng_; // If not empty, we are currently advertising and accepting connection // requests for the given service_id. diff --git a/cpp/core_v2/internal/encryption_runner_test.cc b/cpp/core_v2/internal/encryption_runner_test.cc index cc4839db..094c0114 100644 --- a/cpp/core_v2/internal/encryption_runner_test.cc +++ b/cpp/core_v2/internal/encryption_runner_test.cc @@ -40,8 +40,7 @@ class FakeEndpointChannel : public EndpointChannel { std::string GetType() const override { return "fake-channel-type"; } std::string GetName() const override { return "fake-channel"; } Medium GetMedium() const override { return Medium::BLE; } - void EnableEncryption( - securegcm::D2DConnectionContextV1* connection_context) override {} + void EnableEncryption(std::shared_ptr context) override {} bool IsPaused() const override { return false; } void Pause() override {} void Resume() override {} diff --git a/cpp/core_v2/internal/endpoint_channel.h b/cpp/core_v2/internal/endpoint_channel.h index 6c441191..7cd6877d 100644 --- a/cpp/core_v2/internal/endpoint_channel.h +++ b/cpp/core_v2/internal/endpoint_channel.h @@ -6,6 +6,7 @@ #include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" #include "proto/connections_enums.pb.h" #include "securegcm/d2d_connection_context_v1.h" #include "absl/time/clock.h" @@ -18,6 +19,8 @@ class EndpointChannel { public: virtual ~EndpointChannel() = default; + using EncryptionContext = ::securegcm::D2DConnectionContextV1; + virtual ExceptionOr Read() = 0; // throws Exception::IO, Exception::INTERRUPTED @@ -40,8 +43,7 @@ class EndpointChannel { virtual proto::connections::Medium GetMedium() const = 0; // Enables encryption on the EndpointChannel. - virtual void EnableEncryption( - securegcm::D2DConnectionContextV1* context) = 0; + virtual void EnableEncryption(std::shared_ptr context) = 0; // True if the EndpointChannel is currently pausing all writes. virtual bool IsPaused() const = 0; diff --git a/cpp/core_v2/internal/endpoint_channel_manager.cc b/cpp/core_v2/internal/endpoint_channel_manager.cc index 2e0bdc41..f214c845 100644 --- a/cpp/core_v2/internal/endpoint_channel_manager.cc +++ b/cpp/core_v2/internal/endpoint_channel_manager.cc @@ -66,7 +66,6 @@ std::shared_ptr EndpointChannelManager::GetChannelForEndpoint( 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)); @@ -75,18 +74,19 @@ void EndpointChannelManager::SetActiveEndpointChannel( if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint); } +///////////////////////////////// ChannelState ///////////////////////////////// + // endpoint - channel endpoint to encrypt bool EndpointChannelManager::ChannelState::EncryptChannel( EndpointChannelManager::ChannelState::EndpointData* endpoint) { if (endpoint != nullptr && endpoint->channel != nullptr && endpoint->context != nullptr) { - endpoint->channel->EnableEncryption(endpoint->context.get()); + endpoint->channel->EnableEncryption(endpoint->context); return true; } return false; } -///////////////////////////////// ChannelState ///////////////////////////////// EndpointChannelManager::ChannelState::EndpointData* EndpointChannelManager::ChannelState::LookupEndpointData( const std::string& endpoint_id) { diff --git a/cpp/core_v2/internal/endpoint_channel_manager.h b/cpp/core_v2/internal/endpoint_channel_manager.h index c6e9e9c7..14f8e718 100644 --- a/cpp/core_v2/internal/endpoint_channel_manager.h +++ b/cpp/core_v2/internal/endpoint_channel_manager.h @@ -15,8 +15,6 @@ namespace location { namespace nearby { namespace connections { -using EncryptionContext = ::securegcm::D2DConnectionContextV1; - // NOTE(std::string): // All the strings in internal class public interfaces should be exchanged as // const std::string& if they are immutable, and as std::string @@ -33,6 +31,8 @@ using EncryptionContext = ::securegcm::D2DConnectionContextV1; // are interacting. class EndpointChannelManager final { public: + using EncryptionContext = EndpointChannel::EncryptionContext; + ~EndpointChannelManager(); // Registers the initial EndpointChannel to be associated with an endpoint; @@ -97,10 +97,12 @@ class EndpointChannelManager final { } // True if we have a 'context' for the endpoint. - bool IsEncrypted() const { return context != nullptr; } + bool IsEncrypted() const { + return context != nullptr; + } std::shared_ptr channel; - std::unique_ptr context; + std::shared_ptr context; proto::connections::DisconnectionReason disconnect_reason = proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON; }; diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 5d28a6c0..0852b3b4 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -50,7 +50,7 @@ void EndpointManager::EndpointChannelLoopRunnable( std::shared_ptr channel = channel_manager_->GetChannelForEndpoint(endpoint_id); if (channel == nullptr) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Endpoint channel is nullptr, bail out."); break; } @@ -58,7 +58,8 @@ void EndpointManager::EndpointChannelLoopRunnable( // EndpointChannel for this endpoint, there's nothing more to do here. if ((last_failed_medium != Medium::UNKNOWN_MEDIUM) && (channel->GetMedium() == last_failed_medium)) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG( + INFO, "No new endpoint channel is found after a failure, exit loop."); break; } @@ -68,7 +69,8 @@ void EndpointManager::EndpointChannelLoopRunnable( Exception exception = keep_using_channel.GetException(); if (exception.Raised(Exception::kIo)) { last_failed_medium = channel->GetMedium(); - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Endpoint channel IO exception; last_failed_medium=%d", + last_failed_medium); continue; } if (exception.Raised(Exception::kInterrupted)) { @@ -77,7 +79,8 @@ void EndpointManager::EndpointChannelLoopRunnable( } if (!keep_using_channel.result()) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Dropping current channel: last medium=%d", + last_failed_medium); break; } } @@ -113,7 +116,7 @@ ExceptionOr EndpointManager::HandleData( if (!wrapped_frame.ok()) { if (wrapped_frame.GetException().Raised( Exception::kInvalidProtocolBuffer)) { - NEARBY_LOG(INFO, "failed to decode; endpoint=%s; channel=%s; skip", + NEARBY_LOG(INFO, "Failed to decode; endpoint=%s; channel=%s; skip", endpoint_id.c_str(), endpoint_channel->GetType().c_str()); continue; } else { @@ -129,7 +132,14 @@ ExceptionOr EndpointManager::HandleData( EndpointManager::FrameProcessor* frame_processor = GetFrameProcessor(frame_type); if (frame_processor == nullptr) { - NEARBY_LOG(ERROR, "Unhandled message: type=%d", frame_type); + // report messages without handlers, except KEEP_ALIVE, which has + // no explicit handler. + if (frame_type == V1Frame::KEEP_ALIVE) { + NEARBY_LOG(INFO, "KeepAlive message for: id=%s", endpoint_id.c_str()); + } else { + NEARBY_LOG(ERROR, "Unhandled message: id=%s, type=%d", + endpoint_id.c_str(), frame_type); + } continue; } @@ -142,11 +152,11 @@ 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. + auto last_read_time = endpoint_channel->GetLastReadTimestamp(); + if (last_read_time != kInvalidTimestamp && + SystemClock::ElapsedRealtime() > + (last_read_time + EndpointManager::kKeepAliveReadTimeout)) { + NEARBY_LOG(INFO, "Receive timeout expired; aborting KeepAlive worker."); return ExceptionOr(false); } @@ -226,7 +236,7 @@ EndpointManager::RegisterFrameProcessor( RunOnEndpointManagerThread([this, frame_type, &latch, processor]() { auto it = frame_processors_.find(frame_type); if (it != frame_processors_.end()) { - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Frame processor found, updated; type=%d", frame_type); it->second = processor; } else { frame_processors_.emplace(frame_type, processor); @@ -238,21 +248,27 @@ EndpointManager::RegisterFrameProcessor( } void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, - const void* handle) { - RunOnEndpointManagerThread([this, frame_type, handle]() { + const void* handle, bool sync) { + if (handle == nullptr) return; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, handle, &latch, sync]() { auto it = frame_processors_.find(frame_type); if (it == frame_processors_.end()) return; - if (it->second != handle) { + if (it->second == handle) { + frame_processors_.erase(it); + NEARBY_LOG(INFO, "Unregistered: type=%d", frame_type); + } else { NEARBY_LOG(INFO, "Failed to unregister: type=%d; handle mismatch: passed=%p, " "expected=%p", frame_type, handle, it->second); - return; } - - frame_processors_.erase(it); - NEARBY_LOG(INFO, "unregistered: type=%d", frame_type); + if (sync) latch.CountDown(); }); + if (sync) { + latch.Await(); + NEARBY_LOG(INFO, "Unregistered: [sync done] type=%d", frame_type); + } } EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor( @@ -267,6 +283,8 @@ EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor( latch.CountDown(); }); latch.Await(); + NEARBY_LOG(INFO, "GetFrameProcessor: type=%d; processor=%p", frame_type, + processor); return processor; } @@ -345,7 +363,8 @@ void EndpointManager::RegisterEndpoint(ClientProxy* client, return HandleKeepAlive(channel); }); }); - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Workers started, notifying client; id=%s", + endpoint_id.c_str()); // It's now time to let the client know of this new connection so that // they can accept or reject it. @@ -419,7 +438,8 @@ void EndpointManager::RemoveEndpoint(ClientProxy* client, EnsureWorkersTerminated(endpoint_id); client->OnDisconnected(endpoint_id, notify); - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "Removed endpoint; id=%s", + endpoint_id.c_str()); } } diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h index b9ddd5b7..3d761df7 100644 --- a/cpp/core_v2/internal/endpoint_manager.h +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -51,7 +51,14 @@ class EndpointManager { virtual ~FrameProcessor() = default; // @EndpointManagerReaderThread - virtual void OnIncomingFrame(const OfflineFrame& offline_frame, + // Called for every incoming frame of registered type. + // NOTE(OfflineFrame& frame): + // For large payload in data phase, resources may be saved if data is moved, + // rather than copied (if passing data by reference is not an option). + // To achieve that, OfflineFrame needs to be either mutabe lvalue reference, + // or rvalue reference. Rvalue references are discouraged by go/cstyle, + // and that leaves us with mutable lvalue reference. + virtual void OnIncomingFrame(OfflineFrame& offline_frame, const std::string& from_endpoint_id, ClientProxy* to_client, proto::connections::Medium current_medium) = 0; @@ -77,7 +84,7 @@ class EndpointManager { const FrameProcessor::Handle RegisterFrameProcessor( V1Frame::FrameType frame_type, FrameProcessor* processor); void UnregisterFrameProcessor(V1Frame::FrameType frame_type, - const void* handle); + const void* handle, bool sync = false); // Invoked from the different PcpHandler implementations (of which there can // be only one at a time). diff --git a/cpp/core_v2/internal/endpoint_manager_test.cc b/cpp/core_v2/internal/endpoint_manager_test.cc index 23c816c5..fa9b485a 100644 --- a/cpp/core_v2/internal/endpoint_manager_test.cc +++ b/cpp/core_v2/internal/endpoint_manager_test.cc @@ -25,7 +25,6 @@ namespace { using ::location::nearby::proto::connections::DisconnectionReason; using ::location::nearby::proto::connections::Medium; -using ::securegcm::D2DConnectionContextV1; using ::testing::_; using ::testing::MockFunction; using ::testing::Return; @@ -41,7 +40,7 @@ class MockEndpointChannel : public EndpointChannel { MOCK_METHOD(std::string, GetName, (), (const override)); MOCK_METHOD(Medium, GetMedium, (), (const override)); MOCK_METHOD(void, EnableEncryption, - (D2DConnectionContextV1 * connection_context), + (std::shared_ptr context), (override)); MOCK_METHOD(bool, IsPaused, (), (const override)); MOCK_METHOD(void, Pause, (), (override)); @@ -65,7 +64,7 @@ class MockEndpointChannel : public EndpointChannel { class MockFrameProcessor : public EndpointManager::FrameProcessor { public: MOCK_METHOD(void, OnIncomingFrame, - (const OfflineFrame& offline_frame, + (OfflineFrame & offline_frame, const std::string& from_endpoint_id, ClientProxy* to_client, Medium current_medium), (override)); diff --git a/cpp/core_v2/internal/internal_payload.cc b/cpp/core_v2/internal/internal_payload.cc new file mode 100644 index 00000000..8e042093 --- /dev/null +++ b/cpp/core_v2/internal/internal_payload.cc @@ -0,0 +1,18 @@ +#include "core_v2/internal/internal_payload.h" + +namespace location { +namespace nearby { +namespace connections { + +InternalPayload::InternalPayload(Payload payload) + : payload_(std::move(payload)), payload_id_(payload_.GetId()) {} + +Payload InternalPayload::ReleasePayload() { + return std::move(payload_); +} + +Payload::Id InternalPayload::GetId() const { return payload_id_; } + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/internal_payload.h b/cpp/core_v2/internal/internal_payload.h new file mode 100644 index 00000000..c2bdd868 --- /dev/null +++ b/cpp/core_v2/internal/internal_payload.h @@ -0,0 +1,81 @@ +#ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_ +#define CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_ + +#include + +#include "core_v2/payload.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the operations layered atop a Payload, for use inside the +// OfflineServiceController. +// +//

There will be an extension of this abstract base class per type of +// Payload. +class InternalPayload { + public: + explicit InternalPayload(Payload payload); + virtual ~InternalPayload() = default; + + Payload ReleasePayload(); + + Payload::Id GetId() const; + + // Returns the PayloadType of the Payload to which this object is bound. + // + //

Note that this is supposed to return the type from the OfflineFrame + // proto rather than what is already available via + // Payload::getType(). + // + // @return The PayloadType. + virtual PayloadTransferFrame::PayloadHeader::PayloadType GetType() const = 0; + + // Deduces the total size of the Payload to which this object is bound. + // + // @return The total size, or -1 if it cannot be deduced (for example, when + // dealing with streaming data). + virtual std::int64_t GetTotalSize() const = 0; + + // Breaks off the next chunk from the Payload to which this object is bound. + // + //

Used when we have a complete Payload that we want to break into smaller + // byte blobs for sending across a hard boundary (like the other side of + // a Binder, or another device altogether). + // + // @return The next chunk from the Payload, or null if we've reached the end. + virtual ByteArray DetachNextChunk() = 0; + + // Adds the next chunk that comprises the Payload to which this object is + // bound. + // + //

Used when we are trying to reconstruct a Payload that lives on the + // other side of a hard boundary (like the other side of a Binder, or another + // device altogether), one byte blob at a time. + // + // @param chunk The next chunk; this being null signals that this is the last + // chunk, which will typically be used as a trigger to perform whatever state + // cleanup may be required by the concrete implementation. + virtual Exception AttachNextChunk(const ByteArray& chunk) = 0; + + // Cleans up any resources used by this Payload. Called when we're stopping + // early, e.g. after being cancelled or having no more recipients left. + virtual void Close() {} + + protected: + Payload payload_; + // We're caching the payload ID here because the backing payload will be + // released to another owner during the lifetime of an incoming + // InternalPayload. + Payload::Id payload_id_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_ diff --git a/cpp/core_v2/internal/internal_payload_factory.cc b/cpp/core_v2/internal/internal_payload_factory.cc new file mode 100644 index 00000000..41eb6cca --- /dev/null +++ b/cpp/core_v2/internal/internal_payload_factory.cc @@ -0,0 +1,279 @@ +#include "core_v2/internal/internal_payload_factory.h" + +#include +#include + +#include "core_v2/payload.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/file.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/pipe.h" +#include "absl/memory/memory.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +class BytesInternalPayload : public InternalPayload { + public: + explicit BytesInternalPayload(Payload payload) + : InternalPayload(std::move(payload)), + total_size_(payload_.AsBytes().size()), + detached_only_chunk_(false) {} + + PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override { + return PayloadTransferFrame::PayloadHeader::BYTES; + } + + std::int64_t GetTotalSize() const override { return total_size_; } + + // Relinquishes ownership of the payload_; retrieves and returns the stored + // ByteArray. + ByteArray DetachNextChunk() override { + if (detached_only_chunk_) { + return {}; + } + + detached_only_chunk_ = true; + return std::move(payload_).AsBytes(); + } + + // Does nothing. + Exception AttachNextChunk(const ByteArray& chunk) override { + return {Exception::kSuccess}; + } + + private: + // We're caching the total size here because the backing payload will be + // moved to another owner during the lifetime of an incoming + // InternalPayload. + const std::int64_t total_size_; + bool detached_only_chunk_; +}; + +class OutgoingStreamInternalPayload : public InternalPayload { + public: + explicit OutgoingStreamInternalPayload(Payload payload) + : InternalPayload(std::move(payload)) {} + + PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override { + return PayloadTransferFrame::PayloadHeader::STREAM; + } + + std::int64_t GetTotalSize() const override { return -1; } + + ByteArray DetachNextChunk() override { + InputStream* input_stream = payload_.AsStream(); + if (!input_stream) return {}; + + ExceptionOr bytes_read = input_stream->Read(kChunkSize); + if (!bytes_read.ok()) { + input_stream->Close(); + return {}; + } + + ByteArray scoped_bytes_read = std::move(bytes_read.result()); + + if (scoped_bytes_read.Empty()) { + // TODO(reznor): logger.atVerbose().log("No more data for outgoing payload + // %s, closing InputStream.", this); + + input_stream->Close(); + return {}; + } + + return scoped_bytes_read; + } + + Exception AttachNextChunk(const ByteArray& chunk) override { + return {Exception::kIo}; + } + + void Close() override { + // Ignore the potential Exception returned by close(), as a counterpart + // to Java's closeQuietly(). + InputStream* stream = payload_.AsStream(); + if (stream) stream->Close(); + } + + private: + static constexpr std::int64_t kChunkSize = Pipe::kChunkSize; +}; + +class IncomingStreamInternalPayload : public InternalPayload { + public: + IncomingStreamInternalPayload(Payload payload, OutputStream& output_stream) + : InternalPayload(std::move(payload)), output_stream_(&output_stream) {} + + PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override { + return PayloadTransferFrame::PayloadHeader::STREAM; + } + + std::int64_t GetTotalSize() const override { return -1; } + + ByteArray DetachNextChunk() override { return {}; } + + Exception AttachNextChunk(const ByteArray& chunk) override { + if (chunk.Empty()) { + output_stream_->Close(); + return {Exception::kSuccess}; + } + + return output_stream_->Write(chunk); + } + + void Close() override { output_stream_->Close(); } + + private: + OutputStream* output_stream_; +}; + +class OutgoingFileInternalPayload : public InternalPayload { + public: + explicit OutgoingFileInternalPayload(Payload payload) + : InternalPayload(std::move(payload)), + total_size_{payload_.AsFile()->GetTotalSize()} {} + + PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override { + return PayloadTransferFrame::PayloadHeader::FILE; + } + + std::int64_t GetTotalSize() const override { return total_size_; } + + ByteArray DetachNextChunk() override { + InputFile* file = payload_.AsFile(); + if (!file) return {}; + + ExceptionOr bytes_read = file->Read(kChunkSize); + if (!bytes_read.ok()) { + return {}; + } + + ByteArray bytes = std::move(bytes_read.result()); + + if (bytes.Empty()) { + // No more data for outgoing payload. + + file->Close(); + return {}; + } + + return bytes; + } + + Exception AttachNextChunk(const ByteArray& chunk) override { + return {Exception::kIo}; + } + + void Close() override { + InputFile* file = payload_.AsFile(); + if (file) file->Close(); + } + + private: + std::int64_t total_size_; + static constexpr std::int64_t kChunkSize = 64 * 1024; +}; + +class IncomingFileInternalPayload : public InternalPayload { + public: + IncomingFileInternalPayload(Payload payload, OutputFile output_file, + std::int64_t total_size) + : InternalPayload(std::move(payload)), + output_file_(std::move(output_file)), + total_size_(total_size) {} + + PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override { + return PayloadTransferFrame::PayloadHeader::FILE; + } + + std::int64_t GetTotalSize() const override { return total_size_; } + + ByteArray DetachNextChunk() override { return {}; } + + Exception AttachNextChunk(const ByteArray& chunk) override { + if (chunk.Empty()) { + // Received null last chunk for incoming payload. + output_file_.Close(); + return {Exception::kSuccess}; + } + + return output_file_.Write(chunk); + } + + void Close() override { output_file_.Close(); } + + private: + OutputFile output_file_; + const std::int64_t total_size_; +}; + +} // namespace + +std::unique_ptr CreateOutgoingInternalPayload( + Payload payload) { + switch (payload.GetType()) { + case Payload::Type::kBytes: + return absl::make_unique(std::move(payload)); + + case Payload::Type::kFile: { + InputFile* file = payload.AsFile(); + const PayloadId file_payload_id = file ? file->GetPayloadId() : 0; + const PayloadId payload_id = payload.GetId(); + CHECK(payload_id == file_payload_id); + return absl::make_unique(std::move(payload)); + } + + case Payload::Type::kStream: + return absl::make_unique( + std::move(payload)); + + default: + DCHECK(false); // This should never happen. + return {}; + } +} + +std::unique_ptr CreateIncomingInternalPayload( + const PayloadTransferFrame& frame) { + if (frame.packet_type() != PayloadTransferFrame::DATA) { + return {}; + } + + const Payload::Id payload_id = frame.payload_header().id(); + switch (frame.payload_header().type()) { + case PayloadTransferFrame::PayloadHeader::BYTES: { + return absl::make_unique( + Payload(payload_id, ByteArray(frame.payload_chunk().body()))); + } + + case PayloadTransferFrame::PayloadHeader::STREAM: { + auto pipe = std::make_shared(); + + return absl::make_unique( + Payload(payload_id, + [pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + }), + pipe->GetOutputStream()); + } + + case PayloadTransferFrame::PayloadHeader::FILE: { + std::int64_t total_size = frame.payload_header().total_size(); + return absl::make_unique( + Payload(payload_id, InputFile(payload_id, total_size)), + OutputFile(payload_id), total_size); + } + default: + DCHECK(false); // This should never happen. + return {}; + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/internal_payload_factory.h b/cpp/core_v2/internal/internal_payload_factory.h new file mode 100644 index 00000000..b4e64174 --- /dev/null +++ b/cpp/core_v2/internal/internal_payload_factory.h @@ -0,0 +1,24 @@ +#ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ +#define CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ + +#include "core_v2/internal/internal_payload.h" +#include "core_v2/payload.h" +#include "proto/connections/offline_wire_formats.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Creates an InternalPayload representing an outgoing Payload. +std::unique_ptr CreateOutgoingInternalPayload(Payload payload); + +// Creates an InternalPayload representing an incoming Payload from a remote +// endpoint. +std::unique_ptr CreateIncomingInternalPayload( + const PayloadTransferFrame& frame); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ diff --git a/cpp/core_v2/internal/internal_payload_factory_test.cc b/cpp/core_v2/internal/internal_payload_factory_test.cc new file mode 100644 index 00000000..b6d34037 --- /dev/null +++ b/cpp/core_v2/internal/internal_payload_factory_test.cc @@ -0,0 +1,116 @@ +#include "core_v2/internal/internal_payload_factory.h" + +#include "core_v2/internal/offline_frames.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/pipe.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr char kText[] = "data chunk"; + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromBytePayload) { + ByteArray data(kText); + std::unique_ptr internal_payload = + CreateOutgoingInternalPayload(Payload{data}); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray(kText)); +} + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamPayload) { + auto pipe = std::make_shared(); + std::unique_ptr internal_payload = + CreateOutgoingInternalPayload(Payload{[pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + }}); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_NE(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray()); +} + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFilePayload) { + Payload::Id payload_id = Payload::GenerateId(); + std::unique_ptr internal_payload = + CreateOutgoingInternalPayload( + Payload{payload_id, InputFile(payload_id, 512)}); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_NE(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray()); + EXPECT_EQ(payload.GetId(), payload_id); + EXPECT_EQ(payload.AsFile()->GetPayloadId(), payload_id); +} + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromByteMessage) { + PayloadTransferFrame frame; + frame.set_packet_type(PayloadTransferFrame::DATA); + std::int64_t payload_chunk_offset = 0; + ByteArray data(kText); + PayloadTransferFrame::PayloadChunk payload_chunk; + payload_chunk.set_offset(payload_chunk_offset); + payload_chunk.set_body(std::string(std::move(data))); + payload_chunk.set_flags(0); + auto& header = *frame.mutable_payload_header(); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_id(12345); + header.set_total_size(512); + *frame.mutable_payload_chunk() = std::move(payload_chunk); + std::unique_ptr internal_payload = + CreateIncomingInternalPayload(frame); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray(kText)); +} + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamMessage) { + PayloadTransferFrame frame; + frame.set_packet_type(PayloadTransferFrame::DATA); + auto& header = *frame.mutable_payload_header(); + header.set_type(PayloadTransferFrame::PayloadHeader::STREAM); + header.set_id(12345); + header.set_total_size(0); + std::unique_ptr internal_payload = + CreateIncomingInternalPayload(frame); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_NE(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray()); + EXPECT_EQ(payload.GetType(), Payload::Type::kStream); +} + +TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFileMessage) { + PayloadTransferFrame frame; + frame.set_packet_type(PayloadTransferFrame::DATA); + auto& header = *frame.mutable_payload_header(); + header.set_type(PayloadTransferFrame::PayloadHeader::FILE); + header.set_id(12345); + header.set_total_size(512); + std::unique_ptr internal_payload = + CreateIncomingInternalPayload(frame); + EXPECT_NE(internal_payload, nullptr); + Payload payload = internal_payload->ReleasePayload(); + EXPECT_NE(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray()); + EXPECT_EQ(payload.GetType(), Payload::Type::kFile); + EXPECT_EQ(payload.GetId(), payload.AsFile()->GetPayloadId()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index cba5c8d8..1681a94f 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -10,6 +10,8 @@ cc_library( "bluetooth_radio.cc", "mediums.cc", "uuid.cc", + "webrtc.cc", + "wifi_lan.cc", ], hdrs = [ "advertisement_read_result.h", @@ -23,22 +25,29 @@ cc_library( "lost_entity_tracker.h", "mediums.h", "uuid.h", + "webrtc.h", + "wifi_lan.h", ], visibility = [ "//core_v2/internal:__subpackages__", ], deps = [ "//core_v2:core_types", + "//core_v2/internal/mediums/webrtc", "//platform_v2/base", + "//platform_v2/base:util", "//platform_v2/public:comm", "//platform_v2/public:logging", "//platform_v2/public:types", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", "//absl/container:flat_hash_map", "//absl/container:flat_hash_set", "//absl/numeric:int128", "//absl/strings", "//absl/time", "//smhasher:libmurmur3", + "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:scoped_refptr", ], ) @@ -70,10 +79,13 @@ cc_test( "bluetooth_radio_test.cc", "lost_entity_tracker_test.cc", "uuid_test.cc", + "webrtc_test.cc", + "wifi_lan_test.cc", ], shard_count = 16, deps = [ ":mediums", + "//core_v2/internal/mediums/webrtc", "//platform_v2/base", "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_advertisement.cc index 027a3a92..c3772e4c 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement.cc +++ b/cpp/core_v2/internal/mediums/ble_advertisement.cc @@ -2,7 +2,9 @@ #include +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/str_cat.h" namespace location { namespace nearby { @@ -42,11 +44,15 @@ BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { return; } - // Now, time to read the bytes! - const auto *read_ptr = ble_advertisement_bytes.data(); + ByteArray advertisement_bytes{ble_advertisement_bytes}; + BaseInputStream base_input_stream{advertisement_bytes}; + // The first 1 byte is supposed to be the version and socket version. + auto version_and_socket_version_byte = + static_cast(base_input_stream.ReadUint8()); - // 1. Version. - version_ = static_cast((*read_ptr & kVersionBitmask) >> 5); + // Version. + version_ = static_cast( + (version_and_socket_version_byte & kVersionBitmask) >> 5); if (!IsSupportedVersion(version_)) { NEARBY_LOG(INFO, "Cannot deserialize BleAdvertisement: unsupported Version %u", @@ -54,49 +60,42 @@ BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { return; } - // 2. Socket Version. - socket_version_ = - static_cast((*read_ptr & kSocketVersionBitmask) >> 2); + // Socket version. + socket_version_ = static_cast( + (version_and_socket_version_byte & kSocketVersionBitmask) >> 2); if (!IsSupportedSocketVersion(socket_version_)) { NEARBY_LOG( INFO, - "Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u", + "Cannot deserialize BleAdvertisement: unsupported SocketVersion %u", socket_version_); version_ = Version::kUndefined; return; } - read_ptr += kVersionLength; - // 3. Service ID hash. - service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength); - read_ptr += kServiceIdHashLength; + // The next 3 bytes are supposed to be the service_id_hash. + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); - // 4.1. Data size. - size_t expected_data_size = DeserializeDataSize(read_ptr); + // The next 4 bytes are supposed to be the length of the data. + std::uint32_t expected_data_size = base_input_stream.ReadUint32(); if (expected_data_size < 0) { - NEARBY_LOG( - INFO, - "Cannot deserialize BleAdvertisement: negative data size %" PRIu64, - expected_data_size); - version_ = Version::kUndefined; - return; - } - read_ptr += kDataSizeLength; - - // Check that the stated data size is the same as what we received. - size_t actual_data_size = ComputeDataSize(ble_advertisement_bytes); - if (actual_data_size < expected_data_size) { NEARBY_LOG(INFO, - "Cannot deserialize BLEAdvertisement: expected data to be %zu " - "bytes, got %" PRIu64 " bytes", - expected_data_size, actual_data_size); + "Cannot deserialize BleAdvertisement: negative data size %d", + expected_data_size); version_ = Version::kUndefined; return; } - // 4.2. Data. - data_ = ByteArray(read_ptr, expected_data_size); - read_ptr += expected_data_size; + // The rest bytes are supposed to be the data. + // Check that the stated data size is the same as what we received. + data_ = base_input_stream.ReadBytes(expected_data_size); + if (data_.size() != expected_data_size) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: expected data to be %u " + "bytes, got %" PRIu64 " bytes ", + expected_data_size, data_.size()); + version_ = Version::kUndefined; + return; + } } BleAdvertisement::operator ByteArray() const { @@ -104,8 +103,6 @@ BleAdvertisement::operator ByteArray() const { return ByteArray{}; } - std::string out; - // The first 3 bits are the Version. char version_and_socket_version_byte = (static_cast(version_) << 5) & kVersionBitmask; @@ -117,11 +114,13 @@ BleAdvertisement::operator ByteArray() const { auto *data_size_bytes_write_ptr = data_size_bytes.data(); SerializeDataSize(data_size_bytes_write_ptr, data_.size()); - out.reserve(1 + service_id_hash_.size() + 1 + data_.size()); - out.append(1, version_and_socket_version_byte); - out.append(std::string(service_id_hash_)); - out.append(std::string(data_size_bytes)); - out.append(std::string(data_)); + // clang-format off + std::string out = + absl::StrCat(std::string(1, version_and_socket_version_byte), + std::string(service_id_hash_), + std::string(data_size_bytes), + std::string(data_)); + // clang-format on return ByteArray{std::move(out)}; } @@ -168,33 +167,6 @@ void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr, } } -size_t BleAdvertisement::DeserializeDataSize( - const char *data_size_bytes_read_ptr) const { - // Allocate a chunk of memory to store our deserialized size. - char data_size_bytes[kDataSizeLength]; - - // Assign the bits of our size from the given raw bytes, keeping in mind that - // we need to convert from Big Endian to Little Endian in the process. - for (int i = 0; i < kDataSizeLength; ++i) { - data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1]; - } - - // Interpret the char array as a single int. - return static_cast( - *(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 diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_advertisement.h index 557b93b8..a1da4d4d 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement.h +++ b/cpp/core_v2/internal/mediums/ble_advertisement.h @@ -67,9 +67,6 @@ class BleAdvertisement { bool IsSupportedSocketVersion(SocketVersion socket_version) const; void SerializeDataSize(char *data_size_bytes_write_ptr, size_t data_size) const; - size_t DeserializeDataSize(const char *data_size_bytes_read_ptr) const; - size_t ComputeDataSize(const ByteArray &ble_advertisement_bytes) const; - size_t ComputeAdvertisementLength(const ByteArray &data) const; static constexpr int kVersionLength = 1; // Length of one int. Be sure to re-evaluate how we compute data size in this diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc index e8910194..d1c55de5 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc @@ -3,7 +3,9 @@ #include #include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/str_cat.h" namespace location { namespace nearby { @@ -13,8 +15,7 @@ namespace mediums { BleAdvertisementHeader::BleAdvertisementHeader( Version version, int num_slots, const ByteArray &service_id_bloom_filter, const ByteArray &advertisement_hash) { - // TODO(edwinwu): Checks if num_slots needs to be >= 0 - if (version != Version::kV2 || + if (version != Version::kV2 || num_slots <= 0 || service_id_bloom_filter.size() != kServiceIdBloomFilterLength || advertisement_hash.size() != kAdvertisementHashLength) { return; @@ -47,13 +48,12 @@ BleAdvertisementHeader::BleAdvertisementHeader( return; } - // Start reading the bytes. - auto *ble_advertisement_header_read_ptr = - ble_advertisement_header_bytes.data(); - - // The first 3 bits are supposed to be the version. - version_ = static_cast( - (*ble_advertisement_header_read_ptr & kVersionBitmask) >> 5); + BaseInputStream base_input_stream{ble_advertisement_header_bytes}; + // The first 1 byte is supposed to be the version and number of slots. + auto version_and_pcp_byte = static_cast(base_input_stream.ReadUint8()); + // The upper 3 bits are supposed to be the version. + version_ = + static_cast((version_and_pcp_byte & kVersionBitmask) >> 5); if (version_ != Version::kV2) { NEARBY_LOG( ERROR, @@ -61,20 +61,19 @@ BleAdvertisementHeader::BleAdvertisementHeader( 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++; + // The lower 5 bits are supposed to be the number of slots. + num_slots_ = static_cast(version_and_pcp_byte & kNumSlotsBitmask); + if (num_slots_ <= 0) { + version_ = Version::kUndefined; + return; + } - // Service ID bloom filter. + // The next 10 bytes are supposed to be the service_id_bloom_filter. service_id_bloom_filter_ = - ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength); - ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength; + base_input_stream.ReadBytes(kServiceIdBloomFilterLength); - // Advertisement hash. - advertisement_hash_ = - ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength); - ble_advertisement_header_read_ptr += kAdvertisementHashLength; + // The next 4 bytes are supposed to be the advertisement_hash. + advertisement_hash_ = base_input_stream.ReadBytes(kAdvertisementHashLength); } BleAdvertisementHeader::operator std::string() const { @@ -82,18 +81,18 @@ BleAdvertisementHeader::operator std::string() const { return ""; } - std::string out; - // The first 3 bits are the Version. char version_and_num_slots_byte = (static_cast(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_)); + + // clang-format off + std::string out = absl::StrCat(std::string(1, version_and_num_slots_byte), + std::string(service_id_bloom_filter_), + std::string(advertisement_hash_)); + // clang-format on return Base64Utils::Encode(ByteArray(std::move(out))); } diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc index 36999641..b4911c95 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc @@ -8,16 +8,17 @@ namespace nearby { namespace connections { namespace mediums { namespace { + constexpr BleAdvertisementHeader::Version kVersion = BleAdvertisementHeader::Version::kV2; constexpr int kNumSlots = 2; -constexpr char kServiceIDBloomFilter[] = - "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"; -constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d"; +constexpr absl::string_view kServiceIDBloomFilter{ + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"}; +constexpr absl::string_view kAdvertisementHash{"\x0a\x0b\x0c\x0d"}; TEST(BleAdvertisementHeaderTest, ConstructionWorks) { - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -34,8 +35,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionWorks) { TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -43,12 +44,24 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) { EXPECT_FALSE(ble_advertisement_header.IsValid()); } +TEST(BleAdvertisementHeaderTest, ConstructionFailsWitZeroNumSlot) { + int num_slot = 0; + + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; + + BleAdvertisementHeader ble_advertisement_header{ + kVersion, num_slot, service_id_bloom_filter, advertisement_hash}; + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortServiceIdBloomFilter) { char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09"; ByteArray short_service_id_bloom_filter_bytes{short_service_id_bloom_filter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, short_service_id_bloom_filter_bytes, @@ -63,7 +76,7 @@ TEST(BleAdvertisementHeaderTest, "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"; ByteArray service_id_bloom_filter{long_service_id_bloom_filter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -74,7 +87,7 @@ TEST(BleAdvertisementHeaderTest, TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { char short_advertisement_hash[] = "\x0a\x0b\x0c"; - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; ByteArray advertisement_hash{short_advertisement_hash}; BleAdvertisementHeader ble_advertisement_header{ @@ -86,7 +99,7 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\x0e"; - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; ByteArray advertisement_hash{long_advertisement_hash}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -95,8 +108,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { } TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader org_ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -116,8 +129,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { } TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; @@ -145,8 +158,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { } TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { - ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; - ByteArray advertisement_hash{kAdvertisementHash}; + ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)}; + ByteArray advertisement_hash{std::string(kAdvertisementHash)}; BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc index cefb7f7a..68b80836 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc @@ -10,20 +10,20 @@ namespace connections { namespace mediums { namespace { -const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; -const BleAdvertisement::SocketVersion kSocketVersion = +constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; +constexpr BleAdvertisement::SocketVersion kSocketVersion = BleAdvertisement::SocketVersion::kV2; -const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; -const char kData[] = - "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"}; +constexpr absl::string_view kData{ + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"}; // This corresponds to the length of a specific BleAdvertisement packed with the // kData given above. Be sure to update this if kData ever changes. -const size_t kAdvertisementLength = 77; -const size_t kLongAdvertisementLength = kAdvertisementLength + 1000; +constexpr size_t kAdvertisementLength = 77; +constexpr size_t kLongAdvertisementLength = kAdvertisementLength + 1000; TEST(BleAdvertisementTest, ConstructionWorksV1) { - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, BleAdvertisement::SocketVersion::kV1, @@ -42,8 +42,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { BleAdvertisement::Version bad_version = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement ble_advertisement{bad_version, kSocketVersion, service_id_hash, data}; @@ -55,8 +55,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { BleAdvertisement::SocketVersion bad_socket_version = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement ble_advertisement{kVersion, bad_socket_version, service_id_hash, data}; @@ -68,7 +68,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { char short_service_id_hash_bytes[] = "\x0a\x0b"; ByteArray bad_service_id_hash{short_service_id_hash_bytes}; - ByteArray data{kData}; + ByteArray data{std::string(kData)}; BleAdvertisement ble_advertisement{kVersion, kSocketVersion, bad_service_id_hash, data}; @@ -80,7 +80,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; ByteArray bad_service_id_hash{long_service_id_hash_bytes}; - ByteArray data{kData}; + ByteArray data{std::string(kData)}; BleAdvertisement ble_advertisement{kVersion, kSocketVersion, bad_service_id_hash, data}; @@ -93,7 +93,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { // attribute length because it needs some room for the preceding fields. char long_data[512]{}; - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray bad_data{long_data, 512}; BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash, @@ -103,8 +103,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { } TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, service_id_hash, data}; @@ -120,13 +120,10 @@ TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { } TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { - char empty_data[0]{}; - - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{empty_data}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, data}; + service_id_hash, ByteArray()}; ByteArray ble_advertisement_bytes{org_ble_advertisement}; BleAdvertisement ble_advertisement{ble_advertisement_bytes}; @@ -134,13 +131,12 @@ TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); - EXPECT_EQ(data, ble_advertisement.GetData()); + EXPECT_TRUE(ble_advertisement.GetData().Empty()); } TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, service_id_hash, data}; @@ -173,8 +169,8 @@ TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { } TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, service_id_hash, data}; @@ -190,8 +186,8 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithInvalidDataLengthFails) { - ByteArray service_id_hash{kServiceIDHashBytes}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, service_id_hash, data}; diff --git a/cpp/core_v2/internal/mediums/ble_packet.cc b/cpp/core_v2/internal/mediums/ble_packet.cc index 0cfb14ff..bd05ab8d 100644 --- a/cpp/core_v2/internal/mediums/ble_packet.cc +++ b/cpp/core_v2/internal/mediums/ble_packet.cc @@ -1,6 +1,8 @@ #include "core_v2/internal/mediums/ble_packet.h" +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/str_cat.h" namespace location { namespace nearby { @@ -30,13 +32,14 @@ BlePacket::BlePacket(const ByteArray& ble_packet_bytes) { return; } - const char *ble_packet_bytes_read_ptr = ble_packet_bytes.data(); - service_id_hash_ = - ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength); - ble_packet_bytes_read_ptr += kServiceIdHashLength; + ByteArray packet_bytes{ble_packet_bytes}; + BaseInputStream base_input_stream{packet_bytes}; + // The first 3 bytes are supposed to be the service_id_hash. + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); - data_ = ByteArray(ble_packet_bytes_read_ptr, - ble_packet_bytes.size() - kServiceIdHashLength); + // The rest bytes are supposed to be the data. + data_ = base_input_stream.ReadBytes(ble_packet_bytes.size() - + kServiceIdHashLength); } BlePacket::operator ByteArray() const { @@ -44,11 +47,8 @@ BlePacket::operator ByteArray() const { return ByteArray(); } - std::string out; - - out.reserve(service_id_hash_.size() + data_.size()); - out.append(std::string(service_id_hash_)); - out.append(std::string(data_)); + std::string out = + absl::StrCat(std::string(service_id_hash_), std::string(data_)); return ByteArray(std::move(out)); } diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_packet_test.cc index b5e33d45..6df5b07d 100644 --- a/cpp/core_v2/internal/mediums/ble_packet_test.cc +++ b/cpp/core_v2/internal/mediums/ble_packet_test.cc @@ -7,12 +7,12 @@ namespace nearby { namespace connections { namespace mediums { -constexpr char kServiceIDHash[] = "\x0a\x0b\x0c"; -constexpr char kData[] = "\x01\x02\x03\x04\x05"; +constexpr absl::string_view kServiceIDHash{"\x0a\x0b\x0c"}; +constexpr absl::string_view kData{"\x01\x02\x03\x04\x05"}; TEST(BlePacketTest, ConstructionWorks) { - ByteArray service_id_hash{kServiceIDHash}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHash)}; + ByteArray data{std::string(kData)}; BlePacket ble_packet{service_id_hash, data}; @@ -24,7 +24,7 @@ TEST(BlePacketTest, ConstructionWorks) { TEST(BlePacketTest, ConstructionWorksWithEmptyData) { char empty_data[] = ""; - ByteArray service_id_hash{kServiceIDHash}; + ByteArray service_id_hash{std::string(kServiceIDHash)}; ByteArray data{empty_data}; BlePacket ble_packet{service_id_hash, data}; @@ -38,7 +38,7 @@ TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { char short_service_id_hash[] = "\x0a\x0b"; ByteArray service_id_hash{short_service_id_hash}; - ByteArray data{kData}; + ByteArray data{std::string(kData)}; BlePacket ble_packet(service_id_hash, data); @@ -49,7 +49,7 @@ TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { char long_service_id_hash[] = "\x0a\x0b\x0c\x0d"; ByteArray service_id_hash{long_service_id_hash}; - ByteArray data{kData}; + ByteArray data{std::string(kData)}; BlePacket ble_packet{service_id_hash, data}; @@ -57,8 +57,8 @@ TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { } TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { - ByteArray service_id_hash{kServiceIDHash}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHash)}; + ByteArray data{std::string(kData)}; BlePacket org_ble_packet{service_id_hash, data}; ByteArray ble_packet_bytes{org_ble_packet}; @@ -77,8 +77,8 @@ TEST(BlePacketTest, ConstructionFromNullBytesFails) { } TEST(BlePacketTest, ConstructionFromShortLengthDataFails) { - ByteArray service_id_hash{kServiceIDHash}; - ByteArray data{kData}; + ByteArray service_id_hash{std::string(kServiceIDHash)}; + ByteArray data{std::string(kData)}; BlePacket org_ble_packet{service_id_hash, data}; ByteArray org_ble_packet_bytes{org_ble_packet}; diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc index 887e115e..b3aba76f 100644 --- a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc +++ b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc @@ -8,10 +8,10 @@ namespace connections { namespace mediums { namespace { -const char kId[] = "AB12"; +constexpr absl::string_view kId{"AB12"}; TEST(BlePeripheralTest, ConstructionWorks) { - ByteArray id{kId}; + ByteArray id{std::string(kId)}; BlePeripheral ble_peripheral{id}; diff --git a/cpp/core_v2/internal/mediums/bloom_filter_test.cc b/cpp/core_v2/internal/mediums/bloom_filter_test.cc index b839d499..4464f7f4 100644 --- a/cpp/core_v2/internal/mediums/bloom_filter_test.cc +++ b/cpp/core_v2/internal/mediums/bloom_filter_test.cc @@ -10,7 +10,7 @@ namespace connections { namespace mediums { namespace { -const size_t kByteArrayLength = 100; +constexpr size_t kByteArrayLength = 100; TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) { BloomFilter bloom_filter; diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc index 33fae825..f9a253c0 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc @@ -24,6 +24,7 @@ class BluetoothClassicTest : public ::testing::Test { using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; BluetoothClassicTest() { + env_.Start(); env_.Reset(); radio_a_ = std::make_unique(); radio_b_ = std::make_unique(); @@ -46,6 +47,7 @@ class BluetoothClassicTest : public ::testing::Test { radio_a_.reset(); radio_b_.reset(); env_.Reset(); + env_.Stop(); } MediumEnvironment& env_{MediumEnvironment::Instance()}; diff --git a/cpp/core_v2/internal/mediums/mediums.cc b/cpp/core_v2/internal/mediums/mediums.cc index aa070252..ee2ea3bf 100644 --- a/cpp/core_v2/internal/mediums/mediums.cc +++ b/cpp/core_v2/internal/mediums/mediums.cc @@ -12,6 +12,10 @@ BluetoothClassic& Mediums::GetBluetoothClassic() { return bluetooth_classic_; } +WifiLan& Mediums::GetWifiLan() { + return wifi_lan_; +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/mediums/mediums.h b/cpp/core_v2/internal/mediums/mediums.h index 230ba61e..193bb98b 100644 --- a/cpp/core_v2/internal/mediums/mediums.h +++ b/cpp/core_v2/internal/mediums/mediums.h @@ -3,6 +3,8 @@ #include "core_v2/internal/mediums/bluetooth_classic.h" #include "core_v2/internal/mediums/bluetooth_radio.h" +#include "core_v2/internal/mediums/wifi_lan.h" + namespace location { namespace nearby { @@ -20,6 +22,9 @@ class Mediums { // Returns a handle to the Bluetooth Classic medium. BluetoothClassic& GetBluetoothClassic(); + // Returns a handle to the Wifi-Lan medium. + WifiLan& GetWifiLan(); + private: // The order of declaration is critical for both construction and // destruction. @@ -31,6 +36,7 @@ class Mediums { // corresponding radio. BluetoothRadio bluetooth_radio_; BluetoothClassic bluetooth_classic_{bluetooth_radio_}; + WifiLan wifi_lan_; }; } // namespace connections diff --git a/cpp/core_v2/internal/mediums/uuid_test.cc b/cpp/core_v2/internal/mediums/uuid_test.cc index f5872dfa..b2df4bb8 100644 --- a/cpp/core_v2/internal/mediums/uuid_test.cc +++ b/cpp/core_v2/internal/mediums/uuid_test.cc @@ -10,7 +10,7 @@ namespace nearby { namespace connections { namespace { -constexpr char kString[] = "some string"; +constexpr absl::string_view kString{"some string"}; constexpr std::uint64_t kNum1 = 0x123456789abcdef0; constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f; diff --git a/cpp/core_v2/internal/mediums/webrtc.cc b/cpp/core_v2/internal/mediums/webrtc.cc new file mode 100644 index 00000000..20e2f1af --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc.cc @@ -0,0 +1,448 @@ +#include "core_v2/internal/mediums/webrtc.h" + +#include +#include + +#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h" +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "absl/strings/str_cat.h" +#include "webrtc/api/jsep.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +// The maximum amount of time to wait to connect to a data channel via WebRTC. +// TODO(himanshujaju): Should this be configurable per platform? +constexpr absl::Duration kDataChannelTimeout = absl::Milliseconds(5000); + +} // namespace + +WebRtc::WebRtc() = default; + +WebRtc::~WebRtc() { + single_thread_executor_.Shutdown(); + { + MutexLock lock(&mutex_); + Disconnect(); + } +} + +bool WebRtc::IsAvailable() { return medium_.IsValid(); } + +bool WebRtc::IsAcceptingConnections() { + MutexLock lock(&mutex_); + return role_ == Role::kOfferer; +} + +bool WebRtc::StartAcceptingConnections(const PeerId& self_id, + AcceptedConnectionCallback callback) { + if (!IsAvailable()) { + { + MutexLock lock(&mutex_); + LogAndDisconnect("WebRTC is not available for data transfer."); + } + return false; + } + + if (IsAcceptingConnections()) { + NEARBY_LOG(WARNING, "Already accepting WebRTC connections."); + return false; + } + + { + MutexLock lock(&mutex_); + if (role_ != Role::kNone) { + NEARBY_LOG(WARNING, + "Cannot start accepting WebRTC connections, current role %d", + role_); + return false; + } + + if (!InitWebRtcFlow(Role::kOfferer, self_id)) return false; + + SessionDescriptionWrapper offer = connection_flow_->CreateOffer(); + pending_local_offer_ = webrtc_frames::EncodeOffer(self_id, offer.GetSdp()); + if (!SetLocalSessionDescription(std::move(offer))) { + return false; + } + + // There is no timeout set for the future returned since we do not know how + // much time it will take for the two devices to discover each other before + // the actual transport can begin. + ListenForWebRtcSocketFuture(connection_flow_->GetDataChannel(), + std::move(callback)); + NEARBY_LOG(INFO, "Started listening for WebRtc connections as %s", + self_id.GetId().c_str()); + } + + return true; +} + +WebRtcSocketWrapper WebRtc::Connect(const PeerId& peer_id) { + MutexLock lock(&mutex_); + + if (!IsAvailable()) { + Disconnect(); + return WebRtcSocketWrapper(); + } + + if (role_ != Role::kNone) { + NEARBY_LOG(WARNING, + "Cannot connect with WebRtc because we are already acting as %d", + role_); + return WebRtcSocketWrapper(); + } + + peer_id_ = peer_id; + if (!InitWebRtcFlow(Role::kAnswerer, PeerId::FromRandom())) { + return WebRtcSocketWrapper(); + } + + NEARBY_LOG(INFO, "Attempting to make a WebRTC connection to %s.", + peer_id.GetId().c_str()); + + std::shared_ptr> socket_future = + ListenForWebRtcSocketFuture(connection_flow_->GetDataChannel(), + AcceptedConnectionCallback()); + + // The two devices have discovered each other, hence we have a timeout for + // establishing the transport channel. + ExceptionOr result = + socket_future->Get(kDataChannelTimeout); + if (result.ok()) return result.result(); + + Disconnect(); + return WebRtcSocketWrapper(); +} + +bool WebRtc::SetLocalSessionDescription(SessionDescriptionWrapper sdp) { + if (!connection_flow_->SetLocalSessionDescription(std::move(sdp))) { + LogAndDisconnect("Unable to set local session description"); + return false; + } + + return true; +} + +void WebRtc::StopAcceptingConnections() { + if (!IsAcceptingConnections()) { + NEARBY_LOG(INFO, + "Skipped StopAcceptingConnections since we are not currently " + "accepting WebRTC connections"); + return; + } + + { + MutexLock lock(&mutex_); + ShutdownSignaling(); + } + NEARBY_LOG(INFO, "Stopped accepting WebRTC connections"); +} + +std::shared_ptr> +WebRtc::ListenForWebRtcSocketFuture( + Future>* + data_channel_future, + AcceptedConnectionCallback callback) { + auto socket_future = std::make_shared>(); + auto data_channel_runnable = [this, socket_future, data_channel_future, + callback{std::move(callback)}]() { + // The overall timeout of creating the socket and data channel is controlled + // by the caller of this function. + ExceptionOr> res = + data_channel_future->Get(); + if (res.ok()) { + WebRtcSocketWrapper wrapper = CreateWebRtcSocketWrapper(res.result()); + callback.accepted_cb(wrapper); + { + MutexLock lock(&mutex_); + socket_ = wrapper; + } + socket_future->Set(wrapper); + } else { + NEARBY_LOG(WARNING, "Failed to get WebRtcSocket."); + socket_future->Set(WebRtcSocketWrapper()); + } + }; + + data_channel_future->AddListener(std::move(data_channel_runnable), + &single_thread_executor_); + + return socket_future; +} + +WebRtcSocketWrapper WebRtc::CreateWebRtcSocketWrapper( + rtc::scoped_refptr data_channel) { + if (data_channel == nullptr) { + return WebRtcSocketWrapper(); + } + + auto socket = std::make_unique("WebRtcSocket", data_channel); + socket->SetOnSocketClosedListener({std::bind(&WebRtc::Disconnect, this)}); + return WebRtcSocketWrapper(std::move(socket)); +} + +bool WebRtc::InitWebRtcFlow(Role role, const PeerId& self_id) { + role_ = role; + self_id_ = self_id; + + if (connection_flow_) { + LogAndShutdownSignaling( + "Tried to initialize WebRTC without shutting down the previous " + "connection"); + return false; + } + + if (signaling_messenger_) { + LogAndShutdownSignaling( + "Tried to initialize WebRTC without shutting down signaling messenger"); + return false; + } + + signaling_messenger_ = medium_.GetSignalingMessenger(self_id_.GetId()); + auto signaling_message_callback = [this](ByteArray message) { + OffloadFromSignalingThread([this, message{std::move(message)}]() { + ProcessSignalingMessage(message); + }); + }; + + if (!signaling_messenger_->IsValid() || + !signaling_messenger_->StartReceivingMessages( + signaling_message_callback)) { + Disconnect(); + return false; + } + + if (role_ == Role::kAnswerer && + !signaling_messenger_->SendMessage( + peer_id_.GetId(), + webrtc_frames::EncodeReadyForSignalingPoke(self_id))) { + LogAndDisconnect(absl::StrCat("Could not send signaling poke to peer ", + peer_id_.GetId())); + return false; + } + + connection_flow_ = ConnectionFlow::Create(GetLocalIceCandidateListener(), + GetDataChannelListener(), medium_); + return true; +} + +void WebRtc::OnLocalIceCandidate( + const webrtc::IceCandidateInterface* local_ice_candidate) { + ::location::nearby::mediums::IceCandidate ice_candidate = + webrtc_frames::EncodeIceCandidate(*local_ice_candidate); + + OffloadFromSignalingThread([this, ice_candidate{std::move(ice_candidate)}]() { + MutexLock lock(&mutex_); + if (IsSignaling()) { + signaling_messenger_->SendMessage( + peer_id_.GetId(), webrtc_frames::EncodeIceCandidates( + self_id_, {std::move(ice_candidate)})); + } else { + pending_local_ice_candidates_.push_back(std::move(ice_candidate)); + } + }); +} + +LocalIceCandidateListener WebRtc::GetLocalIceCandidateListener() { + return {std::bind(&WebRtc::OnLocalIceCandidate, this, std::placeholders::_1)}; +} + +void WebRtc::OnDataChannelClosed() { + OffloadFromSignalingThread([this]() { + MutexLock lock(&mutex_); + LogAndDisconnect("WebRTC data channel closed"); + }); +} + +void WebRtc::OnDataChannelMessageReceived(const ByteArray& message) { + OffloadFromSignalingThread([this, message]() { + MutexLock lock(&mutex_); + if (!socket_.IsValid()) { + LogAndDisconnect("Received a data channel message without a socket"); + return; + } + + socket_.NotifyDataChannelMsgReceived(message); + }); +} + +void WebRtc::OnDataChannelBufferedAmountChanged() { + OffloadFromSignalingThread([this]() { + MutexLock lock(&mutex_); + if (!socket_.IsValid()) { + LogAndDisconnect("Data channel buffer changed without a socket"); + return; + } + + socket_.NotifyDataChannelBufferedAmountChanged(); + }); +} + +DataChannelListener WebRtc::GetDataChannelListener() { + return { + .data_channel_closed_cb = std::bind(&WebRtc::OnDataChannelClosed, this), + .data_channel_message_received_cb = std::bind( + &WebRtc::OnDataChannelMessageReceived, this, std::placeholders::_1), + .data_channel_buffered_amount_changed_cb = + std::bind(&WebRtc::OnDataChannelBufferedAmountChanged, this), + }; +} + +bool WebRtc::IsSignaling() { + return (role_ != Role::kNone && self_id_.IsValid() && peer_id_.IsValid()); +} + +void WebRtc::ProcessSignalingMessage(const ByteArray& message) { + MutexLock lock(&mutex_); + + if (!connection_flow_) { + LogAndDisconnect("Received WebRTC frame before signaling was started"); + return; + } + + location::nearby::mediums::WebRtcSignalingFrame frame; + if (!frame.ParseFromString(std::string(message))) { + LogAndDisconnect("Failed to parse signaling message"); + return; + } + + if (!frame.has_sender_id()) { + LogAndDisconnect("Invalid WebRTC frame: Sender ID is missing"); + return; + } + + if (frame.has_ready_for_signaling_poke() && !peer_id_.IsValid()) { + peer_id_ = PeerId(frame.sender_id().id()); + NEARBY_LOG(INFO, "Peer %s is ready for signaling", + peer_id_.GetId().c_str()); + } + + if (!IsSignaling()) { + NEARBY_LOG(INFO, + "Ignoring WebRTC frame: we are not currently listening for " + "signaling messages"); + return; + } + + if (frame.sender_id().id() != peer_id_.GetId()) { + NEARBY_LOG( + INFO, "Ignoring WebRTC frame: we are only listening for another peer."); + return; + } + + if (frame.has_ready_for_signaling_poke()) { + SendOfferAndIceCandidatesToPeer(); + } else if (frame.has_offer()) { + connection_flow_->OnOfferReceived( + SessionDescriptionWrapper(webrtc_frames::DecodeOffer(frame).release())); + SendAnswerToPeer(); + } else if (frame.has_answer()) { + connection_flow_->OnAnswerReceived(SessionDescriptionWrapper( + webrtc_frames::DecodeAnswer(frame).release())); + } else if (frame.has_ice_candidates()) { + if (!connection_flow_->OnRemoteIceCandidatesReceived( + webrtc_frames::DecodeIceCandidates(frame))) { + LogAndDisconnect("Could not add remote ice candidates."); + } + } +} + +void WebRtc::SendOfferAndIceCandidatesToPeer() { + if (pending_local_offer_.Empty()) { + LogAndDisconnect( + "Unable to send pending offer to remote peer: local offer not set"); + return; + } + + if (!signaling_messenger_->SendMessage(peer_id_.GetId(), + pending_local_offer_)) { + LogAndDisconnect("Failed to send local offer via signaling messenger"); + return; + } + pending_local_offer_ = ByteArray(); + + if (!pending_local_ice_candidates_.empty()) { + signaling_messenger_->SendMessage( + peer_id_.GetId(), + webrtc_frames::EncodeIceCandidates( + self_id_, std::move(pending_local_ice_candidates_))); + } +} + +void WebRtc::SendAnswerToPeer() { + SessionDescriptionWrapper answer = connection_flow_->CreateAnswer(); + ByteArray answer_message( + webrtc_frames::EncodeAnswer(self_id_, answer.GetSdp())); + + if (!SetLocalSessionDescription(std::move(answer))) return; + + if (!signaling_messenger_->SendMessage(peer_id_.GetId(), answer_message)) { + LogAndDisconnect("Failed to send local answer via signaling messenger"); + return; + } +} + +void WebRtc::LogAndDisconnect(const std::string& error_message) { + NEARBY_LOG(WARNING, "Disconnecting WebRTC : %s", error_message.c_str()); + Disconnect(); +} + +void WebRtc::LogAndShutdownSignaling(const std::string& error_message) { + NEARBY_LOG(WARNING, "Stopping WebRTC signaling : %s", error_message.c_str()); + ShutdownSignaling(); +} + +void WebRtc::ShutdownSignaling() { + role_ = Role::kNone; + self_id_ = PeerId(); + peer_id_ = PeerId(); + pending_local_offer_ = ByteArray(); + pending_local_ice_candidates_.clear(); + + if (signaling_messenger_) { + signaling_messenger_->StopReceivingMessages(); + signaling_messenger_.reset(); + } + + if (!socket_.IsValid()) ShutdownIceCandidateCollection(); +} + +void WebRtc::Disconnect() { + ShutdownSignaling(); + ShutdownWebRtcSocket(); + ShutdownIceCandidateCollection(); +} + +void WebRtc::ShutdownWebRtcSocket() { + if (socket_.IsValid()) { + socket_.Close(); + socket_ = WebRtcSocketWrapper(); + } +} + +void WebRtc::ShutdownIceCandidateCollection() { + if (connection_flow_) { + connection_flow_->Close(); + connection_flow_.reset(); + } +} + +void WebRtc::OffloadFromSignalingThread(Runnable runnable) { + single_thread_executor_.Execute(std::move(runnable)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h new file mode 100644 index 00000000..0097d2f7 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -0,0 +1,155 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_ + +#include +#include + +#include "core_v2/internal/mediums/webrtc/connection_flow.h" +#include "core_v2/internal/mediums/webrtc/data_channel_listener.h" +#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h" +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" +#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/webrtc.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/api/data_channel_interface.h" +#include "webrtc/api/jsep.h" +#include "webrtc/api/scoped_refptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Callback that is invoked when a new connection is accepted. +struct AcceptedConnectionCallback { + std::function accepted_cb = + DefaultCallback(); +}; + +// Entry point for connecting a data channel between two devices via WebRtc. +class WebRtc { + public: + WebRtc(); + ~WebRtc(); + + // Returns if WebRtc is available as a medium for nearby to transport data. + // Runs on @MainThread. + bool IsAvailable(); + + // Returns if the device is ready to accept connections from remote devices. + // Runs on @MainThread. + bool IsAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_); + + // Prepares the device to accept incoming WebRtc connections. Returns a + // boolean value indicating if the device has started accepting connections. + // Runs on @MainThread. + bool StartAcceptingConnections(const PeerId& self_id, + AcceptedConnectionCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Prevents device from accepting future connections until + // StartAcceptingConnections() is called. + // Runs on @MainThread. + void StopAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_); + + // Initiates a WebRtc connection with peer device identified by |peer_id|. + // Runs on @MainThread. + WebRtcSocketWrapper Connect(const PeerId& peer_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + enum class Role { + kNone = 0, + kOfferer = 1, + kAnswerer = 2, + }; + + bool InitWebRtcFlow(Role role, const PeerId& self_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + std::shared_ptr> ListenForWebRtcSocketFuture( + Future>* + data_channel_future, + AcceptedConnectionCallback callback); + + WebRtcSocketWrapper CreateWebRtcSocketWrapper( + rtc::scoped_refptr data_channel); + + LocalIceCandidateListener GetLocalIceCandidateListener(); + void OnLocalIceCandidate( + const webrtc::IceCandidateInterface* local_ice_candidate); + + DataChannelListener GetDataChannelListener(); + void OnDataChannelClosed(); + void OnDataChannelMessageReceived(const ByteArray& message); + void OnDataChannelBufferedAmountChanged(); + + // Runs on @MainThread and |single_thread_executor_|. + bool SetLocalSessionDescription(SessionDescriptionWrapper sdp) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on |single_thread_executor_|. + bool IsSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on |single_thread_executor_|. + void ProcessSignalingMessage(const ByteArray& message) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Runs on |single_thread_executor_|. + void SendOfferAndIceCandidatesToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on |single_thread_executor_|. + void SendAnswerToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on @MainThread and |single_thread_executor_|. + void LogAndDisconnect(const std::string& error_message) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on @MainThread and |single_thread_executor_|. + void Disconnect() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void LogAndShutdownSignaling(const std::string& error_message) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on @MainThread and |single_thread_executor_|. + void ShutdownSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on @MainThread and |single_thread_executor_|. + void ShutdownWebRtcSocket() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Runs on @MainThread and |single_thread_executor_|. + void ShutdownIceCandidateCollection(); + + void OffloadFromSignalingThread(Runnable runnable); + + Mutex mutex_; + + Role role_ ABSL_GUARDED_BY(mutex_) = Role::kNone; + PeerId self_id_ ABSL_GUARDED_BY(mutex_); + PeerId peer_id_ ABSL_GUARDED_BY(mutex_); + ByteArray pending_local_offer_ ABSL_GUARDED_BY(mutex_); + std::vector<::location::nearby::mediums::IceCandidate> + pending_local_ice_candidates_ ABSL_GUARDED_BY(mutex_); + + std::unique_ptr connection_flow_; + std::unique_ptr signaling_messenger_ + ABSL_GUARDED_BY(mutex_); + WebRtcSocketWrapper socket_ ABSL_GUARDED_BY(mutex_); + WebRtcMedium medium_; + + SingleThreadExecutor single_thread_executor_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD index d354a426..3e7587d6 100644 --- a/cpp/core_v2/internal/mediums/webrtc/BUILD +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -2,24 +2,39 @@ cc_library( name = "webrtc", srcs = [ "connection_flow.cc", + "data_channel_observer_impl.cc", "peer_connection_observer_impl.cc", + "peer_id.cc", + "signaling_frames.cc", "webrtc_socket.cc", ], hdrs = [ "connection_flow.h", "data_channel_listener.h", + "data_channel_observer_impl.h", "local_ice_candidate_listener.h", "peer_connection_observer_impl.h", + "peer_id.h", + "session_description_wrapper.h", + "signaling_frames.h", "webrtc_socket.h", + "webrtc_socket_wrapper.h", + ], + visibility = [ + "//core_v2/internal:__subpackages__", ], deps = [ "//core_v2:core_types", + "//core_v2/internal/mediums:utils", "//platform_v2/base", "//platform_v2/public:comm", "//platform_v2/public:logging", "//platform_v2/public:types", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", "//absl/memory", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//absl/strings", + "//absl/time", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -27,6 +42,8 @@ cc_test( name = "webrtc_test", srcs = [ "connection_flow_test.cc", + "peer_id_test.cc", + "signaling_frames_test.cc", "webrtc_socket_test.cc", ], deps = [ @@ -34,56 +51,12 @@ cc_test( "//platform_v2/base", "//platform_v2/impl/g3", # buildcleaner: keep "//platform_v2/public:comm", - "//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:comm", "//platform_v2/public:types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "signaling_frames_test", - srcs = ["signaling_frames_test.cc"], - deps = [ - ":peer_id", - ":signaling_frames", - "//platform_v2/impl/g3", # buildcleaner: keep "//net/proto2/public:proto2", "//testing/base/public:gunit_main", - "//webrtc/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", + "//absl/time", + "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:rtc_error", + "//webrtc/api:scoped_refptr", ], ) diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 6a673574..6da917b5 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -1,24 +1,78 @@ #include "core_v2/internal/mediums/webrtc/connection_flow.h" +#include #include +#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h" +#include "platform_v2/public/logging.h" #include "platform_v2/public/mutex_lock.h" #include "platform_v2/public/webrtc.h" #include "absl/memory/memory.h" +#include "absl/time/time.h" +#include "webrtc/api/data_channel_interface.h" +#include "webrtc/api/jsep.h" namespace location { namespace nearby { namespace connections { namespace mediums { +namespace { +// This is the same as the nearby data channel name. +const char kDataChannelName[] = "dataChannel"; + +class CreateSessionDescriptionObserverImpl + : public webrtc::CreateSessionDescriptionObserver { + public: + explicit CreateSessionDescriptionObserverImpl( + Future* settable_future) + : settable_future_(settable_future) {} + ~CreateSessionDescriptionObserverImpl() override = default; + + // webrtc::CreateSessionDescriptionObserver + void OnSuccess(webrtc::SessionDescriptionInterface* desc) override { + settable_future_->Set(SessionDescriptionWrapper{desc}); + } + + void OnFailure(webrtc::RTCError error) override { + NEARBY_LOG(ERROR, "Error when creating session description: %s", + error.message()); + settable_future_->SetException({Exception::kFailed}); + } + + private: + std::unique_ptr> settable_future_; +}; + +class SetSessionDescriptionObserverImpl + : public webrtc::SetSessionDescriptionObserver { + public: + explicit SetSessionDescriptionObserverImpl(Future* settable_future) + : settable_future_(settable_future) {} + + void OnSuccess() override { settable_future_->Set(true); } + + void OnFailure(webrtc::RTCError error) override { + NEARBY_LOG(ERROR, "Error when setting session description: %s", + error.message()); + settable_future_->SetException({Exception::kFailed}); + } + + private: + std::unique_ptr> settable_future_; +}; + +using PeerConnectionState = + webrtc::PeerConnectionInterface::PeerConnectionState; + +} // namespace + std::unique_ptr ConnectionFlow::Create( LocalIceCandidateListener local_ice_candidate_listener, - DataChannelListener data_channel_listener, - SingleThreadExecutor* single_threaded_executor, - WebRtcMedium& webrtc_medium) { - auto connection_flow = absl::WrapUnique(new ConnectionFlow( - std::move(local_ice_candidate_listener), std::move(data_channel_listener), - single_threaded_executor)); + DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium) { + auto connection_flow = absl::WrapUnique( + new ConnectionFlow(std::move(local_ice_candidate_listener), + std::move(data_channel_listener))); if (connection_flow->InitPeerConnection(webrtc_medium)) { return connection_flow; } @@ -28,75 +82,149 @@ std::unique_ptr ConnectionFlow::Create( ConnectionFlow::ConnectionFlow( LocalIceCandidateListener local_ice_candidate_listener, - DataChannelListener data_channel_listener, - SingleThreadExecutor* single_threaded_executor) + DataChannelListener data_channel_listener) : data_channel_listener_(std::move(data_channel_listener)), - peer_connection_observer_(this, std::move(local_ice_candidate_listener), - single_threaded_executor) {} - -std::unique_ptr -ConnectionFlow::CreateOffer() { - MutexLock lock(&mutex_); - - // TODO(bfranz): Implement - - return std::unique_ptr(); + peer_connection_observer_(this, std::move(local_ice_candidate_listener)) { } -std::unique_ptr -ConnectionFlow::CreateAnswer() { +ConnectionFlow::~ConnectionFlow() { Close(); } + +SessionDescriptionWrapper ConnectionFlow::CreateOffer() { MutexLock lock(&mutex_); - // TODO(bfranz): Implement + if (!TransitionState(State::kInitialized, State::kCreatingOffer)) { + return SessionDescriptionWrapper(); + } - return std::unique_ptr(); + webrtc::DataChannelInit data_channel_init; + data_channel_init.reliable = true; + rtc::scoped_refptr data_channel = + peer_connection_->CreateDataChannel(kDataChannelName, &data_channel_init); + data_channel->RegisterObserver(CreateDataChannelObserver(data_channel)); + + auto success_future = new Future(); + webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options; + rtc::scoped_refptr observer = + new rtc::RefCountedObject( + success_future); + peer_connection_->CreateOffer(observer, options); + + ExceptionOr result = success_future->Get(kTimeout); + if (result.ok() && + TransitionState(State::kCreatingOffer, State::kWaitingForAnswer)) { + return std::move(result.result()); + } + + return SessionDescriptionWrapper(); } -bool ConnectionFlow::SetLocalSessionDescription( - std::unique_ptr sdp) { +SessionDescriptionWrapper ConnectionFlow::CreateAnswer() { MutexLock lock(&mutex_); - // TODO(bfranz): Implement + if (!TransitionState(State::kReceivedOffer, State::kCreatingAnswer)) { + return SessionDescriptionWrapper(); + } - return false; + auto success_future = new Future(); + webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options; + rtc::scoped_refptr observer = + new rtc::RefCountedObject( + success_future); + peer_connection_->CreateAnswer(observer, options); + + ExceptionOr result = success_future->Get(kTimeout); + if (result.ok() && + TransitionState(State::kCreatingAnswer, State::kWaitingToConnect)) { + return std::move(result.result()); + } + + return SessionDescriptionWrapper(); } -void ConnectionFlow::OnOfferReceived( - std::unique_ptr offer) { +bool ConnectionFlow::SetLocalSessionDescription(SessionDescriptionWrapper sdp) { MutexLock lock(&mutex_); - // TODO(bfranz): Implement + if (!sdp.IsValid()) return false; + + auto success_future = new Future(); + rtc::scoped_refptr observer = + new rtc::RefCountedObject( + success_future); + + peer_connection_->SetLocalDescription(observer, sdp.Release()); + + ExceptionOr result = success_future->Get(kTimeout); + return result.ok() && result.result(); } -void ConnectionFlow::OnAnswerReceived( - std::unique_ptr answer) { +bool ConnectionFlow::SetRemoteSessionDescription( + SessionDescriptionWrapper sdp) { + if (!sdp.IsValid()) return false; + + auto success_future = new Future(); + rtc::scoped_refptr observer = + new rtc::RefCountedObject( + success_future); + + peer_connection_->SetRemoteDescription(observer, sdp.Release()); + + ExceptionOr result = success_future->Get(kTimeout); + return result.ok() && result.result(); +} + +bool ConnectionFlow::OnOfferReceived(SessionDescriptionWrapper offer) { MutexLock lock(&mutex_); - // TODO(bfranz): Implement + if (!TransitionState(State::kInitialized, State::kReceivedOffer)) { + return false; + } + return SetRemoteSessionDescription(std::move(offer)); +} + +bool ConnectionFlow::OnAnswerReceived(SessionDescriptionWrapper answer) { + MutexLock lock(&mutex_); + + if (!TransitionState(State::kWaitingForAnswer, State::kWaitingToConnect)) { + return false; + } + return SetRemoteSessionDescription(std::move(answer)); } bool ConnectionFlow::OnRemoteIceCandidatesReceived( - std::vector ice_candidates) { + std::vector> + ice_candidates) { MutexLock lock(&mutex_); - // TODO(bfranz): Implement + if (state_ == State::kEnded) { + NEARBY_LOG(WARNING, + "You cannot add ice candidates to a disconnected session."); + return false; + } - return false; + if (state_ != State::kWaitingToConnect && state_ != State::kConnected) { + cached_remote_ice_candidates_.insert( + cached_remote_ice_candidates_.end(), + std::make_move_iterator(ice_candidates.begin()), + std::make_move_iterator(ice_candidates.end())); + return true; + } + + for (auto&& ice_candidate : ice_candidates) { + if (!peer_connection_->AddIceCandidate(ice_candidate.get())) { + NEARBY_LOG(WARNING, "Unable to add remote ice candidate."); + } + } + return true; } -api::ListenableFuture>* +Future>* ConnectionFlow::GetDataChannel() { - return static_cast< - api::ListenableFuture>*>( - &data_channel_future_); + return &data_channel_future_; } bool ConnectionFlow::Close() { MutexLock lock(&mutex_); - - // TODO(bfranz): Implement - - return false; + return CloseLocked(); } bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { @@ -114,20 +242,96 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { } void ConnectionFlow::OnSignalingStable() { - // TODO(bfranz): Implement + MutexLock lock(&mutex_); + + if (state_ != State::kWaitingToConnect && state_ != State::kConnected) return; + + for (auto&& ice_candidate : cached_remote_ice_candidates_) { + if (!peer_connection_->AddIceCandidate(ice_candidate.get())) { + NEARBY_LOG(WARNING, "Unable to add remote ice candidate."); + } + } + cached_remote_ice_candidates_.clear(); } void ConnectionFlow::ProcessOnPeerConnectionChange( webrtc::PeerConnectionInterface::PeerConnectionState new_state) { - // TODO(bfranz): Implement + if (new_state == PeerConnectionState::kClosed || + new_state == PeerConnectionState::kFailed || + new_state == PeerConnectionState::kDisconnected) { + MutexLock lock(&mutex_); + CloseAndNotifyLocked(); + } +} + +void ConnectionFlow::ProcessDataChannelConnected() { + MutexLock lock(&mutex_); + NEARBY_LOG(INFO, "Data channel state changed to connected."); + if (!TransitionState(State::kWaitingToConnect, State::kConnected)) + CloseAndNotifyLocked(); } webrtc::DataChannelObserver* ConnectionFlow::CreateDataChannelObserver( rtc::scoped_refptr data_channel) { - // TODO(bfranz): Implement + if (!data_channel_observer_) { + auto state_change_callback = [this, + data_channel{std::move(data_channel)}]() { + if (data_channel->state() == + webrtc::DataChannelInterface::DataState::kOpen) { + data_channel_future_.Set(std::move(data_channel)); + OffloadFromSignalingThread([this]() { ProcessDataChannelConnected(); }); + } else if (data_channel->state() == + webrtc::DataChannelInterface::DataState::kClosed) { + data_channel->UnregisterObserver(); + OffloadFromSignalingThread([this]() { + MutexLock lock(&mutex_); + CloseAndNotifyLocked(); + }); + } + }; + data_channel_observer_ = absl::make_unique( + &data_channel_listener_, std::move(state_change_callback)); + } - return nullptr; + return reinterpret_cast( + data_channel_observer_.get()); } + +bool ConnectionFlow::TransitionState(State current_state, State new_state) { + if (current_state != state_) { + NEARBY_LOG( + WARNING, + "Invalid state transition to %d: current state is %d but expected %d.", + new_state, state_, current_state); + return false; + } + state_ = new_state; + return true; +} + +void ConnectionFlow::CloseAndNotifyLocked() { + if (CloseLocked()) { + data_channel_listener_.data_channel_closed_cb(); + } +} + +bool ConnectionFlow::CloseLocked() { + if (state_ == State::kEnded) { + return false; + } + state_ = State::kEnded; + + data_channel_future_.SetException({Exception::kInterrupted}); + peer_connection_->Close(); + data_channel_observer_.reset(); + NEARBY_LOG(INFO, "Closed WebRTC connection."); + return true; +} + +void ConnectionFlow::OffloadFromSignalingThread(Runnable runnable) { + single_threaded_signaling_offloader_.Execute(std::move(runnable)); +} + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h index b2b4d523..95776ea4 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h @@ -4,14 +4,16 @@ #include #include "core_v2/internal/mediums/webrtc/data_channel_listener.h" +#include "core_v2/internal/mediums/webrtc/data_channel_observer_impl.h" #include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h" #include "core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h" +#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h" #include "platform_v2/base/runnable.h" #include "platform_v2/public/future.h" #include "platform_v2/public/single_thread_executor.h" #include "platform_v2/public/webrtc.h" -#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/data_channel_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { @@ -56,73 +58,98 @@ class ConnectionFlow { // This method blocks on the creation of the peer connection object. static std::unique_ptr Create( LocalIceCandidateListener local_ice_candidate_listener, - DataChannelListener data_channel_listener, - SingleThreadExecutor* single_threaded_executor, - WebRtcMedium& webrtc_medium); - ~ConnectionFlow() = default; + DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium); + ~ConnectionFlow(); // Create the offer that will be sent to the remote. Mirrors the behaviour of // PeerConnectionInterface::CreateOffer. - std::unique_ptr CreateOffer() - ABSL_LOCKS_EXCLUDED(mutex_); + SessionDescriptionWrapper CreateOffer() ABSL_LOCKS_EXCLUDED(mutex_); // Create the answer that will be sent to the remote. Mirrors the behaviour of // PeerConnectionInterface::CreateAnswer. - std::unique_ptr CreateAnswer() - ABSL_LOCKS_EXCLUDED(mutex_); + SessionDescriptionWrapper CreateAnswer() ABSL_LOCKS_EXCLUDED(mutex_); // Set the local session description. |sdp| was created via CreateOffer() // or CreateAnswer(). - bool SetLocalSessionDescription( - std::unique_ptr sdp) + bool SetLocalSessionDescription(SessionDescriptionWrapper sdp) ABSL_LOCKS_EXCLUDED(mutex_); // Invoked when an offer was received from a remote; this will set the remote - // session description on the peer connection. - void OnOfferReceived( - std::unique_ptr offer) + // session description on the peer connection. Returns true if the offer was + // successfully set as remote session description. + bool OnOfferReceived(SessionDescriptionWrapper offer) ABSL_LOCKS_EXCLUDED(mutex_); // Invoked when an answer was received from a remote; this will set the remote - // session description on the peer connection. - void OnAnswerReceived( - std::unique_ptr answer) + // session description on the peer connection. Returns true if the offer was + // successfully set as remote session description. + bool OnAnswerReceived(SessionDescriptionWrapper answer) ABSL_LOCKS_EXCLUDED(mutex_); // Invoked when an ice candidate was received from a remote; this will add the // ice candidate to the peer connection if ready or cache it otherwise. bool OnRemoteIceCandidatesReceived( - std::vector ice_candidates) - ABSL_LOCKS_EXCLUDED(mutex_); + std::vector> + ice_candidates) ABSL_LOCKS_EXCLUDED(mutex_); // Get a future for the data channel. - api::ListenableFuture>* - GetDataChannel(); + Future>* GetDataChannel(); // Close the peer connection and data channel. bool Close() ABSL_LOCKS_EXCLUDED(mutex_); // Invoked when the peer connection indicates that signaling is stable. - void OnSignalingStable(); + void OnSignalingStable() ABSL_LOCKS_EXCLUDED(mutex_); webrtc::DataChannelObserver* CreateDataChannelObserver( rtc::scoped_refptr data_channel); // Invoked upon changes in the state of peer connection, e.g. react to // disconnect. void ProcessOnPeerConnectionChange( - webrtc::PeerConnectionInterface::PeerConnectionState new_state); + webrtc::PeerConnectionInterface::PeerConnectionState new_state) + ABSL_LOCKS_EXCLUDED(mutex_); private: + enum class State { + kInitialized, + kCreatingOffer, + kWaitingForAnswer, + kReceivedOffer, + kCreatingAnswer, + kWaitingToConnect, + kConnected, + kEnded, + }; + ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener, - DataChannelListener data_channel_listener, - SingleThreadExecutor* single_threaded_executor); + DataChannelListener data_channel_listener); // TODO(bfranz): Consider whether this needs to be configurable per platform static constexpr absl::Duration kTimeout = absl::Milliseconds(250); bool InitPeerConnection(WebRtcMedium& webrtc_medium); + bool TransitionState(State current_state, State new_state) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + bool SetRemoteSessionDescription(SessionDescriptionWrapper sdp); + + void ProcessDataChannelConnected() ABSL_LOCKS_EXCLUDED(mutex_); + + void CloseAndNotifyLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + bool CloseLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void OffloadFromSignalingThread(Runnable runnable); + + Mutex mutex_; + + State state_ ABSL_GUARDED_BY(mutex_) = State::kInitialized; DataChannelListener data_channel_listener_; + std::unique_ptr data_channel_observer_; + Future> data_channel_future_; PeerConnectionObserverImpl peer_connection_observer_; rtc::scoped_refptr peer_connection_; - Mutex mutex_; + std::vector> + cached_remote_ice_candidates_ ABSL_GUARDED_BY(mutex_); + + SingleThreadExecutor single_threaded_signaling_offloader_; }; } // namespace mediums diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc index 3b0895bf..cca175bc 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc @@ -1,10 +1,18 @@ #include "core_v2/internal/mediums/webrtc/connection_flow.h" #include +#include +#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h" +#include "platform_v2/base/byte_array.h" #include "platform_v2/public/webrtc.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/time/time.h" +#include "webrtc/api/data_channel_interface.h" +#include "webrtc/api/jsep.h" +#include "webrtc/api/rtc_error.h" +#include "webrtc/api/scoped_refptr.h" namespace location { namespace nearby { @@ -12,17 +20,159 @@ namespace connections { namespace mediums { namespace { -TEST(ConnectionFlowTest, Create) { - LocalIceCandidateListener local_ice_candidate_listener; - DataChannelListener data_channel_listener; - SingleThreadExecutor executor; +std::unique_ptr CopyCandidate( + const webrtc::IceCandidateInterface* candidate) { + return webrtc::CreateIceCandidate(candidate->sdp_mid(), + candidate->sdp_mline_index(), + candidate->candidate()); +} + +// TODO(bfranz) - Add test that deterministically sends answerer_ice_candidates +// before answer is sent. +TEST(ConnectionFlowTest, SuccessfulOfferAnswerFlow) { + WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; + + Future message_received_future; + + std::unique_ptr offerer, answerer; + + // Send Ice Candidates immediately when you retrieve them + offerer = ConnectionFlow::Create( + {.local_ice_candidate_found_cb = + [&answerer](const webrtc::IceCandidateInterface* candidate) { + std::vector> vec; + vec.push_back(CopyCandidate(candidate)); + // The callback might be alive while the objects in test are + // destroyed. + if (answerer) + answerer->OnRemoteIceCandidatesReceived(std::move(vec)); + }}, + DataChannelListener(), webrtc_medium_offerer); + ASSERT_NE(offerer, nullptr); + answerer = ConnectionFlow::Create( + {.local_ice_candidate_found_cb = + [&offerer](const webrtc::IceCandidateInterface* candidate) { + std::vector> vec; + vec.push_back(CopyCandidate(candidate)); + // The callback might be alive while the objects in test are + // destroyed. + if (offerer) + offerer->OnRemoteIceCandidatesReceived(std::move(vec)); + }}, + {.data_channel_message_received_cb = + [&message_received_future](ByteArray bytes) { + message_received_future.Set(std::move(bytes)); + }}, + webrtc_medium_answerer); + ASSERT_NE(answerer, nullptr); + + // Create and send offer + SessionDescriptionWrapper offer = offerer->CreateOffer(); + EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer); + EXPECT_TRUE(answerer->OnOfferReceived(offer)); + EXPECT_TRUE(offerer->SetLocalSessionDescription(std::move(offer))); + + // Create and send answer + SessionDescriptionWrapper answer = answerer->CreateAnswer(); + EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer); + EXPECT_TRUE(offerer->OnAnswerReceived(answer)); + EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer))); + + // Retrieve Data Channels + ExceptionOr> + offerer_channel = offerer->GetDataChannel()->Get(absl::Seconds(1)); + EXPECT_TRUE(offerer_channel.ok()); + ExceptionOr> + answerer_channel = answerer->GetDataChannel()->Get(absl::Seconds(1)); + EXPECT_TRUE(answerer_channel.ok()); + + // Send message on data channel + const char message[] = "Test"; + offerer_channel.result()->Send(webrtc::DataBuffer(message)); + ExceptionOr received_message = + message_received_future.Get(absl::Seconds(1)); + EXPECT_TRUE(received_message.ok()); + EXPECT_EQ(received_message.result(), ByteArray{message}); +} + +TEST(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) { WebRtcMedium webrtc_medium; - std::unique_ptr connection_flow = ConnectionFlow::Create( - std::move(local_ice_candidate_listener), std::move(data_channel_listener), - &executor, webrtc_medium); + std::unique_ptr answerer = ConnectionFlow::Create( + LocalIceCandidateListener(), DataChannelListener(), webrtc_medium); + ASSERT_NE(answerer, nullptr); - EXPECT_NE(connection_flow, nullptr); + SessionDescriptionWrapper answer = answerer->CreateAnswer(); + EXPECT_FALSE(answer.IsValid()); +} + +TEST(ConnectionFlowTest, SetAnswerBeforeOffer) { + WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; + + std::unique_ptr offerer = + ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(), + webrtc_medium_offerer); + ASSERT_NE(offerer, nullptr); + std::unique_ptr answerer = + ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(), + webrtc_medium_answerer); + ASSERT_NE(answerer, nullptr); + + SessionDescriptionWrapper offer = offerer->CreateOffer(); + EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer); + // Did not set offer as local session description + EXPECT_TRUE(answerer->OnOfferReceived(offer)); + + SessionDescriptionWrapper answer = answerer->CreateAnswer(); + EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer); + EXPECT_FALSE(offerer->OnAnswerReceived(answer)); +} + +TEST(ConnectionFlowTest, CannotCreateOfferAfterClose) { + WebRtcMedium webrtc_medium; + + std::unique_ptr offerer = ConnectionFlow::Create( + LocalIceCandidateListener(), DataChannelListener(), webrtc_medium); + ASSERT_NE(offerer, nullptr); + + EXPECT_TRUE(offerer->Close()); + + EXPECT_FALSE(offerer->CreateOffer().IsValid()); +} + +TEST(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) { + WebRtcMedium webrtc_medium; + + std::unique_ptr offerer = ConnectionFlow::Create( + LocalIceCandidateListener(), DataChannelListener(), webrtc_medium); + ASSERT_NE(offerer, nullptr); + + SessionDescriptionWrapper offer = offerer->CreateOffer(); + EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer); + + EXPECT_TRUE(offerer->Close()); + + EXPECT_FALSE(offerer->SetLocalSessionDescription(offer)); +} + +TEST(ConnectionFlowTest, CannotReceiveOfferAfterClose) { + WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; + + std::unique_ptr offerer = + ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(), + webrtc_medium_offerer); + ASSERT_NE(offerer, nullptr); + std::unique_ptr answerer = + ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(), + webrtc_medium_answerer); + ASSERT_NE(answerer, nullptr); + + EXPECT_TRUE(answerer->Close()); + + SessionDescriptionWrapper offer = offerer->CreateOffer(); + EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer); + + EXPECT_FALSE(answerer->OnOfferReceived(offer)); } } // namespace diff --git a/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h b/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h index 2c4cec68..20baef2a 100644 --- a/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h @@ -14,8 +14,8 @@ struct DataChannelListener { std::function data_channel_closed_cb = DefaultCallback<>(); // Called when a new message was received on the data channel. - std::function data_channel_message_received_cb = - DefaultCallback(); + std::function data_channel_message_received_cb = + DefaultCallback(); // Called when the data channel indicates that the buffered amount has // changed. diff --git a/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc new file mode 100644 index 00000000..cf048ab7 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc @@ -0,0 +1,28 @@ +#include "core_v2/internal/mediums/webrtc/data_channel_observer_impl.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +DataChannelObserverImpl::DataChannelObserverImpl( + DataChannelListener* data_channel_listener, + DataChannelStateChangeCallback callback) + : data_channel_listener_(data_channel_listener), + state_change_callback_(std::move(callback)) {} + +void DataChannelObserverImpl::OnStateChange() { state_change_callback_(); } + +void DataChannelObserverImpl::OnMessage(const webrtc::DataBuffer& buffer) { + data_channel_listener_->data_channel_message_received_cb( + ByteArray(buffer.data.data(), buffer.size())); +} + +void DataChannelObserverImpl::OnBufferedAmountChange(uint64_t sent_data_size) { + data_channel_listener_->data_channel_buffered_amount_changed_cb(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h new file mode 100644 index 00000000..f7508c1a --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h @@ -0,0 +1,35 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_ + +#include "core_v2/internal/mediums/webrtc/data_channel_listener.h" +#include "webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class DataChannelObserverImpl : public webrtc::DataChannelObserver { + public: + using DataChannelStateChangeCallback = std::function; + + ~DataChannelObserverImpl() override = default; + DataChannelObserverImpl(DataChannelListener* data_channel_listener, + DataChannelStateChangeCallback callback); + + // webrtc::DataChannelObserver: + void OnStateChange() override; + void OnMessage(const webrtc::DataBuffer& buffer) override; + void OnBufferedAmountChange(uint64_t sent_data_size) override; + + private: + DataChannelListener* data_channel_listener_; + DataChannelStateChangeCallback state_change_callback_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h b/cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h index 101b6ee0..62adf483 100644 --- a/cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h +++ b/cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h @@ -2,7 +2,7 @@ #define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_ #include "core_v2/listeners.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc index e6c5980d..f44e3d7d 100644 --- a/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc +++ b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc @@ -10,11 +10,9 @@ namespace mediums { PeerConnectionObserverImpl::PeerConnectionObserverImpl( ConnectionFlow* connection_flow, - LocalIceCandidateListener local_ice_candidate_listener, - SingleThreadExecutor* executor) + LocalIceCandidateListener local_ice_candidate_listener) : connection_flow_(connection_flow), - local_ice_candidate_listener_(std::move(local_ice_candidate_listener)), - single_threaded_signaling_offloader_(executor) {} + local_ice_candidate_listener_(std::move(local_ice_candidate_listener)) {} void PeerConnectionObserverImpl::OnIceCandidate( const webrtc::IceCandidateInterface* candidate) { @@ -59,7 +57,7 @@ void PeerConnectionObserverImpl ::OnRenegotiationNeeded() { } void PeerConnectionObserverImpl::OffloadFromSignalingThread(Runnable runnable) { - single_threaded_signaling_offloader_->Execute(std::move(runnable)); + single_threaded_signaling_offloader_.Execute(std::move(runnable)); } } // namespace mediums diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h index fd4491d0..a46be455 100644 --- a/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h +++ b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h @@ -3,7 +3,7 @@ #include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h" #include "platform_v2/public/single_thread_executor.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { @@ -17,8 +17,7 @@ class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver { ~PeerConnectionObserverImpl() override = default; PeerConnectionObserverImpl( ConnectionFlow* connection_flow, - LocalIceCandidateListener local_ice_candidate_listener, - SingleThreadExecutor* executor); + LocalIceCandidateListener local_ice_candidate_listener); // webrtc::PeerConnectionObserver: void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; @@ -37,7 +36,7 @@ class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver { ConnectionFlow* connection_flow_; LocalIceCandidateListener local_ice_candidate_listener_; - SingleThreadExecutor* single_threaded_signaling_offloader_; + SingleThreadExecutor single_threaded_signaling_offloader_; }; } // namespace mediums diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc index 71d2c5db..17523381 100644 --- a/cpp/core_v2/internal/mediums/webrtc/peer_id.cc +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc @@ -32,6 +32,8 @@ PeerId PeerId::FromSeed(const ByteArray& seed) { return PeerId(BytesToStringUppercase(hashed_seed)); } +bool PeerId::IsValid() const { return !id_.empty(); } + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.h b/cpp/core_v2/internal/mediums/webrtc/peer_id.h index e2bd1262..5f849d8d 100644 --- a/cpp/core_v2/internal/mediums/webrtc/peer_id.h +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.h @@ -12,19 +12,22 @@ namespace connections { namespace mediums { // PeerId is used as an identifier to exchange SDP messages to establish WebRTC -// p2p connection. +// p2p connection. An empty PeerId is considered to be invalid. class PeerId { public: - explicit PeerId(const string& id) : id_(id) {} + PeerId() = default; + explicit PeerId(const std::string& id) : id_(id) {} ~PeerId() = default; static PeerId FromRandom(); static PeerId FromSeed(const ByteArray& seed); - const string& GetId() const { return id_; } + bool IsValid() const; + + const std::string& GetId() const { return id_; } private: - const string id_; + std::string id_; }; } // namespace mediums diff --git a/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h b/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h new file mode 100644 index 00000000..1c566deb --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h @@ -0,0 +1,50 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_ + +#include "webrtc/api/peer_connection_interface.h" + +// Wrapper object around SessionDescriptionInterface*. +// This object owns the SessionDescriptionInterface* unless Release() has been +// called. +class SessionDescriptionWrapper { + public: + SessionDescriptionWrapper() = default; + explicit SessionDescriptionWrapper(webrtc::SessionDescriptionInterface* sdp) + : impl_(sdp) {} + + // Copy constructor that performs a deep copy, i.e. creates a new + // SessionDescriptionInterface. + SessionDescriptionWrapper(const SessionDescriptionWrapper& sdp) { + if (sdp.IsValid()) { + impl_ = webrtc::CreateSessionDescription(sdp.GetType(), sdp.ToString()); + } + } + + SessionDescriptionWrapper(SessionDescriptionWrapper&&) = default; + SessionDescriptionWrapper& operator=(SessionDescriptionWrapper&&) = default; + + // Release the ownership of the SessionDescriptionInterface*. + webrtc::SessionDescriptionInterface* Release() { return impl_.release(); } + + // Returns a string representation of the sdp. Only call this, if IsValid() is + // true. + std::string ToString() const { + std::string str; + impl_->ToString(&str); + return str; + } + + // Returns the SdpType of the SessionDescriptionInterface. Only call this, if + // IsValid() is true. + webrtc::SdpType GetType() const { return impl_->GetType(); } + + const webrtc::SessionDescriptionInterface& GetSdp() { return *impl_; } + + // Return whether this object currently holds a SessionDescriptionInterface. + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h index 63a92718..78fe328a 100644 --- a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h @@ -6,7 +6,7 @@ #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" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc index a961ee0d..1caa43e3 100644 --- a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc @@ -40,7 +40,7 @@ Exception WebRtcSocket::OutputStreamImpl::Close() { // WebRtcSocket WebRtcSocket::WebRtcSocket( - const string& name, + const std::string& name, rtc::scoped_refptr data_channel) : name_(name), data_channel_(std::move(data_channel)) {} diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h index e5d90939..c416901e 100644 --- a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h @@ -11,7 +11,7 @@ #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" +#include "webrtc/api/data_channel_interface.h" namespace location { namespace nearby { namespace connections { @@ -27,7 +27,7 @@ constexpr int kMaxDataSize = 1 * 1024 * 1024; // which could lead to data loss. class WebRtcSocket : public Socket { public: - WebRtcSocket(const string& name, + WebRtcSocket(const std::string& name, rtc::scoped_refptr data_channel); ~WebRtcSocket() override = default; @@ -78,7 +78,7 @@ class WebRtcSocket : public Socket { bool SendMessage(const ByteArray& data); void BlockUntilSufficientSpaceInBuffer(int length); - string name_; + std::string name_; rtc::scoped_refptr data_channel_; Pipe pipe_; diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc index 89184569..423b06ed 100644 --- a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc @@ -5,7 +5,7 @@ #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" +#include "webrtc/api/data_channel_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h new file mode 100644 index 00000000..e7cc89ee --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h @@ -0,0 +1,49 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_ + +#include + +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class WebRtcSocketWrapper final { + public: + WebRtcSocketWrapper() = default; + WebRtcSocketWrapper(const WebRtcSocketWrapper&) = default; + WebRtcSocketWrapper& operator=(const WebRtcSocketWrapper&) = default; + explicit WebRtcSocketWrapper(std::unique_ptr socket) + : impl_(socket.release()) {} + ~WebRtcSocketWrapper() = default; + + InputStream& GetInputStream() { return impl_->GetInputStream(); } + + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + + void NotifyDataChannelMsgReceived(const ByteArray& message) { + impl_->NotifyDataChannelMsgReceived(message); + } + + void NotifyDataChannelBufferedAmountChanged() { + impl_->NotifyDataChannelBufferedAmountChanged(); + } + + void Close() { return impl_->Close(); } + + bool IsValid() const { return impl_ != nullptr; } + + WebRtcSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc_test.cc b/cpp/core_v2/internal/mediums/webrtc_test.cc new file mode 100644 index 00000000..140571f4 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc_test.cc @@ -0,0 +1,121 @@ +#include "core_v2/internal/mediums/webrtc.h" + +#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/public/mutex_lock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +// Basic test to check that device is accepting connections when initialized. +TEST(WebRtcTest, NotAcceptingConnections) { + WebRtc webrtc; + ASSERT_TRUE(webrtc.IsAvailable()); + EXPECT_FALSE(webrtc.IsAcceptingConnections()); +} + +// Tests the flow when the device tries to accept connections twice. In this +// case, only the first call is successful and subsequent calls fail. +TEST(WebRtcTest, StartAcceptingConnectionTwice) { + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + + WebRtc webrtc; + PeerId self_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + ASSERT_TRUE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); + EXPECT_FALSE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); + EXPECT_TRUE(webrtc.IsAcceptingConnections()); +} + +// Tests the flow when the device tries to connect but the data channel times +// out. +TEST(WebRtcTest, Connect_DataChannelTimeOut) { + WebRtc webrtc; + PeerId peer_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + WebRtcSocketWrapper wrapper_1 = webrtc.Connect(peer_id); + EXPECT_FALSE(wrapper_1.IsValid()); + + EXPECT_TRUE( + webrtc.StartAcceptingConnections(peer_id, AcceptedConnectionCallback())); +} + +// Tests the flow when the device calls Connect() after calling +// StartAcceptingConnections() without StopAcceptingConnections(). +TEST(WebRtcTest, StartAcceptingConnection_ThenConnect) { + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + + WebRtc webrtc; + PeerId self_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + ASSERT_TRUE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); + WebRtcSocketWrapper wrapper = webrtc.Connect(PeerId("random_peer_id")); + EXPECT_TRUE(webrtc.IsAcceptingConnections()); + EXPECT_FALSE(wrapper.IsValid()); + EXPECT_FALSE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); +} + +// Tests the flow when the device calls StartAcceptingConnections but the medium +// is closed before a peer device can connect to it. +TEST(WebRtcTest, StartAndStopAcceptingConnections) { + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + + WebRtc webrtc; + PeerId self_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + ASSERT_TRUE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); + webrtc.StopAcceptingConnections(); + EXPECT_FALSE(webrtc.IsAcceptingConnections()); +} + +// Tests the flow when the device calls StartAcceptingConnections() after +// calling Connect() without disconnecting in between. +TEST(WebRtcTest, Connect_ThenStartAcceptingConnections) { + // TODO(himanshujaju) - Complete the test. +} + +// Tests the flow when the device tries to connect to two different peers +// without disconnecting in between. +TEST(WebRtcTest, ConnectTwice) { + // TODO(himanshujaju) - Complete the test. +} + +// Tests the flow when the two devices exchange SDP messages and connect to each +// other but disconnect before being able to send/receive the actual data. +TEST(WebRtcTest, ConnectBothDevicesAndAbort) { + // TODO(himanshujaju) - Complete the test. +} + +// Tests the flow when the two devices exchange SDP messages and connect to each +// other and the actual data is exchanged successfully between the devices. +TEST(WebRtcTest, ConnectBothDevicesAndSendData) { + // TODO(himanshujaju) - Complete the test. +} + +} // namespace + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/wifi_lan.cc b/cpp/core_v2/internal/mediums/wifi_lan.cc new file mode 100644 index 00000000..894c4b9c --- /dev/null +++ b/cpp/core_v2/internal/mediums/wifi_lan.cc @@ -0,0 +1,230 @@ +#include "core_v2/internal/mediums/wifi_lan.h" + +#include +#include +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +bool WifiLan::IsAvailable() const { + MutexLock lock(&mutex_); + + return IsAvailableLocked(); +} + +bool WifiLan::IsAvailableLocked() const { return medium_.IsValid(); } + +bool WifiLan::StartAdvertising(const std::string& service_id, + const std::string& wifi_lan_service_info_name) { + MutexLock lock(&mutex_); + + if (wifi_lan_service_info_name.empty()) { + NEARBY_LOG( + INFO, + "Refusing to turn on WifiLan advertising. Empty service info name."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, + "Can't turn on WifiLan advertising. WifiLan is not available."); + return false; + } + + if (!medium_.StartAdvertising(service_id, wifi_lan_service_info_name)) { + NEARBY_LOG( + INFO, "Failed to turn on WifiLan advertising with service info name=%s", + wifi_lan_service_info_name.c_str()); + return false; + } + + NEARBY_LOG(INFO, "Turned on WifiLan advertising with service info name=%s", + wifi_lan_service_info_name.c_str()); + advertising_info_.service_id = service_id; + return true; +} + +void WifiLan::StopAdvertising(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsAdvertisingLocked()) { + NEARBY_LOG(INFO, "Can't turn off WifiLan advertising; it is already off"); + return; + } + + medium_.StopAdvertising(advertising_info_.service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.Clear(); +} + +bool WifiLan::IsAdvertising() { + MutexLock lock(&mutex_); + + return IsAdvertisingLocked(); +} + +bool WifiLan::IsAdvertisingLocked() { + return !advertising_info_.Empty(); +} + +bool WifiLan::StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOG(INFO, + "Refusing to start WifiLan discovering with empty service id."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG( + INFO, + "Can't discover WifiLan services because WifiLan isn't available."); + return false; + } + + if (IsDiscoveringLocked(service_id)) { + NEARBY_LOG( + INFO, + "Refusing to start discovery of WifiLan services because another " + "discovery is already in-progress."); + return false; + } + + if (!medium_.StartDiscovery(service_id, callback)) { + NEARBY_LOG(INFO, "Failed to start discovery of WifiLan services."); + return false; + } + + // Mark the fact that we're currently performing a WifiLan discovering. + discovering_info_.service_id = service_id; + return true; +} + +void WifiLan::StopDiscovery(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringLocked(service_id)) { + NEARBY_LOG(INFO, + "Can't turn off WifiLan discovering because we never started " + "discovering."); + return; + } + + medium_.StopDiscovery(service_id); + discovering_info_.Clear(); +} + +bool WifiLan::IsDiscovering(const std::string& service_id) { + MutexLock lock(&mutex_); + + return IsDiscoveringLocked(service_id); +} + +bool WifiLan::IsDiscoveringLocked(const std::string& service_id) { + return !discovering_info_.Empty(); +} + +bool WifiLan::StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOG(INFO, + "Refusing to start accepting WifiLan connections with empty " + "service id."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, + "Can't start accepting WifiLan connections for %s because " + "WifiLan isn't available.", + service_id.c_str()); + return false; + } + + if (IsAcceptingConnectionsLocked(service_id)) { + NEARBY_LOG(INFO, + "Refusing to start accepting WifiLan connections for %s because " + "another WifiLan service socket is already in-progress.", + service_id.c_str()); + return false; + } + + if (!medium_.StartAcceptingConnections(service_id, callback)) { + NEARBY_LOG(INFO, "Failed to accept connections callback for %s.", + service_id.c_str()); + return false; + } + + accepting_connections_info_.service_id = service_id; + return true; +} + +void WifiLan::StopAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsAcceptingConnectionsLocked(service_id)) { + NEARBY_LOG(INFO, + "Can't stop accepting WifiLan connections because it was never " + "started."); + return; + } + + medium_.StopAcceptingConnections(accepting_connections_info_.service_id); + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.Clear(); +} + +bool WifiLan::IsAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + return IsAcceptingConnectionsLocked(service_id); +} + +bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) { + return !accepting_connections_info_.Empty(); +} + +WifiLanSocket WifiLan::Connect(WifiLanService& wifi_lan_service, + const std::string& service_id) { + MutexLock lock(&mutex_); + NEARBY_LOG(INFO, "WifiLan::Connect: service=%p", &wifi_lan_service); + // Socket to return. To allow for NRVO to work, it has to be a single object. + WifiLanSocket socket; + + if (service_id.empty()) { + NEARBY_LOG(INFO, + "Refusing to create WifiLan socket with empty service_id."); + return socket; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, + "Can't create client WifiLan socket [service_id=%s]; WifiLan " + "isn't available.", + service_id.c_str()); + return socket; + } + + socket = medium_.Connect(wifi_lan_service, service_id); + if (!socket.IsValid()) { + NEARBY_LOG(INFO, "Failed to Connect via WifiLan [service=%s]", + service_id.c_str()); + } + + return socket; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/wifi_lan.h b/cpp/core_v2/internal/mediums/wifi_lan.h new file mode 100644 index 00000000..196cc2cd --- /dev/null +++ b/cpp/core_v2/internal/mediums/wifi_lan.h @@ -0,0 +1,118 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/wifi_lan.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiLan { + public: + using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback; + using AcceptedConnectionCallback = WifiLanMedium::AcceptedConnectionCallback; + + // Returns true, if WifiLan communications are supported by a platform. + bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets custom service info name, and then enables WifiLan advertising. + // Returns true, if name is successfully set, and false otherwise. + bool StartAdvertising(const std::string& service_id, + const std::string& wifi_lan_service_info_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables WifiLan advertising, and restores service info name to + // what they were before the call to StartAdvertising(). + void StopAdvertising(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsAdvertising() ABSL_LOCKS_EXCLUDED(mutex_); + + // Enables WifiLan discovery mode. Will report any discoverable services in + // range through a callback. Returns true, if discovery mode was enabled, + // false otherwise. + bool StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables WifiLan discovery mode. + void StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsDiscovering(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Starts a worker thread, creates a WifiLan socket, associates it with a + // service id. + bool StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes socket corresponding to a service id. + void StopAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Establishes connection to WifiLan service that was might be started on + // another service with StartAcceptingConnections() using the same service_id. + // Blocks until connection is established, or server-side is terminated. + // Returns socket instance. On success, WifiLanSocket.IsValid() return true. + WifiLanSocket Connect(WifiLanService& wifi_lan_service, + const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + struct AdvertisingInfo { + bool Empty() const { return service_id.empty(); } + void Clear() { service_id.clear(); } + + std::string service_id; + }; + + struct DiscoveringInfo { + bool Empty() const { return service_id.empty(); } + void Clear() { service_id.clear(); } + + std::string service_id; + }; + + struct AcceptingConnectionsInfo { + bool Empty() const { return service_id.empty(); } + void Clear() { service_id.clear(); } + + std::string service_id; + }; + + // Same as IsAvailable(), but must be called with mutex_ held. + bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsAdvertising(), but must be called with mutex_ held. + bool IsAdvertisingLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsDiscovering(), but must be called with mutex_ held. + bool IsDiscoveringLocked(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsAcceptingConnections(), but must be called with mutex_ held. + bool IsAcceptingConnectionsLocked(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + WifiLanMedium medium_ ABSL_GUARDED_BY(mutex_); + AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); + DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_); + AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_ diff --git a/cpp/core_v2/internal/mediums/wifi_lan_test.cc b/cpp/core_v2/internal/mediums/wifi_lan_test.cc new file mode 100644 index 00000000..545d6c3b --- /dev/null +++ b/cpp/core_v2/internal/mediums/wifi_lan_test.cc @@ -0,0 +1,50 @@ +#include "core_v2/internal/mediums/wifi_lan.h" + +#include + +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/wifi_lan.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; +constexpr absl::string_view kServiceInfoName{ + "Simulated WifiLan service encrypted string #1"}; + +// TODO(edwinwu): Continue writing more tests after medium_environment is done. +class WifiLanTest : public ::testing::Test { + protected: + using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback; + + WifiLanTest() { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(WifiLanTest, CanConstructValidObject) { + env_.Start(); + WifiLan wifi_lan_a; + WifiLan wifi_lan_b; + + EXPECT_TRUE(wifi_lan_a.IsAvailable()); + EXPECT_TRUE(wifi_lan_b.IsAvailable()); + env_.Stop(); +} + +TEST_F(WifiLanTest, CanStartAdvertising) { + env_.Start(); + WifiLan wifi_lan; + EXPECT_TRUE(wifi_lan.StartAdvertising(std::string(kServiceID), + std::string(kServiceInfoName))); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 792922bb..6ccb6d9e 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -14,7 +14,7 @@ namespace { using ExceptionOrOfflineFrame = ExceptionOr; using Medium = proto::connections::Medium; -using MessageLite = ::google3_proto_compat::MessageLite; +using MessageLite = ::google::protobuf::MessageLite; ByteArray ToBytes(OfflineFrame&& frame) { ByteArray bytes(frame.ByteSizeLong()); diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc index b0dedddd..d5ba067b 100644 --- a/cpp/core_v2/internal/offline_frames_test.cc +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -19,8 +19,8 @@ namespace { using Medium = proto::connections::Medium; using ::testing::EqualsProto; -constexpr char kEndpointId[] = "ABC"; -constexpr char kEndpointName[] = "XYZ"; +constexpr absl::string_view kEndpointId{"ABC"}; +constexpr absl::string_view kEndpointName{"XYZ"}; constexpr int kNonce = 1234; constexpr std::array kMediums = { Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, @@ -78,9 +78,9 @@ TEST(OfflineFramesTest, CanGenerateConnectionRequest) { mediums: WEB_RTC > >)pb"; - ByteArray bytes = - ForConnectionRequest(kEndpointId, kEndpointName, kNonce, - std::vector(kMediums.begin(), kMediums.end())); + ByteArray bytes = ForConnectionRequest( + std::string(kEndpointId), std::string(kEndpointName), kNonce, + std::vector(kMediums.begin(), kMediums.end())); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); @@ -223,7 +223,7 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { client_introduction: < endpoint_id: "ABC" > > >)pb"; - ByteArray bytes = ForBandwidthUpgradeIntroduction(kEndpointId); + ByteArray bytes = ForBandwidthUpgradeIntroduction(std::string(kEndpointId)); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc new file mode 100644 index 00000000..126e193b --- /dev/null +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -0,0 +1,659 @@ +#include "core_v2/internal/p2p_cluster_pcp_handler.h" + +#include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/wifi_lan_endpoint_channel.h" +#include "platform_v2/public/crypto.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source, + size_t size) { + ByteArray full_hash = Crypto::Sha256(source); + ByteArray result(size); + result.CopyAt(0, full_hash); + return result; +} + +P2pClusterPcpHandler::P2pClusterPcpHandler( + Mediums& mediums, EndpointManager* endpoint_manager, + EndpointChannelManager* endpoint_channel_manager, Pcp pcp) + : BasePcpHandler(endpoint_manager, endpoint_channel_manager, pcp), + bluetooth_radio_(mediums.GetBluetoothRadio()), + bluetooth_medium_(mediums.GetBluetoothClassic()), + wifi_lan_medium_(mediums.GetWifiLan()) {} + +// Returns a vector or mediums sorted in order or decreasing priority for +// all the supported mediums. +// NOTE: currently we only have BT, but eventually it will be more, and items +// will have to be sorted in the order of decreasing traffic bandwidth. +// Example: WiFi_LAN, BT, BLE +std::vector +P2pClusterPcpHandler::GetConnectionMediumsByPriority() { + std::vector mediums; + if (bluetooth_medium_.IsAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + if (wifi_lan_medium_.IsAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } + return mediums; +} + +proto::connections::Medium P2pClusterPcpHandler::GetDefaultUpgradeMedium() { + return proto::connections::WIFI_LAN; +} + +BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( + ClientProxy* client, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name, const ConnectionOptions& options) { + std::vector mediums_started_successfully; + + const ByteArray bluetooth_hash = + GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); + proto::connections::Medium bluetooth_medium = + StartBluetoothAdvertising(client, service_id, bluetooth_hash, + local_endpoint_id, local_endpoint_name); + if (bluetooth_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: BT added"); + mediums_started_successfully.push_back(bluetooth_medium); + } + + const ByteArray wifi_lan_hash = + GenerateHash(service_id, WifiLanServiceInfo::kServiceIdHashLength); + proto::connections::Medium wifi_lan_medium = + StartWifiLanAdvertising(client, service_id, wifi_lan_hash, + local_endpoint_id, local_endpoint_name); + if (wifi_lan_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartAdvertisingImpl: WifiLan added"); + mediums_started_successfully.push_back(wifi_lan_medium); + } + + if (mediums_started_successfully.empty()) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: not started"); + return { + .status = {Status::kBluetoothError}, + }; + } + + // The rest of the operations for startAdvertising() will continue + // asynchronously via + // IncomingBluetoothConnectionProcessor.onIncomingBluetoothConnection(), so + // leave it to that to signal any errors that may occur. + return { + .status = {Status::kSuccess}, + .mediums = std::move(mediums_started_successfully), + }; +} + +Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { + wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + bluetooth_medium_.TurnOffDiscoverability(); + bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); + return {Status::kSuccess}; +} + +bool P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint( + const std::string& name_string, const std::string& service_id, + const BluetoothDeviceName& name) const { + if (!name.IsValid()) { + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: name is invalid"); + return false; + } + + if (name.GetPcp() != GetPcp()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: Pcp is " + "not matched; name.Pcp=%d, Pcp=%d", + name.GetPcp(), GetPcp()); + return false; + } + + ByteArray expected_service_id_hash = + GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); + + if (name.GetServiceIdHash() != expected_service_id_hash) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: service " + "id hash is " + "not matched; name.service_id_hash=%s, expected=%s", + name.GetServiceIdHash().data(), expected_service_id_hash.data()); + return false; + } + + return true; +} + +std::function +P2pClusterPcpHandler::MakeBluetoothDeviceDiscoveredHandler( + ClientProxy* client, const std::string& service_id) { + return [this, client, service_id](BluetoothDevice& device) { + RunOnPcpHandlerThread([this, client, service_id, &device]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG(INFO, + "BT discovery handler (FOUND) [client=%p, service=%s]: not " + "in discovery mode", + client, service_id.c_str()); + return; + } + + // Parse the Bluetooth device name. + const std::string& device_name_string = device.GetName(); + BluetoothDeviceName device_name(device_name_string); + + // Make sure the Bluetooth device name points to a valid + // endpoint we're discovering. + if (!IsRecognizedBluetoothEndpoint(device_name_string, service_id, + device_name)) + return; + + // Report the discovered endpoint to the client. + NEARBY_LOG(INFO, + "Invoking BasePcpHandler::OnEndpointFound() for BT " + "service=%s; id=%s; name=%s", + service_id.c_str(), device_name.GetEndpointId().c_str(), + device_name.GetEndpointName().c_str()); + OnEndpointFound(client, + std::make_shared(BluetoothEndpoint{ + { + .endpoint_id = device_name.GetEndpointId(), + .endpoint_name = device_name.GetEndpointName(), + .service_id = service_id, + .medium = proto::connections::Medium::BLUETOOTH, + }, + device, + })); + }); + }; +} + +std::function +P2pClusterPcpHandler::MakeBluetoothDeviceLostHandler( + ClientProxy* client, const std::string& service_id) { + return [this, client, service_id](BluetoothDevice& device) { + RunOnPcpHandlerThread([this, client, &service_id, &device]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG(INFO, + "BT discovery handler (LOST) [client=%p, service=%s]: not " + "in discovery mode", + client, service_id.c_str()); + return; + } + + // Parse the Bluetooth device name. + const std::string& device_name_string = device.GetName(); + BluetoothDeviceName device_name(device_name_string); + + // Make sure the Bluetooth device name points to a valid + // endpoint we're discovering. + if (!IsRecognizedBluetoothEndpoint(device_name_string, service_id, + device_name)) + return; + + // Report the discovered endpoint to the client. + NEARBY_LOG(INFO, + "BT discovery handler (LOST) [client=%p, service=%s]: report " + "to client", + client, service_id.c_str()); + OnEndpointLost(client, + BluetoothEndpoint{ + { + .endpoint_id = device_name.GetEndpointId(), + .endpoint_name = device_name.GetEndpointName(), + .service_id = service_id, + .medium = proto::connections::Medium::BLUETOOTH, + }, + device, + }); + }); + }; +} + +bool P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint( + const std::string& name_string, const std::string& service_id, + const WifiLanServiceInfo& name) const { + if (!name.IsValid()) { + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: name is invalid"); + return false; + } + + if (name.GetPcp() != GetPcp()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: Pcp is " + "not matched; name.Pcp=%d, Pcp=%d", + name.GetPcp(), GetPcp()); + return false; + } + + ByteArray expected_service_id_hash = + GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); + + if (name.GetServiceIdHash() != expected_service_id_hash) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: service " + "id hash is " + "not matched; name.service_id_hash=%s, expected=%s", + name.GetServiceIdHash().data(), expected_service_id_hash.data()); + return false; + } + + return true; +} + +std::function +P2pClusterPcpHandler::MakeWifiLanServiceDiscoveredHandler( + ClientProxy* client, const std::string& service_id) { + return [this, client](WifiLanService& service, + const std::string& service_id) { + RunOnPcpHandlerThread([this, client, service_id, &service]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG( + INFO, + "WifiLan discovery handler (FOUND) [client=%p, service=%s]: not " + "in discovery mode", + client, service_id.c_str()); + return; + } + + // Parse the WifiLan service name. + const std::string& service_name_string = service.GetName(); + WifiLanServiceInfo service_name(service_name_string); + + // Make sure the WifiLan service name points to a valid + // endpoint we're discovering. + if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id, + service_name)) + return; + + // Report the discovered endpoint to the client. + NEARBY_LOG(INFO, + "Invoking BasePcpHandler::OnEndpointFound() for WifiLan " + "service=%s; id=%s; name=%s", + service_id.c_str(), service_name.GetEndpointId().c_str(), + service_name.GetEndpointName().c_str()); + OnEndpointFound(client, + std::make_shared(WifiLanEndpoint{ + { + .endpoint_id = service_name.GetEndpointId(), + .endpoint_name = service_name.GetEndpointName(), + .service_id = service_id, + .medium = proto::connections::Medium::WIFI_LAN, + }, + service, + })); + }); + }; +} + +std::function +P2pClusterPcpHandler::MakeWifiLanServiceLostHandler( + ClientProxy* client, const std::string& service_id) { + return [this, client](WifiLanService& service, + const std::string& service_id) { + RunOnPcpHandlerThread([this, client, &service_id, &service]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG( + INFO, + "WifiLan discovery handler (LOST) [client=%p, service=%s]: not " + "in discovery mode", + client, service_id.c_str()); + return; + } + + // Parse the WifiLan service name. + const std::string& service_name_string = service.GetName(); + WifiLanServiceInfo service_name(service_name_string); + + // Make sure the WifiLan service name points to a valid + // endpoint we're discovering. + if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id, + service_name)) + return; + + // Report the discovered endpoint to the client. + NEARBY_LOG( + INFO, + "WifiLan discovery handler (LOST) [client=%p, service=%s]: report " + "to client", + client, service_id.c_str()); + OnEndpointLost(client, + WifiLanEndpoint{ + { + .endpoint_id = service_name.GetEndpointId(), + .endpoint_name = service_name.GetEndpointName(), + .service_id = service_id, + .medium = proto::connections::Medium::WIFI_LAN, + }, + service, + }); + }); + }; +} + +BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( + ClientProxy* client, const std::string& service_id, + const ConnectionOptions& options) { + std::vector mediums_started_successfully; + + proto::connections::Medium bluetooth_medium = StartBluetoothDiscovery( + { + .device_discovered_cb = + MakeBluetoothDeviceDiscoveredHandler(client, service_id), + .device_lost_cb = MakeBluetoothDeviceLostHandler(client, service_id), + }, + client, service_id); + if (bluetooth_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: BT added"); + mediums_started_successfully.push_back(bluetooth_medium); + } + + proto::connections::Medium wifi_lan_medium = StartWifiLanDiscovery( + { + .service_discovered_cb = + MakeWifiLanServiceDiscoveredHandler(client, service_id), + .service_lost_cb = MakeWifiLanServiceLostHandler(client, service_id), + }, + client, service_id); + if (wifi_lan_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: WifiLan added"); + mediums_started_successfully.push_back(wifi_lan_medium); + } + + if (mediums_started_successfully.empty()) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: nothing added"); + return { + .status = {Status::kBluetoothError}, + }; + } + + return { + .status = {Status::kSuccess}, + .mediums = std::move(mediums_started_successfully), + }; +} + +Status P2pClusterPcpHandler::StopDiscoveryImpl(ClientProxy* client) { + wifi_lan_medium_.StopDiscovery(client->GetDiscoveryServiceId()); + bluetooth_medium_.StopDiscovery(); + return {Status::kSuccess}; +} + +BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::ConnectImpl( + ClientProxy* client, BasePcpHandler::DiscoveredEndpoint* endpoint) { + BluetoothEndpoint* bluetooth_endpoint = + static_cast(endpoint); + if (bluetooth_endpoint) { + return BluetoothConnectImpl(client, bluetooth_endpoint); + } + + WifiLanEndpoint* wifi_lan_endpoint = static_cast(endpoint); + if (wifi_lan_endpoint) { + return WifiLanConnectImpl(client, wifi_lan_endpoint); + } + + return BasePcpHandler::ConnectImplResult{ + .status = {Status::kError}, + }; +} + +proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const std::string& local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: start", + service_id.c_str()); + if (bluetooth_medium_.IsAcceptingConnections(service_id)) { + NEARBY_LOG(ERROR, "BT is already accepting connections for service=%s", + service_id.c_str()); + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: invoking", + service_id.c_str()); + if (!bluetooth_radio_.Enable() || + !bluetooth_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_name]( + BluetoothSocket socket) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + local_endpoint_name.c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_name, + socket = std::move(socket)]() mutable { + std::string remote_device_name = + socket.GetRemoteDevice().GetName(); + auto channel = absl::make_unique( + remote_device_name, socket); + OnIncomingConnection(client, remote_device_name, + std::move(channel), + proto::connections::Medium::BLUETOOTH); + }); + }})) { + NEARBY_LOG(ERROR, "BT failed to start accepting connections for service=%s", + service_id.c_str()); + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: " + "make name; id=%s, hash=%s, name=%s", + service_id.c_str(), local_endpoint_id.c_str(), + std::string(service_id_hash).c_str(), local_endpoint_name.c_str()); + // Generate a BluetoothDeviceName with which to become Bluetooth discoverable. + std::string device_name(BluetoothDeviceName( + BluetoothDeviceName::Version::kV1, GetPcp(), local_endpoint_id, + service_id_hash, local_endpoint_name)); + if (device_name.empty()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: generate " + "BluetoothDeviceName failed"); + bluetooth_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: generate " + "BluetoothDeviceName succeeded; device_name=%s", + device_name.c_str()); + } + + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: come up", + service_id.c_str()); + // Become Bluetooth discoverable. + if (!bluetooth_medium_.TurnOnDiscoverability(device_name)) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: failed to " + "turn on discoverability, device_name=%s", + device_name.c_str()); + bluetooth_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBluetoothAdvertising: succeeded to " + "turn on discoverability, device_name=%s", + device_name.c_str()); + } + NEARBY_LOG( + INFO, "P2pClusterPcpHandler::StartBluetoothAdvertising: service=%s: done", + service_id.c_str()); + return proto::connections::BLUETOOTH; +} + +proto::connections::Medium P2pClusterPcpHandler::StartBluetoothDiscovery( + BluetoothDiscoveredDeviceCallback callback, ClientProxy* client, + const std::string& service_id) { + if (bluetooth_radio_.Enable() && + bluetooth_medium_.StartDiscovery(std::move(callback))) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothDiscovery: ok"); + return proto::connections::BLUETOOTH; + } else { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothDiscovery: failed"); + return proto::connections::UNKNOWN_MEDIUM; + } +} + +BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BluetoothConnectImpl( + ClientProxy* client, BluetoothEndpoint* endpoint) { + BluetoothDevice& device = endpoint->bluetooth_device; + + BluetoothSocket bluetooth_socket = + bluetooth_medium_.Connect(device, endpoint->service_id); + if (!bluetooth_socket.IsValid()) { + return BasePcpHandler::ConnectImplResult{ + .status = {Status::kBluetoothError}, + }; + } + + auto channel = absl::make_unique( + endpoint->endpoint_id, bluetooth_socket); + + return BasePcpHandler::ConnectImplResult{ + .medium = proto::connections::Medium::BLUETOOTH, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel), + }; +} + +proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const std::string& local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: start", + service_id.c_str()); + if (wifi_lan_medium_.IsAcceptingConnections(service_id)) { + NEARBY_LOG(ERROR, "WifiLan is already accepting connections for service=%s", + service_id.c_str()); + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: invoking", + service_id.c_str()); + if (!wifi_lan_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_name]( + WifiLanSocket& socket, + const std::string& service_id) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + local_endpoint_name.c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_name, + socket = std::move(socket)]() mutable { + std::string remote_service_name = + socket.GetRemoteWifiLanService().GetName(); + auto channel = absl::make_unique( + remote_service_name, socket); + OnIncomingConnection(client, remote_service_name, + std::move(channel), + proto::connections::Medium::WIFI_LAN); + }); + }})) { + NEARBY_LOG(ERROR, + "WifiLan failed to start accepting connections for service=%s", + service_id.c_str()); + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: " + "make name; id=%s, hash=%s, name=%s", + service_id.c_str(), local_endpoint_id.c_str(), + std::string(service_id_hash).c_str(), local_endpoint_name.c_str()); + // Generate a WifiLanServiceInfo with which to become WifiLan discoverable. + std::string service_name(WifiLanServiceInfo( + WifiLanServiceInfo::Version::kV1, GetPcp(), local_endpoint_id, + service_id_hash, local_endpoint_name)); + if (service_name.empty()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " + "WifiLanServiceInfo failed"); + wifi_lan_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " + "WifiLanServiceInfo succeeded; service_name=%s", + service_name.c_str()); + } + + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: come up", + service_id.c_str()); + + if (!wifi_lan_medium_.StartAdvertising(service_id, service_name)) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: failed to " + "start advertising, service_name=%s", + service_name.c_str()); + wifi_lan_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: done", + service_id.c_str()); + return proto::connections::WIFI_LAN; +} + +proto::connections::Medium P2pClusterPcpHandler::StartWifiLanDiscovery( + WifiLanDiscoveredServiceCallback callback, ClientProxy* client, + const std::string& service_id) { + if (wifi_lan_medium_.StartDiscovery(service_id, std::move(callback))) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanDiscovery: ok"); + return proto::connections::WIFI_LAN; + } else { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanDiscovery: failed"); + return proto::connections::UNKNOWN_MEDIUM; + } +} + +BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WifiLanConnectImpl( + ClientProxy* client, WifiLanEndpoint* endpoint) { + WifiLanService& service = endpoint->wifi_lan_service; + + WifiLanSocket wifi_lan_socket = + wifi_lan_medium_.Connect(service, endpoint->service_id); + if (!wifi_lan_socket.IsValid()) { + return BasePcpHandler::ConnectImplResult{ + .status = {Status::kWifiLanError}, + }; + } + + auto channel = absl::make_unique( + endpoint->endpoint_id, wifi_lan_socket); + + return BasePcpHandler::ConnectImplResult{ + .medium = proto::connections::Medium::WIFI_LAN, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel), + }; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h new file mode 100644 index 00000000..c1c5d19a --- /dev/null +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -0,0 +1,136 @@ +#ifndef CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ + +#include +#include + +#include "core_v2/internal/base_pcp_handler.h" +#include "core_v2/internal/ble_advertisement.h" +#include "core_v2/internal/bluetooth_device_name.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/bluetooth_classic.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/internal/wifi_lan_service_info.h" +#include "core_v2/options.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/wifi_lan.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PCPHandler for the P2P_CLUSTER PCP. This PCP +// is reserved for mediums that can connect to multiple devices simultaneously +// and all devices are considered equal. For asymmetric mediums, where one +// device is a server and the others are clients, use P2PStarPCPHandler instead. +// +// Currently, this implementation advertises/discovers over Bluetooth and +// connects over Bluetooth. +class P2pClusterPcpHandler : public BasePcpHandler { + public: + P2pClusterPcpHandler(Mediums& mediums, EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager, + Pcp pcp = Pcp::kP2pCluster); + ~P2pClusterPcpHandler() override = default; + + protected: + std::vector GetConnectionMediumsByPriority() + override; + proto::connections::Medium GetDefaultUpgradeMedium() override; + + // @PCPHandlerThread + BasePcpHandler::StartOperationResult StartAdvertisingImpl( + ClientProxy* client, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name, + const ConnectionOptions& options) override; + + // @PCPHandlerThread + Status StopAdvertisingImpl(ClientProxy* client) override; + + // @PCPHandlerThread + BasePcpHandler::StartOperationResult StartDiscoveryImpl( + ClientProxy* client, const std::string& service_id, + const ConnectionOptions& options) override; + + // @PCPHandlerThread + Status StopDiscoveryImpl(ClientProxy* client) override; + + // @PCPHandlerThread + BasePcpHandler::ConnectImplResult ConnectImpl( + ClientProxy* client, + BasePcpHandler::DiscoveredEndpoint* endpoint) override; + + private: + struct BluetoothEndpoint : public BasePcpHandler::DiscoveredEndpoint { + BluetoothDevice bluetooth_device; + }; + struct WifiLanEndpoint : public BasePcpHandler::DiscoveredEndpoint { + WifiLanService wifi_lan_service; + }; + + using BluetoothDiscoveredDeviceCallback = + BluetoothClassic::DiscoveredDeviceCallback; + using WifiLanDiscoveredServiceCallback = WifiLan::DiscoveredServiceCallback; + + static constexpr BluetoothDeviceName::Version kBluetoothDeviceNameVersion = + BluetoothDeviceName::Version::kV1; + static constexpr WifiLanServiceInfo::Version kWifiLanServiceInfoVersion = + WifiLanServiceInfo::Version::kV1; + + static ByteArray GenerateHash(const std::string& source, size_t size); + + // Bluetooth. + bool IsRecognizedBluetoothEndpoint(const std::string& name_string, + const std::string& service_id, + const BluetoothDeviceName& name) const; + std::function MakeBluetoothDeviceDiscoveredHandler( + ClientProxy* client, const std::string& service_id); + std::function MakeBluetoothDeviceLostHandler( + ClientProxy* client, const std::string& service_id); + proto::connections::Medium StartBluetoothAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const std::string& local_endpoint_name); + proto::connections::Medium StartBluetoothDiscovery( + BluetoothDiscoveredDeviceCallback callback, ClientProxy* client, + const std::string& service_id); + BasePcpHandler::ConnectImplResult BluetoothConnectImpl( + ClientProxy* client, BluetoothEndpoint* endpoint); + + // WifiLan. + bool IsRecognizedWifiLanEndpoint(const std::string& name_string, + const std::string& service_id, + const WifiLanServiceInfo& name) const; + std::function + MakeWifiLanServiceDiscoveredHandler(ClientProxy* client, + const std::string& service_id); + std::function + MakeWifiLanServiceLostHandler(ClientProxy* client, + const std::string& service_id); + proto::connections::Medium StartWifiLanAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const std::string& local_endpoint_name); + proto::connections::Medium StartWifiLanDiscovery( + WifiLanDiscoveredServiceCallback callback, ClientProxy* client, + const std::string& service_id); + BasePcpHandler::ConnectImplResult WifiLanConnectImpl( + ClientProxy* client, WifiLanEndpoint* endpoint); + + BluetoothRadio& bluetooth_radio_; + BluetoothClassic& bluetooth_medium_; + WifiLan& wifi_lan_medium_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc new file mode 100644 index 00000000..9d3ec83d --- /dev/null +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -0,0 +1,184 @@ +#include "core_v2/internal/p2p_cluster_pcp_handler.h" + +#include + +#include "core_v2/options.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +class P2pClusterPcpHandlerTest : public ::testing::Test { + protected: + void SetUp() override { + NEARBY_LOG(INFO, "SetUp: begin"); + env_.Stop(); + NEARBY_LOG(INFO, "SetUp: end"); + } + + ClientProxy client_a_; + ClientProxy client_b_; + std::string service_id_{"service"}; + ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(P2pClusterPcpHandlerTest, CanConstructOne) { + env_.Start(); + Mediums mediums; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + P2pClusterPcpHandler handler(mediums, &em, &ecm); + env_.Stop(); +} + +TEST_F(P2pClusterPcpHandlerTest, CanConstructMultiple) { + env_.Start(); + Mediums mediums_a; + Mediums mediums_b; + EndpointChannelManager ecm_a; + EndpointChannelManager ecm_b; + EndpointManager em_a(&ecm_a); + EndpointManager em_b(&ecm_b); + P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a); + P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b); + env_.Stop(); +} + +TEST_F(P2pClusterPcpHandlerTest, CanAdvertise) { + env_.Start(); + std::string endpoint_name{"endpoint_name"}; + Mediums mediums_a; + EndpointChannelManager ecm_a; + EndpointManager em_a(&ecm_a); + P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a); + EXPECT_EQ(handler_a.StartAdvertising(&client_a_, service_id_, options_, + {.name = endpoint_name}), + Status{Status::kSuccess}); + env_.Stop(); +} + +TEST_F(P2pClusterPcpHandlerTest, CanDiscover) { + env_.Start(); + std::string endpoint_name{"endpoint_name"}; + Mediums mediums_a; + Mediums mediums_b; + EndpointChannelManager ecm_a; + EndpointChannelManager ecm_b; + EndpointManager em_a(&ecm_a); + EndpointManager em_b(&ecm_b); + P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a); + P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b); + CountDownLatch latch(1); + EXPECT_EQ(handler_a.StartAdvertising(&client_a_, service_id_, options_, + {.name = endpoint_name}), + Status{Status::kSuccess}); + EXPECT_EQ(handler_b.StartDiscovery( + &client_b_, service_id_, options_, + { + .endpoint_found_cb = + [&latch](const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& service_id) { + NEARBY_LOG(INFO, "Device discovered: id=%s", + endpoint_id.c_str()); + latch.CountDown(); + }, + }), + Status{Status::kSuccess}); + EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +TEST_F(P2pClusterPcpHandlerTest, CanConnect) { + env_.Start(); + std::string endpoint_name_a{"endpoint_name"}; + Mediums mediums_a; + Mediums mediums_b; + BluetoothRadio& radio_a = mediums_a.GetBluetoothRadio(); + BluetoothRadio& radio_b = mediums_b.GetBluetoothRadio(); + radio_a.GetBluetoothAdapter().SetName("BT Device A"); + radio_b.GetBluetoothAdapter().SetName("BT Device B"); + EndpointChannelManager ecm_a; + EndpointChannelManager ecm_b; + EndpointManager em_a(&ecm_a); + EndpointManager em_b(&ecm_b); + P2pClusterPcpHandler handler_a(mediums_a, &em_a, &ecm_a); + P2pClusterPcpHandler handler_b(mediums_b, &em_b, &ecm_b); + CountDownLatch discover_latch(1); + CountDownLatch connect_latch(2); + struct DiscoveredInfo { + std::string endpoint_id; + std::string endpoint_name; + std::string service_id; + } discovered; + EXPECT_EQ( + handler_a.StartAdvertising( + &client_a_, service_id_, options_, + { + .name = endpoint_name_a, + .listener = + { + .initiated_cb = + [&connect_latch](const std::string& endpoint_id, + const ConnectionResponseInfo& info) { + NEARBY_LOG(INFO, + "StartAdvertising: initiated_cb called"); + connect_latch.CountDown(); + }, + }, + }), + Status{Status::kSuccess}); + EXPECT_EQ(handler_b.StartDiscovery( + &client_b_, service_id_, options_, + { + .endpoint_found_cb = + [&discover_latch, &discovered]( + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& service_id) { + NEARBY_LOG(INFO, "Device discovered: id=%s", + endpoint_id.c_str()); + discovered = { + .endpoint_id = endpoint_id, + .endpoint_name = endpoint_name, + .service_id = service_id, + }; + discover_latch.CountDown(); + }, + }), + Status{Status::kSuccess}); + + EXPECT_TRUE(discover_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_EQ(endpoint_name_a, discovered.endpoint_name); + + handler_b.RequestConnection( + &client_b_, discovered.endpoint_id, + { + .name = discovered.endpoint_name, + .listener = + { + .initiated_cb = + [&connect_latch](const std::string& endpoint_id, + const ConnectionResponseInfo& info) { + NEARBY_LOG(INFO, + "RequestConnection: initiated_cb called"); + connect_latch.CountDown(); + }, + }, + }); + EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc new file mode 100644 index 00000000..60da6883 --- /dev/null +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -0,0 +1,40 @@ +#include "core_v2/internal/p2p_point_to_point_pcp_handler.h" + +namespace location { +namespace nearby { +namespace connections { + +P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( + Mediums& mediums, EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, Pcp pcp) + : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp), + mediums_(&mediums) {} + +std::vector +P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { + std::vector mediums; + if (mediums_->GetBluetoothClassic().IsAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + return mediums; +} + +bool P2pPointToPointPcpHandler::CanSendOutgoingConnection( + ClientProxy* client) const { + // For point to point, we can only send an outgoing connection while we have + // no other connections. + return !this->HasOutgoingConnections(client) && + !this->HasIncomingConnections(client); +} + +bool P2pPointToPointPcpHandler::CanReceiveIncomingConnection( + ClientProxy* client) const { + // For point to point, we can only receive an incoming connection while we + // have no other connections. + return !this->HasOutgoingConnections(client) && + !this->HasIncomingConnections(client); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h new file mode 100644 index 00000000..e6da2dd9 --- /dev/null +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -0,0 +1,43 @@ +#ifndef CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ + +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/internal/p2p_star_pcp_handler.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PCPHandler for the P2P_POINT_TO_POINT. This +// PCP is for mediums that have limitations on the number of simultaneous +// connections; all mediums in P2P_STAR are valid for P2P_POINT_TO_POINT, but +// not all mediums in P2P_POINT_TO_POINT and valid for P2P_STAR. +// +// Currently, this implementation advertises/discovers over Bluetooth +// and connects over Bluetooth. +class P2pPointToPointPcpHandler : public P2pStarPcpHandler { + public: + P2pPointToPointPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, + Pcp pcp = Pcp::kP2pPointToPoint); + + protected: + std::vector GetConnectionMediumsByPriority() + override; + + bool CanSendOutgoingConnection(ClientProxy* client) const override; + bool CanReceiveIncomingConnection(ClientProxy* client) const override; + + private: + Mediums* mediums_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc new file mode 100644 index 00000000..25901ebc --- /dev/null +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -0,0 +1,45 @@ +#include "core_v2/internal/p2p_star_pcp_handler.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, + EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, + Pcp pcp) + : P2pClusterPcpHandler(mediums, &endpoint_manager, &channel_manager, pcp), + mediums_(&mediums) {} + +std::vector +P2pStarPcpHandler::GetConnectionMediumsByPriority() { + std::vector mediums; + if (mediums_->GetBluetoothClassic().IsAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + return mediums; +} + +proto::connections::Medium P2pStarPcpHandler::GetDefaultUpgradeMedium() { + return proto::connections::Medium::WIFI_HOTSPOT; +} + +bool P2pStarPcpHandler::CanSendOutgoingConnection(ClientProxy* client) const { + // For star, we can only send an outgoing connection while we have no other + // connections. + return !this->HasOutgoingConnections(client) && + !this->HasIncomingConnections(client); +} + +bool P2pStarPcpHandler::CanReceiveIncomingConnection( + ClientProxy* client) const { + // For star, we can only receive an incoming connection if we've sent no + // outgoing connections. + return !this->HasOutgoingConnections(client); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h new file mode 100644 index 00000000..a50bd054 --- /dev/null +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -0,0 +1,47 @@ +#ifndef CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/internal/p2p_cluster_pcp_handler.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PcpHandler for the P2P_STAR PCP. This Pcp is +// for mediums that have one server with (potentially) many clients; all mediums +// in P2P_CLUSTER are valid for P2P_STAR, but not all mediums in P2P_STAR and +// valid for P2P_CLUSTER. +// +// Currently, this implementation advertises/discovers over Bluetooth +// and connects over Bluetooth. +class P2pStarPcpHandler : public P2pClusterPcpHandler { + public: + P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, + Pcp pcp = Pcp::kP2pStar); + + protected: + std::vector GetConnectionMediumsByPriority() + override; + proto::connections::Medium GetDefaultUpgradeMedium() override; + + bool CanSendOutgoingConnection(ClientProxy* client) const override; + bool CanReceiveIncomingConnection(ClientProxy* client) const override; + + private: + Mediums* mediums_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/payload_manager.cc b/cpp/core_v2/internal/payload_manager.cc new file mode 100644 index 00000000..4cb491f0 --- /dev/null +++ b/cpp/core_v2/internal/payload_manager.cc @@ -0,0 +1,1062 @@ +#include "core_v2/internal/payload_manager.h" + +#include +#include +#include +#include +#include + +#include "core_v2/internal/internal_payload_factory.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// C++14 requires to declare this. +// TODO(apolyudov): remove when migration to c++17 is possible. +constexpr const absl::Duration PayloadManager::kWaitCloseTimeout; + +bool PayloadManager::SendPayloadLoop( + ClientProxy* client, PendingPayload& pending_payload, + PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t& next_chunk_offset) { + // in lieu of structured binding: + auto pair = GetAvailableAndUnavailableEndpoints(pending_payload); + const EndpointIds& available_endpoint_ids = + EndpointsToEndpointIds(pair.first); + const Endpoints& unavailable_endpoints = pair.second; + + NEARBY_LOG(INFO, + "SendPayloadLoop: Available: { %s }; Unavailable: { %s }; " + "payload_id=%" PRIX64 "; self=%p", + ToString(available_endpoint_ids).c_str(), + ToString(unavailable_endpoints).c_str(), + static_cast(payload_header.id()), this); + + // First, handle any non-available endpoints. + for (const auto& endpoint : unavailable_endpoints) { + HandleFinishedOutgoingPayload( + client, {endpoint->id}, payload_header, next_chunk_offset, + EndpointInfoStatusToPayloadStatus(endpoint->status)); + } + + // Update the still-active recipients of this payload. + if (available_endpoint_ids.empty()) { + NEARBY_LOG(INFO, "No more available endpoints: payload_id=%" PRIX64, + pending_payload.GetInternalPayload()->GetId()); + return false; + } + + // Check if the payload has been cancelled by the client and, if so, + // notify the remaining recipients. + if (pending_payload.IsLocallyCanceled()) { + NEARBY_LOG(INFO, "Payload canceled locally: payload_id=%" PRIX64, + pending_payload.GetInternalPayload()->GetId()); + HandleFinishedOutgoingPayload( + client, available_endpoint_ids, payload_header, next_chunk_offset, + proto::connections::PayloadStatus::LOCAL_CANCELLATION); + return false; + } + + // Update the current offsets for all endpoints still active for this + // payload. For the sake of accuracy, we update the pending payload here + // because it's after all payload terminating events are handled, but + // right before we actually start detaching the next chunk. + for (const auto& endpoint_id : available_endpoint_ids) { + pending_payload.SetOffsetForEndpoint(endpoint_id, next_chunk_offset); + } + + // This will block if there is no data to transfer. + // It will resume when new data arrives, or if Close() is called. + ByteArray next_chunk = + pending_payload.GetInternalPayload()->DetachNextChunk(); + if (shutdown_.Get()) return false; + // Save chunk size. We'll need it after we move next_chunk. + auto next_chunk_size = next_chunk.size(); + if (!next_chunk_size && + pending_payload.GetInternalPayload()->GetTotalSize() > 0 && + pending_payload.GetInternalPayload()->GetTotalSize() < + next_chunk_offset) { + NEARBY_LOG(INFO, "Payload xfer failed: payload_id=%" PRIX64, + pending_payload.GetInternalPayload()->GetId()); + HandleFinishedOutgoingPayload( + client, available_endpoint_ids, payload_header, next_chunk_offset, + proto::connections::PayloadStatus::LOCAL_ERROR); + return false; + } + + PayloadTransferFrame::PayloadChunk payload_chunk( + CreatePayloadChunk(next_chunk_offset, std::move(next_chunk))); + const EndpointIds& failed_endpoint_ids = endpoint_manager_->SendPayloadChunk( + payload_header, payload_chunk, available_endpoint_ids); + // Check whether at least one endpoint failed. + if (!failed_endpoint_ids.empty()) { + NEARBY_LOG(INFO, + "Payload xfer: endpoints failed: payload_id=%" PRIX64 + "; ids={%s}", + static_cast(payload_header.id()), + ToString(failed_endpoint_ids).c_str()); + HandleFinishedOutgoingPayload( + client, failed_endpoint_ids, payload_header, next_chunk_offset, + proto::connections::PayloadStatus::ENDPOINT_IO_ERROR); + } + + // Check whether at least one endpoint succeeded -- if they all failed, + // we'll just go right back to the top of the loop and break out when + // availableEndpointIds is re-synced and found to be empty at that point. + if (failed_endpoint_ids.size() < available_endpoint_ids.size()) { + for (const auto& endpoint_id : available_endpoint_ids) { + if (std::find(failed_endpoint_ids.begin(), failed_endpoint_ids.end(), + endpoint_id) == failed_endpoint_ids.end()) { + HandleSuccessfulOutgoingChunk( + client, endpoint_id, payload_header, payload_chunk.flags(), + payload_chunk.offset(), payload_chunk.body().size()); + } + } + + next_chunk_offset += next_chunk_size; + + if (!next_chunk_size) { + // That was the last chunk, we're outta here. + NEARBY_LOG( + INFO, "Payload xfer done: payload_id=%" PRIX64 "; size=%" PRId64, + pending_payload.GetInternalPayload()->GetId(), next_chunk_offset); + return false; + } + } + + return true; +} + +std::pair +PayloadManager::GetAvailableAndUnavailableEndpoints( + const PendingPayload& pending_payload) { + Endpoints available; + Endpoints unavailable; + for (auto* endpoint_info : pending_payload.GetEndpoints()) { + NEARBY_LOG(INFO, "EndpointInfo: %p; id=%s; status=%d", endpoint_info, + endpoint_info->id.c_str(), endpoint_info->status); + if (endpoint_info->status == + PayloadManager::EndpointInfo::Status::kAvailable) { + available.push_back(endpoint_info); + } else { + unavailable.push_back(endpoint_info); + } + } + return std::make_pair(std::move(available), std::move(unavailable)); +} + +PayloadManager::EndpointIds PayloadManager::EndpointsToEndpointIds( + const Endpoints& endpoints) { + EndpointIds endpoint_ids; + endpoint_ids.reserve(endpoints.size()); + for (const auto& item : endpoints) { + if (item) { + endpoint_ids.emplace_back(item->id); + } + } + return endpoint_ids; +} + +std::string PayloadManager::ToString(const Endpoints& endpoints) { + std::string endpoints_string = absl::StrCat(endpoints.size(), ": "); + bool first = true; + for (const auto& item : endpoints) { + if (first) { + absl::StrAppend(&endpoints_string, item->id); + first = false; + } else { + absl::StrAppend(&endpoints_string, ", ", item->id); + } + } + return endpoints_string; +} + +std::string PayloadManager::ToString(const EndpointIds& endpoint_ids) { + std::string endpoints_string = absl::StrCat(endpoint_ids.size(), ": "); + bool first = true; + for (const auto& id : endpoint_ids) { + if (first) { + absl::StrAppend(&endpoints_string, id); + first = false; + } else { + absl::StrAppend(&endpoints_string, ", ", id); + } + } + return endpoints_string; +} + +// Creates and starts tracking a PendingPayload for this Payload. +Payload::Id PayloadManager::CreateOutgoingPayload( + Payload payload, const EndpointIds& endpoint_ids) { + auto internal_payload{CreateOutgoingInternalPayload(std::move(payload))}; + Payload::Id payload_id = internal_payload->GetId(); + NEARBY_LOG(INFO, "CreateOutgoingPayload: payload_id=%" PRIX64, payload_id); + MutexLock lock(&mutex_); + pending_payloads_.StartTrackingPayload( + payload_id, absl::make_unique(std::move(internal_payload), + endpoint_ids, + /*is_incoming=*/false)); + + return payload_id; +} + +PayloadManager::PayloadManager(EndpointManager& endpoint_manager) + : endpoint_manager_(&endpoint_manager) { + handle_ = endpoint_manager_->RegisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, + this); +} + +void PayloadManager::CancelAllPayloads() { + NEARBY_LOG(INFO, "PayloadManager: canceling payloads; self=%p", this); + { + MutexLock lock(&mutex_); + int pending_outgoing_payloads = 0; + for (const auto& pending_id : pending_payloads_.GetAllPayloads()) { + auto* pending = pending_payloads_.GetPayload(pending_id); + if (!pending->IsIncoming()) pending_outgoing_payloads++; + pending->MarkLocallyCanceled(); + pending->Close(); // To unblock the sender thread, if there is no data. + } + if (pending_outgoing_payloads) { + shutdown_barrier_ = + absl::make_unique(pending_outgoing_payloads); + } + } + + if (shutdown_barrier_) { + NEARBY_LOG(INFO, + "PayloadManager: waiting for pending outgoing payloads; self=%p", + this); + shutdown_barrier_->Await(); + } +} + +PayloadManager::~PayloadManager() { + NEARBY_LOG(INFO, "PayloadManager: going down; self=%p", this); + shutdown_.Set(true); + // Unregister ourselves from the FrameProcessors. + endpoint_manager_->UnregisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, + handle_, true); + CancelAllPayloads(); + NEARBY_LOG(INFO, "PayloadManager: turn down payload executors; self=%p", + this); + bytes_payload_executor_.Shutdown(); + stream_payload_executor_.Shutdown(); + file_payload_executor_.Shutdown(); + + CountDownLatch stop_latch(1); + // Clear our tracked pending payloads. + RunOnStatusUpdateThread([this, &stop_latch]() { + NEARBY_LOG(INFO, "PayloadManager: stop tracking payloads; self=%p", this); + MutexLock lock(&mutex_); + for (const auto& pending_id : pending_payloads_.GetAllPayloads()) { + pending_payloads_.StopTrackingPayload(pending_id); + } + stop_latch.CountDown(); + }); + stop_latch.Await(); + + NEARBY_LOG(INFO, "PayloadManager: turn down notification executor; self=%p", + this); + // Stop all the ongoing Runnables (as gracefully as possible). + payload_status_update_executor_.Shutdown(); + + NEARBY_LOG(INFO, "PayloadManager: down; self=%p", this); +} + +bool PayloadManager::NotifyShutdown() { + MutexLock lock(&mutex_); + if (!shutdown_.Get()) return false; + if (!shutdown_barrier_) return false; + NEARBY_LOG(INFO, "PayloadManager [shutdown mode]"); + shutdown_barrier_->CountDown(); + return true; +} + +void PayloadManager::SendPayload(ClientProxy* client, + const EndpointIds& endpoint_ids, + Payload payload) { + if (shutdown_.Get()) return; + NEARBY_LOG(INFO, "SendPayload: endpoint_ids={%s}", + ToString(endpoint_ids).c_str()); + auto executor = GetOutgoingPayloadExecutor(payload.GetType()); + // The |executor| will be null if the payload is of a type we cannot work + // with. This should never be reached since the ServiceControllerRouter has + // already checked whether or not we can work with this Payload type. + if (!executor) { + NEARBY_LOG(INFO, + "PayloadManager::SendPayload: unsupported: id=%" PRIX64 + ", type=%d", + payload.GetId(), payload.GetType()); + return; + } + + // Each payload is sent in FCFS order within each Payload type, blocking any + // other payload of the same type from even starting until this one is + // completely done with. If we ever want to provide isolation across + // ClientProxy objects this will need to be significantly re-architected. + Payload::Type payload_type = payload.GetType(); + Payload::Id payload_id = + CreateOutgoingPayload(std::move(payload), endpoint_ids); + executor->Execute([this, client, endpoint_ids, payload_id]() { + if (shutdown_.Get()) return; + PendingPayload* pending_payload = GetPayload(payload_id); + if (!pending_payload) return; + auto* internal_payload = pending_payload->GetInternalPayload(); + if (!internal_payload) return; + PayloadTransferFrame::PayloadHeader payload_header{ + CreatePayloadHeader(*internal_payload)}; + bool should_continue = true; + std::int64_t next_chunk_offset = 0; + while (should_continue && !shutdown_.Get()) { + should_continue = SendPayloadLoop(client, *pending_payload, + payload_header, next_chunk_offset); + } + RunOnStatusUpdateThread( + [this, payload_id]() { DestroyPendingPayload(payload_id); }); + }); + NEARBY_LOG(INFO, + "PayloadManager: xfer scheduled: self=%p; id=%" PRIX64 ", type=%d", + this, payload_id, payload_type); +} + +PayloadManager::PendingPayload* PayloadManager::GetPayload( + Payload::Id payload_id) const { + MutexLock lock(&mutex_); + return pending_payloads_.GetPayload(payload_id); +} + +Status PayloadManager::CancelPayload(ClientProxy* client, + Payload::Id payload_id) { + PendingPayload* canceled_payload = GetPayload(payload_id); + if (!canceled_payload) { + NEARBY_LOG(INFO, "PayloadManager: not found; payload_id=%" PRIX64, + payload_id); + return {Status::kPayloadUnknown}; + } + + // Mark the payload as canceled. + canceled_payload->MarkLocallyCanceled(); + NEARBY_LOG(INFO, "PayloadManager: canceled; id=%" PRIX64, payload_id); + + // Return SUCCESS immediately. Remaining cleanup and updates will be sent in + // SendPayload() or OnIncomingFrame() + return {Status::kSuccess}; +} + +// @EndpointManagerDataPool +void PayloadManager::OnIncomingFrame( + OfflineFrame& offline_frame, const std::string& from_endpoint_id, + ClientProxy* to_client, proto::connections::Medium current_medium) { + PayloadTransferFrame& frame = + *offline_frame.mutable_v1()->mutable_payload_transfer(); + + switch (frame.packet_type()) { + case PayloadTransferFrame::CONTROL: + NEARBY_LOG(INFO, + "PayloadManager::OnIncomingFrame [CONTROL]: self=%p; id=%s", + this, from_endpoint_id.c_str()); + ProcessControlPacket(to_client, from_endpoint_id, frame); + break; + case PayloadTransferFrame::DATA: + NEARBY_LOG(INFO, "PayloadManager::OnIncomingFrame [DATA]: self=%p; id=%s", + this, from_endpoint_id.c_str()); + ProcessDataPacket(to_client, from_endpoint_id, frame); + break; + default: + NEARBY_LOG( + INFO, + "PayloadManager: invalid frame; remote endpoint: self=%p; id=%s", + this, from_endpoint_id.c_str()); + break; + } + NEARBY_LOG(INFO, "PayloadManager::OnIncomingFrame [DONE]: self=%p; id=%s", + this, from_endpoint_id.c_str()); +} + +void PayloadManager::OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier) { + RunOnStatusUpdateThread([this, client, endpoint_id, &barrier]() { + // Iterate through all our payloads and look for payloads associated + // with this endpoint. + MutexLock lock(&mutex_); + for (const auto& payload_id : pending_payloads_.GetAllPayloads()) { + auto* pending_payload = pending_payloads_.GetPayload(payload_id); + if (!pending_payload) continue; + auto endpoint_info = pending_payload->GetEndpoint(endpoint_id); + if (!endpoint_info) continue; + + // Stop tracking the endpoint for this payload. + pending_payload->RemoveEndpoints({endpoint_id}); + + std::int64_t payload_total_size = + pending_payload->GetInternalPayload()->GetTotalSize(); + + // If no endpoints are left for this payload, close it. + if (pending_payload->GetEndpoints().empty()) { + pending_payload->Close(); + } + + // Create the payload transfer update. + PayloadProgressInfo update{payload_id, + PayloadProgressInfo::Status::kFailure, + payload_total_size, endpoint_info->offset}; + + // Send a client notification of a payload transfer failure. + client->OnPayloadProgress(endpoint_id, update); + } + + barrier->CountDown(); + }); +} + +proto::connections::PayloadStatus +PayloadManager::EndpointInfoStatusToPayloadStatus(EndpointInfo::Status status) { + switch (status) { + case EndpointInfo::Status::kCanceled: + return proto::connections::PayloadStatus::REMOTE_CANCELLATION; + case EndpointInfo::Status::kError: + return proto::connections::PayloadStatus::REMOTE_ERROR; + case EndpointInfo::Status::kAvailable: + return proto::connections::PayloadStatus::SUCCESS; + default: + NEARBY_LOG(INFO, "PayloadManager: unknown status=%d", status); + return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; + } +} + +proto::connections::PayloadStatus +PayloadManager::ControlMessageEventToPayloadStatus( + PayloadTransferFrame::ControlMessage::EventType event) { + switch (event) { + case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: + return proto::connections::PayloadStatus::REMOTE_ERROR; + case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: + return proto::connections::PayloadStatus::REMOTE_CANCELLATION; + default: + NEARBY_LOG(INFO, "PayloadManager: unknown event=%d", event); + return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; + } +} + +PayloadProgressInfo::Status PayloadManager::PayloadStatusToTransferUpdateStatus( + proto::connections::PayloadStatus status) { + switch (status) { + case proto::connections::LOCAL_CANCELLATION: + case proto::connections::REMOTE_CANCELLATION: + return PayloadProgressInfo::Status::kCanceled; + case proto::connections::SUCCESS: + return PayloadProgressInfo::Status::kSuccess; + default: + return PayloadProgressInfo::Status::kFailure; + } +} + +SingleThreadExecutor* PayloadManager::GetOutgoingPayloadExecutor( + Payload::Type payload_type) { + switch (payload_type) { + case Payload::Type::kBytes: + return &bytes_payload_executor_; + case Payload::Type::kFile: + return &file_payload_executor_; + case Payload::Type::kStream: + return &stream_payload_executor_; + default: + return nullptr; + } +} + +PayloadTransferFrame::PayloadHeader PayloadManager::CreatePayloadHeader( + const InternalPayload& internal_payload) { + PayloadTransferFrame::PayloadHeader payload_header; + + payload_header.set_id(internal_payload.GetId()); + payload_header.set_type(internal_payload.GetType()); + payload_header.set_total_size(internal_payload.GetTotalSize()); + + return payload_header; +} + +PayloadTransferFrame::PayloadChunk PayloadManager::CreatePayloadChunk( + std::int64_t payload_chunk_offset, ByteArray payload_chunk_body) { + PayloadTransferFrame::PayloadChunk payload_chunk; + + payload_chunk.set_offset(payload_chunk_offset); + payload_chunk.set_flags(0); + if (!payload_chunk_body.Empty()) { + payload_chunk.set_body(std::string(std::move(payload_chunk_body))); + } else { + payload_chunk.set_flags(payload_chunk.flags() | + PayloadTransferFrame::PayloadChunk::LAST_CHUNK); + } + + return payload_chunk; +} + +PayloadManager::PendingPayload* PayloadManager::CreateIncomingPayload( + const PayloadTransferFrame& frame, const std::string& endpoint_id) { + auto internal_payload = CreateIncomingInternalPayload(frame); + if (!internal_payload) { + return nullptr; + } + + Payload::Id payload_id = internal_payload->GetId(); + NEARBY_LOG(INFO, "CreateIncomingPayload: payload_id=%" PRIX64, payload_id); + MutexLock lock(&mutex_); + pending_payloads_.StartTrackingPayload( + payload_id, + absl::make_unique(std::move(internal_payload), + EndpointIds{endpoint_id}, true)); + + return pending_payloads_.GetPayload(payload_id); +} + +void PayloadManager::SendClientCallbacksForFinishedOutgoingPayload( + ClientProxy* client, const EndpointIds& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status) { + RunOnStatusUpdateThread([this, client, finished_endpoint_ids, payload_header, + num_bytes_successfully_transferred, status]() { + // Make sure we're still tracking this payload. + PendingPayload* pending_payload = GetPayload(payload_header.id()); + if (!pending_payload) { + return; + } + + PayloadProgressInfo update{ + payload_header.id(), + PayloadManager::PayloadStatusToTransferUpdateStatus(status), + payload_header.total_size(), num_bytes_successfully_transferred}; + for (const auto& endpoint_id : finished_endpoint_ids) { + // Skip sending notifications if we have stopped tracking this + // endpoint. + if (!pending_payload->GetEndpoint(endpoint_id)) { + continue; + } + + // Notify the client. + client->OnPayloadProgress(endpoint_id, update); + } + + // Remove these endpoints from our tracking list for this payload. + pending_payload->RemoveEndpoints(finished_endpoint_ids); + + // Close the payload if no endpoints remain. + if (pending_payload->GetEndpoints().empty()) { + pending_payload->Close(); + } + }); +} + +void PayloadManager::SendClientCallbacksForFinishedIncomingPayload( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status) { + RunOnStatusUpdateThread( + [this, client, endpoint_id, payload_header, offset_bytes, status]() { + // Make sure we're still tracking this payload. + PendingPayload* pending_payload = GetPayload(payload_header.id()); + if (!pending_payload) { + return; + } + + // Unless we never started tracking this payload (meaning we failed to + // even create the InternalPayload), notify the client (and close it). + PayloadProgressInfo update{ + payload_header.id(), + PayloadManager::PayloadStatusToTransferUpdateStatus(status), + payload_header.total_size(), offset_bytes}; + NotifyClientOfIncomingPayloadProgressInfo(client, endpoint_id, update); + DestroyPendingPayload(payload_header.id()); + }); +} + +void PayloadManager::SendControlMessage( + const EndpointIds& endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + PayloadTransferFrame::ControlMessage::EventType event_type) { + PayloadTransferFrame::ControlMessage control_message; + control_message.set_event(event_type); + control_message.set_offset(num_bytes_successfully_transferred); + + endpoint_manager_->SendControlMessage(payload_header, control_message, + endpoint_ids); +} + +void PayloadManager::HandleFinishedOutgoingPayload( + ClientProxy* client, const EndpointIds& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status) { + // This call will destroy a pending payload. + SendClientCallbacksForFinishedOutgoingPayload( + client, finished_endpoint_ids, payload_header, + num_bytes_successfully_transferred, status); + + switch (status) { + case proto::connections::PayloadStatus::LOCAL_ERROR: + SendControlMessage(finished_endpoint_ids, payload_header, + num_bytes_successfully_transferred, + PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); + break; + case proto::connections::PayloadStatus::LOCAL_CANCELLATION: + NEARBY_LOG(INFO, + "Sending PAYLOAD_CANCEL to receiver side; payload_id=%" PRIX64, + static_cast(payload_header.id())); + SendControlMessage( + finished_endpoint_ids, payload_header, + num_bytes_successfully_transferred, + PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + break; + case proto::connections::PayloadStatus::ENDPOINT_IO_ERROR: + // Unregister these endpoints, since we had an IO error on the physical + // connection. + for (const auto& endpoint_id : finished_endpoint_ids) { + endpoint_manager_->DiscardEndpoint(client, endpoint_id); + } + break; + case proto::connections::PayloadStatus::REMOTE_ERROR: + case proto::connections::PayloadStatus::REMOTE_CANCELLATION: + // No special handling needed for these. + break; + default: + NEARBY_LOG(INFO, "PayloadManager: unknown status=%d", status); + break; + } +} + +void PayloadManager::HandleFinishedIncomingPayload( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status) { + SendClientCallbacksForFinishedIncomingPayload( + client, endpoint_id, payload_header, offset_bytes, status); + + switch (status) { + case proto::connections::PayloadStatus::LOCAL_ERROR: + SendControlMessage({endpoint_id}, payload_header, offset_bytes, + PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); + break; + case proto::connections::PayloadStatus::LOCAL_CANCELLATION: + SendControlMessage( + {endpoint_id}, payload_header, offset_bytes, + PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + break; + default: + // TODO(tracyzhou): Add logging. + break; + } +} + +void PayloadManager::HandleSuccessfulOutgoingChunk( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) { + RunOnStatusUpdateThread([this, client, endpoint_id, payload_header, + payload_chunk_flags, payload_chunk_offset, + payload_chunk_body_size]() { + // Make sure we're still tracking this payload and its associated + // endpoint. + PendingPayload* pending_payload = GetPayload(payload_header.id()); + if (!pending_payload || !pending_payload->GetEndpoint(endpoint_id)) { + NEARBY_LOG(INFO, + "HandleSuccessfulOutgoingChunk: endpoint not found: id=%s", + endpoint_id.c_str()); + return; + } + + bool is_last_chunk = (payload_chunk_flags & + PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; + PayloadProgressInfo update{ + payload_header.id(), + is_last_chunk ? PayloadProgressInfo::Status::kSuccess + : PayloadProgressInfo::Status::kInProgress, + payload_header.total_size(), + is_last_chunk ? payload_chunk_offset + : payload_chunk_offset + payload_chunk_body_size}; + + // Notify the client. + client->OnPayloadProgress(endpoint_id, update); + + if (is_last_chunk) { + // Stop tracking this endpoint. + pending_payload->RemoveEndpoints({endpoint_id}); + + // Close the payload if no endpoints remain. + if (pending_payload->GetEndpoints().empty()) { + pending_payload->Close(); + } + } + }); +} + +// @PayloadManagerStatusUpdateThread +void PayloadManager::DestroyPendingPayload(Payload::Id payload_id) { + bool is_incoming = false; + { + MutexLock lock(&mutex_); + auto pending = pending_payloads_.StopTrackingPayload(payload_id); + if (!pending) return; + is_incoming = pending->IsIncoming(); + const char* direction = is_incoming ? "incoming" : "outgoing"; + NEARBY_LOG(INFO, + "PayloadManager: destroying %s pending payload: " + "self=%p; id=%" PRIX64, + direction, this, payload_id); + pending->Close(); + pending.reset(); + } + if (!is_incoming) NotifyShutdown(); +} + +void PayloadManager::HandleSuccessfulIncomingChunk( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) { + RunOnStatusUpdateThread([this, client, endpoint_id, payload_header, + payload_chunk_flags, payload_chunk_offset, + payload_chunk_body_size]() { + // Make sure we're still tracking this payload. + PendingPayload* pending_payload = GetPayload(payload_header.id()); + if (!pending_payload) { + return; + } + + bool is_last_chunk = (payload_chunk_flags & + PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; + PayloadProgressInfo update{ + payload_header.id(), + is_last_chunk ? PayloadProgressInfo::Status::kSuccess + : PayloadProgressInfo::Status::kInProgress, + payload_header.total_size(), + is_last_chunk ? payload_chunk_offset + : payload_chunk_offset + payload_chunk_body_size}; + + // Notify the client of this update. + NotifyClientOfIncomingPayloadProgressInfo(client, endpoint_id, update); + }); +} + +// @EndpointManagerDataPool +void PayloadManager::ProcessDataPacket( + ClientProxy* to_client, const std::string& from_endpoint_id, + PayloadTransferFrame& payload_transfer_frame) { + PayloadTransferFrame::PayloadHeader& payload_header = + *payload_transfer_frame.mutable_payload_header(); + PayloadTransferFrame::PayloadChunk& payload_chunk = + *payload_transfer_frame.mutable_payload_chunk(); + + PendingPayload* pending_payload; + if (payload_chunk.offset() == 0) { + pending_payload = + CreateIncomingPayload(payload_transfer_frame, from_endpoint_id); + if (!pending_payload) { + // Send the error to the remote endpoint. + SendControlMessage({from_endpoint_id}, payload_header, + payload_chunk.offset(), + PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); + return; + } + + // Also, let the client know of this new incoming payload. + RunOnStatusUpdateThread([to_client, from_endpoint_id, pending_payload]() { + NEARBY_LOG(INFO, "ProcessDataPacket [new]: id=%s; payload_id=%" PRIX64, + from_endpoint_id.c_str(), pending_payload->GetId()); + to_client->OnPayload( + from_endpoint_id, + pending_payload->GetInternalPayload()->ReleasePayload()); + }); + } else { + pending_payload = GetPayload(payload_header.id()); + if (!pending_payload) { + NEARBY_LOG(INFO, + "ProcessDataPacket: [missing] id=%s; payload_id=%" PRIX64, + from_endpoint_id.c_str(), + static_cast(payload_header.id())); + return; + } + } + + if (pending_payload->IsLocallyCanceled()) { + // This incoming payload was canceled by the client. Drop this frame and do + // all the cleanup. See go/nc-cancel-payload + NEARBY_LOG(INFO, "ProcessDataPacket: [cancel] id=%s; payload_id=%" PRIX64, + from_endpoint_id.c_str(), pending_payload->GetId()); + HandleFinishedIncomingPayload( + to_client, from_endpoint_id, payload_header, payload_chunk.offset(), + proto::connections::PayloadStatus::LOCAL_CANCELLATION); + return; + } + + // Update the offset for this payload. An endpoint disconnection might occur + // from another thread and we would need to know the current offset to report + // back to the client. For the sake of accuracy, we update the pending payload + // here because it's after all payload terminating events are handled, but + // right before we actually start attaching the next chunk. + pending_payload->SetOffsetForEndpoint(from_endpoint_id, + payload_chunk.offset()); + + // Save size of packet before we move it. + std::int64_t payload_body_size = payload_chunk.body().size(); + if (pending_payload->GetInternalPayload() + ->AttachNextChunk(ByteArray(std::move(*payload_chunk.mutable_body()))) + .Raised()) { + NEARBY_LOG(INFO, + "ProcessDataPacket: [data: error] id=%s; payload_id=%" PRIX64, + from_endpoint_id.c_str(), pending_payload->GetId()); + HandleFinishedIncomingPayload( + to_client, from_endpoint_id, payload_header, payload_chunk.offset(), + proto::connections::PayloadStatus::LOCAL_ERROR); + return; + } + + NEARBY_LOG(INFO, "ProcessDataPacket: [data: ok] id=%s; payload_id=%" PRIX64, + from_endpoint_id.c_str(), pending_payload->GetId()); + HandleSuccessfulIncomingChunk(to_client, from_endpoint_id, payload_header, + payload_chunk.flags(), payload_chunk.offset(), + payload_body_size); +} + +// @EndpointManagerDataPool +void PayloadManager::ProcessControlPacket( + ClientProxy* to_client, const std::string& from_endpoint_id, + PayloadTransferFrame& payload_transfer_frame) { + const PayloadTransferFrame::PayloadHeader& payload_header = + payload_transfer_frame.payload_header(); + const PayloadTransferFrame::ControlMessage& control_message = + payload_transfer_frame.control_message(); + PendingPayload* pending_payload = GetPayload(payload_header.id()); + if (!pending_payload) { + // TODO(tracyzhou): Add logging. + return; + } + + switch (control_message.event()) { + case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: + if (pending_payload->IsIncoming()) { + NEARBY_LOG(INFO, "Incoming PAYLOAD_CANCELED: from id=%s; self=%p", + from_endpoint_id.c_str(), this); + // No need to mark the pending payload as cancelled, since this is a + // remote cancellation for an incoming payload -- we handle everything + // inline here. + HandleFinishedIncomingPayload( + to_client, from_endpoint_id, payload_header, + control_message.offset(), + ControlMessageEventToPayloadStatus(control_message.event())); + } else { + NEARBY_LOG(INFO, "Outgoing PAYLOAD_CANCELED: from id=%s; self=%p", + from_endpoint_id.c_str(), this); + // Mark the payload as canceled *for this endpoint*. + pending_payload->SetEndpointStatusFromControlMessage(from_endpoint_id, + control_message); + } + // TODO(tracyzhou): Add logging. + break; + case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: + if (pending_payload->IsIncoming()) { + HandleFinishedIncomingPayload( + to_client, from_endpoint_id, payload_header, + control_message.offset(), + ControlMessageEventToPayloadStatus(control_message.event())); + } else { + pending_payload->SetEndpointStatusFromControlMessage(from_endpoint_id, + control_message); + } + break; + default: + // TODO(tracyzhou): Add logging. + break; + } +} + +// @PayloadManagerStatusUpdateThread +void PayloadManager::NotifyClientOfIncomingPayloadProgressInfo( + ClientProxy* client, const std::string& endpoint_id, + const PayloadProgressInfo& payload_transfer_update) { + client->OnPayloadProgress(endpoint_id, payload_transfer_update); +} + +///////////////////////////////// EndpointInfo ///////////////////////////////// + +PayloadManager::EndpointInfo::Status +PayloadManager::EndpointInfo::ControlMessageEventToEndpointInfoStatus( + PayloadTransferFrame::ControlMessage::EventType event) { + switch (event) { + case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: + return Status::kError; + case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: + return Status::kCanceled; + default: + // TODO(tracyzhou): Add logging. + return Status::kUnknown; + } +} + +void PayloadManager::EndpointInfo::SetStatusFromControlMessage( + const PayloadTransferFrame::ControlMessage& control_message) { + status = ControlMessageEventToEndpointInfoStatus(control_message.event()); +} + +//////////////////////////////// PendingPayload //////////////////////////////// + +PayloadManager::PendingPayload::PendingPayload( + std::unique_ptr internal_payload, + const EndpointIds& endpoint_ids, bool is_incoming) + : is_incoming_(is_incoming), + internal_payload_(std::move(internal_payload)) { + // Initially we mark all endpoints as available. + // Later on some may become canceled, some may experience data transfer + // failures. Any of these situations will cause endpoint to be marked as + // unavailable. + for (const auto& id : endpoint_ids) { + endpoints_.emplace(id, EndpointInfo{ + .id = id, + .status = EndpointInfo::Status::kAvailable, + }); + } +} + +Payload::Id PayloadManager::PendingPayload::GetId() const { + return internal_payload_->GetId(); +} + +InternalPayload* PayloadManager::PendingPayload::GetInternalPayload() { + return internal_payload_.get(); +} + +bool PayloadManager::PendingPayload::IsLocallyCanceled() const { + return is_locally_canceled_.Get(); +} + +void PayloadManager::PendingPayload::MarkLocallyCanceled() { + is_locally_canceled_.Set(true); +} + +bool PayloadManager::PendingPayload::IsIncoming() const { return is_incoming_; } + +std::vector +PayloadManager::PendingPayload::GetEndpoints() const { + MutexLock lock(&mutex_); + + std::vector result; + for (const auto& item : endpoints_) { + result.push_back(&item.second); + } + return result; +} + +PayloadManager::EndpointInfo* PayloadManager::PendingPayload::GetEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + auto it = endpoints_.find(endpoint_id); + if (it == endpoints_.end()) { + return {}; + } + + return &it->second; +} + +void PayloadManager::PendingPayload::RemoveEndpoints( + const EndpointIds& endpoint_ids) { + MutexLock lock(&mutex_); + + for (const auto& id : endpoint_ids) { + endpoints_.erase(id); + } +} + +void PayloadManager::PendingPayload::SetEndpointStatusFromControlMessage( + const std::string& endpoint_id, + const PayloadTransferFrame::ControlMessage& control_message) { + MutexLock lock(&mutex_); + + auto item = endpoints_.find(endpoint_id); + if (item != endpoints_.end()) { + item->second.SetStatusFromControlMessage(control_message); + } +} + +void PayloadManager::PendingPayload::SetOffsetForEndpoint( + const std::string& endpoint_id, std::int64_t offset) { + MutexLock lock(&mutex_); + + auto item = endpoints_.find(endpoint_id); + if (item != endpoints_.end()) { + item->second.offset = offset; + } +} + +void PayloadManager::PendingPayload::Close() { + if (internal_payload_) internal_payload_->Close(); + close_event_.CountDown(); +} + +bool PayloadManager::PendingPayload::WaitForClose() { + return close_event_.Await(kWaitCloseTimeout).result(); +} + +bool PayloadManager::PendingPayload::IsClosed() { + return close_event_.Await(absl::ZeroDuration()).result(); +} + +void PayloadManager::RunOnStatusUpdateThread(std::function runnable) { + payload_status_update_executor_.Execute(std::move(runnable)); +} + +/////////////////////////////// PendingPayloads /////////////////////////////// + +void PayloadManager::PendingPayloads::StartTrackingPayload( + Payload::Id payload_id, std::unique_ptr pending_payload) { + MutexLock lock(&mutex_); + + auto pair = pending_payloads_.emplace(payload_id, std::move(pending_payload)); + NEARBY_LOG(INFO, "StartTrackingPayload: payload_id=%" PRIX64 "; inserted=%d", + payload_id, pair.second); +} + +std::unique_ptr +PayloadManager::PendingPayloads::StopTrackingPayload(Payload::Id payload_id) { + MutexLock lock(&mutex_); + + auto it = pending_payloads_.find(payload_id); + if (it == pending_payloads_.end()) return {}; + + auto item = pending_payloads_.extract(it); + return std::move(item.mapped()); +} + +PayloadManager::PendingPayload* PayloadManager::PendingPayloads::GetPayload( + Payload::Id payload_id) const { + MutexLock lock(&mutex_); + + auto item = pending_payloads_.find(payload_id); + return item != pending_payloads_.end() ? item->second.get() : nullptr; +} + +std::vector PayloadManager::PendingPayloads::GetAllPayloads() { + MutexLock lock(&mutex_); + + std::vector result; + for (const auto& item : pending_payloads_) { + result.push_back(item.first); + } + return result; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/payload_manager.h b/cpp/core_v2/internal/payload_manager.h new file mode 100644 index 00000000..9e9000d1 --- /dev/null +++ b/cpp/core_v2/internal/payload_manager.h @@ -0,0 +1,282 @@ +#ifndef CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_ +#define CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_ + +#include +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/internal_payload.h" +#include "core_v2/listeners.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/mutex.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +class PayloadManager : public EndpointManager::FrameProcessor { + public: + using EndpointIds = std::vector; + constexpr static const absl::Duration kWaitCloseTimeout = + absl::Milliseconds(5000); + + explicit PayloadManager(EndpointManager& endpoint_manager); + ~PayloadManager() override; + + void SendPayload(ClientProxy* client, const EndpointIds& endpoint_ids, + Payload payload); + Status CancelPayload(ClientProxy* client, Payload::Id payload_id); + + // @EndpointManagerReaderThread + void OnIncomingFrame(OfflineFrame& offline_frame, + const std::string& from_endpoint_id, + ClientProxy* to_client, + proto::connections::Medium current_medium) override; + + // @EndpointManagerThread + void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, + CountDownLatch* barrier) override; + + private: + // Information about an endpoint for a particular payload. + struct EndpointInfo { + // Status set for the endpoint out-of-band via a ControlMessage. + enum class Status { + kUnknown, + kAvailable, + kCanceled, + kError, + }; + + void SetStatusFromControlMessage( + const PayloadTransferFrame::ControlMessage& control_message); + + static Status ControlMessageEventToEndpointInfoStatus( + PayloadTransferFrame::ControlMessage::EventType event); + + std::string id; + Status status = Status::kUnknown; + std::int64_t offset = 0; + }; + + // Tracks state for an InternalPayload and the endpoints associated with it. + class PendingPayload { + public: + PendingPayload(std::unique_ptr internal_payload, + const EndpointIds& endpoint_ids, bool is_incoming); + PendingPayload(PendingPayload&&) = default; + PendingPayload& operator=(PendingPayload&&) = default; + + ~PendingPayload() { Close(); } + + Payload::Id GetId() const; + + InternalPayload* GetInternalPayload(); + + bool IsLocallyCanceled() const; + void MarkLocallyCanceled(); + bool IsIncoming() const; + + // Gets the EndpointInfo objects for the endpoints (still) associated with + // this payload. + std::vector GetEndpoints() const + ABSL_LOCKS_EXCLUDED(mutex_); + // Returns the EndpointInfo for a given endpoint ID. Returns null if the + // endpoint is not associated with this payload. + EndpointInfo* GetEndpoint(const std::string& endpoint_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Removes the given endpoints, e.g. on error. + void RemoveEndpoints(const EndpointIds& endpoint_ids_to_remove) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets the status for a particular endpoint. + void SetEndpointStatusFromControlMessage( + const std::string& endpoint_id, + const PayloadTransferFrame::ControlMessage& control_message) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets the offset for a particular endpoint. + void SetOffsetForEndpoint(const std::string& endpoint_id, + std::int64_t offset) ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes internal_payload_ and triggers close_event_. + // Close is called when a pending peyload does not have associated + // endpoints. + void Close(); + + // Waits for close_event_ or for timeout to happen. + // Returns true, if event happened, false otherwise. + bool WaitForClose(); + bool IsClosed(); + + private: + mutable Mutex mutex_; + bool is_incoming_; + AtomicBoolean is_locally_canceled_{false}; + CountDownLatch close_event_{1}; + std::unique_ptr internal_payload_; + absl::flat_hash_map endpoints_ + ABSL_GUARDED_BY(mutex_); + }; + + // Tracks and manages PendingPayload objects in a synchronized manner. + class PendingPayloads { + public: + PendingPayloads() = default; + ~PendingPayloads() = default; + + void StartTrackingPayload(Payload::Id payload_id, + std::unique_ptr pending_payload) + ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr StopTrackingPayload(Payload::Id payload_id) + ABSL_LOCKS_EXCLUDED(mutex_); + PendingPayload* GetPayload(Payload::Id payload_id) const + ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetAllPayloads() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + mutable Mutex mutex_; + absl::flat_hash_map> + pending_payloads_ ABSL_GUARDED_BY(mutex_); + }; + + using Endpoints = std::vector; + static std::string ToString(const EndpointIds& endpoint_ids); + static std::string ToString(const Endpoints& endpoints); + + // Splits the endpoints for this payload by availability. + // Returns a pair of lists of EndpointInfo*, with the first being the list of + // still-available endpoints, and the second for unavailable endpoints. + static std::pair GetAvailableAndUnavailableEndpoints( + const PendingPayload& pending_payload); + + // Converts list of EndpointInfo to list of Endpoint ids. + // Returns list of endpoint ids. + static EndpointIds EndpointsToEndpointIds(const Endpoints& endpoints); + + bool SendPayloadLoop(ClientProxy* client, PendingPayload& pending_payload, + PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t& next_chunk_offset); + void SendClientCallbacksForFinishedIncomingPayloadRunnable( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status); + + // Converts the status of an endpoint that's been set out-of-band via a remote + // ControlMessage to the PayloadStatus for handling of that endpoint-payload + // pair. + static proto::connections::PayloadStatus EndpointInfoStatusToPayloadStatus( + EndpointInfo::Status status); + // Converts a ControlMessage::EventType for a particular payload to a + // PayloadStatus. Called when we've received a ControlMessage with this event + // from a remote endpoint; thus the PayloadStatuses are REMOTE_*. + static proto::connections::PayloadStatus ControlMessageEventToPayloadStatus( + PayloadTransferFrame::ControlMessage::EventType event); + static PayloadProgressInfo::Status PayloadStatusToTransferUpdateStatus( + proto::connections::PayloadStatus status); + + PayloadTransferFrame::PayloadHeader CreatePayloadHeader( + const InternalPayload& payload); + PayloadTransferFrame::PayloadChunk CreatePayloadChunk(std::int64_t offset, + ByteArray body); + + PendingPayload* CreateIncomingPayload(const PayloadTransferFrame& frame, + const std::string& endpoint_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + Payload::Id CreateOutgoingPayload(Payload payload, + const EndpointIds& endpoint_ids) + ABSL_LOCKS_EXCLUDED(mutex_); + + void SendClientCallbacksForFinishedOutgoingPayload( + ClientProxy* client, const EndpointIds& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status); + void SendClientCallbacksForFinishedIncomingPayload( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status); + + void SendControlMessage( + const EndpointIds& endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + PayloadTransferFrame::ControlMessage::EventType event_type); + + // Handles a finished outgoing payload for the given endpointIds. All statuses + // except for SUCCESS are handled here. + void HandleFinishedOutgoingPayload( + ClientProxy* client, const EndpointIds& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status = + proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS); + void HandleFinishedIncomingPayload( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status); + + void HandleSuccessfulOutgoingChunk( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size); + void HandleSuccessfulIncomingChunk( + ClientProxy* client, const std::string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size); + + void ProcessDataPacket(ClientProxy* to_client, + const std::string& from_endpoint_id, + PayloadTransferFrame& payload_transfer_frame); + void ProcessControlPacket(ClientProxy* to_client, + const std::string& from_endpoint_id, + PayloadTransferFrame& payload_transfer_frame); + + // @PayloadStatusUpdateThread + void NotifyClientOfIncomingPayloadProgressInfo( + ClientProxy* client, const std::string& endpoint_id, + const PayloadProgressInfo& payload_transfer_update); + + SingleThreadExecutor* GetOutgoingPayloadExecutor(Payload::Type payload_type); + + void RunOnStatusUpdateThread(std::function runnable); + bool NotifyShutdown() ABSL_LOCKS_EXCLUDED(mutex_); + void DestroyPendingPayload(Payload::Id payload_id) + ABSL_LOCKS_EXCLUDED(mutex_); + PendingPayload* GetPayload(Payload::Id payload_id) const + ABSL_LOCKS_EXCLUDED(mutex_); + void CancelAllPayloads() ABSL_LOCKS_EXCLUDED(mutex_); + + mutable Mutex mutex_; + EndpointManager::FrameProcessor::Handle handle_; + AtomicBoolean shutdown_{false}; + std::unique_ptr shutdown_barrier_; + int send_payload_count_ = 0; + PendingPayloads pending_payloads_ ABSL_GUARDED_BY(mutex_); + SingleThreadExecutor bytes_payload_executor_; + SingleThreadExecutor file_payload_executor_; + SingleThreadExecutor stream_payload_executor_; + SingleThreadExecutor payload_status_update_executor_; + + EndpointManager* endpoint_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_ diff --git a/cpp/core_v2/internal/payload_manager_test.cc b/cpp/core_v2/internal/payload_manager_test.cc new file mode 100644 index 00000000..826c6172 --- /dev/null +++ b/cpp/core_v2/internal/payload_manager_test.cc @@ -0,0 +1,278 @@ +#include "core_v2/internal/payload_manager.h" + +#include "core_v2/internal/simulation_user.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/system_clock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::string_view kServiceId = "service-id"; +constexpr absl::string_view kDeviceA = "device-a"; +constexpr absl::string_view kDeviceB = "device-b"; +constexpr absl::string_view kMessage = "message"; +constexpr absl::Duration kProgressTimeout = absl::Milliseconds(1000); +constexpr absl::Duration kDefaultTimeout = absl::Milliseconds(1000); + +class PayloadSimulationUser : public SimulationUser { + public: + explicit PayloadSimulationUser(absl::string_view name) + : SimulationUser(std::string(name)) {} + ~PayloadSimulationUser() override { + // SystemClock::Sleep(kDefaultTimeout); + } + + Payload& GetPayload() { return payload_; } + void SendPayload(Payload payload) { + sender_payload_id_ = payload.GetId(); + pm_.SendPayload(&client_, {discovered_.endpoint_id}, std::move(payload)); + } + + Status CancelPayload() { + if (sender_payload_id_) { + return pm_.CancelPayload(&client_, sender_payload_id_); + } else { + return pm_.CancelPayload(&client_, payload_.GetId()); + } + } + + bool IsConnected() const { + return client_.IsConnectedToEndpoint(discovered_.endpoint_id); + } + + protected: + Payload::Id sender_payload_id_ = 0; +}; + +class PayloadManagerTest : public ::testing::Test { + protected: + PayloadManagerTest() { env_.Stop(); } + + bool SetupConnection(PayloadSimulationUser& user_a, + PayloadSimulationUser& user_b) { + user_a.StartAdvertising(std::string(kServiceId), &connection_latch_); + user_b.StartDiscovery(std::string(kServiceId), &discovery_latch_); + EXPECT_TRUE(discovery_latch_.Await(kDefaultTimeout).result()); + EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); + EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + EXPECT_FALSE(user_b.GetDiscovered().endpoint_id.empty()); + NEARBY_LOG(INFO, "EP-B: [discovered] %s", + user_b.GetDiscovered().endpoint_id.c_str()); + user_b.RequestConnection(&connection_latch_); + EXPECT_TRUE(connection_latch_.Await(kDefaultTimeout).result()); + EXPECT_FALSE(user_a.GetDiscovered().endpoint_id.empty()); + NEARBY_LOG(INFO, "EP-A: [discovered] %s", + user_a.GetDiscovered().endpoint_id.c_str()); + NEARBY_LOG(INFO, "Both users discovered their peers."); + user_a.AcceptConnection(&accept_latch_); + user_b.AcceptConnection(&accept_latch_); + EXPECT_TRUE(accept_latch_.Await(kDefaultTimeout).result()); + NEARBY_LOG(INFO, "Both users reached connected state."); + return user_a.IsConnected() && user_b.IsConnected(); + } + + CountDownLatch discovery_latch_{1}; + CountDownLatch connection_latch_{2}; + CountDownLatch accept_latch_{2}; + CountDownLatch payload_latch_{1}; + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(PayloadManagerTest, CanCreateOne) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + env_.Stop(); +} + +TEST_F(PayloadManagerTest, CanCreateMultiple) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_b(kDeviceB); + env_.Stop(); +} + +TEST_F(PayloadManagerTest, CanSendBytePayload) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + + user_a.ExpectPayload(payload_latch_); + user_b.SendPayload(Payload(ByteArray{std::string(kMessage)})); + EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + EXPECT_EQ(user_a.GetPayload().AsBytes(), ByteArray(std::string(kMessage))); + NEARBY_LOG(INFO, "Test completed."); + + env_.Stop(); +} + +TEST_F(PayloadManagerTest, CanSendStreamPayload) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + + auto pipe = std::make_shared(); + OutputStream& tx = pipe->GetOutputStream(); + + user_a.ExpectPayload(payload_latch_); + const ByteArray message{std::string(kMessage)}; + // The first write to the output stream will send the first PAYLOAD_TRANSFER + // packet with payload info and message data. + tx.Write(message); + + user_b.SendPayload(Payload([pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + })); + ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + ASSERT_NE(user_a.GetPayload().AsStream(), nullptr); + InputStream& rx = *user_a.GetPayload().AsStream(); + NEARBY_LOG(INFO, "Stream extracted."); + + EXPECT_TRUE(user_a.WaitForProgress( + [&message](const PayloadProgressInfo& info) { + return info.bytes_transferred >= message.size(); + }, + kProgressTimeout)); + ByteArray result = rx.Read(Pipe::kChunkSize).result(); + EXPECT_EQ(result, message); + NEARBY_LOG(INFO, "Packet 1 handled."); + + tx.Write(message); + EXPECT_TRUE(user_a.WaitForProgress( + [&message](const PayloadProgressInfo& info) { + return info.bytes_transferred >= 2 * message.size(); + }, + kProgressTimeout)); + ByteArray result2 = rx.Read(Pipe::kChunkSize).result(); + EXPECT_EQ(result2, message); + NEARBY_LOG(INFO, "Packet 2 handled."); + + rx.Close(); + tx.Close(); + NEARBY_LOG(INFO, "Test completed."); + env_.Stop(); +} + +TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + + auto pipe = std::make_shared(); + OutputStream& tx = pipe->GetOutputStream(); + + user_a.ExpectPayload(payload_latch_); + const ByteArray message{std::string(kMessage)}; + tx.Write(message); + + user_b.SendPayload(Payload([pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + })); + ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + ASSERT_NE(user_a.GetPayload().AsStream(), nullptr); + InputStream& rx = *user_a.GetPayload().AsStream(); + NEARBY_LOG(INFO, "Stream extracted."); + + EXPECT_TRUE(user_a.WaitForProgress( + [&message](const PayloadProgressInfo& info) { + return info.bytes_transferred >= message.size(); + }, + kProgressTimeout)); + ByteArray result = rx.Read(Pipe::kChunkSize).result(); + EXPECT_EQ(result, message); + NEARBY_LOG(INFO, "Packet 1 handled."); + + EXPECT_EQ(user_a.CancelPayload(), Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Stream canceled on receiver side."); + + // Sender will only handle cancel event if it is sending. + // Once cancel is handled, write will fail. + int count = 0; + while (true) { + if (!tx.Write(message).Ok()) break; + SystemClock::Sleep(kDefaultTimeout); + count++; + } + ASSERT_LE(count, 10); + + EXPECT_TRUE(user_a.WaitForProgress( + [status = PayloadProgressInfo::Status::kCanceled]( + const PayloadProgressInfo& info) { return info.status == status; }, + kProgressTimeout)); + NEARBY_LOG(INFO, "Stream cancelation recevied."); + + tx.Close(); + rx.Close(); + + NEARBY_LOG(INFO, "Test completed."); + env_.Stop(); +} + +TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) { + env_.Start(); + PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + + auto pipe = std::make_shared(); + OutputStream& tx = pipe->GetOutputStream(); + + user_a.ExpectPayload(payload_latch_); + const ByteArray message{std::string(kMessage)}; + tx.Write(message); + + user_b.SendPayload(Payload([pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + })); + ASSERT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + ASSERT_NE(user_a.GetPayload().AsStream(), nullptr); + InputStream& rx = *user_a.GetPayload().AsStream(); + NEARBY_LOG(INFO, "Stream extracted."); + + EXPECT_TRUE(user_a.WaitForProgress( + [&message](const PayloadProgressInfo& info) { + return info.bytes_transferred >= message.size(); + }, + kProgressTimeout)); + ByteArray result = rx.Read(Pipe::kChunkSize).result(); + EXPECT_EQ(result, message); + NEARBY_LOG(INFO, "Packet 1 handled."); + + EXPECT_EQ(user_b.CancelPayload(), Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Stream canceled on sender side."); + + // Sender will only handle cancel event if it is sending. + // Once cancel is handled, write will fail. + int count = 0; + while (true) { + if (!tx.Write(message).Ok()) break; + SystemClock::Sleep(kDefaultTimeout); + count++; + } + ASSERT_LE(count, 10); + + EXPECT_TRUE(user_a.WaitForProgress( + [status = PayloadProgressInfo::Status::kCanceled]( + const PayloadProgressInfo& info) { return info.status == status; }, + kProgressTimeout)); + NEARBY_LOG(INFO, "Stream cancelation recevied."); + + tx.Close(); + rx.Close(); + + NEARBY_LOG(INFO, "Test completed."); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h index dd753ee7..cb181dd9 100644 --- a/cpp/core_v2/internal/pcp_handler.h +++ b/cpp/core_v2/internal/pcp_handler.h @@ -16,6 +16,20 @@ namespace location { namespace nearby { namespace connections { +inline Pcp StrategyToPcp(Strategy strategy) { + if (strategy == Strategy::kP2pCluster) return Pcp::kP2pCluster; + if (strategy == Strategy::kP2pStar) return Pcp::kP2pStar; + if (strategy == Strategy::kP2pPointToPoint) return Pcp::kP2pPointToPoint; + return Pcp::kUnknown; +} + +inline Strategy PcpToStrategy(Pcp pcp) { + if (pcp == Pcp::kP2pCluster) return Strategy::kP2pCluster; + if (pcp == Pcp::kP2pStar) return Strategy::kP2pStar; + if (pcp == Pcp::kP2pPointToPoint) return Strategy::kP2pPointToPoint; + return Strategy::kNone; +} + // Defines the set of methods that need to be implemented to handle the // per-PCP-specific operations in the OfflineServiceController. // diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc new file mode 100644 index 00000000..caeb6353 --- /dev/null +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -0,0 +1,105 @@ +#include "core_v2/internal/pcp_manager.h" + +#include "core_v2/internal/p2p_cluster_pcp_handler.h" +#include "core_v2/internal/p2p_point_to_point_pcp_handler.h" +#include "core_v2/internal/p2p_star_pcp_handler.h" +#include "core_v2/internal/pcp_handler.h" + +namespace location { +namespace nearby { +namespace connections { + +PcpManager::PcpManager(Mediums& mediums, + EndpointChannelManager& channel_manager, + EndpointManager& endpoint_manager) { + handlers_[Pcp::kP2pCluster] = std::make_unique( + mediums, &endpoint_manager, &channel_manager); + handlers_[Pcp::kP2pStar] = std::make_unique( + mediums, endpoint_manager, channel_manager); + handlers_[Pcp::kP2pPointToPoint] = + std::make_unique(mediums, endpoint_manager, + channel_manager); +} + +Status PcpManager::StartAdvertising(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) { + if (!SetCurrentPcpHandler(options.strategy)) { + return {Status::kError}; + } + + return current_->StartAdvertising(client, service_id, options, info); +} + +void PcpManager::StopAdvertising(ClientProxy* client) { + if (current_) { + current_->StopAdvertising(client); + } +} + +Status PcpManager::StartDiscovery(ClientProxy* client, const string& service_id, + const ConnectionOptions& options, + DiscoveryListener listener) { + if (!SetCurrentPcpHandler(options.strategy)) { + return {Status::kError}; + } + + return current_->StartDiscovery(client, service_id, options, + std::move(listener)); +} + +void PcpManager::StopDiscovery(ClientProxy* client) { + if (current_) { + current_->StopDiscovery(client); + } +} + +Status PcpManager::RequestConnection(ClientProxy* client, + const string& endpoint_id, + const ConnectionRequestInfo& info) { + if (!current_) { + return {Status::kOutOfOrderApiCall}; + } + + return current_->RequestConnection(client, endpoint_id, info); +} + +Status PcpManager::AcceptConnection(ClientProxy* client, + const string& endpoint_id, + const PayloadListener& payload_listener) { + if (!current_) { + return {Status::kOutOfOrderApiCall}; + } + + return current_->AcceptConnection(client, endpoint_id, payload_listener); +} + +Status PcpManager::RejectConnection(ClientProxy* client, + const string& endpoint_id) { + if (!current_) { + return {Status::kOutOfOrderApiCall}; + } + + return current_->RejectConnection(client, endpoint_id); +} + +bool PcpManager::SetCurrentPcpHandler(Strategy strategy) { + current_ = GetPcpHandler(StrategyToPcp(strategy)); + + if (!current_) { + NEARBY_LOG(ERROR, "Failed to set current PCP handler: strategy=%s", + strategy.GetName().c_str()); + } + + return current_; +} + +PcpHandler* PcpManager::GetPcpHandler(Pcp pcp) const { + auto item = handlers_.find(pcp); + return item != handlers_.end() ? item->second.get() : nullptr; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h new file mode 100644 index 00000000..ce1d60ed --- /dev/null +++ b/cpp/core_v2/internal/pcp_manager.h @@ -0,0 +1,64 @@ +#ifndef CORE_V2_INTERNAL_PCP_MANAGER_H_ +#define CORE_V2_INTERNAL_PCP_MANAGER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/internal/pcp_handler.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all known PcpHandler implementations, delegating operations to the +// appropriate one as per the parameters passed in. +// +// This will only ever be used by the OfflineServiceController, which has all +// of its entrypoints invoked serially, so there's no synchronization needed. +// Public method semantics matches definition in the +// https://source.corp.google.com/piper///depot/google3/core_v2/internal/service_controller.h +class PcpManager { + public: + PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, + EndpointManager& endpoint_manager); + ~PcpManager() = default; + + Status StartAdvertising(ClientProxy* client_proxy, const string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info); + void StopAdvertising(ClientProxy* client_proxy); + + Status StartDiscovery(ClientProxy* client_proxy, const string& service_id, + const ConnectionOptions& options, + DiscoveryListener listener); + void StopDiscovery(ClientProxy* client_proxy); + + Status RequestConnection(ClientProxy* client_proxy, const string& endpoint_id, + const ConnectionRequestInfo& info); + Status AcceptConnection(ClientProxy* client_proxy, const string& endpoint_id, + const PayloadListener& payload_listener); + Status RejectConnection(ClientProxy* client_proxy, const string& endpoint_id); + + proto::connections::Medium GetBandwidthUpgradeMedium(); + + private: + bool SetCurrentPcpHandler(Strategy strategy); + PcpHandler* GetPcpHandler(Pcp pcp) const; + + absl::flat_hash_map> handlers_; + PcpHandler* current_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_MANAGER_H_ diff --git a/cpp/core_v2/internal/pcp_manager_test.cc b/cpp/core_v2/internal/pcp_manager_test.cc new file mode 100644 index 00000000..15e1d6c0 --- /dev/null +++ b/cpp/core_v2/internal/pcp_manager_test.cc @@ -0,0 +1,122 @@ +#include "core_v2/internal/pcp_manager.h" + +#include + +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/simulation_user.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/count_down_latch.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr char kServiceId[] = "service-id"; +constexpr char kDeviceA[] = "device-A"; +constexpr char kDeviceB[] = "device-B"; + +class PcpManagerTest : public ::testing::Test { + protected: + PcpManagerTest() { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(PcpManagerTest, CanCreateOne) { + env_.Start(); + SimulationUser user(kDeviceA); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanCreateMany) { + env_.Start(); + SimulationUser user_a(kDeviceA); + SimulationUser user_b(kDeviceB); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanAdvertise) { + env_.Start(); + SimulationUser user_a(kDeviceA); + SimulationUser user_b(kDeviceB); + user_a.StartAdvertising(kServiceId, nullptr); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanDiscover) { + env_.Start(); + SimulationUser user_a("device-a"); + SimulationUser user_b("device-b"); + user_a.StartAdvertising(kServiceId, nullptr); + CountDownLatch latch(1); + user_b.StartDiscovery(kServiceId, &latch); + EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); + EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanConnect) { + env_.Start(); + SimulationUser user_a("device-a"); + SimulationUser user_b("device-b"); + CountDownLatch discovery_latch(1); + CountDownLatch connection_latch(2); + user_a.StartAdvertising(kServiceId, &connection_latch); + user_b.StartDiscovery(kServiceId, &discovery_latch); + EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); + EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + user_b.RequestConnection(&connection_latch); + EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanAccept) { + env_.Start(); + SimulationUser user_a("device-a"); + SimulationUser user_b("device-b"); + CountDownLatch discovery_latch(1); + CountDownLatch connection_latch(2); + CountDownLatch accept_latch(2); + user_a.StartAdvertising(kServiceId, &connection_latch); + user_b.StartDiscovery(kServiceId, &discovery_latch); + EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); + EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + user_b.RequestConnection(&connection_latch); + EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); + user_a.AcceptConnection(&accept_latch); + user_b.AcceptConnection(&accept_latch); + EXPECT_TRUE(accept_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +TEST_F(PcpManagerTest, CanReject) { + env_.Start(); + SimulationUser user_a("device-a"); + SimulationUser user_b("device-b"); + CountDownLatch discovery_latch(1); + CountDownLatch connection_latch(2); + CountDownLatch reject_latch(1); + user_a.StartAdvertising(kServiceId, &connection_latch); + user_b.StartDiscovery(kServiceId, &discovery_latch); + EXPECT_TRUE(discovery_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); + EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + user_b.RequestConnection(&connection_latch); + EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); + user_b.ExpectRejectedConnection(reject_latch); + user_a.RejectConnection(nullptr); + EXPECT_TRUE(reject_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc index 9b4a3d25..17ef83c2 100644 --- a/cpp/core_v2/internal/service_controller_router.cc +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -9,6 +9,7 @@ #include "core_v2/options.h" #include "core_v2/params.h" #include "core_v2/payload.h" +#include "platform_v2/public/logging.h" #include "absl/time/clock.h" namespace location { @@ -16,7 +17,7 @@ namespace nearby { namespace connections { ServiceControllerRouter::~ServiceControllerRouter() { - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, "ServiceControllerRouter going down."); // And make sure that cleanup is the last thing we do. serializer_.Shutdown(); @@ -128,7 +129,10 @@ void ServiceControllerRouter::AcceptConnection(ClientProxy* client, } if (client->HasLocalEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): logging + NEARBY_LOG(INFO, + "[ServiceControllerRouter:Accept]: Client has local " + "endpoint responded; id=%s", + endpoint_id.c_str()); callback.result_cb({Status::kOutOfOrderApiCall}); return; } @@ -154,7 +158,10 @@ void ServiceControllerRouter::RejectConnection(ClientProxy* client, } if (client->HasLocalEndpointResponded(endpoint_id)) { - // TODO(tracyzhou): logging + NEARBY_LOG(INFO, + "[ServiceControllerRouter:Reject]: Client has local " + "endpoint responded; id=%s", + endpoint_id.c_str()); callback.result_cb({Status::kOutOfOrderApiCall}); return; } @@ -264,8 +271,9 @@ void ServiceControllerRouter::ClientDisconnecting( RouteToServiceController([this, client, callback]() { if (ClientHasAcquiredServiceController(client)) { DoneWithStrategySessionForClient(client); - // Log the completion of this client's connection. - // TODO(tracyzhou): Add logging. + NEARBY_LOG(INFO, + "[ServiceControllerRouter:Disconnect]: Client has completed " + "the client's connection"); } callback.result_cb({Status::kSuccess}); }); @@ -298,14 +306,18 @@ Status ServiceControllerRouter::AcquireServiceControllerForClient( bool is_the_only_client_of_service_controller = clients_.size() == 1 && ClientHasAcquiredServiceController(client); if (!is_the_only_client_of_service_controller) { - // TODO(tracyzhou): logging + NEARBY_LOG(INFO, + "[ServiceControllerRouter:AcquireServiceControllerForClient]: " + "Client has already active strategy."); return {Status::kAlreadyHaveActiveStrategy}; } // If the client still has connected endpoints, they must disconnect before // they can switch. if (!client->GetConnectedEndpoints().empty()) { - // TODO(tracyzhou): logging + NEARBY_LOG(INFO, + "[ServiceControllerRouter:AcquireServiceControllerForClient]: " + "Client has connected endpoints."); return {Status::kOutOfOrderApiCall}; } @@ -369,7 +381,7 @@ bool ServiceControllerRouter::ClientHasConnectionToAtLeastOneEndpoint( Status ServiceControllerRouter::UpdateCurrentServiceControllerAndStrategy( Strategy strategy) { if (!strategy.IsValid()) { - // TODO(tracyzhou): logging + NEARBY_LOG(INFO, "Strategy is not valid."); return {Status::kError}; } diff --git a/cpp/core_v2/internal/simulation_user.cc b/cpp/core_v2/internal/simulation_user.cc new file mode 100644 index 00000000..54dac813 --- /dev/null +++ b/cpp/core_v2/internal/simulation_user.cc @@ -0,0 +1,158 @@ +#include "core_v2/internal/simulation_user.h" + +#include "core_v2/listeners.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/system_clock.h" +#include "absl/functional/bind_front.h" + +namespace location { +namespace nearby { +namespace connections { + +void SimulationUser::OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + bool is_outgoing) { + if (is_outgoing) { + NEARBY_LOG(INFO, "RequestConnection: initiated_cb called"); + } else { + NEARBY_LOG(INFO, "StartAdvertising: initiated_cb called"); + discovered_ = DiscoveredInfo{ + .endpoint_id = endpoint_id, + .endpoint_name = name_, + .service_id = service_id_, + }; + } + if (initiated_latch_) initiated_latch_->CountDown(); +} + +void SimulationUser::OnConnectionAccepted(const std::string& endpoint_id) { + if (accept_latch_) accept_latch_->CountDown(); +} + +void SimulationUser::OnConnectionRejected(const std::string& endpoint_id, + Status status) { + if (reject_latch_) reject_latch_->CountDown(); +} + +void SimulationUser::OnEndpointFound(const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& service_id) { + NEARBY_LOG(INFO, "Device discovered: id=%s", endpoint_id.c_str()); + discovered_ = DiscoveredInfo{ + .endpoint_id = endpoint_id, + .endpoint_name = endpoint_name, + .service_id = service_id, + }; + if (found_latch_) found_latch_->CountDown(); +} + +void SimulationUser::OnEndpointLost(const std::string& endpoint_id) { + if (lost_latch_) lost_latch_->CountDown(); +} + +void SimulationUser::OnPayload(const std::string& endpoint_id, + Payload payload) { + payload_ = std::move(payload); + if (payload_latch_) payload_latch_->CountDown(); +} + +void SimulationUser::OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info) { + MutexLock lock(&progress_mutex_); + progress_info_ = info; + if (future_ && predicate_ && predicate_(info)) future_->Set(true); +} + +bool SimulationUser::WaitForProgress( + std::function predicate, + absl::Duration timeout) { + Future future; + { + MutexLock lock(&progress_mutex_); + if (predicate(progress_info_)) return true; + future_ = &future; + predicate_ = std::move(predicate); + } + auto response = future.Get(timeout); + { + MutexLock lock(&progress_mutex_); + future_ = nullptr; + predicate_ = nullptr; + } + return response.ok() && response.result(); +} + +void SimulationUser::StartAdvertising(const std::string& service_id, + CountDownLatch* latch) { + initiated_latch_ = latch; + service_id_ = service_id; + ConnectionListener listener = { + .initiated_cb = + std::bind(&SimulationUser::OnConnectionInitiated, this, + std::placeholders::_1, std::placeholders::_2, false), + .accepted_cb = + absl::bind_front(&SimulationUser::OnConnectionAccepted, this), + .rejected_cb = + absl::bind_front(&SimulationUser::OnConnectionRejected, this), + }; + EXPECT_TRUE(mgr_.StartAdvertising(&client_, service_id_, options_, + { + .name = name_, + .listener = std::move(listener), + }) + .Ok()); +} + +void SimulationUser::StartDiscovery(const std::string& service_id, + CountDownLatch* latch) { + found_latch_ = latch; + EXPECT_TRUE( + mgr_.StartDiscovery(&client_, service_id, options_, + { + .endpoint_found_cb = absl::bind_front( + &SimulationUser::OnEndpointFound, this), + .endpoint_lost_cb = absl::bind_front( + &SimulationUser::OnEndpointLost, this), + }) + .Ok()); +} + +void SimulationUser::RequestConnection(CountDownLatch* latch) { + initiated_latch_ = latch; + ConnectionListener listener = { + .initiated_cb = + std::bind(&SimulationUser::OnConnectionInitiated, this, + std::placeholders::_1, std::placeholders::_2, true), + .accepted_cb = + absl::bind_front(&SimulationUser::OnConnectionAccepted, this), + .rejected_cb = + absl::bind_front(&SimulationUser::OnConnectionRejected, this), + }; + EXPECT_TRUE(mgr_.RequestConnection(&client_, discovered_.endpoint_id, + { + .name = discovered_.endpoint_name, + .listener = std::move(listener), + }) + .Ok()); +} + +void SimulationUser::AcceptConnection(CountDownLatch* latch) { + accept_latch_ = latch; + PayloadListener listener = { + .payload_cb = absl::bind_front(&SimulationUser::OnPayload, this), + .payload_progress_cb = + absl::bind_front(&SimulationUser::OnPayloadProgress, this), + }; + EXPECT_TRUE(mgr_.AcceptConnection(&client_, discovered_.endpoint_id, + std::move(listener)) + .Ok()); +} + +void SimulationUser::RejectConnection(CountDownLatch* latch) { + reject_latch_ = latch; + EXPECT_TRUE(mgr_.RejectConnection(&client_, discovered_.endpoint_id).Ok()); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h new file mode 100644 index 00000000..39fa17ee --- /dev/null +++ b/cpp/core_v2/internal/simulation_user.h @@ -0,0 +1,129 @@ +#ifndef CORE_V2_INTERNAL_SIMULATION_USER_H_ +#define CORE_V2_INTERNAL_SIMULATION_USER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/payload_manager.h" +#include "core_v2/internal/pcp_manager.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/future.h" +#include "gtest/gtest.h" + +// Test-only class to help run end-to-end simulations for nearby connections +// protocol. +// +// This is a "standalone" version of PcpManager. It can run independently, +// provided MediumEnvironment has adequate support for all medium types in use. +namespace location { +namespace nearby { +namespace connections { + +class SimulationUser { + public: + struct DiscoveredInfo { + std::string endpoint_id; + std::string endpoint_name; + std::string service_id; + + bool Empty() const { return endpoint_id.empty(); } + void Clear() { endpoint_id.clear(); } + }; + + explicit SimulationUser(const std::string& device_name) + : name_(device_name) {} + virtual ~SimulationUser() = default; + + // Calls PcpManager::StartAdvertising. + // If latch is provided, will call latch->CountDown() in the initiated_cb + // callback. + void StartAdvertising(const std::string& service_id, CountDownLatch* latch); + + // Calls PcpManager::StartDiscovery. + // If latch is provided, will call latch->CountDown() in the endpoint_found_cb + // callback. + void StartDiscovery(const std::string& service_id, CountDownLatch* latch); + + // Calls PcpManager::RequestConnection. + // If latch is provided, latch->CountDown() will be called in the initiated_cb + // callback. + void RequestConnection(CountDownLatch* latch); + + // Calls PcpManager::AcceptConnection. + // If latch is provided, latch->CountDown() will be called in the accepted_cb + // callback. + void AcceptConnection(CountDownLatch* latch); + + // Calls PcpManager::RejectConnection. + // If latch is provided, latch->CountDown() will be called in the rejected_cb + // callback. + void RejectConnection(CountDownLatch* latch); + + // Unlike acceptance, rejection does not have to be mutual, in order to work. + // This method will allow to synchronize on the remote rejection, without + // performing a local rejection. + // latch.CountDown() will be called in the rejected_cb callback. + void ExpectRejectedConnection(CountDownLatch& latch) { + reject_latch_ = &latch; + } + + void ExpectPayload(CountDownLatch& latch) { payload_latch_ = &latch; } + + const DiscoveredInfo& GetDiscovered() const { return discovered_; } + std::string GetName() const { return name_; } + + bool WaitForProgress(std::function pred, + absl::Duration timeout); + + protected: + // ConnectionListener callbacks + void OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + bool is_outgoing); + void OnConnectionAccepted(const std::string& endpoint_id); + void OnConnectionRejected(const std::string& endpoint_id, Status status); + + // DiscoveryListener callbacks + void OnEndpointFound(const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& service_id); + void OnEndpointLost(const std::string& endpoint_id); + + // PayloadListener callbacks + void OnPayload(const std::string& endpoint_id, Payload payload); + void OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info); + + std::string service_id_; + DiscoveredInfo discovered_; + Mutex progress_mutex_; + ConditionVariable progress_sync_{&progress_mutex_}; + PayloadProgressInfo progress_info_; + Payload payload_; + CountDownLatch* initiated_latch_ = nullptr; + CountDownLatch* accept_latch_ = nullptr; + CountDownLatch* reject_latch_ = nullptr; + CountDownLatch* found_latch_ = nullptr; + CountDownLatch* lost_latch_ = nullptr; + CountDownLatch* payload_latch_ = nullptr; + Future* future_ = nullptr; + std::function predicate_; + std::string name_; + Mediums mediums_; + ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + ClientProxy client_; + EndpointChannelManager ecm_; + EndpointManager em_{&ecm_}; + PcpManager mgr_{mediums_, ecm_, em_}; + PayloadManager pm_{em_}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SIMULATION_USER_H_ diff --git a/cpp/core_v2/internal/webrtc_endpoint_channel.cc b/cpp/core_v2/internal/webrtc_endpoint_channel.cc new file mode 100644 index 00000000..0c22add5 --- /dev/null +++ b/cpp/core_v2/internal/webrtc_endpoint_channel.cc @@ -0,0 +1,23 @@ +#include "core_v2/internal/webrtc_endpoint_channel.h" + +namespace location { +namespace nearby { +namespace connections { + +WebRtcEndpointChannel::WebRtcEndpointChannel( + const std::string& channel_name, mediums::WebRtcSocketWrapper socket) + : BaseEndpointChannel(channel_name, &socket.GetInputStream(), + &socket.GetOutputStream()), + webrtc_socket_(std::move(socket)) {} + +proto::connections::Medium WebRtcEndpointChannel::GetMedium() const { + return proto::connections::Medium::WEB_RTC; +} + +void WebRtcEndpointChannel::CloseImpl() { + webrtc_socket_.Close(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/webrtc_endpoint_channel.h b/cpp/core_v2/internal/webrtc_endpoint_channel.h new file mode 100644 index 00000000..dc5b8512 --- /dev/null +++ b/cpp/core_v2/internal/webrtc_endpoint_channel.h @@ -0,0 +1,29 @@ +#ifndef CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_ + +#include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +class WebRtcEndpointChannel final : public BaseEndpointChannel { + public: + WebRtcEndpointChannel(const std::string& channel_name, + mediums::WebRtcSocketWrapper webrtc_socket); + + proto::connections::Medium GetMedium() const override; + + private: + void CloseImpl() override; + + mediums::WebRtcSocketWrapper webrtc_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/wifi_lan_endpoint_channel.cc b/cpp/core_v2/internal/wifi_lan_endpoint_channel.cc new file mode 100644 index 00000000..a2623a38 --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_endpoint_channel.cc @@ -0,0 +1,48 @@ +#include "core_v2/internal/wifi_lan_endpoint_channel.h" + +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/wifi_lan.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +OutputStream* GetOutputStreamOrNull(WifiLanSocket& socket) { + if (socket.GetRemoteWifiLanService().IsValid()) + return &socket.GetOutputStream(); + return nullptr; +} + +InputStream* GetInputStreamOrNull(WifiLanSocket& socket) { + if (socket.GetRemoteWifiLanService().IsValid()) + return &socket.GetInputStream(); + return nullptr; +} + +} // namespace + +WifiLanEndpointChannel::WifiLanEndpointChannel(const std::string& channel_name, + WifiLanSocket socket) + : BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket), + GetOutputStreamOrNull(socket)), + wifi_lan_socket_(std::move(socket)) {} + +proto::connections::Medium WifiLanEndpointChannel::GetMedium() const { + return proto::connections::Medium::WIFI_LAN; +} + +void WifiLanEndpointChannel::CloseImpl() { + auto status = wifi_lan_socket_.Close(); + if (!status.Ok()) { + NEARBY_LOG(INFO, "Failed to close WifiLan socket: exception=%d", + status.value); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_endpoint_channel.h b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h new file mode 100644 index 00000000..6f985fda --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h @@ -0,0 +1,30 @@ +#ifndef CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ + +#include "core_v2/internal/base_endpoint_channel.h" +#include "platform_v2/public/wifi_lan.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiLanEndpointChannel final : public BaseEndpointChannel { + public: + // Creates both outgoing and incoming WifiLan channels. + WifiLanEndpointChannel(const std::string& channel_name, + WifiLanSocket bluetooth_socket); + + proto::connections::Medium GetMedium() const override; + + private: + void CloseImpl() override; + + WifiLanSocket wifi_lan_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc index 398840d9..75fb5463 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -6,7 +6,9 @@ #include #include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/str_cat.h" namespace location { namespace nearby { @@ -33,7 +35,8 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, version_ = version; pcp_ = pcp; service_id_hash_ = service_id_hash; - endpoint_id_ = std::string(endpoint_id); + endpoint_id_ = endpoint_id; + endpoint_name_ = endpoint_name; } WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { @@ -63,54 +66,63 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { 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( - INFO, - "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", - pcp_); - break; - } - break; - - default: - // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over - // the air, or older versions of GmsCore intermingling with newer ones. - NEARBY_LOG( - INFO, "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", - version_); - break; + if (service_info_bytes.size() > kMaxEndpointNameLength) { + NEARBY_LOG(INFO, + "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " + "bytes, got %" PRIu64, + kMaxEndpointNameLength, service_info_bytes.size()); + return; } + + BaseInputStream base_input_stream{service_info_bytes}; + // The first 1 byte is supposed to be the version and pcp. + auto version_and_pcp_byte = static_cast(base_input_stream.ReadUint8()); + // The upper 3 bits are supposed to be the version. + version_ = + static_cast((version_and_pcp_byte & kVersionBitmask) >> 5); + if (version_ != Version::kV1) { + NEARBY_LOG(INFO, + "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", + version_); + return; + } + // The lower 5 bits are supposed to be the Pcp. + pcp_ = static_cast(version_and_pcp_byte & kPcpBitmask); + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + NEARBY_LOG(INFO, + "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + } + + // The next 4 bytes are supposed to be the endpoint_id. + endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)}; + + // The next 3 bytes are supposed to be the service_id_hash. + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); + + // The next 1 byte are supposed to be the length of the endpoint_name. + std::uint32_t expected_endpoint_name_length = base_input_stream.ReadUint8(); + + // The rest bytes are supposed to be the endpoint_name + auto endpoint_name_bytes = + base_input_stream.ReadBytes(expected_endpoint_name_length); + if (endpoint_name_bytes.Empty() || + endpoint_name_bytes.size() != expected_endpoint_name_length) { + NEARBY_LOG(INFO, + "Cannot deserialize WifiLanServiceInfo: expected " + "endpointName to be %d bytes, got %" PRIu64, + expected_endpoint_name_length, endpoint_name_bytes.size()); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string{endpoint_name_bytes}; } WifiLanServiceInfo::operator std::string() const { @@ -118,8 +130,6 @@ WifiLanServiceInfo::operator std::string() const { return ""; } - std::string out; - // The upper 3 bits are the Version. auto version_and_pcp_byte = static_cast( (static_cast(Version::kV1) << 5) & kVersionBitmask); @@ -127,12 +137,23 @@ WifiLanServiceInfo::operator std::string() const { version_and_pcp_byte |= static_cast(static_cast(pcp_) & kPcpBitmask); - out.reserve(kMinLanServiceNameLength); - out.append(1, version_and_pcp_byte); - out.append(endpoint_id_); - out.append(std::string(service_id_hash_)); - // The last byte is reserved to fit the kMinLanServiceNameLength. - out.append(" "); + std::string usable_endpoint_name(endpoint_name_); + if (endpoint_name_.size() > kMaxEndpointNameLength) { + NEARBY_LOG( + INFO, + "While serializing WifiLanServiceInfo, truncating Endpoint Name %s " + "(%lu bytes) down to %d bytes", + endpoint_name_.c_str(), endpoint_name_.size(), kMaxEndpointNameLength); + usable_endpoint_name.erase(kMaxEndpointNameLength); + } + + // clang-format off + std::string out = absl::StrCat(std::string(1, version_and_pcp_byte), + endpoint_id_, + std::string(service_id_hash_), + std::string(1, usable_endpoint_name.size()), + usable_endpoint_name); + // clang-format on return Base64Utils::Encode(ByteArray{std::move(out)}); } diff --git a/cpp/core_v2/internal/wifi_lan_service_info.h b/cpp/core_v2/internal/wifi_lan_service_info.h index b841e7bd..dff5e0d4 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.h +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -67,8 +67,6 @@ class WifiLanServiceInfo { std::string endpoint_id_; // Connected hash service id. ByteArray service_id_hash_; - // TODO(edwinwu): Replaces endpointName as endPointInfo eventually; - // it is not in this version yet for endpointName. // Connected endpoint name. std::string endpoint_name_; }; diff --git a/cpp/core_v2/internal/wifi_lan_service_info_test.cc b/cpp/core_v2/internal/wifi_lan_service_info_test.cc index 5589089f..31a09955 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info_test.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -11,15 +11,15 @@ namespace nearby { namespace connections { namespace { -const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; -const Pcp kPcp = Pcp::kP2pCluster; -const char kEndPointID[] = "AB12"; -const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; -// TODO(edwinwu): Temp to set empty string for endpoint_name. -const char kEndPointName[] = ""; +constexpr WifiLanServiceInfo::Version kVersion = + WifiLanServiceInfo::Version::kV1; +constexpr Pcp kPcp = Pcp::kP2pCluster; +constexpr absl::string_view kEndPointID{"AB12"}; +constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"}; +constexpr absl::string_view kEndPointName{"RAWK + ROWL!"}; TEST(WifiLanServiceInfoTest, ConstructionWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName}; @@ -28,10 +28,11 @@ TEST(WifiLanServiceInfoTest, ConstructionWorks) { EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); + EXPECT_EQ(kEndPointName, wifi_lan_service_info.GetEndpointName()); } TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo org_wifi_lan_service_info{kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName}; std::string wifi_lan_service_info_string{org_wifi_lan_service_info}; @@ -43,12 +44,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); + EXPECT_EQ(kEndPointName, wifi_lan_service_info.GetEndpointName()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo wifi_lan_service_info{bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName}; @@ -58,7 +60,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName}; @@ -68,7 +70,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { std::string short_endpoint_id("AB1"); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName}; @@ -78,7 +80,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { std::string long_endpoint_id("AB12X"); - ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName}; diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h index 4f375344..649ea6d9 100644 --- a/cpp/core_v2/listeners.h +++ b/cpp/core_v2/listeners.h @@ -39,20 +39,20 @@ struct ConnectionResponseInfo { std::string authentication_token; ByteArray raw_authentication_token; ByteArray endpoint_info; - bool is_incoming_connection; - bool is_connection_verified; + bool is_incoming_connection = false; + bool is_connection_verified = false; }; struct PayloadProgressInfo { - std::int64_t payload_id; + std::int64_t payload_id = 0; enum class Status { kSuccess, kFailure, kInProgress, kCanceled, - } status; - std::int64_t total_bytes; - std::int64_t bytes_transferred; + } status = Status::kSuccess; + std::int64_t total_bytes = 0; + std::int64_t bytes_transferred = 0; }; enum class DistanceInfo { diff --git a/cpp/core_v2/payload.h b/cpp/core_v2/payload.h index c1e81633..30bff4af 100644 --- a/cpp/core_v2/payload.h +++ b/cpp/core_v2/payload.h @@ -2,11 +2,13 @@ #define CORE_V2_PAYLOAD_H_ #include +#include #include #include #include "platform_v2/base/byte_array.h" #include "platform_v2/base/input_stream.h" +#include "platform_v2/base/payload_id.h" #include "platform_v2/base/prng.h" #include "platform_v2/public/file.h" #include "absl/types/variant.h" @@ -20,29 +22,38 @@ namespace connections { // ByteArray, InputStream, or InputFile. class Payload { public: + using Id = PayloadId; // Order of types in variant, and values in Type enum is important. // Enum values must match respective variant types. - using Content = - absl::variant, - std::unique_ptr>; + using Content = absl::variant, InputFile>; enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; Payload(Payload&& other) = default; ~Payload() = default; Payload& operator=(Payload&& other) = default; - // Create Payload from bytes, steam, or file. Payload is immutable. + // Default (invalid) payload. Payload() : content_(absl::monostate()) {} + + // Constructors for outgoing payloads. explicit Payload(ByteArray&& bytes) : content_(std::move(bytes)) {} explicit Payload(const ByteArray& bytes) : content_(bytes) {} - explicit Payload(std::unique_ptr stream) + explicit Payload(std::function stream) : content_(std::move(stream)) {} - explicit Payload(std::unique_ptr file) - : content_(std::move(file)) {} + + // Constructors for incoming payloads. + Payload(Id id, ByteArray&& bytes) : content_(std::move(bytes)), id_(id) {} + Payload(Id id, const ByteArray& bytes) : content_(bytes), id_(id) {} + Payload(Id id, std::function stream) + : content_(std::move(stream)), id_(id) {} + + // Constructor for incoming and outgoing file payloads. + Payload(Id id, InputFile file) : content_(std::move(file)), id_(id) {} // Returns ByteArray payload, if it has been defined, or empty ByteArray. - const ByteArray& AsBytes() const & { - static const ByteArray empty; // NOLINT: function-level static is OK. + const ByteArray& AsBytes() const& { + static const ByteArray empty; // NOLINT: function-level static is OK. auto* result = absl::get_if(&content_); return result ? *result : empty; } @@ -51,30 +62,29 @@ class Payload { return result ? std::move(*result) : std::move(ByteArray()); } // Returns InputStream* payload, if it has been defined, or nullptr. - InputStream* AsStream() const { - auto* result = absl::get_if>(&content_); - return result ? result->get() : nullptr; + InputStream* AsStream() { + auto* result = absl::get_if>(&content_); + return result ? &(*result)() : 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; - } + InputFile* AsFile() { return absl::get_if(&content_); } // Returns Payload unique ID. - std::int64_t GetId() const { return id_; } + Id GetId() const { return id_; } // Returns Payload type. Type GetType() const { return type_; } + // Generate Payload Id; to be passed to outgoing file constructor. + static Id GenerateId() { return Prng().NextInt64(); } + private: - static std::int64_t GenerateId() { return Prng().NextInt64(); } Type FindType(const Content& content) const { return static_cast(content_.index()); } Content content_; - std::int64_t id_{GenerateId()}; + Id id_{GenerateId()}; Type type_{FindType(content_)}; }; diff --git a/cpp/core_v2/payload_test.cc b/cpp/core_v2/payload_test.cc index a839320f..9293194d 100644 --- a/cpp/core_v2/payload_test.cc +++ b/cpp/core_v2/payload_test.cc @@ -6,6 +6,7 @@ #include "platform_v2/base/byte_array.h" #include "platform_v2/base/input_stream.h" #include "platform_v2/public/file.h" +#include "platform_v2/public/pipe.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -28,21 +29,28 @@ TEST(PayloadTest, SupportsByteArrayType) { } TEST(PayloadTest, SupportsFileType) { - InputFile* raw_file = new InputFile(/*payload_id=*/23, 0); - std::unique_ptr file(raw_file); - Payload payload(std::move(file)); + const auto payload_id = Payload::GenerateId(); + InputFile file(payload_id, 100); + InputStream& stream = file.GetInputStream(); + Payload payload(payload_id, std::move(file)); EXPECT_EQ(payload.GetType(), Payload::Type::kFile); EXPECT_EQ(payload.AsStream(), nullptr); - EXPECT_EQ(payload.AsFile(), raw_file); + EXPECT_EQ(&payload.AsFile()->GetInputStream(), &stream); EXPECT_EQ(payload.AsBytes(), ByteArray{}); } TEST(PayloadTest, SupportsStreamType) { - InputFile* raw_file = new InputFile(/*payload_id=*/17, 0); - std::unique_ptr stream(raw_file); - Payload payload(std::move(stream)); + auto pipe = std::make_shared(); + Payload payload( + [streamable = pipe]() -> InputStream& { + // For some reason, linter warns us that we return a dangling reference. + // This is not true: we return a reference to internal variable of a + // shared_ptr which remains valid while Payload is valid, since + // shared_ptr is captured by value. + return streamable->GetInputStream(); // NOLINT + }); EXPECT_EQ(payload.GetType(), Payload::Type::kStream); - EXPECT_EQ(payload.AsStream(), raw_file); + EXPECT_EQ(payload.AsStream(), &pipe->GetInputStream()); EXPECT_EQ(payload.AsFile(), nullptr); EXPECT_EQ(payload.AsBytes(), ByteArray{}); } diff --git a/cpp/core_v2/status.h b/cpp/core_v2/status.h index c4ff633c..d56dab42 100644 --- a/cpp/core_v2/status.h +++ b/cpp/core_v2/status.h @@ -24,6 +24,7 @@ struct Status { kAlreadyConnectedToEndpoint, kNotConnectedToEndpoint, kBluetoothError, + kWifiLanError, kPayloadUnknown, }; Value value {kError}; diff --git a/cpp/core_v2/strategy.h b/cpp/core_v2/strategy.h index de134f78..88eb0206 100644 --- a/cpp/core_v2/strategy.h +++ b/cpp/core_v2/strategy.h @@ -16,7 +16,7 @@ class Strategy { static const Strategy kP2pStar; static const Strategy kP2pPointToPoint; - Strategy() : Strategy(kNone) {} + constexpr Strategy() : Strategy(kNone) {} constexpr Strategy(const Strategy& other) : connection_type_(other.connection_type_), @@ -48,7 +48,7 @@ class Strategy { kOneToMany = 2, kManyToMany = 3, }; - Strategy(ConnectionType connection_type, TopologyType topology_type) + constexpr Strategy(ConnectionType connection_type, TopologyType topology_type) : connection_type_(connection_type), topology_type_(topology_type) {} ConnectionType connection_type_; diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index 1b155f0c..62c38942 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -47,7 +47,7 @@ cc_library( "//platform/port:string", "//absl/strings", "//absl/types:any", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index c428c0cb..39e09515 100644 --- a/cpp/platform/api/webrtc.h +++ b/cpp/platform/api/webrtc.h @@ -5,7 +5,7 @@ #include "platform/byte_array.h" #include "platform/ptr.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/platform_v2/api/BUILD b/cpp/platform_v2/api/BUILD index cfe2df3d..9a09cb2f 100644 --- a/cpp/platform_v2/api/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -11,6 +11,7 @@ cc_library( "future.h", "input_file.h", "listenable_future.h", + "log_message.h", "mutex.h", "output_file.h", "scheduled_executor.h", @@ -52,7 +53,7 @@ cc_library( "//platform_v2/base", "//absl/strings", "//absl/types:optional", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -62,12 +63,14 @@ cc_library( "platform.h", ], visibility = [ + "//platform_v2/base:__pkg__", "//platform_v2/impl:__subpackages__", "//platform_v2/public:__pkg__", ], deps = [ ":comm", ":types", + "//platform_v2/base", "//absl/strings", "//absl/types:any", ], diff --git a/cpp/platform_v2/api/atomic_reference.h b/cpp/platform_v2/api/atomic_reference.h index c6e6a3e4..2c0a2d50 100644 --- a/cpp/platform_v2/api/atomic_reference.h +++ b/cpp/platform_v2/api/atomic_reference.h @@ -1,22 +1,22 @@ #ifndef PLATFORM_V2_API_ATOMIC_REFERENCE_H_ #define PLATFORM_V2_API_ATOMIC_REFERENCE_H_ +#include + namespace location { namespace nearby { namespace api { -// An object reference that may be updated atomically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html -template -class AtomicReference { +// Type that allows 32-bit atomic reads and writes. +class AtomicUint32 { public: - virtual ~AtomicReference() = default; + virtual ~AtomicUint32() = default; - virtual T Get() const & = 0; - virtual T Get() && = 0; - virtual void Set(const T& value) = 0; - virtual void Set(T&& value) = 0; + // Atomically reads and returns stored value. + virtual std::uint32_t Get() const = 0; + + // Atomically stores value. + virtual void Set(std::uint32_t value) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/condition_variable.h b/cpp/platform_v2/api/condition_variable.h index d1d34c98..72c113b2 100644 --- a/cpp/platform_v2/api/condition_variable.h +++ b/cpp/platform_v2/api/condition_variable.h @@ -2,6 +2,7 @@ #define PLATFORM_V2_API_CONDITION_VARIABLE_H_ #include "platform_v2/base/exception.h" +#include "absl/time/clock.h" namespace location { namespace nearby { @@ -15,10 +16,19 @@ class ConditionVariable { public: virtual ~ConditionVariable() {} - // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + // Notifies all the waiters that condition state has changed. virtual void Notify() = 0; - // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- - virtual Exception Wait() = 0; // throws Exception::kInterrupted + + // Waits indefinitely for Notify to be called. + // May return prematurely in case of interrupt, if supported by platform. + // Returns kSuccess, or kInterrupted on interrupt. + virtual Exception Wait() = 0; + + // Waits while timeout has not expired for Notify to be called. + // May return prematurely in case of interrupt, if supported by platform. + // Returns kSuccess, or kInterrupted on interrupt. + // If Timeout expired, and Notify was not called, returns kTimeout. + virtual Exception Wait(absl::Duration timeout) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/log_message.h b/cpp/platform_v2/api/log_message.h new file mode 100644 index 00000000..f2e25e48 --- /dev/null +++ b/cpp/platform_v2/api/log_message.h @@ -0,0 +1,41 @@ +#ifndef PLATFORM_V2_API_LOG_MESSAGE_H_ +#define PLATFORM_V2_API_LOG_MESSAGE_H_ + +#include + +namespace location { +namespace nearby { +namespace api { + +// A log message that prints to appropraite destination when ~LogMessage() is +// called. +class LogMessage { + public: + enum class Severity { + kInfo = 0, + kWarning = 1, + kError = 2, + kFatal = 3, // Terminates the process after logging + }; + + // Configures minimum severity to be logged. + static void SetMinLogSeverity(Severity severity); + + // Returns if a log with |severity| should be logged based on + // SetMinLogSeverity and additional platform requirements. + static bool ShouldCreateLogMessage(Severity severity); + + virtual ~LogMessage() = default; + + // Printf like logging. + virtual void Print(const char* format, ...) = 0; + + // Returns a stream for std::cout like logging. + virtual std::ostream& Stream() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index 05b36280..2b5ca406 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -15,6 +15,7 @@ #include "platform_v2/api/count_down_latch.h" #include "platform_v2/api/crypto.h" #include "platform_v2/api/input_file.h" +#include "platform_v2/api/log_message.h" #include "platform_v2/api/mutex.h" #include "platform_v2/api/output_file.h" #include "platform_v2/api/scheduled_executor.h" @@ -25,8 +26,8 @@ #include "platform_v2/api/webrtc.h" #include "platform_v2/api/wifi.h" #include "platform_v2/api/wifi_lan.h" +#include "platform_v2/base/payload_id.h" #include "absl/strings/string_view.h" -#include "absl/types/any.h" namespace location { namespace nearby { @@ -44,18 +45,32 @@ class ImplementationPlatform { // - Future : to synchronize on Callable schduled to execute. // - CountDownLatch : to ensure at least N threads are waiting. // - file I/O - static std::unique_ptr> CreateAtomicReferenceAny( - absl::any initial_value); - static std::unique_ptr> CreateSettableFutureAny(); + // - Logging + + // Atomics: + // ======= + + // Atomic boolean: special case. Uses native platform atomics. + // Does not use locking. + // Does not use dynamic memory allocations in operations. static std::unique_ptr CreateAtomicBoolean(bool initial_value); + + // Supports enums and integers up to 32-bit. + // Does not use locking, if platform supports 32-bit atimics natively. + // Does not use dynamic memory allocations in operations. + static std::unique_ptr + CreateAtomicUint32(std::uint32_t 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); - static std::unique_ptr CreateInputFile(std::int64_t payload_id, + static std::unique_ptr CreateInputFile(PayloadId payload_id, std::int64_t total_size); - static std::unique_ptr CreateOutputFile(std::int64_t payload_id); + static std::unique_ptr CreateOutputFile(PayloadId payload_id); + static std::unique_ptr CreateLogMessage( + const char* file, int line, LogMessage::Severity severity); // Java-like Executors static std::unique_ptr CreateSingleThreadExecutor(); @@ -74,7 +89,6 @@ class ImplementationPlatform { static std::unique_ptr CreateWifiMedium(); static std::unique_ptr CreateWifiLanMedium(); static std::unique_ptr CreateWebRtcMedium(); - static std::string GetDeviceId(); }; } // namespace api diff --git a/cpp/platform_v2/api/settable_future.h b/cpp/platform_v2/api/settable_future.h index 8298bbfd..db921ff5 100644 --- a/cpp/platform_v2/api/settable_future.h +++ b/cpp/platform_v2/api/settable_future.h @@ -16,8 +16,15 @@ class SettableFuture : public ListenableFuture { public: ~SettableFuture() override = default; - virtual bool Set(const T& value) = 0; - virtual bool Set(T&& value) = 0; + // Completes the future successfully. The value is returned to any waiters. + // Returns true, if value was set. + // Returns false, if Future is already in "done" state. + virtual bool Set(T value) = 0; + + // Completes the future unsuccessfully. The exception value is returned to any + // waiters. + // Returns true, if exception was set. + // Returns false, if Future is already in "done" state. virtual bool SetException(Exception exception) = 0; }; diff --git a/cpp/platform_v2/api/webrtc.h b/cpp/platform_v2/api/webrtc.h index 7d89b281..d07bc699 100644 --- a/cpp/platform_v2/api/webrtc.h +++ b/cpp/platform_v2/api/webrtc.h @@ -5,7 +5,7 @@ #include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h index 3b95420b..49b979a8 100644 --- a/cpp/platform_v2/api/wifi_lan.h +++ b/cpp/platform_v2/api/wifi_lan.h @@ -4,8 +4,8 @@ #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/listeners.h" #include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" @@ -18,25 +18,33 @@ class WifiLanService { public: virtual ~WifiLanService() = default; - virtual std::string GetName() = 0; + virtual std::string GetName() const = 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 InputStream of the WifiLanSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + virtual InputStream& GetInputStream() = 0; - // Returns the OutputStream of the WifiLanSocket, empty std::unique_ptr<> - // on error. - virtual std::unique_ptr GetOutputStream() = 0; + // Returns the OutputStream of the WifiLanSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + virtual OutputStream& GetOutputStream() = 0; // Returns Exception::kIo on error, Exception::kSuccess otherwise. - virtual Exception::Value Close() = 0; + virtual Exception Close() = 0; - virtual WifiLanService& GetRemoteWifiLanService() = 0; + // Returns valid WifiLanService pointer if there is a connection, and + // nullptr otherwise. + virtual WifiLanService* GetRemoteWifiLanService() = 0; }; // Container of operations that can be performed over the WifiLan medium. @@ -45,39 +53,51 @@ class WifiLanMedium { 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; + const std::string& service_id, + const std::string& wifi_lan_service_info_name) = 0; + virtual bool StopAdvertising(const std::string& 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; + struct DiscoveredServiceCallback { + // The WifiLanService* is not owned by callbacks. + // It is passed to give access to its non-const methods. + // It is guaranteed to be valid for the duration of call. + std::function + service_discovered_cb = + DefaultCallback(); + std::function + service_lost_cb = + DefaultCallback(); }; - virtual bool StartDiscovery( - absl::string_view service_id, - DiscoveredServiceCallback* discovered_service_callback) = 0; - virtual void StopDiscovery(absl::string_view service_id) = 0; + // Returns true once the WifiLan discovery has been initiated. + virtual bool StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) = 0; - class AcceptedConnectionCallback { - public: - virtual ~AcceptedConnectionCallback() = default; + // Returns true once WifiLan discovery for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredServiceCallback passed in to StartDiscovery() for service_id. + virtual bool StopDiscovery(const std::string& service_id) = 0; - virtual void OnConnectionAccepted(WifiLanSocket* socket, - absl::string_view service_id) = 0; + // Callback that is invoked when a new connection is accepted. + struct AcceptedConnectionCallback { + std::function + accepted_cb = DefaultCallback(); }; + // Returns true once WifiLan socket connection requests to service_id can be + // accepted. virtual bool StartAcceptingConnections( - absl::string_view service_id, - AcceptedConnectionCallback* accepted_connection_callback) = 0; - virtual void StopAcceptingConnections(absl::string_view service_id) = 0; + const std::string& service_id, + AcceptedConnectionCallback callback) = 0; + virtual bool StopAcceptingConnections(const std::string& service_id) = 0; - virtual WifiLanSocket* Connect(WifiLanService* wifi_lan_service, - absl::string_view service_id) = 0; + // Connects to a WifiLan service. + // On success, returns a new WifiLanSocket. + // On error, returns nullptr. + virtual std::unique_ptr Connect( + WifiLanService& service, const std::string& service_id) = 0; }; } // namespace api diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD index 81c320fa..2fd6a8ca 100644 --- a/cpp/platform_v2/base/BUILD +++ b/cpp/platform_v2/base/BUILD @@ -14,9 +14,11 @@ cc_library( "input_stream.h", "listeners.h", "output_stream.h", + "payload_id.h", "prng.h", "runnable.h", "socket.h", + "types.h", ], visibility = [ "//core_v2:__subpackages__", @@ -42,6 +44,7 @@ cc_library( "base_pipe.h", ], visibility = [ + "//core_v2:__subpackages__", "//platform_v2/impl:__subpackages__", "//platform_v2/public:__pkg__", ], @@ -61,7 +64,8 @@ cc_library( "//platform_v2:__subpackages__", ], deps = [ - "//platform:logging", + "//platform_v2/api:platform", + "//platform_v2/api:types", ], ) @@ -85,6 +89,7 @@ cc_library( "//platform_v2/api:comm", "//platform_v2/public:types", "//absl/container:flat_hash_map", + "//absl/strings", ], ) diff --git a/cpp/platform_v2/base/base_input_stream.h b/cpp/platform_v2/base/base_input_stream.h index 12044b4d..c155e7c9 100644 --- a/cpp/platform_v2/base/base_input_stream.h +++ b/cpp/platform_v2/base/base_input_stream.h @@ -27,13 +27,12 @@ class BaseInputStream : public InputStream { std::uint16_t ReadUint16(); std::uint32_t ReadUint32(); std::uint64_t ReadUint64(); + ByteArray ReadBytes(int size); bool IsAvailable(int size) const { return buffer_.size() - position_ >= size; } private: - ByteArray ReadBytes(int size); - ByteArray &buffer_; int position_{0}; }; diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h index 19063505..df84edb9 100644 --- a/cpp/platform_v2/base/byte_array.h +++ b/cpp/platform_v2/base/byte_array.h @@ -1,10 +1,11 @@ #ifndef PLATFORM_V2_BASE_BYTE_ARRAY_H_ #define PLATFORM_V2_BASE_BYTE_ARRAY_H_ +#include #include #include - -#include "absl/strings/string_view.h" +#include +#include namespace location { namespace nearby { @@ -13,13 +14,22 @@ class ByteArray { public: // Create an empty ByteArray ByteArray() = default; + template + explicit ByteArray(const std::array& data) { + SetData(data.data(), data.size()); + } 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) { + // Moves string out of temporary, allowing for a zero-copy constructions. + // This is an optimization for very large strings. + explicit ByteArray(std::string&& source) : data_(std::move(source)) {} + + // Create ByteArray by copy of a std::string. This can't be a string_view, + // because it will conflict with std::string&& version of constructor. + explicit ByteArray(const std::string& source) { SetData(source.data(), source.size()); } @@ -59,7 +69,12 @@ class ByteArray { 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_; } + // Returns a copy of internal representation as std::string. + explicit operator std::string() const& { return data_; } + + // Moves string out of temporary ByteArray, allowing for a zero-copy + // operation. + explicit operator std::string() const&& { return std::move(data_); } private: std::string data_; diff --git a/cpp/platform_v2/base/byte_array_test.cc b/cpp/platform_v2/base/byte_array_test.cc index 1cc7bb37..3479c673 100644 --- a/cpp/platform_v2/base/byte_array_test.cc +++ b/cpp/platform_v2/base/byte_array_test.cc @@ -65,4 +65,12 @@ TEST(ByteArrayTest, SetExplicitData) { EXPECT_EQ(0, memcmp(message, bytes.data(), kMessageSize)); } +TEST(ByteArrayTest, CreateFromNonNullTerminatedStdArray) { + constexpr static const std::array data{'a', '\x00', 'b'}; + ByteArray bytes{data}; + EXPECT_EQ(bytes.size(), 3); + EXPECT_EQ(bytes.size(), std::string(bytes).size()); + EXPECT_EQ(std::string(bytes), std::string(data.data(), data.size())); +} + } // namespace diff --git a/cpp/platform_v2/base/logging.h b/cpp/platform_v2/base/logging.h index f86e1a2e..ced174e9 100644 --- a/cpp/platform_v2/base/logging.h +++ b/cpp/platform_v2/base/logging.h @@ -1,6 +1,60 @@ #ifndef PLATFORM_V2_BASE_LOGGING_H_ #define PLATFORM_V2_BASE_LOGGING_H_ -#include "platform/logging.h" +#include "platform_v2/api/log_message.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +// This class is used to explicitly ignore values in the conditional +// logging macros. This avoids compiler warnings like "value computed +// is not used" and "statement has no effect". +class LogMessageVoidify { + public: + LogMessageVoidify() = default; + // This has to be an operator with a precedence lower than << but + // higher than ?: + void operator&(std::ostream&) {} +}; + +} // namespace nearby +} // namespace location + +// Severity enum conversion +#define NEARBY_SEVERITY_INFO location::nearby::api::LogMessage::Severity::kInfo +#define NEARBY_SEVERITY_WARNING \ + location::nearby::api::LogMessage::Severity::kWarning +#define NEARBY_SEVERITY_ERROR \ + location::nearby::api::LogMessage::Severity::kError +#define NEARBY_SEVERITY_FATAL \ + location::nearby::api::LogMessage::Severity::kFatal + +#define NEARBY_SEVERITY(severity) NEARBY_SEVERITY_##severity + +// Log enabling +#define NEARBY_LOG_IS_ON(severity) \ + location::nearby::api::LogMessage::ShouldCreateLogMessage( \ + NEARBY_SEVERITY(severity)) + +#define NEARBY_LOG_SET_SEVERITY(severity) \ + location::nearby::api::LogMessage::SetMinLogSeverity( \ + NEARBY_SEVERITY(severity)) + +// Log message creation +#define NEARBY_LOG_MESSAGE(severity) \ + location::nearby::api::ImplementationPlatform::CreateLogMessage( \ + __FILE__, __LINE__, NEARBY_SEVERITY(severity)) + +// Public APIs +// The stream statement must come last or otherwise it won't compile. +#define NEARBY_LOGS(severity) \ + !(NEARBY_LOG_IS_ON(severity)) ? (void)0 \ + : location::nearby::LogMessageVoidify() & \ + NEARBY_LOG_MESSAGE(severity)->Stream() + +#define NEARBY_LOG(severity, ...) \ + NEARBY_LOG_IS_ON(severity) \ + ? NEARBY_LOG_MESSAGE(severity)->Print(__VA_ARGS__) : (void)0 #endif // PLATFORM_V2_BASE_LOGGING_H_ diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 4430a003..d2905ba4 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -7,6 +7,7 @@ #include "platform_v2/api/bluetooth_adapter.h" #include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/wifi_lan.h" #include "platform_v2/base/logging.h" #include "platform_v2/public/count_down_latch.h" @@ -40,6 +41,7 @@ void MediumEnvironment::Reset() { NEARBY_LOG(INFO, "MediumEnvironment::Reset()"); bluetooth_adapters_.clear(); bluetooth_mediums_.clear(); + wifi_lan_mediums_.clear(); }); Sync(); } @@ -77,7 +79,7 @@ void MediumEnvironment::OnBluetoothAdapterChangedState( if (info.adapter == &adapter) continue; NEARBY_LOG(INFO, "[adapter=%p, device=%p] notify: adapter=%p", &adapter, &adapter_device, info.adapter); - OnDeviceStateChanged(info, adapter_device, name, mode, enabled); + OnBluetoothDeviceStateChanged(info, adapter_device, name, mode, enabled); } // We don't care if there is an adapter already since all we store is a // pointer. Pointer must remain valid for the duration of a Core session @@ -87,16 +89,17 @@ void MediumEnvironment::OnBluetoothAdapterChangedState( }); } -void MediumEnvironment::OnDeviceStateChanged( +void MediumEnvironment::OnBluetoothDeviceStateChanged( BluetoothMediumContext& info, api::BluetoothDevice& device, const std::string& name, api::BluetoothAdapter::ScanMode mode, bool enabled) { if (!enabled_) return; auto item = info.devices.find(&device); if (item == info.devices.end()) { - NEARBY_LOG( - INFO, "G3 OnDeviceStateChanged [device impl=%p]: new device; notify=%d", - &device, enable_notifications_.load()); + NEARBY_LOG(INFO, + "G3 OnBluetoothDeviceStateChanged [device impl=%p]: new device; " + "notify=%d", + &device, enable_notifications_.load()); if (mode == api::BluetoothAdapter::ScanMode::kConnectableDiscoverable && enabled) { // New device is turned on, and is in discoverable state. @@ -108,10 +111,10 @@ void MediumEnvironment::OnDeviceStateChanged( } } } else { - NEARBY_LOG( - INFO, - "G3 OnDeviceStateChanged [device impl=%p]: exisitng device; notify=%d", - &device, enable_notifications_.load()); + NEARBY_LOG(INFO, + "G3 OnBluetoothDeviceStateChanged [device impl=%p]: exisitng " + "device; notify=%d", + &device, enable_notifications_.load()); auto& discovered_name = item->second; if (mode == api::BluetoothAdapter::ScanMode::kConnectableDiscoverable && enabled) { @@ -145,6 +148,39 @@ void MediumEnvironment::OnDeviceStateChanged( } } +void MediumEnvironment::OnWifiLanServiceStateChanged( + WifiLanMediumContext& info, api::WifiLanService& service, + const std::string& service_id, bool enabled) { + if (!enabled_) return; + auto item = info.services.find(&service); + if (item == info.services.end()) { + NEARBY_LOG(INFO, + "G3 OnWifiLanServiceStateChanged [service impl=%p]: new service", + &service); + info.services.emplace(&service, service.GetName()); + if (enabled) { + RunOnMediumEnvironmentThread([&info, &service, service_id]() { + info.discovery_callback.service_discovered_cb(service, service_id); + }); + } + } else { + NEARBY_LOG(INFO, + "G3 OnWifiLanServiceStateChanged [service impl=%p]: exisitng " + "service", + &service); + if (enabled) { + RunOnMediumEnvironmentThread([&info, &service, service_id]() { + info.discovery_callback.service_discovered_cb(service, service_id); + }); + } else { + RunOnMediumEnvironmentThread([&info, &service, service_id]() { + info.discovery_callback.service_lost_cb(service, service_id); + }); + info.services.erase(item); + } + } +} + void MediumEnvironment::RunOnMediumEnvironmentThread( std::function runnable) { job_count_++; @@ -167,8 +203,9 @@ void MediumEnvironment::RegisterBluetoothMedium( owned_adapter); for (auto& [adapter, device] : bluetooth_adapters_) { if (adapter == nullptr) continue; - OnDeviceStateChanged(context, *device, adapter->GetName(), - adapter->GetScanMode(), adapter->IsEnabled()); + OnBluetoothDeviceStateChanged(context, *device, adapter->GetName(), + adapter->GetScanMode(), + adapter->IsEnabled()); } }); } @@ -190,8 +227,9 @@ void MediumEnvironment::UpdateBluetoothMedium( owned_adapter->IsEnabled(), owned_adapter->GetScanMode()); for (auto& [adapter, device] : bluetooth_adapters_) { if (adapter == nullptr) continue; - OnDeviceStateChanged(context, *device, adapter->GetName(), - adapter->GetScanMode(), adapter->IsEnabled()); + OnBluetoothDeviceStateChanged(context, *device, adapter->GetName(), + adapter->GetScanMode(), + adapter->IsEnabled()); } }); } @@ -208,5 +246,100 @@ void MediumEnvironment::UnregisterBluetoothMedium( }); } +void MediumEnvironment::RegisterWebRtcSignalingMessenger( + absl::string_view self_id, OnSignalingMessageCallback callback) { + if (!enabled_) return; + RunOnMediumEnvironmentThread( + [this, self_id{std::string(self_id)}, callback{std::move(callback)}]() { + webrtc_signaling_callback_[self_id] = std::move(callback); + NEARBY_LOG(INFO, "Registered signaling message callback for id = %s", + self_id.c_str()); + }); +} + +void MediumEnvironment::UnregisterWebRtcSignalingMessenger( + absl::string_view self_id) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, self_id{std::string(self_id)}]() { + auto item = webrtc_signaling_callback_.extract(self_id); + if (item.empty()) return; + NEARBY_LOG(INFO, "Unregistered signaling message callback for id = %s", + self_id.c_str()); + }); +} + +void MediumEnvironment::SendWebRtcSignalingMessage(absl::string_view peer_id, + const ByteArray& message) { + if (!enabled_) return; + RunOnMediumEnvironmentThread( + [this, peer_id{std::string(peer_id)}, message]() { + auto item = webrtc_signaling_callback_.find(peer_id); + if (item == webrtc_signaling_callback_.end()) { + NEARBY_LOG(WARNING, "No callback registered for peer id = %s", + peer_id.c_str()); + return; + } + + item->second(message); + }); +} + +void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + wifi_lan_mediums_.insert({&medium, WifiLanMediumContext{}}); + NEARBY_LOG(INFO, "Registered: medium=%p", &medium); + }); +} + +void MediumEnvironment::UpdateWifiLanMediumForDiscovery( + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, WifiLanDiscoveredServiceCallback callback, + bool enabled) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, &service, service_id, + callback = std::move(callback), enabled]() { + auto item = wifi_lan_mediums_.find(&medium); + if (item == wifi_lan_mediums_.end()) { + NEARBY_LOG( + INFO, "Update WifiLan medium failed. There is no medium registered."); + return; + } + auto& context = item->second; + context.discovery_callback = std::move(callback); + NEARBY_LOG(INFO, "Updated: this=%p; medium=%p", this, &medium); + OnWifiLanServiceStateChanged(context, service, service_id, enabled); + }); +} + +void MediumEnvironment::UpdateWifiLanMediumForAcceptedConnection( + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanAcceptedConnectionCallback accepted_connection_callback) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, + accepted_connection_callback = + std::move(accepted_connection_callback)]() { + auto item = wifi_lan_mediums_.find(&medium); + if (item == wifi_lan_mediums_.end()) { + NEARBY_LOG( + INFO, "Update WifiLan medium failed. There is no medium registered."); + return; + } + auto& context = item->second; + context.accepted_connection_callback = + std::move(accepted_connection_callback); + NEARBY_LOG(INFO, "Updated: this=%p; medium=%p", this, &medium); + }); +} + +void MediumEnvironment::UnregisterWifiLanMedium(api::WifiLanMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + auto item = wifi_lan_mediums_.extract(&medium); + if (item.empty()) return; + NEARBY_LOG(INFO, "Unregistered WifiLan medium"); + }); +} + } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 44e83e1a..b34f8cf5 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -5,9 +5,12 @@ #include "platform_v2/api/bluetooth_adapter.h" #include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/base/byte_array.h" #include "platform_v2/base/listeners.h" #include "platform_v2/public/single_thread_executor.h" #include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -21,6 +24,12 @@ class MediumEnvironment { public: using BluetoothDiscoveryCallback = api::BluetoothClassicMedium::DiscoveryCallback; + using OnSignalingMessageCallback = + api::WebRtcSignalingMessenger::OnSignalingMessageCallback; + using WifiLanDiscoveredServiceCallback = + api::WifiLanMedium::DiscoveredServiceCallback; + using WifiLanAcceptedConnectionCallback = + api::WifiLanMedium::AcceptedConnectionCallback; MediumEnvironment(const MediumEnvironment&) = delete; MediumEnvironment& operator=(const MediumEnvironment&) = delete; @@ -84,6 +93,28 @@ class MediumEnvironment { // Removes medium-related info. This should correspond to device power off. void UnregisterBluetoothMedium(api::BluetoothClassicMedium& medium); + // Registers |callback| to receive messages sent to device with id |self_id|. + void RegisterWebRtcSignalingMessenger(absl::string_view self_id, + OnSignalingMessageCallback callback); + + // Unregisters the callback listening to incoming messages for |self_id|. + void UnregisterWebRtcSignalingMessenger(absl::string_view self_id); + + // Simulates sending a signaling message |message| to device with id + // |peer_id|. + void SendWebRtcSignalingMessage(absl::string_view peer_id, + const ByteArray& message); + // Wifi-Lan medium registration/update calls. + void RegisterWifiLanMedium(api::WifiLanMedium& medium); + void UpdateWifiLanMediumForDiscovery( + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, + WifiLanDiscoveredServiceCallback discovery_callback, bool enabled); + void UpdateWifiLanMediumForAcceptedConnection( + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanAcceptedConnectionCallback accepted_connection_callback); + void UnregisterWifiLanMedium(api::WifiLanMedium& medium); + private: struct BluetoothMediumContext { BluetoothDiscoveryCallback callback; @@ -92,6 +123,13 @@ class MediumEnvironment { absl::flat_hash_map devices; }; + struct WifiLanMediumContext { + WifiLanDiscoveredServiceCallback discovery_callback; + WifiLanAcceptedConnectionCallback accepted_connection_callback; + // discovered service vs service name map. + absl::flat_hash_map services; + }; + // This is a singleton object, for which destructor will never be called. // Constructor will be invoked once from Instance() static method. // Object is create in-place (with a placement new) to guarantee that @@ -99,10 +137,17 @@ class MediumEnvironment { MediumEnvironment() = default; ~MediumEnvironment() = default; - void OnDeviceStateChanged(BluetoothMediumContext& info, - api::BluetoothDevice& device, - const std::string& name, - api::BluetoothAdapter::ScanMode mode, bool enabled); + void OnBluetoothDeviceStateChanged(BluetoothMediumContext& info, + api::BluetoothDevice& device, + const std::string& name, + api::BluetoothAdapter::ScanMode mode, + bool enabled); + + void OnWifiLanServiceStateChanged(WifiLanMediumContext& info, + api::WifiLanService& service, + const std::string& service_id, + bool enabled); + void RunOnMediumEnvironmentThread(std::function runnable); std::atomic_bool enabled_ = true; @@ -116,6 +161,13 @@ class MediumEnvironment { bluetooth_adapters_; absl::flat_hash_map bluetooth_mediums_; + + // Maps peer id to callback for receiving signaling messages. + absl::flat_hash_map + webrtc_signaling_callback_; + + absl::flat_hash_map + wifi_lan_mediums_; }; } // namespace nearby diff --git a/cpp/platform_v2/base/payload_id.h b/cpp/platform_v2/base/payload_id.h new file mode 100644 index 00000000..81f2e730 --- /dev/null +++ b/cpp/platform_v2/base/payload_id.h @@ -0,0 +1,14 @@ +#ifndef PLATFORM_V2_BASE_PAYLOAD_ID_H_ +#define PLATFORM_V2_BASE_PAYLOAD_ID_H_ + +#include + +namespace location { +namespace nearby { + +using PayloadId = std::int64_t; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_PAYLOAD_ID_H_ diff --git a/cpp/platform_v2/base/prng.cc b/cpp/platform_v2/base/prng.cc index ab5c1f75..ace2928c 100644 --- a/cpp/platform_v2/base/prng.cc +++ b/cpp/platform_v2/base/prng.cc @@ -38,7 +38,7 @@ std::uint32_t Prng::NextUint32() { std::int64_t Prng::NextInt64() { return (static_cast(NextInt32()) << 32) | - (static_cast(NextInt32())); + (static_cast(NextUint32())); } } // namespace nearby diff --git a/cpp/platform_v2/base/prng_test.cc b/cpp/platform_v2/base/prng_test.cc index c8a52065..4d4466e2 100644 --- a/cpp/platform_v2/base/prng_test.cc +++ b/cpp/platform_v2/base/prng_test.cc @@ -5,6 +5,13 @@ namespace location { namespace nearby { +enum class TestMode { + kUpperHalfOfInt64, + kLowerHalfOfInt64, + kInt32, + kUint32, +}; + TEST(PrngTest, NextInt32) { std::int32_t i = Prng().NextInt32(); EXPECT_LE(i, std::numeric_limits::max()); @@ -23,5 +30,48 @@ TEST(PrngTest, NextInt64) { EXPECT_GE(i, std::numeric_limits::min()); } +void ValidateRandom(TestMode mode) { + int count_all_zeros = 0; + int count_all_ones = 0; + std::uint32_t i; + Prng prng; + for (int count = 0; count < 100; ++count) { + switch (mode) { + case TestMode::kUpperHalfOfInt64: + i = static_cast(prng.NextInt64() >> 32); + break; + case TestMode::kLowerHalfOfInt64: + i = static_cast(prng.NextInt64()); + break; + case TestMode::kInt32: + i = static_cast(prng.NextInt32()); + break; + case TestMode::kUint32: + i = static_cast(prng.NextUint32()); + break; + } + if (!i) count_all_zeros++; + if (i == 0xFFFFFFFF) count_all_ones++; + } + EXPECT_LE(count_all_zeros, 1); + EXPECT_LE(count_all_ones, 1); +} + +TEST(PrngTest, ValidateUpperHalfOfInt64) { + ValidateRandom(TestMode::kUpperHalfOfInt64); +} + +TEST(PrngTest, ValidateLowerHalfOfInt64) { + ValidateRandom(TestMode::kLowerHalfOfInt64); +} + +TEST(PrngTest, ValidateInt32) { + ValidateRandom(TestMode::kInt32); +} + +TEST(PrngTest, ValidateUint32) { + ValidateRandom(TestMode::kUint32); +} + } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/base/types.h b/cpp/platform_v2/base/types.h new file mode 100644 index 00000000..3ac71f9a --- /dev/null +++ b/cpp/platform_v2/base/types.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_V2_BASE_TYPES_H_ +#define PLATFORM_V2_BASE_TYPES_H_ + +#include + +namespace location { +namespace nearby { + +// Similar to static_cast, but will assert that Derived is a derived type of +// Base. +// Usage: +// class A {}; +// class B : public A {}; +// class C {}; +// B b; +// A* a = &b; +// B* b2 = down_cast(a); // This is OK. +// C* c = down_cast(a); // This will fail to compile. +template +inline Derived down_cast(Base* value) { + using DerivedType = typename std::remove_pointer::type; + static_assert(std::is_base_of::value); + return static_cast(value); +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_TYPES_H_ diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 69a23663..df4ca186 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -2,25 +2,27 @@ cc_library( name = "types", testonly = True, srcs = [ + "log_message.cc", "scheduled_executor.cc", "system_clock.cc", ], hdrs = [ "atomic_boolean.h", - "atomic_reference_any.h", + "atomic_reference.h", "condition_variable.h", "count_down_latch.h", + "log_message.h", "multi_thread_executor.h", "mutex.h", "pipe.h", "scheduled_executor.h", - "settable_future_any.h", "single_thread_executor.h", ], visibility = [ "//platform_v2/impl/g3:__pkg__", ], deps = [ + "//base", "//platform_v2/api:platform", "//platform_v2/api:types", "//platform_v2/base", @@ -41,11 +43,13 @@ cc_library( "bluetooth_adapter.cc", "bluetooth_classic.cc", "webrtc.cc", + "wifi_lan.cc", ], hdrs = [ "bluetooth_adapter.h", "bluetooth_classic.h", "webrtc.h", + "wifi_lan.h", ], visibility = [ "//platform_v2/impl/g3:__pkg__", @@ -61,9 +65,9 @@ cc_library( "//absl/container:flat_hash_set", "//absl/strings", "//absl/synchronization", - "//webrtc/files/stable/webrtc/api:create_peerconnection_factory", #buildcleaner: keep - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", - "//webrtc/files/stable/webrtc/api/task_queue:default_task_queue_factory", + "//webrtc/api:create_peerconnection_factory", #buildcleaner: keep + "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/api/task_queue:default_task_queue_factory", ], ) @@ -105,6 +109,7 @@ cc_library( "//platform_v2/impl/shared:file", "//absl/base:core_headers", "//absl/memory", + "//absl/strings", "//absl/time", ], ) diff --git a/cpp/platform_v2/impl/g3/atomic_reference.h b/cpp/platform_v2/impl/g3/atomic_reference.h new file mode 100644 index 00000000..2b33860f --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_reference.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_H_ + +#include +#include + +#include "platform_v2/api/atomic_reference.h" + +namespace location { +namespace nearby { +namespace g3 { + +class AtomicUint32 : public api::AtomicUint32 { + public: + explicit AtomicUint32(std::int32_t value) : value_(value) {} + ~AtomicUint32() override = default; + + std::uint32_t Get() const override { + return value_; + } + void Set(std::uint32_t value) override { + value_ = value; + } + + private: + std::atomic value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/impl/g3/atomic_reference_any.h b/cpp/platform_v2/impl/g3/atomic_reference_any.h deleted file mode 100644 index c59e23c3..00000000 --- a/cpp/platform_v2/impl/g3/atomic_reference_any.h +++ /dev/null @@ -1,46 +0,0 @@ -#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_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc index 12232eb6..f0226452 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -13,9 +13,15 @@ namespace location { namespace nearby { namespace g3 { +BluetoothSocket::~BluetoothSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + void BluetoothSocket::Connect(BluetoothSocket& other) { absl::MutexLock lock(&mutex_); remote_socket_ = &other; + input_ = other.output_; } bool BluetoothSocket::IsConnected() const { @@ -29,7 +35,7 @@ bool BluetoothSocket::IsClosed() const { } bool BluetoothSocket::IsConnectedLocked() const { - return remote_socket_ != nullptr; + return input_ != nullptr; } InputStream& BluetoothSocket::GetInputStream() { @@ -44,31 +50,31 @@ OutputStream& BluetoothSocket::GetOutputStream() { InputStream& BluetoothSocket::GetLocalInputStream() { absl::MutexLock lock(&mutex_); - return output_.GetInputStream(); + return output_->GetInputStream(); } OutputStream& BluetoothSocket::GetLocalOutputStream() { absl::MutexLock lock(&mutex_); - return output_.GetOutputStream(); + return output_->GetOutputStream(); } Exception BluetoothSocket::Close() { - BluetoothSocket* remote_socket = nullptr; - { - absl::MutexLock lock(&mutex_); - if (!closed_) { - remote_socket = remote_socket_; - output_.GetOutputStream().Close(); - output_.GetInputStream().Close(); - closed_ = true; - } - } - if (remote_socket != nullptr) { - remote_socket->Close(); - } + absl::MutexLock lock(&mutex_); + DoClose(); return {Exception::kSuccess}; } +void BluetoothSocket::DoClose() { + if (!closed_) { + remote_socket_ = nullptr; + output_->GetOutputStream().Close(); + output_->GetInputStream().Close(); + input_->GetOutputStream().Close(); + input_->GetInputStream().Close(); + closed_ = true; + } +} + BluetoothSocket* BluetoothSocket::GetRemoteSocket() { absl::MutexLock lock(&mutex_); return remote_socket_; diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h index 77dfca5a..ede548b7 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.h +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -25,7 +25,7 @@ class BluetoothSocket : public api::BluetoothSocket { public: BluetoothSocket() = default; explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {} - ~BluetoothSocket() override = default; + ~BluetoothSocket() override; // Connects to another BluetoothSocket, to form a functional low-level // channel. From this point on, and until Close is called, connection exists. @@ -64,6 +64,8 @@ class BluetoothSocket : public api::BluetoothSocket { BluetoothDevice* GetRemoteDevice() override ABSL_LOCKS_EXCLUDED(mutex_); private: + void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Returns true if connection exists to the (possibly closed) remote socket. bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -80,7 +82,8 @@ class BluetoothSocket : public api::BluetoothSocket { // Output pipe is initialized by constructor, it remains always valid, until // it is closed. it represents output part of a local socket. Input part of a // local socket comes from the peer socket, after connection. - Pipe output_; + std::shared_ptr output_ {new Pipe}; + std::shared_ptr input_; mutable absl::Mutex mutex_; BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. BluetoothSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr; diff --git a/cpp/platform_v2/impl/g3/condition_variable.h b/cpp/platform_v2/impl/g3/condition_variable.h index 74ef47ed..82591e97 100644 --- a/cpp/platform_v2/impl/g3/condition_variable.h +++ b/cpp/platform_v2/impl/g3/condition_variable.h @@ -19,6 +19,11 @@ class ConditionVariable : public api::ConditionVariable { cond_var_.Wait(mutex_); return {Exception::kSuccess}; } + Exception Wait(absl::Duration timeout) override { + return cond_var_.WaitWithTimeout(mutex_, timeout) + ? Exception{Exception::kTimeout} + : Exception{Exception::kSuccess}; + } void Notify() override { cond_var_.SignalAll(); } private: diff --git a/cpp/platform_v2/impl/g3/log_message.cc b/cpp/platform_v2/impl/g3/log_message.cc new file mode 100644 index 00000000..a9dce4f3 --- /dev/null +++ b/cpp/platform_v2/impl/g3/log_message.cc @@ -0,0 +1,56 @@ +#include "platform_v2/impl/g3/log_message.h" + +#include + +#include "base/stringprintf.h" + +namespace location { +namespace nearby { +namespace g3 { + +api::LogMessage::Severity g_min_log_severity = api::LogMessage::Severity::kInfo; + +inline absl::LogSeverity ConvertSeverity(api::LogMessage::Severity severity) { + switch (severity) { + case api::LogMessage::Severity::kInfo: + return absl::LogSeverity::kInfo; + case api::LogMessage::Severity::kWarning: + return absl::LogSeverity::kWarning; + case api::LogMessage::Severity::kError: + return absl::LogSeverity::kError; + case api::LogMessage::Severity::kFatal: + return absl::LogSeverity::kFatal; + } +} + +LogMessage::LogMessage(const char* file, int line, Severity severity) + : log_streamer_(ConvertSeverity(severity), file, line) {} + +LogMessage::~LogMessage() = default; + +void LogMessage::Print(const char* format, ...) { + va_list ap; + va_start(ap, format); + std::string result; + StringAppendV(&result, format, ap); + log_streamer_.stream() << result; + va_end(ap); +} + +std::ostream& LogMessage::Stream() { return log_streamer_.stream(); } + +} // namespace g3 + +namespace api { + +void LogMessage::SetMinLogSeverity(Severity severity) { + g3::g_min_log_severity = severity; +} + +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + return severity >= g3::g_min_log_severity; +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/log_message.h b/cpp/platform_v2/impl/g3/log_message.h new file mode 100644 index 00000000..25e1fe89 --- /dev/null +++ b/cpp/platform_v2/impl/g3/log_message.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_V2_IMPL_G3_LOG_MESSAGE_H_ +#define PLATFORM_V2_IMPL_G3_LOG_MESSAGE_H_ + +#include "base/logging.h" +#include "platform_v2/api/log_message.h" + +namespace location { +namespace nearby { +namespace g3 { + +// See documentation in +// https://source.corp.google.com/piper///depot/google3/platform_v2/api/log_message.h +class LogMessage : public api::LogMessage { + public: + LogMessage(const char* file, int line, Severity severity); + ~LogMessage() override; + + void Print(const char* format, ...) override; + + std::ostream& Stream() override; + + private: + absl::LogStreamer log_streamer_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index a77f6695..2996b572 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -11,28 +11,30 @@ #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/log_message.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/atomic_reference.h" #include "platform_v2/impl/g3/bluetooth_adapter.h" #include "platform_v2/impl/g3/bluetooth_classic.h" #include "platform_v2/impl/g3/condition_variable.h" #include "platform_v2/impl/g3/count_down_latch.h" +#include "platform_v2/impl/g3/log_message.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 "platform_v2/impl/g3/webrtc.h" +#include "platform_v2/impl/g3/wifi_lan.h" #include "platform_v2/impl/shared/file.h" #include "absl/base/integral_types.h" #include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" #include "absl/time/time.h" namespace location { @@ -40,8 +42,8 @@ namespace nearby { namespace api { namespace { -std::string GetPayloadPath(std::int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); +std::string GetPayloadPath(PayloadId payload_id) { + return absl::StrCat("/tmp/", payload_id); } } // namespace @@ -60,14 +62,9 @@ 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::CreateAtomicUint32(std::uint32_t value) { + return absl::make_unique(value); } std::unique_ptr @@ -86,16 +83,21 @@ std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( } std::unique_ptr ImplementationPlatform::CreateInputFile( - std::int64_t payload_id, std::int64_t total_size) { + PayloadId payload_id, std::int64_t total_size) { return absl::make_unique(GetPayloadPath(payload_id), total_size); } std::unique_ptr ImplementationPlatform::CreateOutputFile( - std::int64_t payload_id) { + PayloadId payload_id) { return absl::make_unique(GetPayloadPath(payload_id)); } +std::unique_ptr ImplementationPlatform::CreateLogMessage( + const char* file, int line, LogMessage::Severity severity) { + return absl::make_unique(file, line, severity); +} + std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium( api::BluetoothAdapter& adapter) { @@ -122,7 +124,7 @@ std::unique_ptr ImplementationPlatform::CreateWifiMedium() { } std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { - return std::unique_ptr(); + return absl::make_unique(); } std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { @@ -142,11 +144,6 @@ ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { new g3::ConditionVariable(static_cast(mutex))); } -std::string ImplementationPlatform::GetDeviceId() { - // TODO(alexchau): Get deviceId from base - return "google3"; -} - } // namespace api } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/impl/g3/settable_future_any.h b/cpp/platform_v2/impl/g3/settable_future_any.h deleted file mode 100644 index acb1810d..00000000 --- a/cpp/platform_v2/impl/g3/settable_future_any.h +++ /dev/null @@ -1,104 +0,0 @@ -#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/webrtc.cc b/cpp/platform_v2/impl/g3/webrtc.cc index d8f349f4..2d98544c 100644 --- a/cpp/platform_v2/impl/g3/webrtc.cc +++ b/cpp/platform_v2/impl/g3/webrtc.cc @@ -1,24 +1,49 @@ #include "platform_v2/impl/g3/webrtc.h" -#include "webrtc/files/stable/webrtc/api/task_queue/default_task_queue_factory.h" +#include + +#include "platform_v2/base/medium_environment.h" +#include "webrtc/api/task_queue/default_task_queue_factory.h" namespace location { namespace nearby { namespace g3 { +WebRtcSignalingMessenger::WebRtcSignalingMessenger(absl::string_view self_id) + : self_id_(self_id) {} + +bool WebRtcSignalingMessenger::SendMessage(absl::string_view peer_id, + const ByteArray& message) { + auto& env = MediumEnvironment::Instance(); + env.SendWebRtcSignalingMessage(peer_id, message); + return true; +} + +bool WebRtcSignalingMessenger::StartReceivingMessages( + OnSignalingMessageCallback listener) { + auto& env = MediumEnvironment::Instance(); + env.RegisterWebRtcSignalingMessenger(self_id_, listener); + return true; +} + +void WebRtcSignalingMessenger::StopReceivingMessages() { + auto& env = MediumEnvironment::Instance(); + env.UnregisterWebRtcSignalingMessenger(self_id_); +} + void WebRtcMedium::CreatePeerConnection( webrtc::PeerConnectionObserver* observer, PeerConnectionCallback callback) { webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; webrtc::PeerConnectionDependencies dependencies(observer); - std::unique_ptr signaling_thread = rtc::Thread::Create(); - signaling_thread->SetName("signaling_thread", nullptr); - RTC_CHECK(signaling_thread->Start()) << "Failed to start thread"; + signaling_thread_ = rtc::Thread::Create(); + signaling_thread_->SetName("signaling_thread", nullptr); + RTC_CHECK(signaling_thread_->Start()) << "Failed to start thread"; webrtc::PeerConnectionFactoryDependencies factory_dependencies; factory_dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory(); - factory_dependencies.signaling_thread = signaling_thread.release(); + factory_dependencies.signaling_thread = signaling_thread_.get(); callback(webrtc::CreateModularPeerConnectionFactory( std::move(factory_dependencies)) @@ -27,8 +52,7 @@ void WebRtcMedium::CreatePeerConnection( std::unique_ptr WebRtcMedium::GetSignalingMessenger(absl::string_view self_id) { - // TODO(bfranz): Implement - return nullptr; + return std::make_unique(self_id); } } // namespace g3 diff --git a/cpp/platform_v2/impl/g3/webrtc.h b/cpp/platform_v2/impl/g3/webrtc.h index 35a4da10..12cb5a8d 100644 --- a/cpp/platform_v2/impl/g3/webrtc.h +++ b/cpp/platform_v2/impl/g3/webrtc.h @@ -5,12 +5,29 @@ #include "platform_v2/api/webrtc.h" #include "absl/strings/string_view.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { namespace g3 { +class WebRtcSignalingMessenger : public api::WebRtcSignalingMessenger { + public: + using OnSignalingMessageCallback = + api::WebRtcSignalingMessenger::OnSignalingMessageCallback; + + explicit WebRtcSignalingMessenger(absl::string_view self_id); + ~WebRtcSignalingMessenger() override = default; + + bool SendMessage(absl::string_view peer_id, + const ByteArray& message) override; + bool StartReceivingMessages(OnSignalingMessageCallback listener) override; + void StopReceivingMessages() override; + + private: + absl::string_view self_id_; +}; + class WebRtcMedium : public api::WebRtcMedium { public: using PeerConnectionCallback = api::WebRtcMedium::PeerConnectionCallback; @@ -26,6 +43,8 @@ class WebRtcMedium : public api::WebRtcMedium { // Returns a signaling messenger for sending WebRTC signaling messages. std::unique_ptr GetSignalingMessenger( absl::string_view self_id) override; + private: + std::unique_ptr signaling_thread_; }; } // namespace g3 diff --git a/cpp/platform_v2/impl/g3/wifi_lan.cc b/cpp/platform_v2/impl/g3/wifi_lan.cc new file mode 100644 index 00000000..2088c8e0 --- /dev/null +++ b/cpp/platform_v2/impl/g3/wifi_lan.cc @@ -0,0 +1,114 @@ +#include "platform_v2/impl/g3/wifi_lan.h" + +#include +#include + +#include "platform_v2/api/wifi_lan.h" +#include "platform_v2/base/logging.h" +#include "platform_v2/base/medium_environment.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +InputStream& WifiLanSocket::GetInputStream() { + absl::MutexLock lock(&mutex_); + return pipe_.GetInputStream(); +} + +OutputStream& WifiLanSocket::GetOutputStream() { + absl::MutexLock lock(&mutex_); + return pipe_.GetOutputStream(); +} + +Exception WifiLanSocket::Close() { + absl::MutexLock lock(&mutex_); + pipe_.GetOutputStream().Close(); + pipe_.GetInputStream().Close(); + return {Exception::kSuccess}; +} + +WifiLanService* WifiLanSocket::GetRemoteWifiLanService() { + absl::MutexLock lock(&mutex_); + return service_; +} + +WifiLanMedium::WifiLanMedium() { + auto& env = MediumEnvironment::Instance(); + env.RegisterWifiLanMedium(*this); +} + +WifiLanMedium::~WifiLanMedium() { + auto& env = MediumEnvironment::Instance(); + env.UnregisterWifiLanMedium(*this); +} + +bool WifiLanMedium::StartAdvertising( + const std::string& service_id, + const std::string& wifi_lan_service_info_name) { + // TODO(edwinwu): Integrate medium_environment. + // steps: + // 1. create wifi_lan_service as the parameter to create wifi_lan_socket + // auto service = std::make_unique(); + // auto socket = std::make_unique(service); + // 2. callback for accepting connection; otherwise don't callback if not + // accepted connection. + // accepted_connection_callback_.accepted_cb(socket, service_id); + return true; +} + +bool WifiLanMedium::StopAdvertising(const std::string& service_id) { + // TODO(edwinwu): Integrate medium_environment. + return true; +} + +bool WifiLanMedium::StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) { + auto& env = MediumEnvironment::Instance(); + NEARBY_LOG(INFO, "G3 StartDiscovery: service_id=%s", service_id.c_str()); + env.UpdateWifiLanMediumForDiscovery(*this, service_, service_id, + std::move(callback), true); + return true; +} + +bool WifiLanMedium::StopDiscovery(const std::string& service_id) { + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiLanMediumForDiscovery(*this, service_, service_id, {}, false); + return true; +} + +bool WifiLanMedium::StartAcceptingConnections( + const std::string& service_id, AcceptedConnectionCallback callback) { + // TODO(edwinwu): Integrate medium_environment. + // steps: + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, callback); + return true; +} + +bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { + // TODO(edwinwu): Integrate medium_environment. + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, {}); + return true; +} + +std::unique_ptr WifiLanMedium::Connect( + api::WifiLanService& service, const std::string& service_id) { + auto socket = std::make_unique(); + NEARBY_LOG(INFO, "G3 Connect: medium=%p, service_id=%s", this, + service_id.c_str()); + return socket; + // TODO(edwinwu): Integrate medium_environment. + // steps: + // Request a connection, and block until the socket is provided via the + // callback. + // 1. connection = wifi_lan_service.requestConnection_(); + // 2. create wifi_lan_socket with wifi_lan_service and connection + // return wifi_lan_socket; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/wifi_lan.h b/cpp/platform_v2/impl/g3/wifi_lan.h new file mode 100644 index 00000000..c8995c02 --- /dev/null +++ b/cpp/platform_v2/impl/g3/wifi_lan.h @@ -0,0 +1,109 @@ +#ifndef PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ +#define PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ + +#include + +#include "platform_v2/api/wifi_lan.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/impl/g3/pipe.h" +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// Opaque wrapper over a WifiLan service which contains encoded WifiLan service +// info name. +class WifiLanService : public api::WifiLanService { + public: + explicit WifiLanService(std::string name) : name_(std::move(name)) {} + ~WifiLanService() override = default; + + void SetName(std::string name) { name_ = std::move(name); } + std::string GetName() const override { return name_; } + + private: + std::string name_; +}; + +class WifiLanSocket : public api::WifiLanSocket { + public: + WifiLanSocket() = default; + explicit WifiLanSocket(WifiLanService* service) : service_(service) {} + ~WifiLanSocket() override = default; + + // Connect to another WifiLanSocket, to form a functional low-level channel. + // from this point on, and until Close is called, connection exists. + void ConnectTo(WifiLanSocket* other) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns the InputStream of this connected WifiLanSocket. + InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns the OutputStream of this connected WifiLanSocket. + // This stream is for local side to write. + OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns valid WifiLanService pointer if there is a connection, and + // nullptr otherwise. + WifiLanService* GetRemoteWifiLanService() override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Pipe pipe_; + WifiLanService* service_; + mutable absl::Mutex mutex_; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium : public api::WifiLanMedium { + public: + WifiLanMedium(); + ~WifiLanMedium() override; + + bool StartAdvertising(const std::string& service_id, + const std::string& wifi_lan_service_info_name) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool StopAdvertising(const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once the WifiLan discovery has been initiated. + bool StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once WifiLan discovery for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredServiceCallback passed in to StartDiscovery() for service_id. + bool StopDiscovery(const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once WifiLan socket connection requests to service_id can be + // accepted. + bool StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool StopAcceptingConnections(const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns a new WifiLanSocket. On Success, WifiLanSocket::IsValid() + // returns true. + std::unique_ptr Connect( + api::WifiLanService& service, const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + WifiLanService service_{"wifi_lan_service_info_name"}; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 0ff90145..6abe18d2 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -13,11 +13,13 @@ cc_library( "crypto.h", "file.h", "future.h", + "logging.h", "multi_thread_executor.h", "mutex.h", "mutex_lock.h", "pipe.h", "scheduled_executor.h", + "settable_future.h", "single_thread_executor.h", "submittable_executor.h", "system_clock.h", @@ -32,6 +34,7 @@ cc_library( "//platform_v2/api:platform", "//platform_v2/api:types", "//platform_v2/base", + "//platform_v2/base:logging", "//platform_v2/base:util", "//absl/base:core_headers", "//absl/container:flat_hash_map", @@ -44,11 +47,13 @@ cc_library( name = "comm", srcs = [ "bluetooth_classic.cc", + "wifi_lan.cc", ], hdrs = [ "bluetooth_adapter.h", "bluetooth_classic.h", "webrtc.h", + "wifi_lan.h", ], visibility = [ "//core_v2:__subpackages__", @@ -62,7 +67,7 @@ cc_library( "//platform_v2/base", "//absl/container:flat_hash_map", "//absl/strings", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -89,6 +94,7 @@ cc_test( "atomic_reference_test.cc", "bluetooth_adapter_test.cc", "bluetooth_classic_test.cc", + "condition_variable_test.cc", "count_down_latch_test.cc", "crypto_test.cc", "future_test.cc", @@ -98,6 +104,7 @@ cc_test( "pipe_test.cc", "scheduled_executor_test.cc", "single_thread_executor_test.cc", + "wifi_lan_test.cc", ], shard_count = 16, deps = [ diff --git a/cpp/platform_v2/public/atomic_reference.h b/cpp/platform_v2/public/atomic_reference.h index 1fc02fac..049b9f31 100644 --- a/cpp/platform_v2/public/atomic_reference.h +++ b/cpp/platform_v2/public/atomic_reference.h @@ -2,36 +2,71 @@ #define PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ #include +#include #include "platform_v2/api/atomic_reference.h" #include "platform_v2/api/platform.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.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; + +// Platform-based atomic type, for something convertible to std::uint32_t. template -class AtomicReference final : public api::AtomicReference { +class AtomicReference, + void>> + final { 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; + explicit AtomicReference(T value) + : impl_(Platform::CreateAtomicUint32(static_cast(value))) { + } + ~AtomicReference() = 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)); } + T Get() const { return static_cast(impl_->Get()); } + void Set(T value) { impl_->Set(static_cast(value)); } private: - std::unique_ptr> impl_; + std::unique_ptr impl_; +}; + +// Atomic type that is using Platform mutex to provide atomicity. +// Supports any copyable type. +template +class AtomicReference sizeof(std::uint32_t) || + !std::is_trivially_copyable_v), + void>> + final { + public: + explicit AtomicReference(T value) { + MutexLock lock(&mutex_); + value_ = std::move(value); + } + void Set(T value) { + MutexLock lock(&mutex_); + value_ = std::move(value); + } + T Get() const& { + MutexLock lock(&mutex_); + return value_; + } + T&& Get() const&& { + MutexLock lock(&mutex_); + return std::move(value_); + } + + private: + mutable Mutex mutex_; + T value_; }; } // namespace nearby diff --git a/cpp/platform_v2/public/bluetooth_classic_test.cc b/cpp/platform_v2/public/bluetooth_classic_test.cc index 8bba6c44..42787a31 100644 --- a/cpp/platform_v2/public/bluetooth_classic_test.cc +++ b/cpp/platform_v2/public/bluetooth_classic_test.cc @@ -19,6 +19,7 @@ class BluetoothClassicMediumTest : public ::testing::Test { protected: using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; BluetoothClassicMediumTest() { + env_.Start(); env_.Reset(); adapter_a_ = std::make_unique(); adapter_b_ = std::make_unique(); @@ -40,6 +41,7 @@ class BluetoothClassicMediumTest : public ::testing::Test { adapter_a_.reset(); adapter_b_.reset(); env_.Reset(); + env_.Stop(); } MediumEnvironment& env_{MediumEnvironment::Instance()}; diff --git a/cpp/platform_v2/public/condition_variable.h b/cpp/platform_v2/public/condition_variable.h index 54f83cfe..81c9c951 100644 --- a/cpp/platform_v2/public/condition_variable.h +++ b/cpp/platform_v2/public/condition_variable.h @@ -21,10 +21,9 @@ class ConditionVariable final { 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(); } + Exception Wait(absl::Duration timeout) { return impl_->Wait(timeout); } private: std::unique_ptr impl_; diff --git a/cpp/platform_v2/public/condition_variable_test.cc b/cpp/platform_v2/public/condition_variable_test.cc new file mode 100644 index 00000000..6a3dd610 --- /dev/null +++ b/cpp/platform_v2/public/condition_variable_test.cc @@ -0,0 +1,62 @@ +#include "platform_v2/public/condition_variable.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/single_thread_executor.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +TEST(ConditionVariableTest, CanCreate) { + Mutex mutex; + ConditionVariable cond{&mutex}; +} + +TEST(ConditionVariableTest, CanWakeupWaiter) { + Mutex mutex; + ConditionVariable cond{&mutex}; + bool done = false; + bool waiting = false; + NEARBY_LOG(INFO, "At start; done=%d", done); + { + SingleThreadExecutor executor; + executor.Execute([&cond, &mutex, &done, &waiting]() { + MutexLock lock(&mutex); + NEARBY_LOG(INFO, "Before cond.Wait(); done=%d", done); + waiting = true; + cond.Wait(); + waiting = false; + done = true; + NEARBY_LOG(INFO, "After cond.Wait(); done=%d", done); + }); + while (true) { + { + MutexLock lock(&mutex); + if (waiting) break; + } + SystemClock::Sleep(absl::Milliseconds(100)); + } + { + MutexLock lock(&mutex); + cond.Notify(); + EXPECT_FALSE(done); + } + } + NEARBY_LOG(INFO, "After executor shutdown: done=%d", done); + EXPECT_TRUE(done); +} + +TEST(ConditionVariableTest, WaitTerminatesOnTimeoutWithoutNotify) { + Mutex mutex; + ConditionVariable cond{&mutex}; + MutexLock lock(&mutex); + EXPECT_EQ(cond.Wait(absl::Milliseconds(100)), Exception{Exception::kTimeout}); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.h b/cpp/platform_v2/public/file.h index 59a46282..1bd6ae30 100644 --- a/cpp/platform_v2/public/file.h +++ b/cpp/platform_v2/public/file.h @@ -10,45 +10,89 @@ #include "platform_v2/api/platform.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" namespace location { namespace nearby { -class InputFile final : public api::InputFile { +class InputFile final { public: using Platform = api::ImplementationPlatform; - InputFile(std::int64_t payload_id, std::int64_t size) - : impl_(Platform::CreateInputFile(payload_id, size)) {} - ~InputFile() override = default; + InputFile(PayloadId payload_id, std::int64_t size) + : impl_(Platform::CreateInputFile(payload_id, size)), id_(payload_id) {} + ~InputFile() = default; InputFile(InputFile&&) = default; InputFile& operator=(InputFile&&) = default; - ExceptionOr Read(std::int64_t size) override { - return impl_->Read(size); - } - std::string GetFilePath() const override { return impl_->GetFilePath(); } - std::int64_t GetTotalSize() const override { return impl_->GetTotalSize(); } - Exception Close() override { return impl_->Close(); } + // Reads up to size bytes and returns as a ByteArray object wrapped by + // ExceptionOr. + // Returns Exception::kIo on error, or end of file. + ExceptionOr Read(std::int64_t size) { return impl_->Read(size); } + + // Returns a string that uniqely identifies this file. + std::string GetFilePath() const { return impl_->GetFilePath(); } + + // Returns total size of this file in bytes. + std::int64_t GetTotalSize() const { return impl_->GetTotalSize(); } + + // Disallows further reads from the file and frees system resources, + // associated with it. + Exception Close() { return impl_->Close(); } + + // Returns a handle to the underlying input stream. + // + // Returned handle will remain valid even if InputFile is moved, for as long + // as original InputFile lifetime continues. + // Side effects of any non-const operation invoked for InputFile (such as + // Read, or Close will be observable through InputStream& handle, and vice + // versa. + InputStream& GetInputStream() { return *impl_; } + + // Returns payload id of this file. The closest "file" equivalent is inode. + PayloadId GetPayloadId() const { return id_; } private: std::unique_ptr impl_; + PayloadId id_; }; -class OutputFile final : public api::OutputFile { +class OutputFile final { public: using Platform = api::ImplementationPlatform; - explicit OutputFile(std::int64_t payload_id) - : impl_(Platform::CreateOutputFile(payload_id)) {} - ~OutputFile() override = default; + explicit OutputFile(PayloadId payload_id) + : impl_(Platform::CreateOutputFile(payload_id)), id_(payload_id) {} + ~OutputFile() = default; OutputFile(OutputFile&&) = default; OutputFile& operator=(OutputFile&&) = default; - Exception Write(const ByteArray& data) override { return impl_->Write(data); } - Exception Flush() override { return impl_->Flush(); } - Exception Close() override { return impl_->Close(); } + // Writes all data from ByteArray object to the underlying stream. + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Write(const ByteArray& data) { return impl_->Write(data); } + + // Ensures that all data written by previous calls to Write() is passed + // down to the applicable transport layer. + Exception Flush() { return impl_->Flush(); } + + // Disallows further writes to the file and frees system resources, + // associated with it. + Exception Close() { return impl_->Close(); } + + // Returns a handle to the underlying output stream. + // + // Returned handle will remain valid even if OutputFile is moved, for as long + // as original OutputFile lifetime continues. + // Side effects of any non-const operation invoked for OutputFile (such as + // Write, or Close will be observable through OutputStream& handle, and vice + // versa. + OutputStream& GetOutputStream() { return *impl_; } + + // Returns payload id of this file. The closest "file" equivalent is inode. + PayloadId GetPayloadId() const { return id_; } private: std::unique_ptr impl_; + PayloadId id_; }; } // namespace nearby diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h index fcd7b0ba..df9fcae8 100644 --- a/cpp/platform_v2/public/future.h +++ b/cpp/platform_v2/public/future.h @@ -1,60 +1,38 @@ #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" +#include "platform_v2/public/settable_future.h" namespace location { namespace nearby { template -class Future final : public api::SettableFuture { +class Future final { 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 { + virtual bool Set(T value) { return impl_->Set(std::move(value)); } + virtual bool SetException(Exception exception) { return impl_->SetException(exception); } - // throws Exception::kInterrupted, Exception::kExecution - ExceptionOr Get() override { - auto ret_val = impl_->Get(); - if (ret_val.ok()) { - T result = absl::any_cast(ret_val.result()); - return ExceptionOr{result}; - } else { - return ExceptionOr{ret_val.exception()}; - } + virtual ExceptionOr Get() { return impl_->Get(); } + virtual ExceptionOr Get(absl::Duration timeout) { + return impl_->Get(timeout); } - - // 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 = absl::any_cast(ret_val.result()); - return ExceptionOr{result}; - } else { - return ExceptionOr{ret_val.exception()}; - } + void AddListener(Runnable runnable, api::Executor* executor) { + impl_->AddListener(std::move(runnable), executor); } private: - std::unique_ptr> impl_; + // Instance of future implementation is wrapped in shared_ptr<> to make + // it possible to pass Future by value and share the implementation. + // This allows for the following constructions: + // 1) + // Future future; + // RunOnXyzThread([future]() { future.Set(DoTheJobAndReport()); }); + // if (future.Get().Ok()) { /*...*/ } + // 2) + // Future future = DoSomeAsyncWork(); // Returns future, but keeps copy. + // if (future.Get().Ok()) { /*...*/ } + std::shared_ptr> impl_{new SettableFuture()}; }; } // namespace nearby diff --git a/cpp/platform_v2/public/logging_test.cc b/cpp/platform_v2/public/logging_test.cc index fc010372..16aa64d1 100644 --- a/cpp/platform_v2/public/logging_test.cc +++ b/cpp/platform_v2/public/logging_test.cc @@ -6,7 +6,33 @@ namespace { TEST(LoggingTest, CanLog) { - NEARBY_LOG(INFO, "message"); + NEARBY_LOG_SET_SEVERITY(INFO); + int num = 42; + NEARBY_LOG(INFO, "The answer to everything: %d", num++); + EXPECT_EQ(num, 43); } +TEST(LoggingTest, CanLog_LoggingDisabled) { + NEARBY_LOG_SET_SEVERITY(ERROR); + int num = 42; + NEARBY_LOG(INFO, "The answer to everything: %d", num++); + // num++ should not be evaluated + EXPECT_EQ(num, 42); } + +TEST(LoggingTest, CanStream) { + NEARBY_LOG_SET_SEVERITY(INFO); + int num = 42; + NEARBY_LOGS(INFO) << "The answer to everything: " << num++; + EXPECT_EQ(num, 43); +} + +TEST(LoggingTest, CanStream_LoggingDisabled) { + NEARBY_LOG_SET_SEVERITY(ERROR); + int num = 42; + NEARBY_LOGS(INFO) << "The answer to everything: " << num++; + // num++ should not be evaluated + EXPECT_EQ(num, 42); +} + +} // namespace diff --git a/cpp/platform_v2/public/mutex_test.cc b/cpp/platform_v2/public/mutex_test.cc index 9928f01d..54760cfd 100644 --- a/cpp/platform_v2/public/mutex_test.cc +++ b/cpp/platform_v2/public/mutex_test.cc @@ -27,7 +27,7 @@ class MutexTest : public testing::Test { protected: SingleThreadExecutor executor_; - const absl::Duration kTimeToWait = absl::Milliseconds(200); + const absl::Duration kTimeToWait = absl::Milliseconds(500); std::atomic_int step_ = 0; absl::Mutex step_mutex_; absl::CondVar step_cond_; diff --git a/cpp/platform_v2/public/pipe.h b/cpp/platform_v2/public/pipe.h index a80eda19..f277e156 100644 --- a/cpp/platform_v2/public/pipe.h +++ b/cpp/platform_v2/public/pipe.h @@ -7,8 +7,7 @@ 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 +// http://google3/platform_v2/base/base_pipe.h class Pipe final : public BasePipe { public: Pipe(); diff --git a/cpp/platform_v2/public/scheduled_executor_test.cc b/cpp/platform_v2/public/scheduled_executor_test.cc index 9efb844a..5a760b69 100644 --- a/cpp/platform_v2/public/scheduled_executor_test.cc +++ b/cpp/platform_v2/public/scheduled_executor_test.cc @@ -12,6 +12,14 @@ namespace location { namespace nearby { +// kShortDelay must be significant enough to guarantee that OS under heavy load +// should be able to execute the non-blocking test paths within this time. +absl::Duration kShortDelay = absl::Milliseconds(100); + +// kLongDelay must be long enough to make sure that under OS under heavy load +// will let kShortDelay fire and jobs scheduled before the kLongDelay fires. +absl::Duration kLongDelay = 10 * kShortDelay; + TEST(ScheduledExecutorTest, ConsructorDestructorWorks) { ScheduledExecutor executor; } @@ -28,7 +36,7 @@ TEST(ScheduledExecutorTest, CanExecute) { { absl::MutexLock lock(&mutex); if (!done) { - cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + cond.WaitWithTimeout(&mutex, kLongDelay); } } EXPECT_TRUE(done); @@ -39,25 +47,25 @@ TEST(ScheduledExecutorTest, CanSchedule) { std::atomic_int value = 0; absl::Mutex mutex; absl::CondVar cond; - // schedule job due in 100 ms. + // schedule job due in kLongDelay. 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. + kLongDelay); + // schedule job due in kShortDelay; must fire before the first one. executor.Schedule( [&value]() { EXPECT_EQ(value, 0); value = 1; }, - absl::Milliseconds(10)); + kShortDelay); { - // wait for the final job to unblock us. + // wait for the final job to unblock us; wait longer than kLongDelay. absl::MutexLock lock(&mutex); - cond.WaitWithTimeout(&mutex, absl::Milliseconds(1000)); + cond.WaitWithTimeout(&mutex, 2 * kLongDelay); } EXPECT_EQ(value, 5); } @@ -66,10 +74,10 @@ TEST(ScheduledExecutorTest, CanCancel) { ScheduledExecutor executor; std::atomic_int value = 0; Cancelable cancelable = - executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + executor.Schedule([&value]() { value += 1; }, kShortDelay); EXPECT_EQ(value, 0); EXPECT_TRUE(cancelable.Cancel()); - absl::SleepFor(absl::Milliseconds(500)); + absl::SleepFor(kLongDelay); EXPECT_EQ(value, 0); } @@ -78,17 +86,17 @@ TEST(ScheduledExecutorTest, FailToCancel) { absl::CondVar cond; ScheduledExecutor executor; std::atomic_int value = 0; - // Schedule job in 10ms, which will we will attempt to cancel later. + // Schedule job in kShortDelay, 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([&value]() { value += 1; }, kShortDelay); + // schedule another job to test results of the first one, in kLongDelay. executor.Schedule( [&cancelable, &cond]() { EXPECT_FALSE(cancelable.Cancel()); // Wake up main thread. cond.Signal(); }, - absl::Milliseconds(50)); + kLongDelay); { absl::MutexLock lock(&mutex); cond.Wait(&mutex); diff --git a/cpp/platform_v2/public/settable_future.h b/cpp/platform_v2/public/settable_future.h new file mode 100644 index 00000000..bf0d459c --- /dev/null +++ b/cpp/platform_v2/public/settable_future.h @@ -0,0 +1,108 @@ +#ifndef PLATFORM_V2_PUBLIC_SETTABLE_FUTURE_H_ +#define PLATFORM_V2_PUBLIC_SETTABLE_FUTURE_H_ + +#include + +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/system_clock.h" + +namespace location { +namespace nearby { + +template +class SettableFuture : public api::SettableFuture { + public: + SettableFuture() = default; + ~SettableFuture() override = default; + + bool Set(T value) override { + MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.Notify(); + InvokeAllLocked(); + } + return true; + } + + void AddListener(Runnable runnable, api::Executor* executor) override { + MutexLock lock(&mutex_); + if (done_) { + executor->Execute(std::move(runnable)); + } else { + listeners_.emplace_back(std::make_pair(executor, std::move(runnable))); + } + } + + bool SetException(Exception exception) override { + MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + ExceptionOr Get() override { + MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr Get(absl::Duration timeout) override { + MutexLock lock(&mutex_); + while (!done_) { + absl::Time start_time = SystemClock::ElapsedRealtime(); + if (completed_.Wait(timeout).Raised(Exception::kTimeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = SystemClock::ElapsedRealtime() - 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_.Notify(); + InvokeAllLocked(); + } + return true; + } + + void InvokeAllLocked() { + for (auto& item : listeners_) { + item.first->Execute(std::move(item.second)); + } + listeners_.clear(); + } + + Mutex mutex_; + ConditionVariable completed_{&mutex_}; + std::vector>> listeners_; + bool done_{false}; + T value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SETTABLE_FUTURE_H_ diff --git a/cpp/platform_v2/public/webrtc.h b/cpp/platform_v2/public/webrtc.h index f0700cef..8884e66d 100644 --- a/cpp/platform_v2/public/webrtc.h +++ b/cpp/platform_v2/public/webrtc.h @@ -5,11 +5,39 @@ #include "platform_v2/api/platform.h" #include "platform_v2/api/webrtc.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { +class WebRtcSignalingMessenger final { + public: + using OnSignalingMessageCallback = + api::WebRtcSignalingMessenger::OnSignalingMessageCallback; + + explicit WebRtcSignalingMessenger( + std::unique_ptr messenger) + : impl_(std::move(messenger)) {} + ~WebRtcSignalingMessenger() = default; + WebRtcSignalingMessenger(WebRtcSignalingMessenger&&) = default; + WebRtcSignalingMessenger operator=(WebRtcSignalingMessenger&&) = delete; + + bool SendMessage(absl::string_view peer_id, const ByteArray& message) { + return impl_->SendMessage(peer_id, message); + } + + bool StartReceivingMessages(OnSignalingMessageCallback listener) { + return impl_->StartReceivingMessages(listener); + } + + void StopReceivingMessages() { impl_->StopReceivingMessages(); } + + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + class WebRtcMedium final { public: using PeerConnectionCallback = api::WebRtcMedium::PeerConnectionCallback; @@ -27,9 +55,10 @@ class WebRtcMedium final { } // Returns a signaling messenger for sending WebRTC signaling messages. - std::unique_ptr GetSignalingMessenger( + std::unique_ptr GetSignalingMessenger( absl::string_view self_id) { - return impl_->GetSignalingMessenger(self_id); + return std::make_unique( + impl_->GetSignalingMessenger(self_id)); } bool IsValid() const { return impl_ != nullptr; } diff --git a/cpp/platform_v2/public/wifi_lan.cc b/cpp/platform_v2/public/wifi_lan.cc new file mode 100644 index 00000000..32eefa18 --- /dev/null +++ b/cpp/platform_v2/public/wifi_lan.cc @@ -0,0 +1,120 @@ +#include "platform_v2/public/wifi_lan.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +bool WifiLanMedium::StartAdvertising( + const std::string& service_id, + const std::string& wifi_lan_service_info_name) { + return impl_->StartAdvertising(service_id, wifi_lan_service_info_name); +} + +bool WifiLanMedium::StopAdvertising(const std::string& service_id) { + return impl_->StopAdvertising(service_id); +} + +bool WifiLanMedium::StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback) { + { + MutexLock lock(&mutex_); + discovered_service_callback_ = std::move(callback); + services_.clear(); + } + return impl_->StartDiscovery( + service_id, + { + .service_discovered_cb = + [this](api::WifiLanService& service, + const std::string& service_id) { + MutexLock lock(&mutex_); + auto pair = services_.emplace( + &service, absl::make_unique()); + auto& context = *pair.first->second; + if (!pair.second) { + NEARBY_LOG(INFO, "Adding (again) service=%p, impl=%p", + &context.service, &service); + return; + } + context.service = WifiLanService(&service); + NEARBY_LOG(INFO, "Adding service=%p, impl=%p", &context.service, + &service); + discovered_service_callback_.service_discovered_cb( + context.service, service_id); + }, + .service_lost_cb = + [this](api::WifiLanService& service, + const std::string& service_id) { + MutexLock lock(&mutex_); + auto item = services_.extract(&service); + auto& context = *item.mapped(); + NEARBY_LOG(INFO, "Removing service=%p, impl=%p", + &context.service, &service); + discovered_service_callback_.service_lost_cb(context.service, + service_id); + }, + }); +} + +bool WifiLanMedium::StopDiscovery(const std::string& service_id) { + { + MutexLock lock(&mutex_); + discovered_service_callback_ = {}; + services_.clear(); + NEARBY_LOG(INFO, "WifiLan Discovery disabled: impl=%p", &GetImpl()); + } + return impl_->StopDiscovery(service_id); +} + +bool WifiLanMedium::StartAcceptingConnections( + const std::string& service_id, AcceptedConnectionCallback callback) { + { + MutexLock lock(&mutex_); + accepted_connection_callback_ = std::move(callback); + } + return impl_->StartAcceptingConnections( + service_id, + { + .accepted_cb = + [this](api::WifiLanSocket& socket, + const std::string& service_id) { + MutexLock lock(&mutex_); + auto pair = sockets_.emplace( + &socket, absl::make_unique()); + auto& context = *pair.first->second; + if (!pair.second) { + NEARBY_LOG(INFO, "Adding (again) socket=%p, impl=%p", + &context.socket, &socket); + return; + } + context.socket = WifiLanSocket(&socket); + NEARBY_LOG(INFO, "Adding socket=%p, impl=%p", &context.socket, + &socket); + accepted_connection_callback_.accepted_cb(context.socket, + service_id); + }, + }); +} + +bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { + { + MutexLock lock(&mutex_); + accepted_connection_callback_ = {}; + sockets_.clear(); + NEARBY_LOG(INFO, "WifiLan accepted connection disabled: impl=%p", + &GetImpl()); + } + return impl_->StopDiscovery(service_id); +} + +WifiLanSocket WifiLanMedium::Connect(WifiLanService& service, + const std::string& service_id) { + NEARBY_LOG(INFO, "WifiLanMedium::Connect: service=%p [impl=%p]", &service, + &service.GetImpl()); + return WifiLanSocket(impl_->Connect(service.GetImpl(), service_id)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/wifi_lan.h b/cpp/platform_v2/public/wifi_lan.h new file mode 100644 index 00000000..7274414f --- /dev/null +++ b/cpp/platform_v2/public/wifi_lan.h @@ -0,0 +1,160 @@ +#ifndef PLATFORM_V2_PUBLIC_WIFI_LAN_H_ +#define PLATFORM_V2_PUBLIC_WIFI_LAN_H_ + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/wifi_lan.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/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { + +// Opaque wrapper over a WifiLan service which contains encoded service name. +class WifiLanService final { + public: + WifiLanService() = default; + WifiLanService(const WifiLanService&) = default; + WifiLanService& operator=(const WifiLanService&) = default; + explicit WifiLanService(api::WifiLanService* service) : impl_(service) {} + ~WifiLanService() = default; + + std::string GetName() const { return impl_->GetName(); } + + api::WifiLanService& GetImpl() { return *impl_; } + bool IsValid() const { return impl_ != nullptr; } + + private: + api::WifiLanService* impl_; +}; + +class WifiLanSocket final { + public: + WifiLanSocket() = default; + WifiLanSocket(const WifiLanSocket&) = default; + WifiLanSocket& operator=(const WifiLanSocket&) = default; + explicit WifiLanSocket(api::WifiLanSocket* socket) : impl_(socket) {} + explicit WifiLanSocket(std::unique_ptr socket) + : impl_(socket.release()) {} + ~WifiLanSocket() = default; + + // Returns the InputStream of the WifiLanSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + InputStream& GetInputStream() { return impl_->GetInputStream(); } + + // Returns the OutputStream of the WifiLanSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + WifiLanService GetRemoteWifiLanService() { + return WifiLanService(impl_->GetRemoteWifiLanService()); + } + + // Returns true if a socket is usable. If this method returns false, + // it is not safe to call any other method. + // NOTE(socket validity): + // Socket created by a default public constructor is not valid, because + // it is missing platform implementation. + // The only way to obtain a valid socket is through connection, such as + // an object returned by WifiLanMedium::Connect + // These methods may also return an invalid socket if connection failed for + // any reason. + bool IsValid() const { return impl_ != nullptr; } + + // Returns reference to platform implementation. + // This is used to communicate with platform code, and for debugging purposes. + // Returned reference will remain valid for while WifiLanSocket object is + // itself valid. Typically WifiLanSocket lifetime matches duration of the + // connection, and is controlled by end user, since they hold the instance. + api::WifiLanSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium final { + public: + using Platform = api::ImplementationPlatform; + struct DiscoveredServiceCallback { + std::function + service_discovered_cb = + DefaultCallback(); + std::function + service_lost_cb = + DefaultCallback(); + }; + struct ServiceDiscoveryInfo { + WifiLanService service; + }; + + struct AcceptedConnectionCallback { + std::function + accepted_cb = DefaultCallback(); + }; + struct AcceptedConnectionInfo { + WifiLanSocket socket; + }; + + WifiLanMedium() : impl_(Platform::CreateWifiLanMedium()) {} + ~WifiLanMedium() = default; + + bool StartAdvertising(const std::string& service_id, + const std::string& wifi_lan_service_info_name); + bool StopAdvertising(const std::string& service_id); + + // Returns true once the WifiLan discovery has been initiated. + bool StartDiscovery(const std::string& service_id, + DiscoveredServiceCallback callback); + + // Returns true once WifiLan discovery for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredServiceCallback passed in to StartDiscovery() for service_id. + bool StopDiscovery(const std::string& service_id); + + // Returns true once WifiLan socket connection requests to service_id can be + // accepted. + bool StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback); + bool StopAcceptingConnections(const std::string& service_id); + + // Returns a new WifiLanSocket. On Success, WifiLanSocket::IsValid() + // returns true. + WifiLanSocket Connect(WifiLanService& service, const std::string& service_id); + + bool IsValid() const { return impl_ != nullptr; } + + api::WifiLanMedium& GetImpl() { return *impl_; } + + private: + Mutex mutex_; + std::unique_ptr impl_; + absl::flat_hash_map> + services_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map> + sockets_ ABSL_GUARDED_BY(mutex_); + DiscoveredServiceCallback discovered_service_callback_ + ABSL_GUARDED_BY(mutex_); + AcceptedConnectionCallback accepted_connection_callback_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_WIFI_LAN_H_ diff --git a/cpp/platform_v2/public/wifi_lan_test.cc b/cpp/platform_v2/public/wifi_lan_test.cc new file mode 100644 index 00000000..398fa242 --- /dev/null +++ b/cpp/platform_v2/public/wifi_lan_test.cc @@ -0,0 +1,102 @@ +#include "platform_v2/public/wifi_lan.h" + +#include + +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; + +class WifiLanMediumTest : public ::testing::Test { + protected: + using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback; + + WifiLanMediumTest() { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(WifiLanMediumTest, ConstructorDestructorWorks) { + env_.Start(); + WifiLanMedium medium_a; + WifiLanMedium medium_b; + + // Make sure we can create functional mediums. + ASSERT_TRUE(medium_a.IsValid()); + ASSERT_TRUE(medium_b.IsValid()); + + // Make sure we can create 2 distinct mediums. + EXPECT_NE(&medium_a.GetImpl(), &medium_b.GetImpl()); + env_.Stop(); +} + +TEST_F(WifiLanMediumTest, CanStartDiscoveryAndServiceIndeedDiscovered) { + env_.Start(); + WifiLanMedium medium; + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + + medium.StartDiscovery(std::string(kServiceID), + DiscoveredServiceCallback{ + .service_discovered_cb = + [&found_latch](WifiLanService& service, + const std::string& service_id) { + NEARBY_LOG(INFO, "Service discovered: %s", + service.GetName().c_str()); + EXPECT_EQ(kServiceID, service_id); + found_latch.CountDown(); + }, + .service_lost_cb = + [&lost_latch](WifiLanService& service, + const std::string& service_id) { + NEARBY_LOG(INFO, "Service lost: %s", + service.GetName().c_str()); + EXPECT_EQ(kServiceID, service_id); + lost_latch.CountDown(); + }, + }); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +TEST_F(WifiLanMediumTest, CanStopDiscovery) { + env_.Start(); + WifiLanMedium medium; + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + + medium.StartDiscovery(std::string(kServiceID), + DiscoveredServiceCallback{ + .service_discovered_cb = + [&found_latch](WifiLanService& service, + const std::string& service_id) { + NEARBY_LOG(INFO, "Service discovered: %s", + service.GetName().c_str()); + EXPECT_EQ(kServiceID, service_id); + found_latch.CountDown(); + }, + .service_lost_cb = + [&lost_latch](WifiLanService& service, + const std::string& service_id) { + NEARBY_LOG(INFO, "Service lost: %s", + service.GetName().c_str()); + EXPECT_EQ(kServiceID, service_id); + lost_latch.CountDown(); + }, + }); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + bool stop = medium.StopDiscovery(std::string(kServiceID)); + EXPECT_TRUE(stop); + env_.Stop(); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto index 9f942b8e..a36fd12f 100644 --- a/proto/bootstrap_enums.proto +++ b/proto/bootstrap_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index e44e11b2..7bce87eb 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -2,6 +2,7 @@ syntax = "proto2"; package location.nearby.connections; +option optimize_for = LITE_RUNTIME; option java_outer_classname = "OfflineWireFormatsProto"; option java_package = "com.google.location.nearby.connections.proto"; option objc_class_prefix = "GNCP"; diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index a7f1ff1c..99729d7f 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -15,6 +15,7 @@ package location.nearby.proto.connections; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index 7d229dce..a3ca8c3e 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 5e45283b..62232412 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; @@ -69,18 +70,24 @@ enum CommonError { // 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; + // Device error, the medium not available on this device. + MEDIUM_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; + // System error, the medim not available because the resource ran out. e.g. + // the Wi-Fi Direct initialized cause Wi-Fi Aware not available, or BLE + // connections hit the maximan number, or Wi-Fi Hotstop already created. + OUT_OF_RESOURCE = 4; - // Reserved 4 to 30 + // Reserved 5 to 30 } // The error for event START_ADVERTISING. The range between 31 and 99. enum StartAdvertisingError { + reserved 37, 39; + // 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. @@ -98,17 +105,32 @@ enum StartAdvertisingError { 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; + // System error, there's already someone advertising on Bluetooth, not allow + // to start another one. + BLUETOOTH_ALREADY_ADVERTISED = 40; + // System error, failed to modify the Bluetooth name. + MODIFY_BLUETOOTH_NAME_FAILED = 41; + // System error, failed to persist the original Bluetooth name into shared + // preference. + PERSIST_ORIGINAL_BLUETOOTH_NAME_FAILED = 42; + // System error, failed to start advertising. + START_ADVERTISING_FAILED = 43; - // Next ID :40 + // Developing error, not allow to advertising on Wi-Fi Lan(TDLS) without + // accetpting connections. The connection may comes in very quickly, so need + // to accetpt connections before advertising. + SHOULD_ACCEPT_CONNECTIONS_BEFORE_ADVERTISING_ON_WIFI_LAN = 44; + // System error, failed to acquire WifiAwareSession + ACQUIRE_WIFI_AWARE_SESSION_FAILED = 45; + + // Next ID :46 } enum Description { + reserved 28; + UNKNOWN = 0; NULL_SERVICE_ID = 1; NULL_ADVERTISEMENT_BYTES = 2; @@ -130,4 +152,23 @@ enum Description { ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 18; INTERRUPTED_EXCEPTION = 19; EXECUTION_EXCEPTION = 20; + NULL_BLUETOOTH_DEVICE_NAME = 21; + SET_SCAN_MODE_FAILED = 22; + INVOKE_API_FAILED = 23; + TIMEOUT = 24; + NULL_NFC_TAG = 25; + FEATURE_NFC_NOT_SUPPORTED = 26; + FEATURE_NFC_HOST_CARD_EMULATION_NOT_SUPPORTED = 27; + WITHOUT_CONNECTED_WIFI_NETWOR = 29; + MULTICAST_NOT_SUPPORTED = 30; + NSD_NOT_ENABLED = 31; + INVALID_PORT_NUMBER = 32; + NULL_SERVICE_NAME = 33; + NULL_SERVICE_TYPE = 34; + WITHOUT_CONNECTED_WIFI_NETWORK = 35; + FEATURE_WIFI_AWARE_NOT_SUPPORTED = 36; + NULL_CONNECTIVITY_MANAGER = 37; + NULL_WIFI_AWARE_MANAGER = 38; + STALE_ANDROID_VERSION = 39; + NULL_SERVICE_INFO = 40; } diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 63045db8..506c116a 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 36bda7dc..90c01688 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto index 0c6f78d0..1469305a 100644 --- a/proto/nearby_event_codes.proto +++ b/proto/nearby_event_codes.proto @@ -4,6 +4,7 @@ package location.nearby.proto; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto index d821ce49..73f95e43 100644 --- a/proto/setup_enums.proto +++ b/proto/setup_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto.setup; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index 6e87d13d..32aeee98 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -4,6 +4,7 @@ package location.nearby.proto.sharing; import "logs/proto/logs_annotations/logs_annotations.proto"; +option optimize_for = LITE_RUNTIME; option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; @@ -90,7 +91,7 @@ enum EventType { OPEN_RECEIVED_ATTACHMENTS = 21; // User opens the setup activity. - LAUNCH_SETUP_ACTIVITY = 22; + LAUNCH_SETUP_ACTIVITY = 22 [deprecated = true]; // User adds a contact. ADD_CONTACT = 23; @@ -115,6 +116,16 @@ enum EventType { // Cancel connection. CANCEL_CONNECTION = 30; + + // User starts a chimera activity (e.g. ConsentsChimeraActivity, + // ContactSelectChimeraActivity...) + LAUNCH_ACTIVITY = 31; + + // Receiver dismisses a privacy notification. + DISMISS_PRIVACY_NOTIFICATION = 32; + + // Receiver taps a privacy notification. + TAP_PRIVACY_NOTIFICATION = 33; } // Event category to differentiate whether this comes from sender or receiver, @@ -266,3 +277,15 @@ enum ScanType { FOREGROUND_RETRY_SCAN = 2; DIRECT_SHARE_SCAN = 3; } + +// The class name of chimera activity. +enum ActivityName { + UNKNOWN_ACTIVITY = 0; + + SHARE_SHEET_ACTIVITY = 1; + SETTINGS_ACTIVITY = 2; + RECEIVE_SURFACE_ACTIVITY = 3; + SETUP_ACTIVITY = 4; + CONTACT_SELECT_ACTIVITY = 5; + CONSENTS_ACTIVITY = 6; +}