From 598516303b3a3078eabb3fb2c347f4cd657b4fd9 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Sat, 4 Apr 2020 10:26:26 -0700 Subject: [PATCH 01/52] Add conversion script Signed-off-by: Alexey Polyudov Change-Id: I408debe584bcacad37e0d2f67a59440bb7ea4728 --- script/oss.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 script/oss.py diff --git a/script/oss.py b/script/oss.py new file mode 100755 index 00000000..2e4b7f43 --- /dev/null +++ b/script/oss.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 + +import os +import shutil +import sys + +def copy_files_to_oss_project(src_root, dst_root): + shutil.rmtree(dst_root + "/cpp", ignore_errors=True) + shutil.rmtree(dst_root + "/proto", ignore_errors=True) + shutil.copytree(src_root + "/proto", dst_root + "/proto/") + shutil.copytree(src_root + "/cpp/platform/", dst_root + "/cpp/platform/") + shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") + shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") + +def post_process_oss_files(path): + modified_total = 0 + top_level = True + top_dirs = ["cpp", "proto"] + transforms = ( + ("third_party/", ""), + ("location/nearby/connections/core", "core"), + ("location/nearby/cpp/platform", "platform"), + ("security/cryptauth/lib/securegcm", "securegcm"), + ("testing/base/public/gmock.h", "gmock/gmock.h"), + ("testing/base/public/gunit.h", "gtest/gtest.h"), + ("net/proto2/compat/public/message_lite.h", "google/protobuf/message_lite.h"), + ("LOCATION_NEARBY_CONNECTIONS_", ""), + ("LOCATION_NEARBY_CPP_", ""), + ("location/nearby/proto", "proto"), + ("location/nearby/connections/proto", "proto/connections"), + ("_portable_proto.pb.h", ".pb.h"), + (".proto.h", ".pb.h"), + ) + for root, dirs, files in os.walk(path): + if top_level: + # we must convert cpp/ and proto/ subtrees. + # everything else is not parsed. + dirs.clear() + dirs.extend(top_dirs) + top_level = False + continue + for file in files: + fname = root + "/" + file + if file in ["METADATA"]: + os.remove(fname) + continue + modified = False + lines=[] + with open(fname, "r") as f: + for line in f: + orig = line + for lookup, substitute in transforms: + line = line.replace(lookup, substitute) + if orig != line: + modified = True + lines.append(line) + if modified: + with open(fname, "w") as f: + for line in lines: + f.write(line) + modified_total += 1 + return modified_total + +def main(args): + src = "/google/src/cloud/%s/%s/google3/location/nearby" % (os.environ["USER"], args[1]) + dst = args[2] + copy_files_to_oss_project(src, dst) + total = post_process_oss_files(dst) + print("Total modified: {} files".format(total)) + +if __name__ == "__main__": + sys.exit(main(sys.argv)) From 204f76077dab090c2efee05aa3331f5ce19ecd04 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Mon, 23 Mar 2020 14:01:55 -0700 Subject: [PATCH 02/52] nearby: snapshot as of cl/296436629 Signed-off-by: Alexey Polyudov Change-Id: I2cf5bf225b76f4c1541954651f3a7544a14e0cec --- cpp/core/BUILD | 55 + cpp/core/check_compilation.cc | 119 ++ cpp/core/core.cc | 137 ++ cpp/core/core.h | 83 + cpp/core/internal/BUILD | 102 ++ cpp/core/internal/bandwidth_upgrade_handler.h | 50 + .../internal/bandwidth_upgrade_manager.cc | 47 + cpp/core/internal/bandwidth_upgrade_manager.h | 66 + .../base_bandwidth_upgrade_handler.cc | 145 ++ .../internal/base_bandwidth_upgrade_handler.h | 189 ++ cpp/core/internal/base_endpoint_channel.cc | 352 ++++ cpp/core/internal/base_endpoint_channel.h | 112 ++ cpp/core/internal/base_pcp_handler.cc | 1573 +++++++++++++++++ cpp/core/internal/base_pcp_handler.h | 507 ++++++ cpp/core/internal/ble_advertisement.cc | 277 +++ cpp/core/internal/ble_advertisement.h | 95 + cpp/core/internal/ble_advertisement_test.cc | 343 ++++ cpp/core/internal/ble_compat.h | 26 + cpp/core/internal/ble_endpoint_channel.cc | 55 + cpp/core/internal/ble_endpoint_channel.h | 44 + cpp/core/internal/bluetooth_device_name.cc | 292 +++ cpp/core/internal/bluetooth_device_name.h | 86 + .../internal/bluetooth_device_name_test.cc | 198 +++ .../internal/bluetooth_endpoint_channel.cc | 55 + .../internal/bluetooth_endpoint_channel.h | 45 + cpp/core/internal/client_proxy.cc | 590 +++++++ cpp/core/internal/client_proxy.h | 241 +++ cpp/core/internal/encryption_runner.cc | 437 +++++ cpp/core/internal/encryption_runner.h | 73 + cpp/core/internal/endpoint_channel.h | 69 + cpp/core/internal/endpoint_channel_manager.cc | 299 ++++ cpp/core/internal/endpoint_channel_manager.h | 143 ++ cpp/core/internal/endpoint_manager.cc | 749 ++++++++ cpp/core/internal/endpoint_manager.h | 232 +++ cpp/core/internal/internal_payload.cc | 20 + cpp/core/internal/internal_payload.h | 82 + cpp/core/internal/internal_payload_factory.cc | 312 ++++ cpp/core/internal/internal_payload_factory.h | 34 + cpp/core/internal/loop_runner.cc | 54 + cpp/core/internal/loop_runner.h | 42 + cpp/core/internal/medium_manager.cc | 361 ++++ cpp/core/internal/medium_manager.h | 140 ++ cpp/core/internal/mediums/BUILD | 107 ++ .../mediums/advertisement_read_result.cc | 186 ++ .../mediums/advertisement_read_result.h | 73 + .../mediums/advertisement_read_result_test.cc | 148 ++ cpp/core/internal/mediums/ble.cc | 281 +++ cpp/core/internal/mediums/ble.h | 197 +++ .../internal/mediums/ble_advertisement.cc | 288 +++ cpp/core/internal/mediums/ble_advertisement.h | 100 ++ .../mediums/ble_advertisement_header.cc | 208 +++ .../mediums/ble_advertisement_header.h | 91 + .../mediums/ble_advertisement_header_test.cc | 221 +++ .../mediums/ble_advertisement_test.cc | 319 ++++ cpp/core/internal/mediums/ble_packet.cc | 112 ++ cpp/core/internal/mediums/ble_packet.h | 49 + cpp/core/internal/mediums/ble_packet_test.cc | 108 ++ cpp/core/internal/mediums/ble_peripheral.cc | 19 + cpp/core/internal/mediums/ble_peripheral.h | 30 + cpp/core/internal/mediums/ble_v2.cc | 831 +++++++++ cpp/core/internal/mediums/ble_v2.h | 312 ++++ cpp/core/internal/mediums/bloom_filter.cc | 109 ++ cpp/core/internal/mediums/bloom_filter.h | 54 + .../internal/mediums/bloom_filter_test.cc | 162 ++ .../internal/mediums/bluetooth_classic.cc | 468 +++++ cpp/core/internal/mediums/bluetooth_classic.h | 169 ++ cpp/core/internal/mediums/bluetooth_radio.cc | 122 ++ cpp/core/internal/mediums/bluetooth_radio.h | 69 + .../mediums/discovered_peripheral_callback.h | 32 + .../mediums/discovered_peripheral_tracker.cc | 744 ++++++++ .../mediums/discovered_peripheral_tracker.h | 218 +++ .../internal/mediums/lost_entity_tracker.cc | 56 + .../internal/mediums/lost_entity_tracker.h | 49 + .../mediums/lost_entity_tracker_test.cc | 121 ++ cpp/core/internal/mediums/mediums.cc | 42 + cpp/core/internal/mediums/mediums.h | 52 + cpp/core/internal/mediums/utils.cc | 73 + cpp/core/internal/mediums/utils.h | 32 + cpp/core/internal/mediums/uuid.cc | 100 ++ cpp/core/internal/mediums/uuid.h | 39 + cpp/core/internal/offline_frames.cc | 268 +++ cpp/core/internal/offline_frames.h | 70 + .../internal/offline_service_controller.cc | 110 ++ .../internal/offline_service_controller.h | 85 + cpp/core/internal/p2p_cluster_pcp_handler.cc | 788 +++++++++ cpp/core/internal/p2p_cluster_pcp_handler.h | 378 ++++ .../p2p_point_to_point_pcp_handler.cc | 61 + .../internal/p2p_point_to_point_pcp_handler.h | 55 + cpp/core/internal/p2p_star_pcp_handler.cc | 71 + cpp/core/internal/p2p_star_pcp_handler.h | 60 + cpp/core/internal/payload_manager.cc | 1355 ++++++++++++++ cpp/core/internal/payload_manager.h | 288 +++ cpp/core/internal/pcp.h | 21 + cpp/core/internal/pcp_handler.h | 63 + cpp/core/internal/pcp_manager.cc | 160 ++ cpp/core/internal/pcp_manager.h | 77 + cpp/core/internal/service_controller.h | 67 + .../internal/service_controller_router.cc | 750 ++++++++ cpp/core/internal/service_controller_router.h | 151 ++ cpp/core/internal/wifi_lan_upgrade_handler.cc | 63 + cpp/core/internal/wifi_lan_upgrade_handler.h | 93 + cpp/core/listeners.h | 167 ++ cpp/core/options.h | 32 + cpp/core/params.h | 148 ++ cpp/core/payload.cc | 80 + cpp/core/payload.h | 86 + cpp/core/status.h | 30 + cpp/core/strategy.cc | 51 + cpp/core/strategy.h | 43 + cpp/platform/BUILD | 144 ++ cpp/platform/api/BUILD | 58 + cpp/platform/api/atomic_boolean.h | 21 + cpp/platform/api/atomic_reference.h | 22 + cpp/platform/api/ble.h | 124 ++ cpp/platform/api/ble_v2.h | 401 +++++ cpp/platform/api/bluetooth_adapter.h | 58 + cpp/platform/api/bluetooth_classic.h | 139 ++ cpp/platform/api/condition_variable.h | 26 + cpp/platform/api/count_down_latch.h | 28 + cpp/platform/api/executor.h | 20 + cpp/platform/api/future.h | 24 + cpp/platform/api/hash_utils.h | 23 + cpp/platform/api/input_file.h | 30 + cpp/platform/api/input_stream.h | 31 + cpp/platform/api/lock.h | 22 + cpp/platform/api/multi_thread_executor.h | 23 + cpp/platform/api/output_file.h | 26 + cpp/platform/api/output_stream.h | 29 + cpp/platform/api/scheduled_executor.h | 29 + cpp/platform/api/settable_future.h | 23 + cpp/platform/api/single_thread_executor.h | 23 + cpp/platform/api/socket.h | 26 + cpp/platform/api/submittable_executor.h | 43 + cpp/platform/api/system_clock.h | 22 + cpp/platform/api/thread_utils.h | 23 + cpp/platform/api/wifi.h | 90 + cpp/platform/base64_utils.cc | 56 + cpp/platform/base64_utils.h | 31 + cpp/platform/byte_array.h | 66 + cpp/platform/byte_array_test.cc | 39 + cpp/platform/callable.h | 26 + cpp/platform/cancelable.h | 19 + cpp/platform/cancelable_alarm.cc | 37 + cpp/platform/cancelable_alarm.h | 41 + cpp/platform/container_of.h | 71 + cpp/platform/container_of_test.cc | 47 + cpp/platform/exception.cc | 30 + cpp/platform/exception.h | 57 + cpp/platform/file_impl.cc | 75 + cpp/platform/file_impl.h | 46 + cpp/platform/file_impl_test.cc | 133 ++ cpp/platform/impl/default/BUILD | 45 + .../default/default_condition_variable.cc | 28 + .../impl/default/default_condition_variable.h | 30 + cpp/platform/impl/default/default_lock.cc | 24 + cpp/platform/impl/default/default_lock.h | 29 + cpp/platform/impl/default/default_platform.cc | 17 + cpp/platform/impl/default/default_platform.h | 26 + cpp/platform/impl/g3/BUILD | 0 cpp/platform/impl/ios/BUILD | 9 + cpp/platform/impl/sample/BUILD | 20 + cpp/platform/impl/sample/sample_platform.h | 141 ++ .../impl/sample/sample_wifi_medium.cc | 110 ++ cpp/platform/impl/sample/sample_wifi_medium.h | 59 + cpp/platform/logging.h | 29 + cpp/platform/pipe.cc | 199 +++ cpp/platform/pipe.h | 75 + cpp/platform/pipe_test.cc | 407 +++++ cpp/platform/port/BUILD | 38 + cpp/platform/port/config.h | 22 + cpp/platform/port/down_cast.h | 12 + cpp/platform/port/string.h | 12 + cpp/platform/prng.cc | 45 + cpp/platform/prng.h | 23 + cpp/platform/prng_test.cc | 27 + cpp/platform/ptr.cc | 13 + cpp/platform/ptr.h | 400 +++++ cpp/platform/ptr_test.cc | 272 +++ cpp/platform/reliability_utils.cc | 42 + cpp/platform/reliability_utils.h | 43 + cpp/platform/runnable.h | 22 + cpp/platform/synchronized.h | 26 + proto/BUILD | 219 +++ proto/bootstrap_enums.proto | 86 + proto/connections/BUILD | 50 + proto/connections/offline_wire_formats.proto | 239 +++ .../offline_wire_formats_proto_config.asciipb | 25 + proto/connections_enums.proto | 252 +++ proto/connections_enums_proto_config.asciipb | 5 + proto/discovery_enums.proto | 471 +++++ proto/magic_pair_enums.proto | 66 + proto/nearby_client_enums.proto | 30 + proto/nearby_event_codes.proto | 58 + proto/setup_enums.proto | 28 + proto/sharing_enums.proto | 249 +++ 195 files changed, 27318 insertions(+) create mode 100644 cpp/core/BUILD create mode 100644 cpp/core/check_compilation.cc create mode 100644 cpp/core/core.cc create mode 100644 cpp/core/core.h create mode 100644 cpp/core/internal/BUILD create mode 100644 cpp/core/internal/bandwidth_upgrade_handler.h create mode 100644 cpp/core/internal/bandwidth_upgrade_manager.cc create mode 100644 cpp/core/internal/bandwidth_upgrade_manager.h create mode 100644 cpp/core/internal/base_bandwidth_upgrade_handler.cc create mode 100644 cpp/core/internal/base_bandwidth_upgrade_handler.h create mode 100644 cpp/core/internal/base_endpoint_channel.cc create mode 100644 cpp/core/internal/base_endpoint_channel.h create mode 100644 cpp/core/internal/base_pcp_handler.cc create mode 100644 cpp/core/internal/base_pcp_handler.h create mode 100644 cpp/core/internal/ble_advertisement.cc create mode 100644 cpp/core/internal/ble_advertisement.h create mode 100644 cpp/core/internal/ble_advertisement_test.cc create mode 100644 cpp/core/internal/ble_compat.h create mode 100644 cpp/core/internal/ble_endpoint_channel.cc create mode 100644 cpp/core/internal/ble_endpoint_channel.h create mode 100644 cpp/core/internal/bluetooth_device_name.cc create mode 100644 cpp/core/internal/bluetooth_device_name.h create mode 100644 cpp/core/internal/bluetooth_device_name_test.cc create mode 100644 cpp/core/internal/bluetooth_endpoint_channel.cc create mode 100644 cpp/core/internal/bluetooth_endpoint_channel.h create mode 100644 cpp/core/internal/client_proxy.cc create mode 100644 cpp/core/internal/client_proxy.h create mode 100644 cpp/core/internal/encryption_runner.cc create mode 100644 cpp/core/internal/encryption_runner.h create mode 100644 cpp/core/internal/endpoint_channel.h create mode 100644 cpp/core/internal/endpoint_channel_manager.cc create mode 100644 cpp/core/internal/endpoint_channel_manager.h create mode 100644 cpp/core/internal/endpoint_manager.cc create mode 100644 cpp/core/internal/endpoint_manager.h create mode 100644 cpp/core/internal/internal_payload.cc create mode 100644 cpp/core/internal/internal_payload.h create mode 100644 cpp/core/internal/internal_payload_factory.cc create mode 100644 cpp/core/internal/internal_payload_factory.h create mode 100644 cpp/core/internal/loop_runner.cc create mode 100644 cpp/core/internal/loop_runner.h create mode 100644 cpp/core/internal/medium_manager.cc create mode 100644 cpp/core/internal/medium_manager.h create mode 100644 cpp/core/internal/mediums/BUILD create mode 100644 cpp/core/internal/mediums/advertisement_read_result.cc create mode 100644 cpp/core/internal/mediums/advertisement_read_result.h create mode 100644 cpp/core/internal/mediums/advertisement_read_result_test.cc create mode 100644 cpp/core/internal/mediums/ble.cc create mode 100644 cpp/core/internal/mediums/ble.h create mode 100644 cpp/core/internal/mediums/ble_advertisement.cc create mode 100644 cpp/core/internal/mediums/ble_advertisement.h create mode 100644 cpp/core/internal/mediums/ble_advertisement_header.cc create mode 100644 cpp/core/internal/mediums/ble_advertisement_header.h create mode 100644 cpp/core/internal/mediums/ble_advertisement_header_test.cc create mode 100644 cpp/core/internal/mediums/ble_advertisement_test.cc create mode 100644 cpp/core/internal/mediums/ble_packet.cc create mode 100644 cpp/core/internal/mediums/ble_packet.h create mode 100644 cpp/core/internal/mediums/ble_packet_test.cc create mode 100644 cpp/core/internal/mediums/ble_peripheral.cc create mode 100644 cpp/core/internal/mediums/ble_peripheral.h create mode 100644 cpp/core/internal/mediums/ble_v2.cc create mode 100644 cpp/core/internal/mediums/ble_v2.h create mode 100644 cpp/core/internal/mediums/bloom_filter.cc create mode 100644 cpp/core/internal/mediums/bloom_filter.h create mode 100644 cpp/core/internal/mediums/bloom_filter_test.cc create mode 100644 cpp/core/internal/mediums/bluetooth_classic.cc create mode 100644 cpp/core/internal/mediums/bluetooth_classic.h create mode 100644 cpp/core/internal/mediums/bluetooth_radio.cc create mode 100644 cpp/core/internal/mediums/bluetooth_radio.h create mode 100644 cpp/core/internal/mediums/discovered_peripheral_callback.h create mode 100644 cpp/core/internal/mediums/discovered_peripheral_tracker.cc create mode 100644 cpp/core/internal/mediums/discovered_peripheral_tracker.h create mode 100644 cpp/core/internal/mediums/lost_entity_tracker.cc create mode 100644 cpp/core/internal/mediums/lost_entity_tracker.h create mode 100644 cpp/core/internal/mediums/lost_entity_tracker_test.cc create mode 100644 cpp/core/internal/mediums/mediums.cc create mode 100644 cpp/core/internal/mediums/mediums.h create mode 100644 cpp/core/internal/mediums/utils.cc create mode 100644 cpp/core/internal/mediums/utils.h create mode 100644 cpp/core/internal/mediums/uuid.cc create mode 100644 cpp/core/internal/mediums/uuid.h create mode 100644 cpp/core/internal/offline_frames.cc create mode 100644 cpp/core/internal/offline_frames.h create mode 100644 cpp/core/internal/offline_service_controller.cc create mode 100644 cpp/core/internal/offline_service_controller.h create mode 100644 cpp/core/internal/p2p_cluster_pcp_handler.cc create mode 100644 cpp/core/internal/p2p_cluster_pcp_handler.h create mode 100644 cpp/core/internal/p2p_point_to_point_pcp_handler.cc create mode 100644 cpp/core/internal/p2p_point_to_point_pcp_handler.h create mode 100644 cpp/core/internal/p2p_star_pcp_handler.cc create mode 100644 cpp/core/internal/p2p_star_pcp_handler.h create mode 100644 cpp/core/internal/payload_manager.cc create mode 100644 cpp/core/internal/payload_manager.h create mode 100644 cpp/core/internal/pcp.h create mode 100644 cpp/core/internal/pcp_handler.h create mode 100644 cpp/core/internal/pcp_manager.cc create mode 100644 cpp/core/internal/pcp_manager.h create mode 100644 cpp/core/internal/service_controller.h create mode 100644 cpp/core/internal/service_controller_router.cc create mode 100644 cpp/core/internal/service_controller_router.h create mode 100644 cpp/core/internal/wifi_lan_upgrade_handler.cc create mode 100644 cpp/core/internal/wifi_lan_upgrade_handler.h create mode 100644 cpp/core/listeners.h create mode 100644 cpp/core/options.h create mode 100644 cpp/core/params.h create mode 100644 cpp/core/payload.cc create mode 100644 cpp/core/payload.h create mode 100644 cpp/core/status.h create mode 100644 cpp/core/strategy.cc create mode 100644 cpp/core/strategy.h create mode 100644 cpp/platform/BUILD create mode 100644 cpp/platform/api/BUILD create mode 100644 cpp/platform/api/atomic_boolean.h create mode 100644 cpp/platform/api/atomic_reference.h create mode 100644 cpp/platform/api/ble.h create mode 100644 cpp/platform/api/ble_v2.h create mode 100644 cpp/platform/api/bluetooth_adapter.h create mode 100644 cpp/platform/api/bluetooth_classic.h create mode 100644 cpp/platform/api/condition_variable.h create mode 100644 cpp/platform/api/count_down_latch.h create mode 100644 cpp/platform/api/executor.h create mode 100644 cpp/platform/api/future.h create mode 100644 cpp/platform/api/hash_utils.h create mode 100644 cpp/platform/api/input_file.h create mode 100644 cpp/platform/api/input_stream.h create mode 100644 cpp/platform/api/lock.h create mode 100644 cpp/platform/api/multi_thread_executor.h create mode 100644 cpp/platform/api/output_file.h create mode 100644 cpp/platform/api/output_stream.h create mode 100644 cpp/platform/api/scheduled_executor.h create mode 100644 cpp/platform/api/settable_future.h create mode 100644 cpp/platform/api/single_thread_executor.h create mode 100644 cpp/platform/api/socket.h create mode 100644 cpp/platform/api/submittable_executor.h create mode 100644 cpp/platform/api/system_clock.h create mode 100644 cpp/platform/api/thread_utils.h create mode 100644 cpp/platform/api/wifi.h create mode 100644 cpp/platform/base64_utils.cc create mode 100644 cpp/platform/base64_utils.h create mode 100644 cpp/platform/byte_array.h create mode 100644 cpp/platform/byte_array_test.cc create mode 100644 cpp/platform/callable.h create mode 100644 cpp/platform/cancelable.h create mode 100644 cpp/platform/cancelable_alarm.cc create mode 100644 cpp/platform/cancelable_alarm.h create mode 100644 cpp/platform/container_of.h create mode 100644 cpp/platform/container_of_test.cc create mode 100644 cpp/platform/exception.cc create mode 100644 cpp/platform/exception.h create mode 100644 cpp/platform/file_impl.cc create mode 100644 cpp/platform/file_impl.h create mode 100644 cpp/platform/file_impl_test.cc create mode 100644 cpp/platform/impl/default/BUILD create mode 100644 cpp/platform/impl/default/default_condition_variable.cc create mode 100644 cpp/platform/impl/default/default_condition_variable.h create mode 100644 cpp/platform/impl/default/default_lock.cc create mode 100644 cpp/platform/impl/default/default_lock.h create mode 100644 cpp/platform/impl/default/default_platform.cc create mode 100644 cpp/platform/impl/default/default_platform.h create mode 100644 cpp/platform/impl/g3/BUILD create mode 100644 cpp/platform/impl/ios/BUILD create mode 100644 cpp/platform/impl/sample/BUILD create mode 100644 cpp/platform/impl/sample/sample_platform.h create mode 100644 cpp/platform/impl/sample/sample_wifi_medium.cc create mode 100644 cpp/platform/impl/sample/sample_wifi_medium.h create mode 100644 cpp/platform/logging.h create mode 100644 cpp/platform/pipe.cc create mode 100644 cpp/platform/pipe.h create mode 100644 cpp/platform/pipe_test.cc create mode 100644 cpp/platform/port/BUILD create mode 100644 cpp/platform/port/config.h create mode 100644 cpp/platform/port/down_cast.h create mode 100644 cpp/platform/port/string.h create mode 100644 cpp/platform/prng.cc create mode 100644 cpp/platform/prng.h create mode 100644 cpp/platform/prng_test.cc create mode 100644 cpp/platform/ptr.cc create mode 100644 cpp/platform/ptr.h create mode 100644 cpp/platform/ptr_test.cc create mode 100644 cpp/platform/reliability_utils.cc create mode 100644 cpp/platform/reliability_utils.h create mode 100644 cpp/platform/runnable.h create mode 100644 cpp/platform/synchronized.h create mode 100644 proto/BUILD create mode 100644 proto/bootstrap_enums.proto create mode 100644 proto/connections/BUILD create mode 100644 proto/connections/offline_wire_formats.proto create mode 100644 proto/connections/offline_wire_formats_proto_config.asciipb create mode 100644 proto/connections_enums.proto create mode 100644 proto/connections_enums_proto_config.asciipb create mode 100644 proto/discovery_enums.proto create mode 100644 proto/magic_pair_enums.proto create mode 100644 proto/nearby_client_enums.proto create mode 100644 proto/nearby_event_codes.proto create mode 100644 proto/setup_enums.proto create mode 100644 proto/sharing_enums.proto diff --git a/cpp/core/BUILD b/cpp/core/BUILD new file mode 100644 index 00000000..fa226c20 --- /dev/null +++ b/cpp/core/BUILD @@ -0,0 +1,55 @@ +cc_library( + name = "core", + hdrs = [ + "core.cc", + "core.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__pkg__", + "//location/nearby/setup/core/internal:__pkg__", + ], + deps = [ + ":types", + "//core/internal", + "//platform:types", + ], +) + +cc_library( + name = "types", + srcs = [ + "payload.cc", + "strategy.cc", + ], + hdrs = [ + "listeners.h", + "options.h", + "params.h", + "payload.h", + "status.h", + "strategy.h", + ], + visibility = [ + "//core/internal:__pkg__", + "//location/nearby/setup/core/internal:__pkg__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + ], +) + +cc_library( + name = "check_compilation", + srcs = ["check_compilation.cc"], + deps = [ + ":core", + ":types", + "//platform:types", + "//platform:utils", + "//platform/impl/sample", + "//platform/port:string", + ], +) diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc new file mode 100644 index 00000000..23941a86 --- /dev/null +++ b/cpp/core/check_compilation.cc @@ -0,0 +1,119 @@ + +#include + +#include "core/core.h" +#include "core/listeners.h" +#include "core/params.h" +#include "core/payload.h" +#include "core/status.h" +#include "platform/byte_array.h" +#include "platform/file_impl.h" +#include "platform/impl/sample/sample_platform.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +class ResultListenerImpl : public ResultListener { + public: + void onResult(Status::Value status) override {} +}; + +class ConnectionLifecycleListenerImpl : public ConnectionLifecycleListener { + public: + void onConnectionInitiated(ConstPtr + on_connection_initiated_params) override {} + void onConnectionResult( + ConstPtr on_connection_result_params) override { + } + void onDisconnected( + ConstPtr on_disconnected_params) override {} + void onBandwidthChanged( + ConstPtr on_bandwidth_changed_params) override { + } +}; + +class DiscoveryListenerImpl : public DiscoveryListener { + public: + void onEndpointFound( + ConstPtr on_endpoint_found_params) override {} + void onEndpointLost( + ConstPtr on_endpoint_lost_params) override {} +}; + +class PayloadListenerImpl : public PayloadListener { + public: + void onPayloadReceived( + ConstPtr on_payload_received_params) override {} + void onPayloadTransferUpdate(ConstPtr + on_payload_transfer_update_params) override { + } +}; + +void check_compilation() { + Core core; + + const string name = "name"; + const string service_id = "service_id"; + const string remote_endpoint_id = "remote_endpoint_id"; + + core.startAdvertising(MakeConstPtr(new StartAdvertisingParams( + MakePtr(new ResultListenerImpl()), name, service_id, + AdvertisingOptions(Strategy::kP2PCluster, + /* auto_upgrade_bandwidth= */ false, + /* enforce_topology_constraints= */ false), + MakePtr(new ConnectionLifecycleListenerImpl())))); + + core.stopAdvertising(MakeConstPtr(new StopAdvertisingParams())); + + core.startDiscovery(MakeConstPtr( + new StartDiscoveryParams(MakePtr(new ResultListenerImpl()), service_id, + DiscoveryOptions(Strategy::kP2PCluster), + MakePtr(new DiscoveryListenerImpl())))); + + core.stopDiscovery(MakeConstPtr(new StopDiscoveryParams())); + + core.requestConnection(MakeConstPtr(new RequestConnectionParams( + MakePtr(new ResultListenerImpl()), name, remote_endpoint_id, + MakePtr(new ConnectionLifecycleListenerImpl())))); + + core.acceptConnection(MakeConstPtr(new AcceptConnectionParams( + MakePtr(new ResultListenerImpl()), remote_endpoint_id, + MakePtr(new PayloadListenerImpl())))); + + core.rejectConnection(MakeConstPtr(new RejectConnectionParams( + MakePtr(new ResultListenerImpl()), remote_endpoint_id))); + + core.initiateBandwidthUpgrade(MakeConstPtr(new InitiateBandwidthUpgradeParams( + MakePtr(new ResultListenerImpl()), remote_endpoint_id))); + + core.sendPayload(MakeConstPtr(new SendPayloadParams( + MakePtr(new ResultListenerImpl()), + std::vector(1, remote_endpoint_id), + ConstifyPtr( + Payload::fromBytes(MakeConstPtr(new ByteArray("bytes", 5))))))); + + core.cancelPayload(MakeConstPtr( + new CancelPayloadParams(MakePtr(new ResultListenerImpl()), 1))); + + core.sendPayload(MakeConstPtr(new SendPayloadParams( + MakePtr(new ResultListenerImpl()), + std::vector(2, remote_endpoint_id), + ConstifyPtr(Payload::fromFile(MakePtr( + new InputFileImpl("/some/arbitrary/file/path.txt", 1024))))))); + + core.cancelPayload(MakeConstPtr( + new CancelPayloadParams(MakePtr(new ResultListenerImpl()), 2))); + + core.disconnectFromEndpoint( + MakeConstPtr(new DisconnectFromEndpointParams(remote_endpoint_id))); + + core.stopAllEndpoints(MakeConstPtr( + new StopAllEndpointsParams(MakePtr(new ResultListenerImpl())))); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/core.cc b/cpp/core/core.cc new file mode 100644 index 00000000..8409d0cc --- /dev/null +++ b/cpp/core/core.cc @@ -0,0 +1,137 @@ +#include "core/core.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +template +Core::Core() + : client_proxy_(new ClientProxy()), + service_controller_router_(new ServiceControllerRouter()) {} + +template +Core::~Core() { + service_controller_router_->clientDisconnecting(client_proxy_.get()); +} + +template +void Core::startAdvertising( + ConstPtr start_advertising_params) { + assert(!start_advertising_params->result_listener.isNull()); + assert(!start_advertising_params->connection_lifecycle_listener.isNull()); + assert(!start_advertising_params->service_id.empty()); + assert(start_advertising_params->advertising_options.strategy.isValid()); + + service_controller_router_->startAdvertising(client_proxy_.get(), + start_advertising_params); +} + +template +void Core::stopAdvertising( + ConstPtr stop_advertising_params) { + service_controller_router_->stopAdvertising(client_proxy_.get(), + stop_advertising_params); +} + +template +void Core::startDiscovery( + ConstPtr start_discovery_params) { + assert(!start_discovery_params->result_listener.isNull()); + assert(!start_discovery_params->discovery_listener.isNull()); + assert(!start_discovery_params->service_id.empty()); + assert(start_discovery_params->discovery_options.strategy.isValid()); + + service_controller_router_->startDiscovery(client_proxy_.get(), + start_discovery_params); +} + +template +void Core::stopDiscovery( + ConstPtr stop_discovery_params) { + service_controller_router_->stopDiscovery(client_proxy_.get(), + stop_discovery_params); +} + +template +void Core::requestConnection( + ConstPtr request_connection_params) { + assert(!request_connection_params->result_listener.isNull()); + assert(!request_connection_params->connection_lifecycle_listener.isNull()); + assert(!request_connection_params->remote_endpoint_id.empty()); + + service_controller_router_->requestConnection(client_proxy_.get(), + request_connection_params); +} + +template +void Core::acceptConnection( + ConstPtr accept_connection_params) { + assert(!accept_connection_params->result_listener.isNull()); + assert(!accept_connection_params->payload_listener.isNull()); + assert(!accept_connection_params->remote_endpoint_id.empty()); + + service_controller_router_->acceptConnection(client_proxy_.get(), + accept_connection_params); +} + +template +void Core::rejectConnection( + ConstPtr reject_connection_params) { + assert(!reject_connection_params->result_listener.isNull()); + assert(!reject_connection_params->remote_endpoint_id.empty()); + + service_controller_router_->rejectConnection(client_proxy_.get(), + reject_connection_params); +} + +template +void Core::initiateBandwidthUpgrade( + ConstPtr + initiate_bandwidth_upgrade_params) { + service_controller_router_->initiateBandwidthUpgrade( + client_proxy_.get(), initiate_bandwidth_upgrade_params); +} + +template +void Core::sendPayload( + ConstPtr send_payload_params) { + assert(!send_payload_params->result_listener.isNull()); + assert(!send_payload_params->remote_endpoint_ids.empty()); + assert(!send_payload_params->payload.isNull()); + // TODO(tracyzhou): Do sanity check on payload based on payload type. + + service_controller_router_->sendPayload(client_proxy_.get(), + send_payload_params); +} + +template +void Core::cancelPayload( + ConstPtr cancel_payload_params) { + assert(!cancel_payload_params->result_listener.isNull()); + assert(cancel_payload_params->payload_id != 0); + + service_controller_router_->cancelPayload(client_proxy_.get(), + cancel_payload_params); +} + +template +void Core::disconnectFromEndpoint( + ConstPtr disconnect_from_endpoint_params) { + assert(!disconnect_from_endpoint_params->remote_endpoint_id.empty()); + + service_controller_router_->disconnectFromEndpoint( + client_proxy_.get(), disconnect_from_endpoint_params); +} + +template +void Core::stopAllEndpoints( + ConstPtr stop_all_endpoints_params) { + service_controller_router_->stopAllEndpoints(client_proxy_.get(), + stop_all_endpoints_params); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/core.h b/cpp/core/core.h new file mode 100644 index 00000000..d148d6df --- /dev/null +++ b/cpp/core/core.h @@ -0,0 +1,83 @@ +#ifndef CORE_CORE_H_ +#define CORE_CORE_H_ + +#include "core/internal/client_proxy.h" +#include "core/internal/service_controller_router.h" +#include "core/params.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +/* + * This class defines the API of the Nearby Connections Core library. + * + * Each passed-in Platform must provide a set of primitives with platform- + * specific implementations. The Platform class must provide factory functions + * for the following primitives: + * + * SingleThreadExecutor + * MultiThreadExecutor + * ScheduledExecutor + * Lock + * CountDownLatch + * AtomicBoolean + * AtomicReference + * SettableFuture + * BluetoothAdapter + * BluetoothClassicMedium + * HashUtils + * ThreadUtils + * SystemClock + * ConditionVariable + * + * The Platform class must also provide typedefs for the following subset of + * primitives to identify the concrete classes: + * + * SingleThreadExecutorType + * MultiThreadExecutorType + * ScheduledExecutorType + * + * A sample Platform class can be found at + * //platform/impl/sample/sample_platform.h + */ +template +class Core { + public: + Core(); + ~Core(); + + void startAdvertising( + ConstPtr start_advertising_params); + void stopAdvertising(ConstPtr stop_advertising_params); + void startDiscovery(ConstPtr start_discovery_params); + void stopDiscovery(ConstPtr stop_discovery_params); + void requestConnection( + ConstPtr request_connection_params); + void acceptConnection( + ConstPtr accept_connection_params); + void rejectConnection( + ConstPtr reject_connection_params); + void initiateBandwidthUpgrade(ConstPtr + initiate_bandwidth_upgrade_params); + void sendPayload(ConstPtr send_payload_params); + void cancelPayload(ConstPtr cancel_payload_params); + void disconnectFromEndpoint( + ConstPtr disconnect_from_endpoint_params); + void stopAllEndpoints( + ConstPtr stop_all_endpoints_params); + + private: + ScopedPtr > > client_proxy_; + ScopedPtr > > + service_controller_router_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/core.cc" + +#endif // CORE_CORE_H_ diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD new file mode 100644 index 00000000..e9d2dd67 --- /dev/null +++ b/cpp/core/internal/BUILD @@ -0,0 +1,102 @@ +cc_library( + name = "internal", + srcs = [ + "ble_advertisement.cc", + "bluetooth_device_name.cc", + "internal_payload.cc", + "internal_payload.h", + "loop_runner.cc", + "loop_runner.h", + "offline_frames.cc", + "offline_frames.h", + ], + hdrs = [ + "bandwidth_upgrade_handler.h", + "bandwidth_upgrade_manager.cc", + "bandwidth_upgrade_manager.h", + "base_bandwidth_upgrade_handler.cc", + "base_bandwidth_upgrade_handler.h", + "base_endpoint_channel.cc", + "base_endpoint_channel.h", + "base_pcp_handler.cc", + "base_pcp_handler.h", + "ble_advertisement.h", + "ble_compat.h", + "ble_endpoint_channel.cc", + "ble_endpoint_channel.h", + "bluetooth_device_name.h", + "bluetooth_endpoint_channel.cc", + "bluetooth_endpoint_channel.h", + "client_proxy.cc", + "client_proxy.h", + "encryption_runner.cc", + "encryption_runner.h", + "endpoint_channel.h", + "endpoint_channel_manager.cc", + "endpoint_channel_manager.h", + "endpoint_manager.cc", + "endpoint_manager.h", + "internal_payload_factory.cc", + "internal_payload_factory.h", + "medium_manager.cc", + "medium_manager.h", + "offline_service_controller.cc", + "offline_service_controller.h", + "p2p_cluster_pcp_handler.cc", + "p2p_cluster_pcp_handler.h", + "p2p_point_to_point_pcp_handler.cc", + "p2p_point_to_point_pcp_handler.h", + "p2p_star_pcp_handler.cc", + "p2p_star_pcp_handler.h", + "payload_manager.cc", + "payload_manager.h", + "pcp.h", + "pcp_handler.h", + "pcp_manager.cc", + "pcp_manager.h", + "service_controller.h", + "service_controller_router.cc", + "service_controller_router.h", + "wifi_lan_upgrade_handler.cc", + "wifi_lan_upgrade_handler.h", + ], + visibility = [ + "//core:__pkg__", + ], + deps = [ + "//core:types", + "//core/internal/mediums", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform:logging", + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:down_cast", + "//platform/port:string", + "//proto:connections_enums_portable_proto", + "//net/proto2/compat/public:proto2_lite", + "//securegcm:ukey2", + "//absl/strings", + ], +) + +cc_test( + name = "bluetooth_device_name_test", + srcs = ["bluetooth_device_name_test.cc"], + deps = [ + ":internal", + "//platform:utils", + "//platform/port:string", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "ble_advertisement_test", + srcs = ["ble_advertisement_test.cc"], + deps = [ + ":internal", + "//platform/port:string", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h new file mode 100644 index 00000000..6e8c0775 --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -0,0 +1,50 @@ +#ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_ +#define CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_ + +#include "core/internal/client_proxy.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/count_down_latch.h" +#include "platform/port/string.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the set of methods that need to be implemented to handle the +// per-Medium-specific operations needed to upgrade an EndpointChannel. +template +class BandwidthUpgradeHandler { + public: + virtual ~BandwidthUpgradeHandler() {} + + // Reverts any changes made to the device in the process of upgrading + // endpoints. + virtual void revert() = 0; + + // Cleans up in-progress upgrades after endpoint disconnection. + virtual void processEndpointDisconnection( + Ptr > client_proxy, const std::string& endpoint_id, + Ptr process_disconnection_barrier) = 0; + + // Initiates the upgrade for the endpoint and starts listening for upgraded + // incoming connections on the initiator side of the bandwidth upgrade. + virtual void initiateBandwidthUpgradeForEndpoint( + Ptr > client_proxy, + const std::string& endpoint_id) = 0; + + // Processes the BandwidthUpgradeNegotiationFrames that come over the + // EndpointChannel on the non-initiator side of the bandwidth upgrade. + // TODO(ahlee): Rename parameters in the java code. + virtual void processBandwidthUpgradeNegotiationFrame( + ConstPtr bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, + const std::string& from_endpoint_id, + proto::connections::Medium current_medium) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/bandwidth_upgrade_manager.cc b/cpp/core/internal/bandwidth_upgrade_manager.cc new file mode 100644 index 00000000..4c502beb --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_manager.cc @@ -0,0 +1,47 @@ +#include "core/internal/bandwidth_upgrade_manager.h" + +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +template +BandwidthUpgradeManager::BandwidthUpgradeManager( + Ptr > medium_manager, + Ptr > endpoint_channel_manager, + Ptr > endpoint_manager) + : endpoint_manager_(endpoint_manager), + bandwidth_upgrade_handlers_(), + current_bandwidth_upgrade_handler_() {} + +template +BandwidthUpgradeManager::~BandwidthUpgradeManager() { + // TODO(ahlee): Make sure we don't repeat the mistake fixed in cl/201883908. +} + +template +void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( + Ptr > client_proxy, const string& endpoint_id, + proto::connections::Medium medium) {} + +template +void BandwidthUpgradeManager::processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) {} + +template +void BandwidthUpgradeManager::processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) {} + +template +bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( + proto::connections::Medium medium) { + return false; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/bandwidth_upgrade_manager.h b/cpp/core/internal/bandwidth_upgrade_manager.h new file mode 100644 index 00000000..5aab4033 --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -0,0 +1,66 @@ +#ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ +#define CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ + +#include + +#include "core/internal/bandwidth_upgrade_handler.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all known {@link BandwidthUpgradeHandler} implementations, delegating +// operations to the appropriate one as per the parameters passed in. +template +class BandwidthUpgradeManager + : public EndpointManager::IncomingOfflineFrameProcessor { + public: + BandwidthUpgradeManager( + Ptr > medium_manager, + Ptr > endpoint_channel_manager, + Ptr > endpoint_manager); + ~BandwidthUpgradeManager() override; + + // This is the point on the initiator side where the + // current_bandwidth_upgrade_handler_ is set. + void initiateBandwidthUpgradeForEndpoint( + Ptr > client_proxy, const string& endpoint_id, + proto::connections::Medium medium); + // This is the point on the non-initiator side where the + // current_bandwidth_upgrade_handler_ is set. + // @EndpointManagerReaderThread + void processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) override; + // @EndpointManagerReaderThread + void processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) override; + + private: + bool setCurrentBandwidthUpgradeHandler(proto::connections::Medium medium); + + Ptr > endpoint_manager_; + typedef std::map > > + BandwidthUpgradeHandlersMap; + BandwidthUpgradeHandlersMap bandwidth_upgrade_handlers_; + Ptr > current_bandwidth_upgrade_handler_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/bandwidth_upgrade_manager.cc" + +#endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.cc b/cpp/core/internal/base_bandwidth_upgrade_handler.cc new file mode 100644 index 00000000..9970bb8e --- /dev/null +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -0,0 +1,145 @@ +#include "core/internal/base_bandwidth_upgrade_handler.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace base_bandwidth_upgrade_handler { + +template +class RevertRunnable : public Runnable { + public: + void run() {} +}; + +template +class InitiateBandwidthUpgradeForEndpointRunnable : public Runnable { + public: + void run() {} +}; + +template +class ProcessEndpointDisconnectionRunnable : public Runnable { + public: + void run() {} +}; + +template +class ProcessBandwidthUpgradeNegotiationFrameRunnable : public Runnable { + public: + void run() {} +}; + +} // namespace base_bandwidth_upgrade_handler + +template +BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( + Ptr > endpoint_channel_manager) + : endpoint_channel_manager_(endpoint_channel_manager), + alarm_executor_(), + serial_executor_(), + previous_endpoint_channels_(), + in_progress_upgrades_(), + safe_to_close_write_timestamps_() {} + +template +BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} + +template +void BaseBandwidthUpgradeHandler::revert() {} + +template +void BaseBandwidthUpgradeHandler::processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) {} + +template +void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( + Ptr > client_proxy, const string& endpoint_id) {} + +template +void BaseBandwidthUpgradeHandler:: + processBandwidthUpgradeNegotiationFrame( + ConstPtr + bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, + const string& from_endpoint_id, + proto::connections::Medium current_medium) {} + +template +Ptr > +BaseBandwidthUpgradeHandler::getEndpointChannelManager() { + return endpoint_channel_manager_; +} + +template +void BaseBandwidthUpgradeHandler::onIncomingConnection( + Ptr incoming_socket_connection) {} + +template +void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( + Ptr runnable) {} + +template +void BaseBandwidthUpgradeHandler::runUpgradeProtocol( + Ptr > client_proxy, const string& endpoint_id, + Ptr new_endpoint_channel) {} + +template +void BaseBandwidthUpgradeHandler:: + processBandwidthUpgradePathAvailableEvent( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info, + proto::connections::Medium current_medium) {} + +template +Ptr BaseBandwidthUpgradeHandler:: + processBandwidthUpgradePathAvailableEventInternal( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info) { + return Ptr(); +} + +template +void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( + Ptr > client_proxy, const string& endpoint_id) {} + +template +void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( + Ptr > client_proxy, const string& endpoint_id) {} + +template +std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( + const string& endpoint_id) { + return 0; +} + +template +std::int64_t +BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( + const string& endpoint_id) { + return 0; +} + +// TODO(ahlee): This will differ from the Java code as we don't have to handle +// analytics in the C++ code. +template +void BaseBandwidthUpgradeHandler:: + attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( + proto::connections::BandwidthUpgradeResult result, + proto::connections::BandwidthUpgradeErrorStage error_stage) {} + +// TODO(ahlee): This will differ from the Java code (previously threw an +// UpgradeException). +template +Ptr +BaseBandwidthUpgradeHandler::readClientIntroductionFrame( + Ptr endpoint_channel) { + return Ptr(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.h b/cpp/core/internal/base_bandwidth_upgrade_handler.h new file mode 100644 index 00000000..c4be0a7d --- /dev/null +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -0,0 +1,189 @@ +#ifndef CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ +#define CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ + +#include +#include + +#include "core/internal/bandwidth_upgrade_handler.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/count_down_latch.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace base_bandwidth_upgrade_handler { + +template +class RevertRunnable; +template +class InitiateBandwidthUpgradeForEndpointRunnable; +template +class ProcessEndpointDisconnectionRunnable; +template +class ProcessBandwidthUpgradeNegotiationFrameRunnable; + +} // namespace base_bandwidth_upgrade_handler + +// Base class for managing the upgrade of endpoints to a different medium for +// communication (from whatever they were previously using). +// +//

The sequencing of the upgrade protocol is as follows: +//

    +//
  • Initiator sets up an upgrade path, sends +// BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE to Responder over +// the prior EndpointChannel. +//
  • Responder joins the upgrade path, sends (without encryption) +// BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION over the new +// EndpointChannel, and sends +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the +// prior EndpointChannel. +//
  • Initiator receives BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION +// over the newly-established EndpointChannel, and sends +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the +// prior EndpointChannel. +//
  • Both wait to receive +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL from the +// other, and upon doing so, send +// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL to each other +//
  • Both then wait to receive +// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the +// other, and upon doing so, close the prior EndpointChannel. +//
+template +class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { + public: + BaseBandwidthUpgradeHandler( + Ptr > endpoint_channel_manager); + ~BaseBandwidthUpgradeHandler(); + + void revert(); + void processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier); + // Initiates the bandwidth upgrade and sends an UPGRADE_PATH_AVAILABLE + // OfflineFrame. + void initiateBandwidthUpgradeForEndpoint( + Ptr > client_proxy, const string& endpoint_id); + void processBandwidthUpgradeNegotiationFrame( + ConstPtr bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, + const string& from_endpoint_id, + proto::connections::Medium current_medium); + + protected: + // Represents the incoming Socket the Initiator has gotten after initializing + // its upgraded bandwidth medium. + class IncomingSocketConnection { + public: + virtual ~IncomingSocketConnection() {} + + virtual string socketToString() = 0; + virtual void closeSocket() = 0; + // TODO(ahlee): Make sure to be careful with the ownership story of this. + // Leaning towards releasing to the caller. + virtual Ptr getEndpointChannel() = 0; + }; + + // Called by the Initiator to setup the upgraded medium for this endpoint (if + // that hasn't already been done), and returns a serialized UpgradePathInfo + // that can be sent to the Responder. + // TODO(ahlee): This will differ from the Java code (previously threw an + // UpgradeException). Leaving the return type simple for the skeleton - I'll + // switch to a pair if the result enum is needed. + // @BandwidthUpgradeHandlerThread + virtual ConstPtr initializeUpgradedMediumForEndpoint( + const string& endpoint_id) = 0; + // Called to revert any state changed by the Initiator to setup the upgraded + // medium for an endpoint. + // @BandwidthUpgradeHandlerThread + virtual void revertImpl() = 0; + // Called by the Responder to setup the upgraded medium for this endpoint (if + // that hasn't already been done) using the UpgradePathInfo sent by the + // Initiator, and returns a new EndpointChannel for the upgraded medium. + // @BandwidthUpgradeHandlerThread + // TODO(ahlee): This will differ from the Java code (previously threw an + // exception). + virtual Ptr createUpgradedEndpointChannel( + const string& endpoint_id, + ConstPtr + upgrade_path_info) = 0; + // Returns the upgrade medium of the BandwidthUpgradeHandler. + // @BandwidthUpgradeHandlerThread + virtual proto::connections::Medium getUpgradeMedium() = 0; + + Ptr > getEndpointChannelManager(); + // Common functionality to take an incoming connection and go through the + // upgrade process. + // @BandwidthUpgradeHandlerThread + void onIncomingConnection( + Ptr incoming_socket_connection); + void runOnBandwidthUpgradeHandlerThread(Ptr runnable); + + private: + template + friend class base_bandwidth_upgrade_handler::RevertRunnable; + template + friend class base_bandwidth_upgrade_handler:: + InitiateBandwidthUpgradeForEndpointRunnable; + template + friend class base_bandwidth_upgrade_handler:: + ProcessEndpointDisconnectionRunnable; + template + friend class base_bandwidth_upgrade_handler:: + ProcessBandwidthUpgradeNegotiationFrameRunnable; + + void runUpgradeProtocol(Ptr > client_proxy, + const string& endpoint_id, + Ptr new_endpoint_channel); + void processBandwidthUpgradePathAvailableEvent( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info, + proto::connections::Medium current_medium); + Ptr processBandwidthUpgradePathAvailableEventInternal( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info); + void processLastWriteToPriorChannelEvent( + Ptr > client_proxy, const string& endpoint_id); + void processSafeToClosePriorChannelEvent( + Ptr > client_proxy, const string& endpoint_id); + std::int64_t calculateCloseDelay(const string& endpoint_id); + std::int64_t getMillisSinceSafeCloseWritten(const string& endpoint_id); + void attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( + proto::connections::BandwidthUpgradeResult result, + proto::connections::BandwidthUpgradeErrorStage error_stage); + Ptr + readClientIntroductionFrame(Ptr endpoint_channel); + + Ptr > endpoint_channel_manager_; + ScopedPtr > alarm_executor_; + ScopedPtr > serial_executor_; + // Stores each upgraded endpoint's previous EndpointChannel (that was + // displaced in favor of a new EndpointChannel) temporarily, until it can + // safely be shut down for good in processLastWriteToPriorChannelEvent(). + typedef std::map > PreviousEndpointChannelsMap; + PreviousEndpointChannelsMap previous_endpoint_channels_; + // Maps endpointId -> ClientProxy for which + // initiateBandwidthUpgradeForEndpoint() has been called but which have not + // yet completed the upgrade via onIncomingConnection(). + typedef std::map > > InProgressUpgradesMap; + InProgressUpgradesMap in_progress_upgrades_; + // Maps endpointId -> timestamp of when the SAFE_TO_CLOSE message was written. + typedef std::map SafeToCloseWriteTimestampsMap; + SafeToCloseWriteTimestampsMap safe_to_close_write_timestamps_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/base_bandwidth_upgrade_handler.cc" + +#endif // CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc new file mode 100644 index 00000000..e6d288c7 --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -0,0 +1,352 @@ +#include "core/internal/base_endpoint_channel.h" + +#include + +#include "platform/synchronized.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +std::int32_t bytesToInt(ConstPtr bytes) { + const char* int_bytes = bytes->getData(); + + std::int32_t result = 0; + result |= (static_cast(int_bytes[0]) & 0x0FF) << 24; + result |= (static_cast(int_bytes[1]) & 0x0FF) << 16; + result |= (static_cast(int_bytes[2]) & 0x0FF) << 8; + result |= (static_cast(int_bytes[3]) & 0x0FF); + + return result; +} + +ConstPtr intToBytes(std::int32_t value) { + char int_bytes[sizeof(std::int32_t)]; + int_bytes[0] = static_cast((value >> 24) & 0x0FF); + int_bytes[1] = static_cast((value >> 16) & 0x0FF); + int_bytes[2] = static_cast((value >> 8) & 0x0FF); + int_bytes[3] = static_cast((value)&0x0FF); + + return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes))); +} + +ExceptionOr > readExactly(Ptr reader, + std::int64_t size) { + string buffer; + std::int64_t remaining_size = size; + + while (remaining_size > 0) { + ExceptionOr > read_bytes = reader->read(remaining_size); + if (!read_bytes.ok()) { + if (Exception::IO == read_bytes.exception()) { + return ExceptionOr >(read_bytes.exception()); + } + } + // Avoid leaks. + ScopedPtr > scoped_read_bytes(read_bytes.result()); + + // In Java, EOFException is a sub-variant of IOException. + if (scoped_read_bytes->size() == 0) { + return ExceptionOr >(Exception::IO); + } + + buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size()); + remaining_size -= scoped_read_bytes->size(); + } + + return ExceptionOr >( + MakeConstPtr(new ByteArray(buffer.data(), buffer.size()))); +} + +ExceptionOr readInt(Ptr reader) { + ExceptionOr > read_bytes = + readExactly(reader, sizeof(std::int32_t)); + if (!read_bytes.ok()) { + if (Exception::IO == read_bytes.exception()) { + return ExceptionOr(read_bytes.exception()); + } + } + // Avoid leaks. + ScopedPtr > scoped_read_bytes(read_bytes.result()); + + return ExceptionOr(bytesToInt(scoped_read_bytes.get())); +} + +Exception::Value writeInt(Ptr writer, std::int32_t value) { + return writer->write(intToBytes(value)); +} + +} // namespace + +template +BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, + Ptr reader, + Ptr writer) + : last_read_timestamp_(-1), + channel_name_(channel_name), + system_clock_(Platform::createSystemClock()), + reader_lock_(Platform::createLock()), + reader_(reader), + writer_lock_(Platform::createLock()), + writer_(writer), + encryption_context_(Platform::createAtomicReference( + Ptr())), + is_paused_lock_(Platform::createLock()), + is_paused_condition_variable_( + Platform::createConditionVariable(is_paused_lock_.get())), + is_paused_(Platform::createAtomicBoolean(false)) {} + +template +BaseEndpointChannel::~BaseEndpointChannel() { + // WARNING: Make sure to never access reader_ and writer_ from here. + // + // They're owned by the specialized *Socket classes that are in turn + // owned by the *EndpointChannel children of this class, so by this point, + // they've been destroyed and now point to invalid memory. + // + // "Ugh!" is right -- this won't be a problem once we have a standardized + // Socket interface we can hold up in this class (instead of holding + // specialized implementations of that hypothetical interface in each child + // of this class). +} + +template +ExceptionOr > BaseEndpointChannel::read() { + Synchronized s(reader_lock_.get()); + + ExceptionOr read_int = readInt(reader_); + if (!read_int.ok()) { + if (Exception::IO == read_int.exception()) { + return ExceptionOr >(read_int.exception()); + } + } + + if (read_int.result() < 0) { + return ExceptionOr>(Exception::IO); + } else if (read_int.result() > kMaxAllowedReadBytes) { + return ExceptionOr>(Exception::IO); + } + + ExceptionOr > read_bytes = + readExactly(reader_, read_int.result()); + if (!read_bytes.ok()) { + if (Exception::IO == read_bytes.exception()) { + return ExceptionOr >(read_bytes.exception()); + } + } + + // This should be ScopedPtr usually, but because of the unique requirement of + // reassigning this variable when encryption is enabled, we can't make use of + // the power of ScopedPtr, and instead have to do manual memory management. + ConstPtr read_bytes_result = read_bytes.result(); + + // If encryption is enabled, decode the message. + if (isEncryptionEnabled()) { + std::unique_ptr decoded_bytes = + encryption_context_->get()->DecodeMessageFromPeer( + string(read_bytes_result->getData(), read_bytes_result->size())); + // Now that we are done using read_bytes_result, we should unconditionally + // destroy it, because we either reassign to the value of decoded_bytes, or + // short-circuit out of here on error. + read_bytes_result.destroy(); + if (decoded_bytes == nullptr) { + return ExceptionOr >( + Exception::INVALID_PROTOCOL_BUFFER); + } + read_bytes_result = MakeConstPtr( + new ByteArray(decoded_bytes->data(), decoded_bytes->size())); + } + + last_read_timestamp_ = system_clock_->elapsedRealtime(); + return ExceptionOr >(read_bytes_result); +} + +template +Exception::Value BaseEndpointChannel::write( + ConstPtr data) { + Synchronized s(writer_lock_.get()); + + // Avoid leaks. + ScopedPtr > scoped_data(data); + + if (isPaused()) { + blockUntilUnpaused(); + } + + ConstPtr data_to_write; + // If encryption is enabled, encode the message. + if (isEncryptionEnabled()) { + std::unique_ptr message = + encryption_context_->get()->EncodeMessageToPeer( + string(scoped_data->getData(), scoped_data->size())); + assert(message != nullptr); + data_to_write = + MakeConstPtr(new ByteArray(message->data(), message->size())); + } else { + // Else, just make data_to_write point to the passed-in data. + data_to_write = scoped_data.release(); + } + // Avoid leaks. + ScopedPtr > scoped_data_to_write(data_to_write); + + Exception::Value write_exception = writeInt( + writer_, static_cast(scoped_data_to_write->size())); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + return write_exception; + } + } + + write_exception = writer_->write(scoped_data_to_write.release()); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + return write_exception; + } + } + + Exception::Value flush_exception = writer_->flush(); + if (Exception::NONE != flush_exception) { + if (Exception::IO == flush_exception) { + return flush_exception; + } + } + + return Exception::NONE; +} + +template +void BaseEndpointChannel::close() { + // WARNING WARNING WARNING + // + // This block deviates from the corresponding Java code. + // + // In the corresponding Java code, close() calls + // close(proto::connections::DisconnectionReason) while here we do the + // opposite. This is because proto::connections::DisconnectionReason can be + // null in Java but not in C++. + Exception::Value reader_close_exception = reader_->close(); + if (Exception::NONE != reader_close_exception) { + if (Exception::IO == reader_close_exception) { + // Add logging. + } + } + Exception::Value writer_close_exception = writer_->close(); + if (Exception::NONE != writer_close_exception) { + if (Exception::IO == writer_close_exception) { + // Add logging. + } + } + + closeImpl(); + + // TODO(tracyzhou): Add logging. +} + +template +void BaseEndpointChannel::close( + proto::connections::DisconnectionReason reason) { + // WARNING WARNING WARNING + // + // This block deviates from the corresponding Java code. + // Look at the corresponding block in the close() method above for details on + // the deviation. + close(); + + // TODO(tracyzhou): Add logging. +} + +template +string BaseEndpointChannel::getType() { + string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : ""; + switch (getMedium()) { + case proto::connections::Medium::BLUETOOTH: + return subtype + "BLUETOOTH"; + case proto::connections::Medium::BLE: + return subtype + "BLE"; + case proto::connections::Medium::MDNS: + return subtype + "MDNS"; + case proto::connections::Medium::WIFI_HOTSPOT: + return subtype + "WIFI_HOTSPOT"; + case proto::connections::Medium::WIFI_LAN: + return subtype + "WIFI_LAN"; + default: + return "UNKNOWN"; + } +} + +template +string BaseEndpointChannel::getName() { + return channel_name_; +} + +template +void BaseEndpointChannel::enableEncryption( + Ptr encryption_context) { + assert(!encryption_context.isNull()); + encryption_context_->set(encryption_context); +} + +template +bool BaseEndpointChannel::isPaused() { + return is_paused_->get(); +} + +template +void BaseEndpointChannel::pause() { + is_paused_->set(true); +} + +template +void BaseEndpointChannel::resume() { + is_paused_->set(false); + unblockPausedWriter(); +} + +template +std::int64_t BaseEndpointChannel::getLastReadTimestamp() { + return last_read_timestamp_; +} + +template +bool BaseEndpointChannel::isEncryptionEnabled() { + return !encryption_context_->get().isNull(); +} + +template +void BaseEndpointChannel::unblockPausedWriter() { + Synchronized s(is_paused_lock_.get()); + + // Notify to tell the thread calling wait() to check again. + // NOTE: There is only ever one thread blocked by wait() at a time, because + // EndpointChannel.write(Ptr) is synchronized on writer. That means + // the first thread to call write(byte[]) will be blocked via + // blockUntilUnpaused() and all future threads will be blocked via + // synchronized(writer_lock_). + is_paused_condition_variable_->notify(); +} + +template +void BaseEndpointChannel::blockUntilUnpaused() { + Synchronized s(is_paused_lock_.get()); + + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + while (is_paused_->get()) { + Exception::Value wait_succeeded = is_paused_condition_variable_->wait(); + if (Exception::NONE != wait_succeeded) { + if (Exception::INTERRUPTED == wait_succeeded) { + // If we were interrupted, pass the interrupt up the stack and then exit + // immediately. + // Thread.currentThread().interrupt(); + return; + } + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/base_endpoint_channel.h b/cpp/core/internal/base_endpoint_channel.h new file mode 100644 index 00000000..4e3c112e --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel.h @@ -0,0 +1,112 @@ +#ifndef CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ + +#include + +#include "core/internal/endpoint_channel.h" +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/condition_variable.h" +#include "platform/api/input_stream.h" +#include "platform/api/lock.h" +#include "platform/api/output_stream.h" +#include "platform/api/system_clock.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class BaseEndpointChannel : public EndpointChannel { + public: + BaseEndpointChannel(const string& channel_name, Ptr reader, + Ptr writer); + ~BaseEndpointChannel() override; + + ExceptionOr > read() override; + + Exception::Value write(ConstPtr data) override; + + // Closes this EndpointChannel, without tracking the closure in analytics. + void close() override; + + // Closes this EndpointChannel and records the closure with the given reason. + void close(proto::connections::DisconnectionReason reason) override; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, + // WIFI. + string getType() override; + + // Returns the name of the EndpointChannel. + string getName() override; + + // Enables encryption on the EndpointChannel. + void enableEncryption( + Ptr encryption_context) override; + + // True if the EndpointChannel is currently pausing all writes. + bool isPaused() override; + + // Pauses all writes on this EndpointChannel until resume() is called. + void pause() override; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + void resume() override; + + // Returns the timestamp (in elapsedRealtime) of the last read from this + // endpoint, or -1 if no reads have occurred. + std::int64_t getLastReadTimestamp() override; + + protected: + virtual void closeImpl() = 0; + + private: + // Used to sanity check that our frame sizes are reasonable. + static const std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + + bool isEncryptionEnabled(); + void unblockPausedWriter(); + void blockUntilUnpaused(); + + volatile std::int64_t last_read_timestamp_; + + const string channel_name_; + + ScopedPtr > system_clock_; + + // The reader and writer are synchronized independently since we can't have + // writes waiting on reads that might potentially block forever. + ScopedPtr > reader_lock_; + // Not owned by this class, see the note in the destructor for a special + // restriction on usage. + Ptr reader_; + + ScopedPtr > writer_lock_; + // Not owned by this class, see the note in the destructor for a special + // restriction on usage. + Ptr writer_; + + // An encryptor/decryptor. May be null. + ScopedPtr > > > + encryption_context_; + + ScopedPtr > is_paused_lock_; + ScopedPtr > is_paused_condition_variable_; + // If true, writes should block until this has been set to false. + ScopedPtr > is_paused_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/base_endpoint_channel.cc" + +#endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc new file mode 100644 index 00000000..84e736f6 --- /dev/null +++ b/cpp/core/internal/base_pcp_handler.cc @@ -0,0 +1,1573 @@ +#include "core/internal/base_pcp_handler.h" + +#include +#include +#include +#include + +namespace location { +namespace nearby { +namespace connections { + +namespace base_pcp_handler { + +// TODO(reznor): Implement this method in-terms-of removeOwnedPtrFromMap() +// below. +template +void eraseOwnedPtrFromMap(std::map>& m, const K& k) { + typename std::map>::iterator it = m.find(k); + if (it != m.end()) { + it->second.destroy(); + m.erase(it); + } +} + +template +Ptr removeOwnedPtrFromMap(std::map>& m, const K& k) { + Ptr removed_ptr; + + typename std::map>::iterator it = m.find(k); + if (it != m.end()) { + removed_ptr = it->second; + m.erase(it); + } + + return removed_ptr; +} + +template +class StartAdvertisingCallable : public Callable { + public: + StartAdvertisingCallable( + Ptr> base_pcp_handler, + Ptr> client_proxy, const string& service_id, + const string& local_endpoint_name, const AdvertisingOptions& options, + Ptr connection_lifecycle_listener) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + local_endpoint_name_(local_endpoint_name), + options_(options), + // Convert the passed in connection_lifecycle_listener Ptr into a + // reference counted one. The advertising session and any connected + // endpoints need a handle to the same connection_lifecycle_listener, so + // there is no clear model of who actually owns the listener. + connection_lifecycle_listener_( + MakeRefCountedPtr(&(*connection_lifecycle_listener))) {} + + ExceptionOr call() override { + // Ask the implementation to attempt to start advertising. + ScopedPtr::StartOperationResult>> + result(base_pcp_handler_->startAdvertisingImpl( + client_proxy_, service_id_, + client_proxy_->generateLocalEndpointId(), local_endpoint_name_, + options_)); + if (Status::SUCCESS != result->status_) { + return ExceptionOr(result->status_); + } + + // Now that we've succeeded, mark the client as advertising. + // Previous advertising_options_ and + // advertising_connection_lifecycle_listener_ is not destroyed here because + // stopAdvertising() is expected to be called before startAdvertising(). + base_pcp_handler_->advertising_options_ = + MakePtr(new AdvertisingOptions(options_)); + base_pcp_handler_->advertising_connection_lifecycle_listener_ = + connection_lifecycle_listener_; + client_proxy_->startedAdvertising( + service_id_, base_pcp_handler_->getStrategy(), + connection_lifecycle_listener_, result->mediums_); + return ExceptionOr(Status::SUCCESS); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string service_id_; + const string local_endpoint_name_; + const AdvertisingOptions options_; + Ptr connection_lifecycle_listener_; +}; + +template +class StopAdvertisingRunnable : public Runnable { + public: + StopAdvertisingRunnable(Ptr> base_pcp_handler, + Ptr> client_proxy, + Ptr latch) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + latch_(latch) {} + + void run() override { + base_pcp_handler_->stopAdvertisingImpl(client_proxy_); + client_proxy_->stoppedAdvertising(); + // base_pcp_handler_->advertising_options_ is purposefully not destroyed + // here. + base_pcp_handler_->advertising_connection_lifecycle_listener_.destroy(); + latch_->countDown(); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + Ptr latch_; +}; + +template +class StartDiscoveryCallable : public Callable { + public: + StartDiscoveryCallable(Ptr> base_pcp_handler, + Ptr> client_proxy, + const string& service_id, + const DiscoveryOptions& options, + Ptr discovery_listener) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + options_(options), + discovery_listener_(discovery_listener) {} + + ExceptionOr call() override { + // Ask the implementation to attempt to start discovery. + ScopedPtr::StartOperationResult>> + result(base_pcp_handler_->startDiscoveryImpl(client_proxy_, service_id_, + options_)); + if (Status::SUCCESS != result->status_) { + return ExceptionOr(result->status_); + } + + // Now that we've succeeded, mark the client as discovering and clear out + // any old endpoints we had discovered. + // Previous discovery_options_ is not destroyed here because stopDiscovery() + // is expected to be called before startDiscovery(). + base_pcp_handler_->discovery_options_ = + MakePtr(new DiscoveryOptions(options_)); + for (typename BasePCPHandler::DiscoveredEndpointsMap::iterator + it = base_pcp_handler_->discovered_endpoints_.begin(); + it != base_pcp_handler_->discovered_endpoints_.end(); it++) { + it->second.destroy(); + } + base_pcp_handler_->discovered_endpoints_.clear(); + client_proxy_->startedDiscovery( + service_id_, base_pcp_handler_->getStrategy(), + discovery_listener_.release(), result->mediums_); + return ExceptionOr(Status::SUCCESS); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string service_id_; + const DiscoveryOptions options_; + ScopedPtr> discovery_listener_; +}; + +template +class StopDiscoveryRunnable : public Runnable { + public: + StopDiscoveryRunnable(Ptr> base_pcp_handler, + Ptr> client_proxy, + Ptr latch) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + latch_(latch) {} + + void run() override { + base_pcp_handler_->stopDiscoveryImpl(client_proxy_); + client_proxy_->stoppedDiscovery(); + // base_pcp_handler_->discovery_options_ is purposefully not destroyed here. + latch_->countDown(); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + Ptr latch_; +}; + +template +class RequestConnectionRunnable : public Runnable { + public: + RequestConnectionRunnable( + Ptr> base_pcp_handler, + Ptr> client_proxy, + const string& local_endpoint_name, const string& endpoint_id, + Ptr connection_lifecycle_listener, + Ptr> result) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name), + endpoint_id_(endpoint_id), + connection_lifecycle_listener_(connection_lifecycle_listener), + result_(result) {} + + void run() override { + std::int64_t start_time_millis = + base_pcp_handler_->system_clock_->elapsedRealtime(); + + // If we already have a pending connection, then we shouldn't allow any more + // outgoing connections to this endpoint. + typename BasePCPHandler::PendingConnectionsMap::iterator it = + base_pcp_handler_->pending_connections_.find(endpoint_id_); + if (it != base_pcp_handler_->pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + result_->set(Status::ALREADY_CONNECTED_TO_ENDPOINT); + return; + } + + // If our child class says we can't send any more outgoing connections, + // listen to them. + if (base_pcp_handler_->shouldEnforceTopologyConstraints() && + !base_pcp_handler_->canSendOutgoingConnection(client_proxy_)) { + // TODO(tracyzhou): Add logging. + result_->set(Status::OUT_OF_ORDER_API_CALL); + return; + } + + Ptr::DiscoveredEndpoint> endpoint = + base_pcp_handler_->getDiscoveredEndpoint(endpoint_id_); + if (endpoint.isNull()) { + // TODO(tracyzhou): Add logging. + result_->set(Status::ENDPOINT_UNKNOWN); + return; + } + + typename BasePCPHandler::ConnectImplResult connect_impl_result = + base_pcp_handler_->connectImpl(client_proxy_, endpoint); + + if (connect_impl_result.endpoint_channel.isNull()) { + // TODO(tracyzhou): Add logging + base_pcp_handler_->processPreConnectionInitiationFailure( + client_proxy_, connect_impl_result.medium, endpoint_id_, + connect_impl_result.endpoint_channel, false /* is_incoming */, + start_time_millis, connect_impl_result.status, result_); + return; + } + + ScopedPtr> scoped_endpoint_channel( + connect_impl_result.endpoint_channel); + + // TODO(tracyzhou): Add logging. + // Generate the nonce to use for this connection. + std::int32_t nonce = base_pcp_handler_->prng_.nextInt32(); + + // The first message we have to send, after connecting, is to tell the + // endpoint about ourselves. + Exception::Value write_exception = + base_pcp_handler_->writeConnectionRequestFrame( + scoped_endpoint_channel.get(), + client_proxy_->generateLocalEndpointId(), local_endpoint_name_, + nonce, base_pcp_handler_->getConnectionMediumsByPriority()); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + base_pcp_handler_->processPreConnectionInitiationFailure( + client_proxy_, scoped_endpoint_channel->getMedium(), endpoint_id_, + scoped_endpoint_channel.get(), false /* is_incoming */, + start_time_millis, Status::ENDPOINT_IO_ERROR, result_); + return; + } + } + + // TODO(tracyzhou): Add logging. + + // We've successfully connected to the device, and are now about to jump on + // to the EncryptionRunner thread to start running our encryption protocol. + // We'll mark ourselves as pending in case we get another call to + // requestConnection or onIncomingConnection, so that we can cancel the + // connection if needed. + Ptr endpoint_channel = + base_pcp_handler_->pending_connections_ + .insert(std::make_pair( + endpoint_id_, + BasePCPHandler::PendingConnectionInfo:: + newOutgoingPendingConnectionInfo( + client_proxy_, endpoint->getEndpointName(), + scoped_endpoint_channel.release(), nonce, + start_time_millis, + connection_lifecycle_listener_.release(), result_))) + .first->second->endpoint_channel_.get(); + + // Next, we'll set up encryption. When it's done, our future will return and + // requestConnection() will finish. + base_pcp_handler_->encryption_runner_->startClient( + client_proxy_, endpoint_id_, endpoint_channel, + MakePtr(new typename BasePCPHandler::ResultListenerFacade( + base_pcp_handler_))); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string local_endpoint_name_; + const string endpoint_id_; + ScopedPtr> connection_lifecycle_listener_; + Ptr> result_; +}; + +template +class AcceptConnectionCallable : public Callable { + public: + AcceptConnectionCallable(Ptr> base_pcp_handler, + Ptr> client_proxy, + const string& endpoint_id, + Ptr payload_listener) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + payload_listener_(payload_listener) {} + + ExceptionOr call() override { + // TODO(tracyzhou): Add logging. + typename BasePCPHandler::PendingConnectionsMap::iterator it = + base_pcp_handler_->pending_connections_.find(endpoint_id_); + if (it == base_pcp_handler_->pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(Status::ENDPOINT_UNKNOWN); + } + Ptr::PendingConnectionInfo> + connection_info = it->second; + + // By this point in the flow, connection_info->endpoint_channel_ has been + // nulled out because ownership of that EndpointChannel was passed on to + // EndpointChannelManager via a call to + // EndpointManager::registerEndpoint(), so we now need to get access to the + // EndpointChannel from the authoritative owner. + ScopedPtr> scoped_endpoint_channel( + base_pcp_handler_->endpoint_channel_manager_->getChannelForEndpoint( + endpoint_id_)); + if (scoped_endpoint_channel.isNull()) { + // TODO(reznor): Add logging. + base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, + endpoint_id_); + return ExceptionOr(Status::ENDPOINT_UNKNOWN); + } + + Exception::Value write_exception = scoped_endpoint_channel->write( + OfflineFrames::forConnectionResponse(Status::SUCCESS)); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + // TODO(tracyzhou): Add logging. + base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, + endpoint_id_); + return ExceptionOr(Status::ENDPOINT_IO_ERROR); + } + } + + // TODO(tracyzhou): Add logging. + connection_info->localEndpointAcceptedConnection( + endpoint_id_, payload_listener_.release()); + base_pcp_handler_->evaluateConnectionResult( + client_proxy_, endpoint_id_, false /* can_close_immediately */); + return ExceptionOr(Status::SUCCESS); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string endpoint_id_; + ScopedPtr> payload_listener_; +}; + +template +class RejectConnectionCallable : public Callable { + public: + RejectConnectionCallable(Ptr> base_pcp_handler, + Ptr> client_proxy, + const string& endpoint_id) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id) {} + + ExceptionOr call() override { + // TODO(tracyzhou): Add logging. + typename BasePCPHandler::PendingConnectionsMap::iterator it = + base_pcp_handler_->pending_connections_.find(endpoint_id_); + if (it == base_pcp_handler_->pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(Status::ENDPOINT_UNKNOWN); + } + Ptr::PendingConnectionInfo> + connection_info = it->second; + + // By this point in the flow, connection_info->endpoint_channel_ has been + // nulled out because ownership of that EndpointChannel was passed on to + // EndpointChannelManager via a call to + // EndpointManager::registerEndpoint(), so we now need to get access to the + // EndpointChannel from the authoritative owner. + ScopedPtr> scoped_endpoint_channel( + base_pcp_handler_->endpoint_channel_manager_->getChannelForEndpoint( + endpoint_id_)); + if (scoped_endpoint_channel.isNull()) { + // TODO(reznor): Add logging. + base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, + endpoint_id_); + return ExceptionOr(Status::ENDPOINT_UNKNOWN); + } + + Exception::Value write_exception = scoped_endpoint_channel->write( + OfflineFrames::forConnectionResponse(Status::CONNECTION_REJECTED)); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + // TODO(tracyzhou): Add logging. + base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, + endpoint_id_); + return ExceptionOr(Status::ENDPOINT_IO_ERROR); + } + } + + // TODO(tracyzhou): Add logging. + connection_info->localEndpointRejectedConnection(endpoint_id_); + base_pcp_handler_->evaluateConnectionResult( + client_proxy_, endpoint_id_, false /* can_close_immediately */); + return ExceptionOr(Status::SUCCESS); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string endpoint_id_; +}; + +class ReadConnectionRequestCancelableAlarmRunnable : public Runnable { + public: + explicit ReadConnectionRequestCancelableAlarmRunnable( + Ptr endpoint_channel) + : endpoint_channel_(endpoint_channel) {} + + void run() override { + // TODO(tracyzhou): Add logging. + endpoint_channel_->close(); + } + + private: + Ptr endpoint_channel_; +}; + +template +class EvaluateConnectionResultCancelableAlarmRunnable : public Runnable { + public: + EvaluateConnectionResultCancelableAlarmRunnable( + Ptr> endpoint_manager, + Ptr> client_proxy, const string& endpoint_id) + : endpoint_manager_(endpoint_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id) {} + + void run() override { + // TODO(tracyzhou): Add logging. + endpoint_manager_->discardEndpoint(client_proxy_, endpoint_id_); + } + + private: + Ptr> endpoint_manager_; + Ptr> client_proxy_; + const string endpoint_id_; +}; + +template +class ProcessEndpointDisconnectionRunnable : public Runnable { + public: + ProcessEndpointDisconnectionRunnable( + Ptr> base_pcp_handler, + Ptr> client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + process_disconnection_barrier_(process_disconnection_barrier) {} + + void run() override { + typename BasePCPHandler< + Platform>::PendingRejectedConnectionCloseAlarmsMap::iterator it = + base_pcp_handler_->pending_rejected_connection_close_alarms_.find( + endpoint_id_); + if (it != + base_pcp_handler_->pending_rejected_connection_close_alarms_.end()) { + it->second->cancel(); + it->second.destroy(); + base_pcp_handler_->pending_rejected_connection_close_alarms_.erase(it); + } + base_pcp_handler_->processPreConnectionResultFailure(client_proxy_, + endpoint_id_); + + process_disconnection_barrier_->countDown(); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string endpoint_id_; + Ptr process_disconnection_barrier_; +}; + +template +class OnConnectionResponseRunnable : public Runnable { + public: + OnConnectionResponseRunnable(Ptr> base_pcp_handler, + Ptr> client_proxy, + const string& endpoint_id, + ConstPtr offline_frame, + Ptr latch) + : base_pcp_handler_(base_pcp_handler), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + offline_frame_(offline_frame), + latch_(latch) {} + + void run() override { + // TODO(tracyzhou): Add logging. + + if (client_proxy_->hasRemoteEndpointResponded(endpoint_id_)) { + // TODO(tracyzhou): Add logging. + return; + } + + const ConnectionResponseFrame& connection_response = + offline_frame_->v1().connection_response(); + + // TODO(tracyzhou): Assign int values to Status. + if (Status::SUCCESS == connection_response.status()) { + // TODO(tracyzhou): Add logging. + client_proxy_->remoteEndpointAcceptedConnection(endpoint_id_); + } else { + // TODO(tracyzhou): Add logging. + client_proxy_->remoteEndpointRejectedConnection(endpoint_id_); + } + + base_pcp_handler_->evaluateConnectionResult( + client_proxy_, endpoint_id_, + /* can_close_immediately= */ true); + + latch_->countDown(); + } + + private: + Ptr> base_pcp_handler_; + Ptr> client_proxy_; + const string endpoint_id_; + ScopedPtr> offline_frame_; + Ptr latch_; +}; + +template +class OnEncryptionSuccessRunnable : public Runnable { + public: + OnEncryptionSuccessRunnable(Ptr> base_pcp_handler, + const string& endpoint_id, + Ptr ukey2_handshake, + const string& authentication_token, + ConstPtr raw_authentication_token) + : base_pcp_handler_(base_pcp_handler), + endpoint_id_(endpoint_id), + ukey2_handshake_(ukey2_handshake), + authentication_token_(authentication_token), + raw_authentication_token_(raw_authentication_token) {} + + void run() override { + // Quick fail if we've been removed from pending connections while we were + // busy running UKEY2. + typename BasePCPHandler::PendingConnectionsMap::iterator it = + base_pcp_handler_->pending_connections_.find(endpoint_id_); + if (it == base_pcp_handler_->pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + return; + } + + Ptr::PendingConnectionInfo> + connection_info = it->second; + connection_info->setUKey2Handshake(ukey2_handshake_.release()); + // TODO(tracyzhou): Add logging. + + // Set ourselves up so that we receive all acceptance/rejection messages + base_pcp_handler_->endpoint_manager_->registerIncomingOfflineFrameProcessor( + V1Frame::CONNECTION_RESPONSE, base_pcp_handler_); + + // Now we register our endpoint so that we can listen for both sides to + // accept. + base_pcp_handler_->endpoint_manager_->registerEndpoint( + connection_info->client_proxy_, endpoint_id_, + connection_info->remote_endpoint_name_, authentication_token_, + raw_authentication_token_.release(), connection_info->is_incoming_, + connection_info->endpoint_channel_.release(), + connection_info->connection_lifecycle_listener_.release()); + + if (!connection_info->request_connection_result_.isNull()) { + connection_info->request_connection_result_->set(Status::SUCCESS); + connection_info->request_connection_result_.clear(); + } + } + + private: + Ptr> base_pcp_handler_; + const string endpoint_id_; + ScopedPtr> ukey2_handshake_; + const string authentication_token_; + ScopedPtr> raw_authentication_token_; +}; + +template +class OnEncryptionFailureRunnable : public Runnable { + public: + OnEncryptionFailureRunnable(Ptr> base_pcp_handler, + const string& endpoint_id, + Ptr endpoint_channel) + : base_pcp_handler_(base_pcp_handler), + endpoint_id_(endpoint_id), + endpoint_channel_(endpoint_channel) {} + + void run() override { + typename BasePCPHandler::PendingConnectionsMap::iterator it = + base_pcp_handler_->pending_connections_.find(endpoint_id_); + if (it == base_pcp_handler_->pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + return; + } + + Ptr::PendingConnectionInfo> + connection_info = it->second; + // We had a bug here, caused by a race with EncryptionRunner. We now verify + // the EndpointChannel to avoid it. In a simultaneous connection, we clean + // up one of the two EndpointChannels and then update our pendingConnections + // with the winning channel's state. Closing a channel that was in the + // middle of EncryptionRunner would trigger onEncryptionFailed, and, since + // the map had already updated with the winning EndpointChannel, we closed + // it too by accident. + if (!endpointChannelsAreEqual(endpoint_channel_, + connection_info->endpoint_channel_.get())) { + // TODO(tracyzhou): Add logging. + return; + } + + base_pcp_handler_->processPreConnectionInitiationFailure( + connection_info->client_proxy_, + connection_info->endpoint_channel_->getMedium(), endpoint_id_, + connection_info->endpoint_channel_.get(), connection_info->is_incoming_, + connection_info->start_time_millis_, Status::ENDPOINT_IO_ERROR, + connection_info->request_connection_result_); + connection_info->request_connection_result_.clear(); + } + + private: + static bool endpointChannelsAreEqual(Ptr lhs, + Ptr rhs) { + return (lhs->getType() == rhs->getType()) && + (lhs->getName() == rhs->getName()) && + (lhs->getMedium() == rhs->getMedium()); + } + + Ptr> base_pcp_handler_; + const string endpoint_id_; + Ptr endpoint_channel_; +}; + +} // namespace base_pcp_handler + +template +const std::int64_t + BasePCPHandler::kConnectionRequestReadTimeoutMillis = + 2 * 1000; // 2 seconds +template +const std::int64_t + BasePCPHandler::kRejectedConnectionCloseDelayMillis = + 2 * 1000; // 2 seconds + +template +BasePCPHandler::BasePCPHandler( + Ptr> endpoint_manager, + Ptr> endpoint_channel_manager, + Ptr> bandwidth_upgrade_manager) + : endpoint_manager_(endpoint_manager), + endpoint_channel_manager_(endpoint_channel_manager), + bandwidth_upgrade_manager_(bandwidth_upgrade_manager), + bandwidth_upgrade_medium_(Platform::createAtomicReference( + proto::connections::Medium::UNKNOWN_MEDIUM)), + alarm_executor_(Platform::createScheduledExecutor()), + serial_executor_(Platform::createSingleThreadExecutor()), + system_clock_(Platform::createSystemClock()), + prng_(), + pending_connections_(), + discovered_endpoints_(), + pending_rejected_connection_close_alarms_(), + advertising_options_(), + discovery_options_(), + encryption_runner_(MakePtr(new EncryptionRunner())) {} + +template +BasePCPHandler::~BasePCPHandler() { + // TODO(reznor): + // logger.atDebug().log("Initiating shutdown of PCPHandler(%s).", + // getStrategy().getName()); + + // Unregister ourselves from the IncomingOfflineFrameProcessors. + endpoint_manager_->unregisterIncomingOfflineFrameProcessor( + V1Frame::CONNECTION_RESPONSE, MakePtr(this)); + + encryption_runner_.destroy(); + + // Stop all the ongoing Runnables (as gracefully as possible). + serial_executor_->shutdown(); + alarm_executor_->shutdown(); + + // With the alarmExecutor shut down already, we can safely clear out our + // pending alarms. + for (typename PendingRejectedConnectionCloseAlarmsMap::iterator it = + pending_rejected_connection_close_alarms_.begin(); + it != pending_rejected_connection_close_alarms_.end(); it++) { + it->second.destroy(); + } + pending_rejected_connection_close_alarms_.clear(); + + for (typename DiscoveredEndpointsMap::iterator it = + discovered_endpoints_.begin(); + it != discovered_endpoints_.end(); it++) { + it->second.destroy(); + } + discovered_endpoints_.clear(); + + // Unblock all Futures that were stored in our pendingConnections. + for (typename PendingConnectionsMap::iterator it = + pending_connections_.begin(); + it != pending_connections_.end(); it++) { + it->second.destroy(); + } + pending_connections_.clear(); + + // TODO(reznor): + // logger.atVerbose().log("PCPHandler(%s) has shut down.", + // getStrategy().getName()); +} + +template +Status::Value BasePCPHandler::startAdvertising( + Ptr> client_proxy, const string& service_id, + const string& local_endpoint_name, + const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) { + ScopedPtr>> result( + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::StartAdvertisingCallable( + MakePtr(this), client_proxy, service_id, local_endpoint_name, + advertising_options, connection_lifecycle_listener)))); + return waitForResult("startAdvertising(" + local_endpoint_name + ")", + client_proxy->getClientId(), result.get()); +} + +template +void BasePCPHandler::stopAdvertising( + Ptr> client_proxy) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::StopAdvertisingRunnable( + MakePtr(this), client_proxy, latch.get()))); + waitForLatch("stopAdvertising", latch.get()); +} + +template +Status::Value BasePCPHandler::startDiscovery( + Ptr> client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) { + ScopedPtr>> result( + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::StartDiscoveryCallable( + MakePtr(this), client_proxy, service_id, discovery_options, + discovery_listener)))); + return waitForResult("startDiscovery(" + service_id + ")", + client_proxy->getClientId(), result.get()); +} + +template +void BasePCPHandler::stopDiscovery( + Ptr> client_proxy) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::StopDiscoveryRunnable( + MakePtr(this), client_proxy, latch.get()))); + waitForLatch("stopDiscovery", latch.get()); +} + +template +Status::Value BasePCPHandler::requestConnection( + Ptr> client_proxy, const string& local_endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener) { + ScopedPtr>> result( + Platform::template createSettableFuture()); + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::RequestConnectionRunnable( + MakePtr(this), client_proxy, local_endpoint_name, endpoint_id, + connection_lifecycle_listener, result.get()))); + return waitForResult("requestConnection(" + endpoint_id + ")", + client_proxy->getClientId(), result.get()); +} + +template +Status::Value BasePCPHandler::acceptConnection( + Ptr> client_proxy, const string& endpoint_id, + Ptr payload_listener) { + ScopedPtr>> result( + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::AcceptConnectionCallable( + MakePtr(this), client_proxy, endpoint_id, payload_listener)))); + return waitForResult("acceptConnection(" + endpoint_id + ")", + client_proxy->getClientId(), result.get()); +} + +template +Status::Value BasePCPHandler::rejectConnection( + Ptr> client_proxy, const string& endpoint_id) { + ScopedPtr>> result( + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::RejectConnectionCallable( + MakePtr(this), client_proxy, endpoint_id)))); + return waitForResult("rejectConnection(" + endpoint_id + ")", + client_proxy->getClientId(), result.get()); +} + +template +proto::connections::Medium +BasePCPHandler::getBandwidthUpgradeMedium() { + return bandwidth_upgrade_medium_->get(); +} + +template +void BasePCPHandler::processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr> to_client_proxy, + proto::connections::Medium current_medium) { + onConnectionResponse(to_client_proxy, from_endpoint_id, offline_frame); +} + +template +void BasePCPHandler::processEndpointDisconnection( + Ptr> client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) { + runOnPCPHandlerThread(MakePtr( + new base_pcp_handler::ProcessEndpointDisconnectionRunnable( + MakePtr(this), client_proxy, endpoint_id, + process_disconnection_barrier))); +} + +template +void BasePCPHandler::onEncryptionSuccessImpl( + const string& endpoint_id, Ptr ukey2_handshake, + const string& authentication_token, + ConstPtr raw_authentication_token) { + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::OnEncryptionSuccessRunnable( + MakePtr(this), endpoint_id, ukey2_handshake, authentication_token, + raw_authentication_token))); +} + +template +void BasePCPHandler::onEncryptionFailureImpl( + const string& endpoint_id, Ptr channel) { + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::OnEncryptionFailureRunnable( + MakePtr(this), endpoint_id, channel))); +} + +template +void BasePCPHandler::runOnPCPHandlerThread(Ptr runnable) { + serial_executor_->execute(runnable); +} + +template +Ptr BasePCPHandler::getAdvertisingOptions() { + return advertising_options_; +} + +template +void BasePCPHandler::onEndpointFound( + Ptr> client_proxy, + Ptr::DiscoveredEndpoint> endpoint) { + ScopedPtr::DiscoveredEndpoint>> + scoped_endpoint(endpoint); + + // Check if we've seen this endpoint ID before. + Ptr::DiscoveredEndpoint> + previously_discovered_endpoint = + getDiscoveredEndpoint(scoped_endpoint->getEndpointId()); + + if (previously_discovered_endpoint.isNull()) { + const string endpoint_id = scoped_endpoint->getEndpointId(); + const string service_id = scoped_endpoint->getServiceId(); + const string endpoint_name = scoped_endpoint->getEndpointName(); + const proto::connections::Medium medium = scoped_endpoint->getMedium(); + + // If this is the first medium we've discovered this endpoint over, then add + // it to the map. + discovered_endpoints_.insert( + std::make_pair(endpoint_id, scoped_endpoint.release())); + + // And, as it's the first time, report it to the client. + client_proxy->onEndpointFound(endpoint_id, service_id, endpoint_name, + medium); + } else if (previously_discovered_endpoint->getEndpointName() != + scoped_endpoint->getEndpointName()) { + // If we've already seen this endpoint before, check if there was a name + // change. If there was, report the previous endpoint as lost. + // TODO(tracyzhou): Add logging. + onEndpointLost(client_proxy, previously_discovered_endpoint); + onEndpointFound(client_proxy, scoped_endpoint.release()); + } else { + // Otherwise, we need to see if the medium we discovered the endpoint over + // this time is better than the medium we originally discovered the endpoint + // over. + if (isPreferred(scoped_endpoint.get(), previously_discovered_endpoint)) { + base_pcp_handler::eraseOwnedPtrFromMap(discovered_endpoints_, + scoped_endpoint->getEndpointId()); + discovered_endpoints_.insert(std::make_pair( + scoped_endpoint->getEndpointId(), scoped_endpoint.release())); + } + } +} + +template +void BasePCPHandler::onEndpointLost( + Ptr> client_proxy, + Ptr::DiscoveredEndpoint> endpoint) { + ScopedPtr::DiscoveredEndpoint>> + scoped_endpoint(endpoint); + + // Look up the DiscoveredEndpoint we have in our cache. + Ptr::DiscoveredEndpoint> + discoveredEndpoint = + getDiscoveredEndpoint(scoped_endpoint->getEndpointId()); + if (discoveredEndpoint.isNull()) { + // TODO(tracyzhou): Add logging. + return; + } + + // Validate that the cached endpoint has the same name as the one reported as + // onLost. If the name differs, then no-op. This likely means that the remote + // device changed their name. We reported onFound for the new name and are + // just now figuring out that we lost the old name. + if (discoveredEndpoint->getEndpointName() != + scoped_endpoint->getEndpointName()) { + // TODO(tracyzhou): Add logging. + return; + } + + base_pcp_handler::eraseOwnedPtrFromMap(discovered_endpoints_, + scoped_endpoint->getEndpointId()); + client_proxy->onEndpointLost(scoped_endpoint->getServiceId(), + scoped_endpoint->getEndpointId()); +} + +template +bool BasePCPHandler::hasOutgoingConnections( + Ptr> client_proxy) { + for (typename PendingConnectionsMap::iterator it = + pending_connections_.begin(); + it != pending_connections_.end(); it++) { + if (!it->second->is_incoming_) { + return true; + } + } + return client_proxy->getNumOutgoingConnections() > 0; +} + +template +bool BasePCPHandler::hasIncomingConnections( + Ptr> client_proxy) { + for (typename PendingConnectionsMap::iterator it = + pending_connections_.begin(); + it != pending_connections_.end(); it++) { + if (it->second->is_incoming_) { + return true; + } + } + return client_proxy->getNumIncomingConnections() > 0; +} + +template +bool BasePCPHandler::canSendOutgoingConnection( + Ptr> client_proxy) { + return true; +} + +template +bool BasePCPHandler::canReceiveIncomingConnection( + Ptr> client_proxy) { + return true; +} + +template +Exception::Value BasePCPHandler::writeConnectionRequestFrame( + Ptr endpoint_channel, const string& local_endpoint_id, + const string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums) { + Exception::Value write_exception = + endpoint_channel->write(OfflineFrames::forConnectionRequest( + local_endpoint_id, local_endpoint_name, nonce, supported_mediums)); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + return write_exception; + } + } + + return Exception::NONE; +} + +template +template +Ptr> BasePCPHandler::runOnPCPHandlerThread( + Ptr> callable) { + return serial_executor_->submit(callable); +} + +template +void BasePCPHandler::onConnectionResponse( + Ptr> client_proxy, const string& endpoint_id, + ConstPtr connection_response_offline_frame) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnPCPHandlerThread( + MakePtr(new base_pcp_handler::OnConnectionResponseRunnable( + MakePtr(this), client_proxy, endpoint_id, + connection_response_offline_frame, latch.get()))); + waitForLatch("onConnectionResponse()", latch.get()); +} + +template +bool BasePCPHandler::isPreferred( + Ptr::DiscoveredEndpoint> new_endpoint, + Ptr::DiscoveredEndpoint> old_endpoint) { + std::vector mediums = + getConnectionMediumsByPriority(); + // As we iterate through the list of mediums, we see if we run into the new + // endpoint's medium or the old endpoint's medium first. + for (std::vector::const_iterator it = + mediums.begin(); + it != mediums.end(); it++) { + const proto::connections::Medium& medium = *it; + if (medium == new_endpoint->getMedium()) { + // The new endpoint's medium came first. It's preferred! + return true; + } + + if (medium == old_endpoint->getMedium()) { + // The old endpoint's medium came first. Stick with the old endpoint! + return false; + } + } + // TODO(tracyzhou): Add logging. + assert(false); + return false; +} + +template +bool BasePCPHandler::shouldEnforceTopologyConstraints() { + // Topology constraints only matter for the advertiser. + // For discoverers, we'll always enforce them. + if (getAdvertisingOptions().isNull()) { + return true; + } + + return getAdvertisingOptions()->enforce_topology_constraints; +} + +template +bool BasePCPHandler::autoUpgradeBandwidth() { + if (getAdvertisingOptions().isNull()) { + return true; + } + + return getAdvertisingOptions()->auto_upgrade_bandwidth; +} + +template +Exception::Value BasePCPHandler::onIncomingConnection( + Ptr> client_proxy, const string& remote_device_name, + Ptr endpoint_channel, proto::connections::Medium medium) { + ScopedPtr> scoped_endpoint_channel(endpoint_channel); + + std::int64_t start_time_millis = system_clock_->elapsedRealtime(); + + // Fixes an NPE in ClientProxy.onConnectionResult. The crash happened when + // the client stopped advertising and we nulled out state, followed by an + // incoming connection where we attempted to check that state. + if (!client_proxy->isAdvertising()) { + NEARBY_LOG(WARNING, + "Ignoring incoming connection because client %" PRId64 + " is no longer advertising.", + client_proxy->getClientId()); + return Exception::IO; + } + + // Endpoints connecting to us will always tell us about themselves first. + ExceptionOr> read_offline_frame = + readConnectionRequestFrame(scoped_endpoint_channel.get()); + + if (!read_offline_frame.ok()) { + if (Exception::IO == read_offline_frame.exception()) { + // TODO(tracyzhou): Add logging. + processPreConnectionInitiationFailure( + client_proxy, medium, "", scoped_endpoint_channel.get(), + /* is_incoming= */ true, start_time_millis, Status::ERROR, + Ptr>()); + return Exception::NONE; + } + } + + // TODO(tracyzhou): Add logging. + ScopedPtr> scoped_read_offline_frame( + read_offline_frame.result()); + + const ConnectionRequestFrame& connection_request = + scoped_read_offline_frame->v1().connection_request(); + if (client_proxy->isConnectedToEndpoint(connection_request.endpoint_id())) { + return Exception::IO; + } + + // If we've already sent out a connection request to this endpoint, then this + // is where we need to decide which connection to break. + if (breakTie(client_proxy, connection_request.endpoint_id(), + connection_request.nonce(), scoped_endpoint_channel.get())) { + return Exception::NONE; + } + + // If our child class says we can't accept any more incoming connections, + // listen to them. + if (shouldEnforceTopologyConstraints() && + !canReceiveIncomingConnection(client_proxy)) { + return Exception::IO; + } + + // We've successfully connected to the device, and are now about to jump on to + // the EncryptionRunner thread to start running our encryption protocol. We'll + // mark ourselves as pending in case we get another call to requestConnection + // or onIncomingConnection, so that we can cancel the connection if needed. + endpoint_channel = + pending_connections_ + .insert(std::make_pair( + connection_request.endpoint_id(), + PendingConnectionInfo::newIncomingPendingConnectionInfo( + client_proxy, connection_request.endpoint_name(), + scoped_endpoint_channel.release(), connection_request.nonce(), + start_time_millis, advertising_connection_lifecycle_listener_, + OfflineFrames::connectionRequestMediumsToMediums( + connection_request)))) + .first->second->endpoint_channel_.get(); + + // Next, we'll set up encryption. + encryption_runner_->startServer( + client_proxy, connection_request.endpoint_id(), endpoint_channel, + MakePtr(new typename BasePCPHandler::ResultListenerFacade( + MakePtr(this)))); + return Exception::NONE; +} + +template +bool BasePCPHandler::breakTie(Ptr> client_proxy, + const string& endpoint_id, + std::int32_t incoming_nonce, + Ptr endpoint_channel) { + typename PendingConnectionsMap::iterator it = + pending_connections_.find(endpoint_id); + if (it != pending_connections_.end()) { + Ptr::PendingConnectionInfo> + pending_connection_info = it->second; + + // TODO(tracyzhou): Add logging. + + // Break the lowest connection. In the (extremely) rare case of a tie, break + // both. + if (pending_connection_info->nonce_ > incoming_nonce) { + // Our connection won! Clean up their connection. + endpoint_channel->close(); + + // TODO(tracyzhou): Add logging. + return true; + } else if (pending_connection_info->nonce_ < incoming_nonce) { + // Aw, we lost. Clean up our connection, and then we'll let their + // connection continue on. + processTieBreakLoss(client_proxy, endpoint_id, pending_connection_info); + + // TODO(tracyzhou): Add logging. + } else { + // Oh. Huh. We both lost. Well, that's awkward. We'll clean up both and + // just force the devices to retry. + endpoint_channel->close(); + + processTieBreakLoss(client_proxy, endpoint_id, pending_connection_info); + + // TODO(tracyzhou): Add logging. + return true; + } + } + + return false; +} + +template +void BasePCPHandler::processTieBreakLoss( + Ptr> client_proxy, const string& endpoint_id, + Ptr connection_info) { + processPreConnectionInitiationFailure( + client_proxy, connection_info->endpoint_channel_->getMedium(), + endpoint_id, connection_info->endpoint_channel_.get(), + connection_info->is_incoming_, connection_info->start_time_millis_, + Status::ENDPOINT_IO_ERROR, connection_info->request_connection_result_); + connection_info->request_connection_result_.clear(); + processPreConnectionResultFailure(client_proxy, endpoint_id); +} + +template +void BasePCPHandler::initiateBandwidthUpgrade( + Ptr> client_proxy, const string& endpoint_id, + const std::vector& supported_mediums) { + // When we successfully connect to a remote endpoint and a bandwidth upgrade + // medium has not yet been decided, we'll pick the highest bandwidth medium + // supported by both us and the remote endpoint. Once we pick a medium, all + // future connections will use it too. eg. If we chose Wifi LAN, we'll attempt + // to upgrade the 2nd, 3rd, etc remote endpoints with Wifi LAN even if they're + // on a different network (or had a better medium). This is a quick and easy + // way to prevent mediums, like Wifi Hotspot, from interfering with active + // connections (although it's suboptimal for bandwidth throughput). When all + // endpoints disconnect, we reset the bandwidth upgrade medium. + if (bandwidth_upgrade_medium_->get() == + proto::connections::Medium::UNKNOWN_MEDIUM) { + bandwidth_upgrade_medium_->set(chooseBestUpgradeMedium(supported_mediums)); + } + + if (autoUpgradeBandwidth() && (bandwidth_upgrade_medium_->get() != + proto::connections::Medium::UNKNOWN_MEDIUM)) { + bandwidth_upgrade_manager_->initiateBandwidthUpgradeForEndpoint( + client_proxy, endpoint_id, bandwidth_upgrade_medium_->get()); + } +} + +template +proto::connections::Medium BasePCPHandler::chooseBestUpgradeMedium( + const std::vector& their_supported_mediums) { + // If the remote side did not report their supported mediums, choose an + // appropriate default. + std::vector their_mediums = + their_supported_mediums; + if (their_supported_mediums.empty()) { + their_mediums.push_back(getDefaultUpgradeMedium()); + } + + // Otherwise, pick the best medium we support. + std::vector my_mediums = + getConnectionMediumsByPriority(); + for (std::vector::iterator my_medium = + my_mediums.begin(); + my_medium != my_mediums.end(); my_medium++) { + for (std::vector::iterator their_medium = + their_mediums.begin(); + their_medium != their_mediums.end(); their_medium++) { + if (*my_medium == *their_medium) { + return *my_medium; + } + } + } + + return proto::connections::Medium::UNKNOWN_MEDIUM; +} + +template +void BasePCPHandler::processPreConnectionInitiationFailure( + Ptr> client_proxy, proto::connections::Medium medium, + const string& endpoint_id, Ptr endpoint_channel, + bool is_incoming, std::int64_t start_time_millis, Status::Value status, + Ptr> request_connection_result) { + // Only *remove* this -- as opposed to *destroying* it by invoking + // eraseOwnedPtrFromMap() -- because if endpoint_channel is non-null, it's + // owned by the PendingConnectionInfo in pending_connections_, which means + // destroying the PendingConnectionInfo right now will lead to a dangling + // pointer access when we invoke endpoint_channel->close() below. + ScopedPtr> failed_pending_connection( + base_pcp_handler::removeOwnedPtrFromMap(pending_connections_, + endpoint_id)); + + if (!endpoint_channel.isNull()) { + endpoint_channel->close(); + } + + if (!request_connection_result.isNull()) { + request_connection_result->set(status); + } +} + +template +void BasePCPHandler::processPreConnectionResultFailure( + Ptr> client_proxy, const string& endpoint_id) { + base_pcp_handler::eraseOwnedPtrFromMap(pending_connections_, endpoint_id); + endpoint_manager_->discardEndpoint(client_proxy, endpoint_id); + client_proxy->onConnectionResult(endpoint_id, Status::ERROR); +} + +template +Ptr::DiscoveredEndpoint> +BasePCPHandler::getDiscoveredEndpoint(const string& endpoint_id) { + typename DiscoveredEndpointsMap::iterator it = + discovered_endpoints_.find(endpoint_id); + if (it == discovered_endpoints_.end()) { + return Ptr::DiscoveredEndpoint>(); + } + return it->second; +} + +template +void BasePCPHandler::evaluateConnectionResult( + Ptr> client_proxy, const string& endpoint_id, + bool can_close_immediately) { + // Short-circuit immediately if we're not in an actionable state yet. We will + // be called again once the other side has made their decision. + if (!client_proxy->isConnectionAccepted(endpoint_id) && + !client_proxy->isConnectionRejected(endpoint_id)) { + if (!client_proxy->hasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + } else if (!client_proxy->hasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + } + return; + } + + // Clean up the endpoint channel from our list of 'pending' connections. It's + // no longer pending. + typename PendingConnectionsMap::iterator it = + pending_connections_.find(endpoint_id); + if (it == pending_connections_.end()) { + // TODO(tracyzhou): Add logging. + return; + } + + ScopedPtr::PendingConnectionInfo>> + connection_info(it->second); + pending_connections_.erase(it); + + bool is_connection_accepted = client_proxy->isConnectionAccepted(endpoint_id); + + Status::Value response_code; + if (is_connection_accepted) { + // TODO(tracyzhou): Add logging. + response_code = Status::SUCCESS; + + // Both sides have accepted, so we can now start talking over encrypted + // channels + std::unique_ptr encryption_context = + connection_info->ukey2_handshake_->ToConnectionContext(); + // Java code throws an HandshakeException. + if (encryption_context == nullptr) { + // TODO(tracyzhou): Add logging. + processPreConnectionResultFailure(client_proxy, endpoint_id); + return; + } + + endpoint_channel_manager_->encryptChannelForEndpoint( + endpoint_id, MakeRefCountedPtr(encryption_context.release())); + } else { + // TODO(tracyzhou): Add logging. + response_code = Status::CONNECTION_REJECTED; + } + + // Invoke the client callback to let it know of the connection result. + client_proxy->onConnectionResult(endpoint_id, response_code); + + // If the connection failed, clean everything up and short circuit. + if (!is_connection_accepted) { + // Clean up the channel in EndpointManager if it's no longer required. + if (can_close_immediately) { + endpoint_manager_->discardEndpoint(client_proxy, endpoint_id); + } else { + pending_rejected_connection_close_alarms_.insert(std::make_pair( + endpoint_id, + MakePtr(new CancelableAlarm( + "BasePCPHandler.evaluateConnectionResult() delayed close", + MakePtr( + new base_pcp_handler:: + EvaluateConnectionResultCancelableAlarmRunnable( + endpoint_manager_, client_proxy, endpoint_id)), + kRejectedConnectionCloseDelayMillis, alarm_executor_.get())))); + } + + return; + } + + // Kick off the bandwidth upgrade for incoming connections. + if (connection_info->is_incoming_) { + initiateBandwidthUpgrade(client_proxy, endpoint_id, + connection_info->supported_mediums_); + } +} + +template +ExceptionOr> +BasePCPHandler::readConnectionRequestFrame( + Ptr endpoint_channel) { + if (endpoint_channel.isNull()) { + return ExceptionOr>(Exception::IO); + } + + // To avoid a device connecting but never sending their introductory frame, we + // time out the connection after a certain amount of time. + CancelableAlarm timeout_alarm( + "PCPHandler(" + this->getStrategy().getName() + + ").readConnectionRequestFrame", + MakePtr( + new base_pcp_handler::ReadConnectionRequestCancelableAlarmRunnable( + endpoint_channel)), + kConnectionRequestReadTimeoutMillis, alarm_executor_.get()); + + // Do a blocking read to try and find the ConnectionRequestFrame + ExceptionOr> read_bytes = endpoint_channel->read(); + if (!read_bytes.ok()) { + if (Exception::IO == read_bytes.exception()) { + timeout_alarm.cancel(); + return ExceptionOr>(read_bytes.exception()); + } + } + + ScopedPtr> scoped_read_bytes(read_bytes.result()); + ExceptionOr> offline_frame = + OfflineFrames::fromBytes(scoped_read_bytes.get()); + if (!offline_frame.ok()) { + if (Exception::INVALID_PROTOCOL_BUFFER == offline_frame.exception()) { + timeout_alarm.cancel(); + // In Java code, INVALID_PROTOCOL_BUFFER is a subtype of IO exception. + return ExceptionOr>(Exception::IO); + } + } + timeout_alarm.cancel(); + + ScopedPtr> scoped_offline_frame( + offline_frame.result()); + if (V1Frame::CONNECTION_REQUEST != + OfflineFrames::getFrameType(scoped_offline_frame.get())) { + return ExceptionOr>(Exception::IO); + } + + return ExceptionOr>(scoped_offline_frame.release()); +} + +template +void BasePCPHandler::waitForLatch(const string& method_name, + Ptr latch) { + Exception::Value await_exception = latch->await(); + if (Exception::NONE != await_exception) { + if (Exception::INTERRUPTED == await_exception) { + // TODO(tracyzhou): Add logging. + // Thread.currentThread().interrupt(); + } + } +} + +template +Status::Value BasePCPHandler::waitForResult( + const string& method_name, std::int64_t client_id, + Ptr> result_future) { + ExceptionOr result = result_future->get(); + if (!result.ok()) { + Exception::Value exception = result.exception(); + if (Exception::INTERRUPTED == exception || + Exception::EXECUTION == exception) { + // TODO(tracyzhou): Add logging. + if (Exception::INTERRUPTED == exception) { + // Thread.currentThread().interrupt(); + } + return Status::ERROR; + } + } + return result.result(); +} + +///////////////////// BasePCPHandler::PendingConnectionInfo /////////////////// + +template +Ptr::PendingConnectionInfo> +BasePCPHandler::PendingConnectionInfo:: + newIncomingPendingConnectionInfo( + Ptr> client_proxy, + const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, + std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + const std::vector& supported_mediums) { + return MakePtr(new PendingConnectionInfo( + client_proxy, remote_endpoint_name, endpoint_channel, nonce, true, + start_time_millis, connection_lifecycle_listener, + Ptr>(), supported_mediums)); +} + +template +Ptr::PendingConnectionInfo> +BasePCPHandler::PendingConnectionInfo:: + newOutgoingPendingConnectionInfo( + Ptr> client_proxy, + const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, + std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + Ptr> request_connection_result) { + return MakePtr(new PendingConnectionInfo( + client_proxy, remote_endpoint_name, endpoint_channel, nonce, false, + start_time_millis, connection_lifecycle_listener, + request_connection_result, std::vector())); +} + +template +BasePCPHandler::PendingConnectionInfo::PendingConnectionInfo( + Ptr> client_proxy, const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, bool is_incoming, + std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + Ptr> request_connection_result, + const std::vector& supported_mediums) + : client_proxy_(client_proxy), + remote_endpoint_name_(remote_endpoint_name), + endpoint_channel_(endpoint_channel), + nonce_(nonce), + is_incoming_(is_incoming), + start_time_millis_(start_time_millis), + connection_lifecycle_listener_(connection_lifecycle_listener), + request_connection_result_(request_connection_result), + supported_mediums_(supported_mediums), + ukey2_handshake_() {} + +template +BasePCPHandler::PendingConnectionInfo::~PendingConnectionInfo() { + if (!request_connection_result_.isNull()) { + request_connection_result_->set(Status::ERROR); + } + + if (!endpoint_channel_.isNull()) { + endpoint_channel_->close(proto::connections::DisconnectionReason::SHUTDOWN); + } + + // Done with operational cleanup, now deallocate memory as needed. + ukey2_handshake_.destroy(); +} + +template +void BasePCPHandler::PendingConnectionInfo::setUKey2Handshake( + Ptr ukey2_handshake) { + this->ukey2_handshake_ = ukey2_handshake; +} + +template +void BasePCPHandler::PendingConnectionInfo:: + localEndpointAcceptedConnection(const string& endpoint_id, + Ptr payload_listener) { + if (!ukey2_handshake_->VerifyHandshake()) { + NEARBY_LOG( + FATAL, + "Failed to verify UKEY2 handshake with %s after accepting locally.", + endpoint_id.c_str()); + } + + client_proxy_->localEndpointAcceptedConnection(endpoint_id, payload_listener); +} + +template +void BasePCPHandler::PendingConnectionInfo:: + localEndpointRejectedConnection(const string& endpoint_id) { + client_proxy_->localEndpointRejectedConnection(endpoint_id); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h new file mode 100644 index 00000000..0d243165 --- /dev/null +++ b/cpp/core/internal/base_pcp_handler.h @@ -0,0 +1,507 @@ +#ifndef CORE_INTERNAL_BASE_PCP_HANDLER_H_ +#define CORE_INTERNAL_BASE_PCP_HANDLER_H_ + +#include +#include +#include + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/client_proxy.h" +#include "core/internal/encryption_runner.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/pcp.h" +#include "core/internal/pcp_handler.h" +#include "core/listeners.h" +#include "core/options.h" +#include "core/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/settable_future.h" +#include "platform/api/system_clock.h" +#include "platform/cancelable_alarm.h" +#include "platform/port/string.h" +#include "platform/prng.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/ukey2_handshake.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace base_pcp_handler { + +template +class StartAdvertisingCallable; +template +class StopAdvertisingRunnable; +template +class StartDiscoveryCallable; +template +class StopDiscoveryRunnable; +template +class RequestConnectionRunnable; +template +class AcceptConnectionCallable; +template +class RejectConnectionCallable; +template +class ProcessEndpointDisconnectionRunnable; +template +class OnConnectionResponseRunnable; +template +class OnEncryptionSuccessRunnable; +template +class OnEncryptionFailureRunnable; + +} // namespace base_pcp_handler + +// A base implementation of the PCPHandler interface that takes care of all +// bookkeeping and handshake protocols that are common across all PCPHandler +// implementations -- thus, every concrete PCPHandler implementation must extend +// this class, so that they can focus exclusively on the medium-specific +// operations. +template +class BasePCPHandler + : public PCPHandler, + public EndpointManager::IncomingOfflineFrameProcessor { + public: + // TODO(tracyzhou): Add SecureRandom. + BasePCPHandler( + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager); + ~BasePCPHandler() override; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + Status::Value startAdvertising( + Ptr > client_proxy, const string& service_id, + const string& local_endpoint_name, + const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) override; + void stopAdvertising(Ptr > client_proxy) override; + + Status::Value startDiscovery( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) override; + void stopDiscovery(Ptr > client_proxy) override; + + Status::Value requestConnection( + Ptr > client_proxy, const string& endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener) override; + Status::Value acceptConnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr payload_listener) override; + Status::Value rejectConnection(Ptr > client_proxy, + const string& endpoint_id) override; + + proto::connections::Medium getBandwidthUpgradeMedium() override; + + // @EndpointManagerReaderThread + void processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) override; + + // Called when an endpoint disconnects while we're waiting for both sides to + // approve/reject the connection. + // @EndpointManagerThread + void processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) override; + + // Conforms to EncryptionRunner::ResultListener::onEncryptionSuccess(). + // @EncryptionRunnerThread + void onEncryptionSuccessImpl(const string& endpoint_id, + Ptr ukey2_handshake, + const string& authentication_token, + ConstPtr raw_authentication_token); + + // EncryptionRunner::ResultListener::onEncryptionFailure(). + // @EncryptionRunnerThread + void onEncryptionFailureImpl(const string& endpoint_id, + Ptr channel); + + protected: + // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). + class StartOperationResult { + public: + static Ptr error(Status::Value status) { + return MakePtr(new StartOperationResult(status)); + } + + static Ptr success( + const std::vector& mediums) { + // Note: check here and not in the constructor, since for errors we have + // null mediums. + return MakePtr(new StartOperationResult(mediums)); + } + + private: + template + friend class base_pcp_handler::StartAdvertisingCallable; + template + friend class base_pcp_handler::StartDiscoveryCallable; + + explicit StartOperationResult(Status::Value status) + : status_(status), mediums_() {} + explicit StartOperationResult( + const std::vector& mediums) + : status_(Status::SUCCESS), mediums_(mediums) {} + + // The status to be returned to the client. + Status::Value status_; + // If success, the mediums on which we are now advertising/discovering, for + // analytics. + std::vector mediums_; + }; + + // Represents an endpoint that we've discovered. Typically, the implementation + // will know how to connect to this endpoint if asked. (eg. It holds on to a + // BluetoothDevice) + class DiscoveredEndpoint { + public: + virtual ~DiscoveredEndpoint() {} + + virtual string getEndpointId() = 0; + virtual string getEndpointName() = 0; + virtual string getServiceId() = 0; + virtual proto::connections::Medium getMedium() = 0; + }; + + struct ConnectImplResult { + proto::connections::Medium medium; + Status::Value status; + Ptr endpoint_channel; + + explicit ConnectImplResult(Ptr endpoint_channel) + : medium(proto::connections::Medium::UNKNOWN_MEDIUM), + status(Status::SUCCESS), + endpoint_channel(endpoint_channel) {} + ConnectImplResult(proto::connections::Medium medium, Status::Value status) + : medium(medium), status(status), endpoint_channel() {} + }; + + void runOnPCPHandlerThread(Ptr runnable); + + Ptr getAdvertisingOptions(); + + // @PCPHandlerThread + void onEndpointFound(Ptr > client_proxy, + Ptr endpoint); + + // @PCPHandlerThread + void onEndpointLost(Ptr > client_proxy, + Ptr endpoint); + + Exception::Value onIncomingConnection( + Ptr > client_proxy, + const string& remote_device_name, Ptr endpoint_channel, + proto::connections::Medium medium); // throws Exception::IO + + virtual bool hasOutgoingConnections(Ptr > client_proxy); + virtual bool hasIncomingConnections(Ptr > client_proxy); + + virtual bool canSendOutgoingConnection( + Ptr > client_proxy); + virtual bool canReceiveIncomingConnection( + Ptr > client_proxy); + + // @PCPHandlerThread + virtual Ptr startAdvertisingImpl( + Ptr > client_proxy, const string& service_id, + const string& local_endpoint_id, const string& local_endpoint_name, + const AdvertisingOptions& options) = 0; + // @PCPHandlerThread + virtual Status::Value stopAdvertisingImpl( + Ptr > client_proxy) = 0; + + // @PCPHandlerThread + virtual Ptr startDiscoveryImpl( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& options) = 0; + // @PCPHandlerThread + virtual Status::Value stopDiscoveryImpl( + Ptr > client_proxy) = 0; + + // @PCPHandlerThread + virtual ConnectImplResult connectImpl( + Ptr > client_proxy, + Ptr endpoint) = 0; + + virtual std::vector + getConnectionMediumsByPriority() = 0; + virtual proto::connections::Medium getDefaultUpgradeMedium() = 0; + + Ptr > endpoint_manager_; + Ptr > endpoint_channel_manager_; + Ptr > bandwidth_upgrade_manager_; + + private: + template + friend class base_pcp_handler::StartAdvertisingCallable; + template + friend class base_pcp_handler::StopAdvertisingRunnable; + template + friend class base_pcp_handler::StartDiscoveryCallable; + template + friend class base_pcp_handler::StopDiscoveryRunnable; + template + friend class base_pcp_handler::RequestConnectionRunnable; + template + friend class base_pcp_handler::AcceptConnectionCallable; + template + friend class base_pcp_handler::RejectConnectionCallable; + template + friend class base_pcp_handler::OnConnectionResponseRunnable; + template + friend class base_pcp_handler::ProcessEndpointDisconnectionRunnable; + template + friend class base_pcp_handler::OnEncryptionSuccessRunnable; + template + friend class base_pcp_handler::OnEncryptionFailureRunnable; + + class ResultListenerFacade + : public EncryptionRunner::ResultListener { + public: + explicit ResultListenerFacade(Ptr > impl) + : impl_(impl) {} + + void onEncryptionSuccess( + const string& endpoint_id, + Ptr ukey2_handshake, + const string& authentication_token, + ConstPtr raw_authentication_token) override { + impl_->onEncryptionSuccessImpl(endpoint_id, ukey2_handshake, + authentication_token, + raw_authentication_token); + } + + void onEncryptionFailure(const string& endpoint_id, + Ptr channel) override { + impl_->onEncryptionFailureImpl(endpoint_id, channel); + } + + private: + Ptr > impl_; + }; + + class PendingConnectionInfo { + public: + static Ptr newIncomingPendingConnectionInfo( + Ptr > client_proxy, + const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, + std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + const std::vector& supported_mediums); + + static Ptr newOutgoingPendingConnectionInfo( + Ptr > client_proxy, + const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, + std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + Ptr > request_connection_result); + + ~PendingConnectionInfo(); + + void setUKey2Handshake(Ptr ukey2_handshake); + + void localEndpointAcceptedConnection(const string& endpoint_id, + Ptr payload_listener); + + void localEndpointRejectedConnection(const string& endpoint_id); + + private: + template + friend class BasePCPHandler; + template + friend class base_pcp_handler::RequestConnectionRunnable; + template + friend class base_pcp_handler::AcceptConnectionCallable; + template + friend class base_pcp_handler::RejectConnectionCallable; + template + friend class base_pcp_handler::OnEncryptionSuccessRunnable; + template + friend class base_pcp_handler::OnEncryptionFailureRunnable; + + PendingConnectionInfo( + Ptr > client_proxy, + const string& remote_endpoint_name, + Ptr endpoint_channel, std::int32_t nonce, + bool is_incoming, std::int64_t start_time_millis, + Ptr connection_lifecycle_listener, + Ptr > request_connection_result, + const std::vector& supported_mediums); + + Ptr > client_proxy_; + const string remote_endpoint_name_; + // Can be released prior to destructor. + ScopedPtr > endpoint_channel_; + const std::int32_t nonce_; + const bool is_incoming_; + const std::int64_t start_time_millis_; + // Can be released prior to destructor. + ScopedPtr > connection_lifecycle_listener_; + + // Only set for outgoing connections. Can be released prior to destructor. + // TODO(b/77783039): Consider creating a one-time-use-only wrapper class + // around the Ptr that's passed in (that also implements the + // SettableFuture interface) so we can avoid the easy-to-forget calls to + // request_connection_result_.clear() peppered through multiple places in + // the code. + Ptr > request_connection_result_; + + // Only (possibly) set for incoming connections. + const std::vector supported_mediums_; + + // If set, this is owned. + Ptr ukey2_handshake_; + }; + + static Exception::Value writeConnectionRequestFrame( + Ptr endpoint_channel, const string& local_endpoint_id, + const string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums); + + static const std::int64_t kConnectionRequestReadTimeoutMillis; + static const std::int64_t kRejectedConnectionCloseDelayMillis; + + template + Ptr > runOnPCPHandlerThread(Ptr > callable); + + // The interface deviates from the Java code to convey a better ownership + // story. Ownership of 'connection_response_offline_frame' is transferred to + // the callee by calling this method. + void onConnectionResponse( + Ptr > client_proxy, const string& endpoint_id, + ConstPtr connection_response_offline_frame); + + // Returns true if the new endpoint is preferred over the old endpoint. + bool isPreferred(Ptr new_endpoint, + Ptr old_endpoint); + + bool shouldEnforceTopologyConstraints(); + bool autoUpgradeBandwidth(); + + // Returns true if the incoming connection should be killed. This only happens + // when an incoming connection arrives while we have an outgoing connection to + // the same endpoint and we need to stop one connection. + bool breakTie(Ptr > client_proxy, + const string& endpoint_id, std::int32_t incoming_nonce, + Ptr endpoint_channel); + // We're not sure how far our outgoing connection has gotten. We may (or may + // not) have called ClientProxy.onConnectionInitiated. Therefore, we'll call + // both preInit and preResult failures. + void processTieBreakLoss(Ptr > client_proxy, + const string& endpoint_id, + Ptr connection_info); + + // Called when an incoming connection has been accepted by both sides. + // + // @param client_proxy The client + // @param endpoint_id The id of the remote device + // @param supported_mediums The mediums supported by the remote device. Empty + // for outgoing connections and older devices that don't report their + // supported mediums. + void initiateBandwidthUpgrade( + Ptr > client_proxy, const string& endpoint_id, + const std::vector& supported_mediums); + + // Returns the optimal medium supported by both devices. + proto::connections::Medium chooseBestUpgradeMedium( + const std::vector& their_supported_mediums); + + // This method should assume ownership of endpoint_id. + void processPreConnectionInitiationFailure( + Ptr > client_proxy, + proto::connections::Medium medium, const string& endpoint_id, + Ptr endpoint_channel, bool is_incoming, + std::int64_t start_time_millis, Status::Value status, + Ptr > request_connection_result); + void processPreConnectionResultFailure( + Ptr > client_proxy, const string& endpoint_id); + Ptr getDiscoveredEndpoint(const string& endpoint_id); + + // Called when either side accepts/rejects the connection, but only takes + // effect after both have accepted or one side has rejected. + // + // NOTE: We also take in a 'can_close_immediately' variable. This is because + // any writes in transit are dropped when we close. To avoid having a reject + // write being dropped (which causes the other side to report + // onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our close. + // If the other side behaves properly, we shouldn't even see the delay + // (because they will also close the connection). + void evaluateConnectionResult(Ptr > client_proxy, + const string& endpoint_id, + bool can_close_immediately); + + ExceptionOr > readConnectionRequestFrame( + Ptr endpoint_channel); + + void waitForLatch(const string& method_name, Ptr latch); + Status::Value waitForResult(const string& method_name, std::int64_t client_id, + Ptr > result_future); + + ScopedPtr > > + bandwidth_upgrade_medium_; + ScopedPtr > alarm_executor_; + ScopedPtr > serial_executor_; + ScopedPtr > system_clock_; + Prng prng_; + + // A map of endpoint id -> PendingConnectionInfo. Entries in this map imply + // that there is an active connection to the endpoint and we're waiting for + // both sides to accept before allowing payloads through. Once the fate of the + // connection is decided (either accepted or rejected), it should be removed + // from this map. + typedef std::map > PendingConnectionsMap; + PendingConnectionsMap pending_connections_; + // A map of endpoint id -> DiscoveredEndpoint. + typedef std::map > DiscoveredEndpointsMap; + DiscoveredEndpointsMap discovered_endpoints_; + // A map of endpoint id -> alarm. These alarms delay closing the + // EndpointChannel to give the other side enough time to read the rejection + // message. It's expected that the other side will close the connection after + // reading the message (in which case, this alarm should be cancelled as it's + // no longer needed), but this alarm is the fallback in case that doesn't + // happen. + typedef std::map > > + PendingRejectedConnectionCloseAlarmsMap; + PendingRejectedConnectionCloseAlarmsMap + pending_rejected_connection_close_alarms_; + + // The active ClientProxy's advertising constraints. Null if the client hasn't + // started advertising. Note: this is not cleared when the client stops + // advertising because it might still be useful downstream of advertising (eg: + // establishing connections, performing bandwidth upgrades, etc.) + Ptr advertising_options_; + // The active ClientProxy's connection lifecycle listener. Non-null while + // advertising. + Ptr advertising_connection_lifecycle_listener_; + + // The active ClientProxy's discovery constraints. Null if the client + // hasn't started discovering. Note: this is not cleared when the client + // stops discovering because it might still be useful downstream of + // discovery (eg: connection speed, etc.) + Ptr discovery_options_; + + // This should have been a ScopedPtr, but we are making this a Ptr to manually + // control the order of destruction. + Ptr > encryption_runner_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/base_pcp_handler.cc" + +#endif // CORE_INTERNAL_BASE_PCP_HANDLER_H_ diff --git a/cpp/core/internal/ble_advertisement.cc b/cpp/core/internal/ble_advertisement.cc new file mode 100644 index 00000000..e97ba86b --- /dev/null +++ b/cpp/core/internal/ble_advertisement.cc @@ -0,0 +1,277 @@ +#include "core/internal/ble_advertisement.h" + +#include + +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { + +const std::uint32_t BLEAdvertisement::kServiceIdHashLength = 3; + +const std::uint32_t BLEAdvertisement::kVersionAndPcpLength = 1; +// Should be defined as EndpointManager::kEndpointIdLength, but that +// involves making BLEAdvertisement templatized on Platform just for +// that one little thing, so forego it (at least for now). +const std::uint32_t BLEAdvertisement::kEndpointIdLength = 4; +const std::uint32_t BLEAdvertisement::kEndpointNameSizeLength = 1; +const std::uint32_t BLEAdvertisement::kBluetoothMacAddressLength = 6; +const std::uint32_t BLEAdvertisement::kMinAdvertisementLength = + kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + + kEndpointNameSizeLength + kBluetoothMacAddressLength; +const std::uint32_t BLEAdvertisement::kMaxEndpointNameLength = 131; + +const std::uint16_t BLEAdvertisement::kVersionBitmask = 0x0E0; +const std::uint16_t BLEAdvertisement::kPCPBitmask = 0x01F; +const std::uint16_t BLEAdvertisement::kEndpointNameLengthBitmask = 0x0FF; + +Ptr BLEAdvertisement::fromBytes( + ConstPtr ble_advertisement_bytes) { + if (ble_advertisement_bytes.isNull()) { + // TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement: + // null bytes passed in."); + return Ptr(); + } + + if (ble_advertisement_bytes->size() < kMinAdvertisementLength) { + // TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement: + // expecting min %d raw bytes, got %d", kMinAdvertisementLength, + // ble_advertisement_bytes->size()); + return Ptr(); + } + + // Start reading the bytes. + const char* ble_advertisement_bytes_read_ptr = + ble_advertisement_bytes->getData(); + + // The first 3 bits are supposed to be the version. + Version::Value version = static_cast( + (*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5); + if (version != Version::V1) { + // TODO(ahlee): logger.atDebug().log("Cannot deserialize BleAdvertisement: + // unsupported Version %d", version); + return Ptr(); + } + + PCP::Value pcp = + static_cast(*ble_advertisement_bytes_read_ptr & kPCPBitmask); + ble_advertisement_bytes_read_ptr++; + if (pcp != PCP::P2P_CLUSTER && pcp != PCP::P2P_STAR && + pcp != PCP::P2P_POINT_TO_POINT) { + // TODO(ahlee): logger.atDebug().log("Cannot deserialize BleAdvertisement: + // unsupported V1 PCP %d", pcp); + return Ptr(); + } + + // Avoid leaks. + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength))); + ble_advertisement_bytes_read_ptr += kServiceIdHashLength; + + std::string endpoint_id(ble_advertisement_bytes_read_ptr, kEndpointIdLength); + ble_advertisement_bytes_read_ptr += kEndpointIdLength; + + std::uint32_t expected_endpoint_name_length = static_cast( + *ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask); + ble_advertisement_bytes_read_ptr++; + + // Check that the stated endpoint_name_length is the same as what we + // received (based off of the length of ble_advertisement_bytes). + std::uint32_t actual_endpoint_name_length = + computeEndpointNameLength(ble_advertisement_bytes); + if (actual_endpoint_name_length < expected_endpoint_name_length) { + // TODO(ahlee): Logger.atDebug().log("Cannot deserialize BleAdvertisement: + // expected endpointName to be %d bytes, got %d bytes", + // expected_endpoint_name_length, actual_endpoint_name_length); + return Ptr(); + } + + std::string endpoint_name(ble_advertisement_bytes_read_ptr, + expected_endpoint_name_length); + ble_advertisement_bytes_read_ptr += expected_endpoint_name_length; + + // Avoid leaks. + ScopedPtr > scoped_bluetooth_mac_address_bytes( + MakeConstPtr(new ByteArray(ble_advertisement_bytes_read_ptr, + kBluetoothMacAddressLength))); + std::string bluetooth_mac_address; + // If the Bluetooth MAC Address bytes are unset or invalid, leave the string + // empty. Otherwise, convert it to the proper colon delimited format. + if (!isBluetoothMacAddressUnset(scoped_bluetooth_mac_address_bytes.get())) { + bluetooth_mac_address = hexBytesToColonDelimitedString( + scoped_bluetooth_mac_address_bytes.get()); + } + + return MakePtr( + new BLEAdvertisement(version, pcp, scoped_service_id_hash.release(), + endpoint_id, endpoint_name, bluetooth_mac_address)); +} + +ConstPtr BLEAdvertisement::toBytes( + Version::Value version, PCP::Value pcp, ConstPtr service_id_hash, + const std::string& endpoint_id, const std::string& endpoint_name, + const std::string& bluetooth_mac_address) { + if (version != Version::V1) { + // TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement: + // unsupported Version %d", version); + return ConstPtr(); + } + + if (pcp != PCP::P2P_CLUSTER && pcp != PCP::P2P_STAR && + pcp != PCP::P2P_POINT_TO_POINT) { + // TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement: + // unsupported V1 PCP %d", pcp); + return ConstPtr(); + } + + if (endpoint_name.size() > kMaxEndpointNameLength) { + // TODO(ahlee): logger.atDebug().log("Cannot serialize BleAdvertisement: + // expected an endpointName of at most %d bytes but got %d", + // kMaxEndpoingNameLength, endpoint_name.size()); + return ConstPtr(); + } + + std::uint32_t ble_advertisement_length = + computeAdvertisementLength(endpoint_name); + Ptr ble_advertisement_bytes{ + new ByteArray{ble_advertisement_length}}; + char* ble_advertisement_bytes_write_ptr = ble_advertisement_bytes->getData(); + + // The first 3 bits are the Version. + char version_and_pcp_byte = + static_cast((version << 5) & kVersionBitmask); + // The next 5 bits are the PCP. + version_and_pcp_byte |= static_cast(pcp & kPCPBitmask); + *ble_advertisement_bytes_write_ptr = version_and_pcp_byte; + ble_advertisement_bytes_write_ptr++; + + // The next 24 bits are the service id hash. + memcpy(ble_advertisement_bytes_write_ptr, service_id_hash->getData(), + kServiceIdHashLength); + ble_advertisement_bytes_write_ptr += kServiceIdHashLength; + + // The next 32 bits are the endpoint id. + memcpy(ble_advertisement_bytes_write_ptr, endpoint_id.data(), + kEndpointIdLength); + ble_advertisement_bytes_write_ptr += kEndpointIdLength; + + // The next 8 bits are the length of the endpoint name. + *ble_advertisement_bytes_write_ptr = + static_cast(endpoint_name.size() & kEndpointNameLengthBitmask); + ble_advertisement_bytes_write_ptr++; + + // The next x bits are the endpoint name. (Max length is 131 bytes). + memcpy(ble_advertisement_bytes_write_ptr, endpoint_name.data(), + endpoint_name.size()); + ble_advertisement_bytes_write_ptr += endpoint_name.size(); + + // The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is + // invalid or empty, we get back a null byte array. + // Avoid leaks. + ScopedPtr > scoped_bluetooth_mac_address_bytes( + bluetoothMacAddressToHexBytes(bluetooth_mac_address)); + if (!scoped_bluetooth_mac_address_bytes.isNull()) { + memcpy(ble_advertisement_bytes_write_ptr, + scoped_bluetooth_mac_address_bytes->getData(), + kBluetoothMacAddressLength); + } + ble_advertisement_bytes_write_ptr += kBluetoothMacAddressLength; + + return ConstifyPtr(ble_advertisement_bytes); +} + +std::string BLEAdvertisement::hexBytesToColonDelimitedString( + ConstPtr hex_bytes) { + // Convert the hex bytes to a string. + std::string colon_delimited_string(absl::BytesToHexString( + std::string(hex_bytes->getData(), hex_bytes->size()))); + absl::AsciiStrToUpper(&colon_delimited_string); + + // Insert the colons. + for (int i = colon_delimited_string.length() - 2; i > 0; i -= 2) { + colon_delimited_string.insert(i, ":"); + } + return colon_delimited_string; +} + +// TODO(ahlee): Rename to bluetoothMacAddressHexStringToBytes +ConstPtr BLEAdvertisement::bluetoothMacAddressToHexBytes( + const std::string& bluetooth_mac_address) { + std::string bt_mac_address(bluetooth_mac_address); + + // Remove the colon delimiters. + bt_mac_address.erase( + std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'), + bt_mac_address.end()); + + // If the bluetooth mac address is invalid (wrong size), return a null byte + // array. + if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) { + return ConstPtr(); + } + + // Convert to bytes. + std::string bt_mac_address_bytes(absl::HexStringToBytes(bt_mac_address)); + return MakeConstPtr( + new ByteArray(bt_mac_address_bytes.data(), bt_mac_address_bytes.size())); +} + +bool BLEAdvertisement::isBluetoothMacAddressUnset( + ConstPtr bluetooth_mac_address_bytes) { + for (int i = 0; i < bluetooth_mac_address_bytes->size(); i++) { + if (bluetooth_mac_address_bytes->getData()[i] != 0) { + return false; + } + } + return true; +} + +std::uint32_t BLEAdvertisement::computeEndpointNameLength( + ConstPtr ble_advertisement_bytes) { + return ble_advertisement_bytes->size() - kMinAdvertisementLength; +} + +std::uint32_t BLEAdvertisement::computeAdvertisementLength( + const std::string& endpoint_name) { + return kMinAdvertisementLength + endpoint_name.size(); +} + +BLEAdvertisement::BLEAdvertisement(Version::Value version, PCP::Value pcp, + ConstPtr service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address) + : version_(version), + pcp_(pcp), + service_id_hash_(service_id_hash), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + bluetooth_mac_address_(bluetooth_mac_address) {} + +BLEAdvertisement::~BLEAdvertisement() { + // Nothing to do. +} + +BLEAdvertisement::Version::Value BLEAdvertisement::getVersion() const { + return version_; +} + +PCP::Value BLEAdvertisement::getPCP() const { return pcp_; } + +std::string BLEAdvertisement::getEndpointId() const { return endpoint_id_; } + +ConstPtr BLEAdvertisement::getServiceIdHash() const { + return service_id_hash_.get(); +} + +std::string BLEAdvertisement::getEndpointName() const { return endpoint_name_; } + +std::string BLEAdvertisement::getBluetoothMacAddress() const { + return bluetooth_mac_address_; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/ble_advertisement.h b/cpp/core/internal/ble_advertisement.h new file mode 100644 index 00000000..64eb6a20 --- /dev/null +++ b/cpp/core/internal/ble_advertisement.h @@ -0,0 +1,95 @@ +#ifndef CORE_INTERNAL_BLE_ADVERTISEMENT_H_ +#define CORE_INTERNAL_BLE_ADVERTISEMENT_H_ + +#include + +#include "core/internal/pcp.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Connections BLE Advertisement used in +// Advertising + Discovery. +// +//

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

See go/connections-ble-advertisement for more information. +class BLEAdvertisement { + public: + // Versions of the BLEAdvertisement. + struct Version { + enum Value { + V1 = 1, + // Version is only allocated 3 bits in the BLEAdvertisement, so this + // can never go beyond V7. + }; + }; + + static Ptr fromBytes( + ConstPtr ble_advertisement_bytes); + + static ConstPtr toBytes(Version::Value version, PCP::Value pcp, + ConstPtr service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address); + + static const std::uint32_t kServiceIdHashLength; + static const std::uint32_t kMinAdvertisementLength; + // TODO(ahlee): Make sure names match for both Java and C++ implementations. + static const std::uint32_t kMaxEndpointNameLength; + + ~BLEAdvertisement(); + + Version::Value getVersion() const; + PCP::Value getPCP() const; + ConstPtr getServiceIdHash() const; + std::string getEndpointId() const; + std::string getEndpointName() const; + std::string getBluetoothMacAddress() const; + + private: + static std::string hexBytesToColonDelimitedString( + ConstPtr hex_bytes); + // TODO(ahlee): Rename to bluetoothMacAddressHexStringToBytes + static ConstPtr bluetoothMacAddressToHexBytes( + const std::string& bluetooth_mac_address); + static std::uint32_t computeEndpointNameLength( + ConstPtr ble_advertisement_bytes); + static std::uint32_t computeAdvertisementLength( + const std::string& endpoint_name); + static bool isBluetoothMacAddressUnset( + ConstPtr bluetooth_mac_address_bytes); + + static const std::uint32_t kVersionAndPcpLength; + static const std::uint32_t kEndpointIdLength; + static const std::uint32_t kEndpointNameSizeLength; + static const std::uint32_t kBluetoothMacAddressLength; + static const std::uint16_t kVersionBitmask; + static const std::uint16_t kPCPBitmask; + static const std::uint16_t kEndpointNameLengthBitmask; + + BLEAdvertisement(Version::Value version, PCP::Value pcp, + ConstPtr service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address); + + const Version::Value version_; + const PCP::Value pcp_; + ScopedPtr > service_id_hash_; + const std::string endpoint_id_; + const std::string endpoint_name_; + const std::string bluetooth_mac_address_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core/internal/ble_advertisement_test.cc b/cpp/core/internal/ble_advertisement_test.cc new file mode 100644 index 00000000..953e5262 --- /dev/null +++ b/cpp/core/internal/ble_advertisement_test.cc @@ -0,0 +1,343 @@ +#include "core/internal/ble_advertisement.h" + +#include + +#include "platform/port/string.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const BLEAdvertisement::Version::Value version = BLEAdvertisement::Version::V1; +const PCP::Value pcp = PCP::P2P_CLUSTER; +const char endpoint_id[] = "AB12"; +const char service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C}; +const char endpoint_name[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +const char bluetooth_mac_address[] = "00:00:E6:88:64:13"; + +TEST(BLEAdvertisementTest, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_EQ(bluetooth_mac_address, + scoped_ble_advertisement->getBluetoothMacAddress()); +} + +TEST(BLEAdvertisementTest, SerializationDeserializationWorksWithGoodPCP) { + PCP::Value good_pcp = PCP::P2P_STAR; + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, good_pcp, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_id, endpoint_name, bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(good_pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_EQ(bluetooth_mac_address, + scoped_ble_advertisement->getBluetoothMacAddress()); +} + +TEST(BLEAdvertisementTest, + SerializationDeserializationWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + empty_endpoint_name, bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(empty_endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_EQ(bluetooth_mac_address, + scoped_ble_advertisement->getBluetoothMacAddress()); +} + +TEST(BLEAdvertisementTest, + SerializationDeSerializationFailsWithLongEndpointName) { + std::string long_endpoint_name(BLEAdvertisement::kMaxEndpointNameLength + 1, + 'x'); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + long_endpoint_name, bluetooth_mac_address)); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull()); +} + +TEST(BLEAdvertisementTest, + SerializationDeserializationWorksWithEmojiEndpointName) { + std::string emoji_endpoint_name("\u0001F450 \u0001F450"); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + emoji_endpoint_name, bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(emoji_endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_EQ(bluetooth_mac_address, + scoped_ble_advertisement->getBluetoothMacAddress()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithBadVersion) { + BLEAdvertisement::Version::Value bad_version = + static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + bad_version, pcp, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_id, endpoint_name, bluetooth_mac_address)); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithBadPCP) { + PCP::Value bad_pcp = static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, bad_pcp, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_id, endpoint_name, bluetooth_mac_address)); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.get().isNull()); +} + +TEST(BLEAdvertisementTest, SerializationSucceedsWithEmptyBluetoothMacAddress) { + std::string empty_bluetooth_mac_address = ""; + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, empty_bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_EQ(empty_bluetooth_mac_address, + scoped_ble_advertisement->getBluetoothMacAddress()); +} + +TEST(BLEAdvertisementTest, + SerializationSucceedsWithInvalidBluetoothMacAddress) { + std::string bad_bluetooth_mac_address = "022:00"; + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bad_bluetooth_mac_address)); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(pcp, scoped_ble_advertisement->getPCP()); + ASSERT_EQ(version, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(endpoint_id, scoped_ble_advertisement->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(service_id_hash_bytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(endpoint_name, scoped_ble_advertisement->getEndpointName()); + ASSERT_TRUE(scoped_ble_advertisement->getBluetoothMacAddress().empty()); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithNullBytes) { + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(ConstPtr())); + + ASSERT_TRUE(scoped_ble_advertisement.get().isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithShortLength) { + // Serialize good data into a good BLE Advertisement. + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bluetooth_mac_address)); + + // Shorten the valid BLE Advertisement. + ScopedPtr > short_ble_advertisement_bytes(MakeConstPtr( + new ByteArray(scoped_ble_advertisement_bytes.get()->getData(), + BLEAdvertisement::kMinAdvertisementLength - 1))); + + // Fail to deserialize the short BLE Advertisement. + ScopedPtr > scoped_short_ble_advertisement( + BLEAdvertisement::fromBytes(short_ble_advertisement_bytes.get())); + ASSERT_TRUE(scoped_short_ble_advertisement.get().isNull()); + + // Make sure deserialization succeeds with the valid BLE Advertisement. + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + ASSERT_FALSE(scoped_ble_advertisement.get().isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithWrongEndpointNameLength) { + // Serialize good data into a good BLE Advertisement. + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bluetooth_mac_address)); + + // Corrupt the EndpointNameLength bits. + std::string corrupt_ble_advertisement_bytes( + scoped_ble_advertisement_bytes->getData(), + scoped_ble_advertisement_bytes->size()); + corrupt_ble_advertisement_bytes[8] ^= 0x0FF; + ScopedPtr > scoped_corrupt_ble_advertisement_bytes( + MakeConstPtr(new ByteArray(corrupt_ble_advertisement_bytes.data(), + corrupt_ble_advertisement_bytes.size()))); + + // And deserialize the corrupt BLE Advertisement. + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes( + scoped_corrupt_ble_advertisement_bytes.get())); + ASSERT_TRUE(scoped_ble_advertisement.isNull()); +} + +// Bytes at the end should be ignored so that they can be used as reserve bytes +// in the future. +TEST(BLEAdvertisementTest, DeserializationPassesWithLongLength) { + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bluetooth_mac_address)); + + // Add bytes to the end of the valid BLE advertisement. + ScopedPtr > long_ble_advertisement_bytes(MakeConstPtr( + new ByteArray(scoped_ble_advertisement_bytes.get()->getData(), + BLEAdvertisement::kMinAdvertisementLength + 1000))); + + // Deserialize the long BLE advertisement. + ScopedPtr > scoped_long_ble_advertisement( + BLEAdvertisement::fromBytes(long_ble_advertisement_bytes.get())); + ASSERT_FALSE(scoped_long_ble_advertisement.get().isNull()); + + // Make sure deserialization succeeds with the valid BLE Advertisement. + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + ASSERT_FALSE(scoped_ble_advertisement.get().isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationWorksWithLongEndpointName) { + // Serialize good data into a good BLE Advertisement. + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + version, pcp, ConstifyPtr(scoped_service_id_hash.get()), endpoint_id, + endpoint_name, bluetooth_mac_address)); + + // Corrupt the EndpointNameLength bits and increase it past the accepted max + // length. + std::string corrupt_ble_advertisement_bytes( + scoped_ble_advertisement_bytes->getData(), + scoped_ble_advertisement_bytes->size()); + corrupt_ble_advertisement_bytes[8] ^= + BLEAdvertisement::kMaxEndpointNameLength + 10; + ScopedPtr > scoped_corrupt_ble_advertisement_bytes( + MakeConstPtr(new ByteArray(corrupt_ble_advertisement_bytes.data(), + corrupt_ble_advertisement_bytes.size()))); + // Increase the size of the advertisement so that there's enough data for the + // now-longer endpoint name. + ScopedPtr > long_ble_advertisement_bytes(MakeConstPtr( + new ByteArray(scoped_corrupt_ble_advertisement_bytes.get()->getData(), + BLEAdvertisement::kMinAdvertisementLength + 1000))); + + // And deserialize the changed BLE Advertisement. + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(long_ble_advertisement_bytes.get())); + ASSERT_FALSE(scoped_ble_advertisement.isNull()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/ble_compat.h b/cpp/core/internal/ble_compat.h new file mode 100644 index 00000000..264cb39e --- /dev/null +++ b/cpp/core/internal/ble_compat.h @@ -0,0 +1,26 @@ +#ifndef CORE_INTERNAL_BLE_COMPAT_H_ +#define CORE_INTERNAL_BLE_COMPAT_H_ + +#ifndef BLE_V2_IMPLEMENTED +// Flip to true when BLE_V2 is fully implemented and ready to be tested. +#define BLE_V2_IMPLEMENTED 0 +#endif + +#if BLE_V2_IMPLEMENTED + +#include "core/internal/mediums/ble_peripheral.h" +#include "core/internal/mediums/discovered_peripheral_callback.h" +#define BLE_PERIPHERAL location::nearby::connections::mediums::BLEPeripheral +#define DISCOVERED_PERIPHERAL_CALLBACK \ + location::nearby::connections::mediums::DiscoveredPeripheralCallback + +#else + +#include "platform/api/ble.h" +#define BLE_PERIPHERAL location::nearby::BLEPeripheral +#define DISCOVERED_PERIPHERAL_CALLBACK \ + BLE::DiscoveredPeripheralCallback + +#endif // BLE_V2_IMPLEMENTED + +#endif // CORE_INTERNAL_BLE_COMPAT_H_ diff --git a/cpp/core/internal/ble_endpoint_channel.cc b/cpp/core/internal/ble_endpoint_channel.cc new file mode 100644 index 00000000..8684dcc1 --- /dev/null +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -0,0 +1,55 @@ +#include "core/internal/ble_endpoint_channel.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +template +Ptr > +BLEEndpointChannel::createOutgoing( + Ptr > medium_manager, const string& channel_name, + Ptr ble_socket) { + return MakePtr( + new BLEEndpointChannel(channel_name, ble_socket)); +} + +template +Ptr > +BLEEndpointChannel::createIncoming( + Ptr > medium_manager, const string& channel_name, + Ptr ble_socket) { + return MakePtr( + new BLEEndpointChannel(channel_name, ble_socket)); +} + +template +BLEEndpointChannel::BLEEndpointChannel( + const string& channel_name, Ptr ble_socket) + : BaseEndpointChannel(channel_name, + ble_socket->getInputStream(), + ble_socket->getOutputStream()), + ble_socket_(ble_socket) {} + +template +BLEEndpointChannel::~BLEEndpointChannel() {} + +template +proto::connections::Medium BLEEndpointChannel::getMedium() { + return proto::connections::Medium::BLE; +} + +template +void BLEEndpointChannel::closeImpl() { + Exception::Value exception = ble_socket_->close(); + if (exception != Exception::NONE) { + if (exception == Exception::IO) { + // TODO(ahlee): Add logging. + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/ble_endpoint_channel.h b/cpp/core/internal/ble_endpoint_channel.h new file mode 100644 index 00000000..fb92dc4d --- /dev/null +++ b/cpp/core/internal/ble_endpoint_channel.h @@ -0,0 +1,44 @@ +#ifndef CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ + +#include "core/internal/base_endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/ble.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class BLEEndpointChannel : public BaseEndpointChannel { + public: + static Ptr > createOutgoing( + Ptr > medium_manager, const string& channel_name, + Ptr ble_socket); + static Ptr > createIncoming( + Ptr > medium_manager, const string& channel_name, + Ptr ble_socket); + + ~BLEEndpointChannel() override; + + proto::connections::Medium getMedium() override; + + protected: + void closeImpl() override; + + private: + BLEEndpointChannel(const string& channel_name, Ptr ble_socket); + + ScopedPtr > ble_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/ble_endpoint_channel.cc" + +#endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/bluetooth_device_name.cc b/cpp/core/internal/bluetooth_device_name.cc new file mode 100644 index 00000000..d305167d --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name.cc @@ -0,0 +1,292 @@ +#include "core/internal/bluetooth_device_name.h" + +#include + +#include "platform/base64_utils.h" + +namespace location { +namespace nearby { +namespace connections { + +const std::uint32_t BluetoothDeviceName::kServiceIdHashLength = 3; + +const std::uint32_t BluetoothDeviceName::kMaxBluetoothDeviceNameLength = 147; +// Should be defined as ClientProxy::kEndpointIdLength, but that +// involves making BluetoothDeviceName templatized on Platform just for +// that one little thing, so forego it (at least for now). +const std::uint32_t BluetoothDeviceName::kEndpointIdLength = 4; +const std::uint32_t BluetoothDeviceName::kReservedLength = 7; +const std::uint32_t BluetoothDeviceName::kMaxEndpointNameLength = 131; +const std::uint32_t BluetoothDeviceName::kMinBluetoothDeviceNameLength = + kMaxBluetoothDeviceNameLength - kMaxEndpointNameLength; + +const std::uint16_t BluetoothDeviceName::kVersionBitmask = 0x0E0; +const std::uint16_t BluetoothDeviceName::kPCPBitmask = 0x01F; +const std::uint16_t BluetoothDeviceName::kEndpointNameLengthBitmask = 0x0FF; + +Ptr BluetoothDeviceName::fromString( + const std::string& bluetooth_device_name_string) { + ScopedPtr > scoped_bluetooth_device_name_bytes( + Base64Utils::decode(bluetooth_device_name_string)); + if (scoped_bluetooth_device_name_bytes.isNull()) { + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: failed Base64 decoding of %s", + // bluetoothDeviceNameString); + return Ptr(); + } + + if (scoped_bluetooth_device_name_bytes->size() > + kMaxBluetoothDeviceNameLength) { + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: expecting max %d raw bytes, got %d", + // MAX_BLUETOOTH_DEVICE_NAME_LENGTH, bluetoothDeviceNameBytes.length); + return Ptr(); + } + + if (scoped_bluetooth_device_name_bytes->size() < + kMinBluetoothDeviceNameLength) { + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: expecting min %d raw bytes, got %d", + // MIN_BLUETOOTH_DEVICE_NAME_LENGTH, bluetoothDeviceNameBytes.length); + return Ptr(); + } + + // The first 3 bits are supposed to be the version. + Version::Value version = static_cast( + (scoped_bluetooth_device_name_bytes->getData()[0] & kVersionBitmask) >> + 5); + + switch (version) { + case Version::V1: + return createV1BluetoothDeviceName( + ConstifyPtr(scoped_bluetooth_device_name_bytes.get())); + + default: + // TODO(reznor): [ANALYTICIZE] This either represents corruption over the + // air, or older versions of GmsCore intermingling with newer ones. + + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: unsupported Version %d", version); + return Ptr(); + } +} + +std::string BluetoothDeviceName::asString(Version::Value version, + PCP::Value pcp, + const std::string& endpoint_id, + ConstPtr service_id_hash, + const std::string& endpoint_name) { + std::string usable_endpoint_name(endpoint_name); + if (endpoint_name.size() > kMaxEndpointNameLength) { + // TODO(reznor): logger.atWarning().log("While serializing Advertisement, + // truncating Endpoint Name %s (%d bytes) down to %d bytes", endpointName, + // endpointNameBytes.length, MAX_ENDPOINT_NAME_LENGTH); + usable_endpoint_name.erase(kMaxEndpointNameLength); + } + ScopedPtr > scoped_endpoint_name_bytes( + new ByteArray(usable_endpoint_name.data(), usable_endpoint_name.size())); + + Ptr bluetooth_device_name_bytes; + switch (version) { + case Version::V1: + bluetooth_device_name_bytes = + createV1Bytes(pcp, endpoint_id, service_id_hash, + ConstifyPtr(scoped_endpoint_name_bytes.get())); + if (bluetooth_device_name_bytes.isNull()) { + return ""; + } + break; + + default: + // TODO(reznor): logger.atDebug().log("Cannot serialize + // BluetoothDeviceName: unsupported Version %d", version); + return ""; + } + ScopedPtr > scoped_bluetooth_device_name_bytes( + bluetooth_device_name_bytes); + + // BluetoothDeviceName needs to be binary safe, so apply a Base64 encoding + // over the raw bytes. + return Base64Utils::encode( + ConstifyPtr(scoped_bluetooth_device_name_bytes.get())); +} + +Ptr BluetoothDeviceName::createV1BluetoothDeviceName( + ConstPtr bluetooth_device_name_bytes) { + const char* bluetooth_device_name_bytes_read_ptr = + bluetooth_device_name_bytes->getData(); + + // The first 5 bits of the V1 payload are supposed to be the PCP. + PCP::Value pcp = static_cast( + *bluetooth_device_name_bytes_read_ptr & kPCPBitmask); + bluetooth_device_name_bytes_read_ptr++; + + switch (pcp) { + case PCP::P2P_CLUSTER: // Fall through + case PCP::P2P_STAR: // Fall through + case PCP::P2P_POINT_TO_POINT: { + // The next 32 bits are supposed to be the endpoint_id. + std::string endpoint_id(bluetooth_device_name_bytes_read_ptr, + kEndpointIdLength); + bluetooth_device_name_bytes_read_ptr += kEndpointIdLength; + + // The next 24 bits are supposed to be the scoped_service_id_hash. + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(bluetooth_device_name_bytes_read_ptr, + kServiceIdHashLength))); + bluetooth_device_name_bytes_read_ptr += kServiceIdHashLength; + + // The next 56 bits are supposed to be reserved, and can be left + // untouched. + bluetooth_device_name_bytes_read_ptr += kReservedLength; + + // The next 8 bits are supposed to be the length of the endpoint_name. + std::uint32_t expected_endpoint_name_length = static_cast( + *bluetooth_device_name_bytes_read_ptr & kEndpointNameLengthBitmask); + bluetooth_device_name_bytes_read_ptr++; + + // Check that the stated endpoint_name_length is the same as what we + // received (based off of the length of bluetooth_device_name_bytes). + std::uint32_t actual_endpoint_name_length = + computeEndpointNameLength(bluetooth_device_name_bytes); + if (actual_endpoint_name_length != expected_endpoint_name_length) { + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: expected endpointName to be %d bytes, got %d + // bytes", expectedEndpointNameLength, actualEndpointNameLength); + return Ptr(); + } + + std::string endpoint_name(bluetooth_device_name_bytes_read_ptr, + actual_endpoint_name_length); + bluetooth_device_name_bytes_read_ptr += actual_endpoint_name_length; + + return MakePtr(new BluetoothDeviceName(Version::V1, pcp, endpoint_id, + scoped_service_id_hash.release(), + endpoint_name)); + } + default: + // TODO(reznor): [ANALYTICIZE] This either represents corruption over the + // air, or older versions of GmsCore intermingling with newer ones. + + // TODO(reznor): logger.atDebug().log("Cannot deserialize + // BluetoothDeviceName: unsupported V1 PCP %d", pcp); + return Ptr(); + } +} + +std::uint32_t BluetoothDeviceName::computeEndpointNameLength( + ConstPtr bluetooth_device_name_bytes) { + return kMaxEndpointNameLength - + (kMaxBluetoothDeviceNameLength - bluetooth_device_name_bytes->size()); +} + +std::uint32_t BluetoothDeviceName::computeBluetoothDeviceNameLength( + ConstPtr endpoint_name_bytes) { + return kMaxBluetoothDeviceNameLength - + (kMaxEndpointNameLength - endpoint_name_bytes->size()); +} + +Ptr BluetoothDeviceName::createV1Bytes( + PCP::Value pcp, const std::string& endpoint_id, + ConstPtr service_id_hash, + ConstPtr endpoint_name_bytes) { + std::uint32_t bluetooth_device_name_length = + computeBluetoothDeviceNameLength(endpoint_name_bytes); + Ptr bluetooth_device_name_bytes{ + new ByteArray{bluetooth_device_name_length}}; + + char* bluetooth_device_name_bytes_write_ptr = + bluetooth_device_name_bytes->getData(); + + // The first 3 bits are the Version. + char version_and_pcp_byte = + static_cast((Version::V1 << 5) & kVersionBitmask); + // The next 5 bits are the PCP. + version_and_pcp_byte |= static_cast(pcp & kPCPBitmask); + *bluetooth_device_name_bytes_write_ptr = version_and_pcp_byte; + bluetooth_device_name_bytes_write_ptr++; + + switch (pcp) { + case PCP::P2P_CLUSTER: // Fall through + case PCP::P2P_STAR: // Fall through + case PCP::P2P_POINT_TO_POINT: + // The next 32 bits are the endpoint_id. + if (endpoint_id.size() != kEndpointIdLength) { + // TODO(reznor): logger.atDebug().log("Cannot serialize + // BluetoothDeviceName: V1 Endpoint ID %s (%d bytes) should be exactly + // %d bytes", endpointId, endpointId.length(), ENDPOINT_ID_LENGTH); + return Ptr(); + } + memcpy(bluetooth_device_name_bytes_write_ptr, endpoint_id.data(), + kEndpointIdLength); + bluetooth_device_name_bytes_write_ptr += kEndpointIdLength; + + // The next 24 bits are the service_id_hash. + if (service_id_hash->size() != kServiceIdHashLength) { + // TODO(reznor): logger.atDebug().log("Cannot serialize + // BluetoothDeviceName: V1 ServiceID hash (%d bytes) should be exactly + // %d bytes", serviceIdHash.length, SERVICE_ID_HASH_LENGTH); + return Ptr(); + } + memcpy(bluetooth_device_name_bytes_write_ptr, service_id_hash->getData(), + kServiceIdHashLength); + bluetooth_device_name_bytes_write_ptr += kServiceIdHashLength; + + // The next 56 bits are reserved, and should all be zeroed out, so do + // that and then jump over 56 bits to position things for the next write. + memset(bluetooth_device_name_bytes_write_ptr, 0, kReservedLength); + bluetooth_device_name_bytes_write_ptr += kReservedLength; + + // The next 8 bits are the length of the endpoint_name. + *bluetooth_device_name_bytes_write_ptr = static_cast( + endpoint_name_bytes->size() & kEndpointNameLengthBitmask); + bluetooth_device_name_bytes_write_ptr++; + + // The remaining bits are filled with the endpoint_name. + memcpy(bluetooth_device_name_bytes_write_ptr, + endpoint_name_bytes->getData(), endpoint_name_bytes->size()); + bluetooth_device_name_bytes_write_ptr += endpoint_name_bytes->size(); + + break; + default: + // TODO(reznor): logger.atDebug().log("Cannot serialize + // BluetoothDeviceName: unsupported V1 PCP %d", pcp); + return Ptr(); + } + + return bluetooth_device_name_bytes; +} + +BluetoothDeviceName::BluetoothDeviceName(Version::Value version, PCP::Value pcp, + const std::string& endpoint_id, + ConstPtr service_id_hash, + const std::string& endpoint_name) + : version_(version), + pcp_(pcp), + endpoint_id_(endpoint_id), + service_id_hash_(service_id_hash), + endpoint_name_(endpoint_name) {} + +BluetoothDeviceName::~BluetoothDeviceName() { + // Nothing to do. +} + +BluetoothDeviceName::Version::Value BluetoothDeviceName::getVersion() const { + return version_; +} + +PCP::Value BluetoothDeviceName::getPCP() const { return pcp_; } + +std::string BluetoothDeviceName::getEndpointId() const { return endpoint_id_; } + +ConstPtr BluetoothDeviceName::getServiceIdHash() const { + return service_id_hash_.get(); +} + +std::string BluetoothDeviceName::getEndpointName() const { + return endpoint_name_; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/bluetooth_device_name.h b/cpp/core/internal/bluetooth_device_name.h new file mode 100644 index 00000000..de81dee7 --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name.h @@ -0,0 +1,86 @@ +#ifndef CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ +#define CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ + +#include + +#include "core/internal/pcp.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Bluetooth device name used in Advertising + +// Discovery. +// +//

See go/nearby-offline-data-interchange-formats for the specification. +class BluetoothDeviceName { + public: + // Versions of the BluetoothDeviceName. + struct Version { + enum Value { + V1 = 1, + // Version is only allocated 3 bits in the BluetoothDeviceName, so this + // can never go beyond V7. + }; + }; + + static Ptr fromString( + const std::string& bluetooth_device_name_string); + + static std::string asString(Version::Value version, PCP::Value pcp, + const std::string& endpoint_id, + ConstPtr service_id_hash, + const std::string& endpoint_name); + + static const std::uint32_t kServiceIdHashLength; + + ~BluetoothDeviceName(); + + Version::Value getVersion() const; + PCP::Value getPCP() const; + std::string getEndpointId() const; + ConstPtr getServiceIdHash() const; + std::string getEndpointName() const; + + private: + static Ptr createV1BluetoothDeviceName( + ConstPtr bluetooth_device_name_bytes); + static std::uint32_t computeEndpointNameLength( + ConstPtr bluetooth_device_name_bytes); + static std::uint32_t computeBluetoothDeviceNameLength( + ConstPtr endpoint_name_bytes); + static Ptr createV1Bytes(PCP::Value pcp, + const std::string& endpoint_id, + ConstPtr service_id_hash, + ConstPtr endpoint_name_bytes); + + static const std::uint32_t kMaxBluetoothDeviceNameLength; + static const std::uint32_t kEndpointIdLength; + static const std::uint32_t kReservedLength; + static const std::uint32_t kMaxEndpointNameLength; + static const std::uint32_t kMinBluetoothDeviceNameLength; + + static const std::uint16_t kVersionBitmask; + static const std::uint16_t kPCPBitmask; + static const std::uint16_t kEndpointNameLengthBitmask; + + BluetoothDeviceName(Version::Value version, PCP::Value pcp, + const std::string& endpoint_id, + ConstPtr service_id_hash, + const std::string& endpoint_name); + + const Version::Value version_; + const PCP::Value pcp_; + const std::string endpoint_id_; + ScopedPtr > service_id_hash_; + const std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ diff --git a/cpp/core/internal/bluetooth_device_name_test.cc b/cpp/core/internal/bluetooth_device_name_test.cc new file mode 100644 index 00000000..90a789ea --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name_test.cc @@ -0,0 +1,198 @@ +#include "core/internal/bluetooth_device_name.h" + +#include + +#include "platform/base64_utils.h" +#include "platform/port/string.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const BluetoothDeviceName::Version::Value version = + BluetoothDeviceName::Version::V1; +const PCP::Value pcp = PCP::P2P_CLUSTER; +const char endpoint_id[] = "AB12"; +const char service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C}; +const char endpoint_name[] = "RAWK + ROWL!"; + +TEST(BluetoothDeviceNameTest, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_name); + ScopedPtr > scoped_bluetooth_device_name( + BluetoothDeviceName::fromString(bluetooth_device_name_string)); + + ASSERT_EQ(pcp, scoped_bluetooth_device_name->getPCP()); + ASSERT_EQ(version, scoped_bluetooth_device_name->getVersion()); + ASSERT_EQ(endpoint_id, scoped_bluetooth_device_name->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_bluetooth_device_name->getServiceIdHash()->size()); + ASSERT_EQ(0, + memcmp(service_id_hash_bytes, + scoped_bluetooth_device_name->getServiceIdHash()->getData(), + scoped_bluetooth_device_name->getServiceIdHash()->size())); + ASSERT_EQ(endpoint_name, scoped_bluetooth_device_name->getEndpointName()); +} + +TEST(BluetoothDeviceNameTest, + SerializationDeserializationWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + empty_endpoint_name); + ScopedPtr > scoped_bluetooth_device_name( + BluetoothDeviceName::fromString(bluetooth_device_name_string)); + + ASSERT_EQ(pcp, scoped_bluetooth_device_name->getPCP()); + ASSERT_EQ(version, scoped_bluetooth_device_name->getVersion()); + ASSERT_EQ(endpoint_id, scoped_bluetooth_device_name->getEndpointId()); + ASSERT_EQ(sizeof(service_id_hash_bytes) / sizeof(char), + scoped_bluetooth_device_name->getServiceIdHash()->size()); + ASSERT_EQ(0, + memcmp(service_id_hash_bytes, + scoped_bluetooth_device_name->getServiceIdHash()->getData(), + scoped_bluetooth_device_name->getServiceIdHash()->size())); + ASSERT_EQ(empty_endpoint_name, + scoped_bluetooth_device_name->getEndpointName()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithBadVersion) { + BluetoothDeviceName::Version::Value bad_version = + static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + bad_version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithBadPCP) { + PCP::Value bad_pcp = static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, bad_pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, short_endpoint_id, + ConstifyPtr(scoped_service_id_hash.get()), endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, long_endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + ScopedPtr > scoped_short_service_id_hash( + new ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, endpoint_id, + ConstifyPtr(scoped_short_service_id_hash.get()), endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, SerializationFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + ScopedPtr > scoped_long_service_id_hash( + new ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, endpoint_id, ConstifyPtr(scoped_long_service_id_hash.get()), + endpoint_name); + + ASSERT_TRUE(bluetooth_device_name_string.empty()); +} + +TEST(BluetoothDeviceNameTest, DeserializationFailsWithShortLength) { + char bluetooth_device_name_bytes[] = {'X'}; + + ScopedPtr > scoped_bluetooth_device_name_bytes( + new ByteArray(bluetooth_device_name_bytes, + sizeof(bluetooth_device_name_bytes) / sizeof(char))); + + ScopedPtr > scoped_bluetooth_device_name( + BluetoothDeviceName::fromString(Base64Utils::encode( + ConstifyPtr(scoped_bluetooth_device_name_bytes.get())))); + + ASSERT_TRUE(scoped_bluetooth_device_name.isNull()); +} + +TEST(BluetoothDeviceNameTest, DeserializationFailsWithWrongEndpointNameLength) { + // Serialize good data into a good Bluetooth Device Name. + ScopedPtr > scoped_service_id_hash(new ByteArray( + service_id_hash_bytes, sizeof(service_id_hash_bytes) / sizeof(char))); + + std::string bluetooth_device_name_string = BluetoothDeviceName::asString( + version, pcp, endpoint_id, ConstifyPtr(scoped_service_id_hash.get()), + endpoint_name); + + // Base64-decode the good Bluetooth Device Name. + ScopedPtr > scoped_bluetooth_device_name_bytes( + Base64Utils::decode(bluetooth_device_name_string)); + // Corrupt the EndpointNameLength bits (120-127) by reversing all of them. + std::string corrupt_bluetooth_device_name_bytes( + scoped_bluetooth_device_name_bytes->getData(), + scoped_bluetooth_device_name_bytes->size()); + corrupt_bluetooth_device_name_bytes[15] ^= 0x0FF; + // Base64-encode the corrupted bytes into a corrupt Bluetooth Device Name. + ScopedPtr > scoped_corrupt_bluetooth_device_name_bytes( + new ByteArray(corrupt_bluetooth_device_name_bytes.data(), + corrupt_bluetooth_device_name_bytes.size())); + std::string corrupt_bluetooth_device_name_string(Base64Utils::encode( + ConstifyPtr(scoped_corrupt_bluetooth_device_name_bytes.get()))); + + // And deserialize the corrupt Bluetooth Device Name. + ScopedPtr > scoped_bluetooth_device_name( + BluetoothDeviceName::fromString(corrupt_bluetooth_device_name_string)); + + ASSERT_TRUE(scoped_bluetooth_device_name.isNull()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/bluetooth_endpoint_channel.cc b/cpp/core/internal/bluetooth_endpoint_channel.cc new file mode 100644 index 00000000..f9525b36 --- /dev/null +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -0,0 +1,55 @@ +#include "core/internal/bluetooth_endpoint_channel.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +template +Ptr > +BluetoothEndpointChannel::createOutgoing( + Ptr > medium_manager, const string& channel_name, + Ptr bluetooth_socket) { + return MakePtr( + new BluetoothEndpointChannel(channel_name, bluetooth_socket)); +} + +template +Ptr > +BluetoothEndpointChannel::createIncoming( + Ptr > medium_manager, const string& channel_name, + Ptr bluetooth_socket) { + return MakePtr( + new BluetoothEndpointChannel(channel_name, bluetooth_socket)); +} + +template +BluetoothEndpointChannel::BluetoothEndpointChannel( + const string& channel_name, Ptr bluetooth_socket) + : BaseEndpointChannel(channel_name, + bluetooth_socket->getInputStream(), + bluetooth_socket->getOutputStream()), + bluetooth_socket_(bluetooth_socket) {} + +template +BluetoothEndpointChannel::~BluetoothEndpointChannel() {} + +template +proto::connections::Medium BluetoothEndpointChannel::getMedium() { + return proto::connections::Medium::BLUETOOTH; +} + +template +void BluetoothEndpointChannel::closeImpl() { + Exception::Value exception = bluetooth_socket_->close(); + if (exception != Exception::NONE) { + if (exception == Exception::IO) { + // TODO(tracyzhou): Add logging. + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/bluetooth_endpoint_channel.h b/cpp/core/internal/bluetooth_endpoint_channel.h new file mode 100644 index 00000000..f9be2269 --- /dev/null +++ b/cpp/core/internal/bluetooth_endpoint_channel.h @@ -0,0 +1,45 @@ +#ifndef CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ + +#include "core/internal/base_endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class BluetoothEndpointChannel : public BaseEndpointChannel { + public: + static Ptr > createOutgoing( + Ptr > medium_manager, const string& channel_name, + Ptr bluetooth_socket); + static Ptr > createIncoming( + Ptr > medium_manager, const string& channel_name, + Ptr bluetooth_socket); + + ~BluetoothEndpointChannel() override; + + proto::connections::Medium getMedium() override; + + protected: + void closeImpl() override; + + private: + BluetoothEndpointChannel(const string& channel_name, + Ptr bluetooth_socket); + + ScopedPtr > bluetooth_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/bluetooth_endpoint_channel.cc" + +#endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/client_proxy.cc b/cpp/core/internal/client_proxy.cc new file mode 100644 index 00000000..55d6ea7f --- /dev/null +++ b/cpp/core/internal/client_proxy.cc @@ -0,0 +1,590 @@ +#include "core/internal/client_proxy.h" + +#include +#include +#include +#include + +#include "platform/api/hash_utils.h" +#include "platform/base64_utils.h" +#include "platform/prng.h" +#include "platform/synchronized.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace client_proxy { + +template +void eraseOwnedPtrFromMap(std::map>& m, const K& k) { + typename std::map>::iterator it = m.find(k); + if (it != m.end()) { + it->second.destroy(); + m.erase(it); + } +} + +} // namespace client_proxy + +template +const std::int32_t ClientProxy::kEndpointIdLength = 4; + +template +ClientProxy::ClientProxy() + : lock_(Platform::createLock()), client_id_(Prng().nextInt64()) {} + +template +ClientProxy::~ClientProxy() { + reset(); +} + +template +std::int64_t ClientProxy::getClientId() const { + return client_id_; +} + +template +std::string ClientProxy::generateLocalEndpointId() { + // 1) Concatenate the DeviceID with this ClientID. + // 2) Compute a hash of that concatenation. + // 3) Base64-encode that hash, to make it human-readable. + // 4) Use only the first 4 bytes of that Base64 encoding. + + std::ostringstream client_id_str; + client_id_str << getClientId(); + + ScopedPtr> hash_utils(Platform::createHashUtils()); + ScopedPtr> id_hash( + hash_utils->sha256(Platform::getDeviceId() + client_id_str.str())); + + return Base64Utils::encode(id_hash.get()).substr(0, kEndpointIdLength); +} + +template +void ClientProxy::reset() { + Synchronized s(lock_.get()); + + stoppedAdvertising(); + stoppedDiscovery(); + removeAllEndpoints(); +} + +template +void ClientProxy::startedAdvertising( + const std::string& service_id, const Strategy& strategy, + Ptr connection_lifecycle_listener, + const std::vector& mediums) { + Synchronized s(lock_.get()); + + advertising_info_.destroy(); + advertising_info_ = + MakePtr(new AdvertisingInfo(service_id, connection_lifecycle_listener)); +} + +template +void ClientProxy::stoppedAdvertising() { + Synchronized s(lock_.get()); + + if (isAdvertising()) { + advertising_info_.destroy(); + } +} + +template +bool ClientProxy::isAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.isNull(); +} + +template +std::string ClientProxy::getAdvertisingServiceId() { + Synchronized s(lock_.get()); + + if (!isAdvertising()) { + return ""; + } + + return advertising_info_->service_id; +} + +template +void ClientProxy::startedDiscovery( + const std::string& service_id, const Strategy& strategy, + Ptr discovery_listener, + const std::vector& mediums) { + Synchronized s(lock_.get()); + + discovery_info_.destroy(); + discovery_info_ = MakePtr(new DiscoveryInfo(service_id, discovery_listener)); +} + +template +void ClientProxy::stoppedDiscovery() { + Synchronized s(lock_.get()); + + if (isDiscovering()) { + discovered_endpoint_ids_.clear(); + discovery_info_.destroy(); + } +} + +template +bool ClientProxy::isDiscoveringServiceId( + const std::string& service_id) { + Synchronized s(lock_.get()); + + return isDiscovering() && service_id == discovery_info_->service_id; +} + +template +bool ClientProxy::isDiscovering() { + Synchronized s(lock_.get()); + + return !discovery_info_.isNull(); +} + +template +std::string ClientProxy::getDiscoveryServiceId() { + Synchronized s(lock_.get()); + + if (!isDiscovering()) { + return ""; + } + + return discovery_info_->service_id; +} + +template +void ClientProxy::onEndpointFound(const std::string& endpoint_id, + const std::string& service_id, + const std::string& endpoint_name, + proto::connections::Medium medium) { + Synchronized s(lock_.get()); + + if (isDiscoveringServiceId(service_id)) { + if (discovered_endpoint_ids_.find(endpoint_id) != + discovered_endpoint_ids_.end()) { + // TODO(tracyzhou): Add logging. + return; + } + discovered_endpoint_ids_.insert(endpoint_id); + discovery_info_->discovery_listener->onEndpointFound(MakeConstPtr( + new OnEndpointFoundParams(endpoint_id, service_id, endpoint_name))); + } +} + +template +void ClientProxy::onEndpointLost(const std::string& service_id, + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + if (isDiscoveringServiceId(service_id)) { + std::set::const_iterator it = + discovered_endpoint_ids_.find(endpoint_id); + if (it == discovered_endpoint_ids_.end()) { + return; + } + discovered_endpoint_ids_.erase(it); + discovery_info_->discovery_listener->onEndpointLost( + MakeConstPtr(new OnEndpointLostParams(endpoint_id))); + } +} + +template +void ClientProxy::onConnectionInitiated( + const std::string& endpoint_id, const std::string& endpoint_name, + const std::string& authentication_token, + ConstPtr raw_authentication_token, bool is_incoming_connection, + Ptr connection_lifecycle_listener) { + Synchronized s(lock_.get()); + + ScopedPtr> scoped_raw_authentication_token( + raw_authentication_token); + + // Whether this is incoming or outgoing, the local and remote endpoints both + // still need to accept this connection, so set its establishment status to + // PENDING. + connection_establishment_statuses_.insert( + std::make_pair(endpoint_id, ConnectionMetadata(is_incoming_connection))); + + // Remember the ConnectionLifecycleListener for this endpoint. + connection_lifecycle_listeners_.insert( + std::make_pair(endpoint_id, connection_lifecycle_listener)); + + // Notify the client. + // + // Note: we allow devices to connect to an advertiser even after it stops + // advertising, so no need to check isAdvertising() here. + connection_lifecycle_listeners_.find(endpoint_id) + ->second->onConnectionInitiated( + MakeConstPtr(new OnConnectionInitiatedParams( + endpoint_id, endpoint_name, authentication_token, + scoped_raw_authentication_token.release(), + is_incoming_connection))); +} + +template +void ClientProxy::onConnectionResult(const std::string& endpoint_id, + Status::Value status) { + Synchronized s(lock_.get()); + + if (!hasPendingConnectionToEndpoint(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + // Notify the client. + connection_lifecycle_listeners_.find(endpoint_id) + ->second->onConnectionResult( + MakeConstPtr(new OnConnectionResultParams(endpoint_id, status))); + if (Status::SUCCESS == status) { + // Mark ourselves as connected. Payloads should now be allowed. + typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.find(endpoint_id); + if (it != connection_establishment_statuses_.end()) { + it->second.status = ConnectionEstablishmentStatus::CONNECTED; + } + } else { + // Otherwise, clean up. + onDisconnected(endpoint_id, false /* notify */); + } +} + +template +void ClientProxy::onBandwidthChanged(const std::string& endpoint_id, + std::int32_t quality) { + Synchronized s(lock_.get()); + + ConnectionLifecycleListenersMap::iterator it = + connection_lifecycle_listeners_.find(endpoint_id); + if (it != connection_lifecycle_listeners_.end()) { + it->second->onBandwidthChanged( + MakeConstPtr(new OnBandwidthChangedParams(endpoint_id, quality))); + } +} + +template +void ClientProxy::onDisconnected(const std::string& endpoint_id, + bool notify) { + Synchronized s(lock_.get()); + + connection_establishment_statuses_.erase(endpoint_id); + + client_proxy::eraseOwnedPtrFromMap(payload_listeners_, endpoint_id); + + ConnectionLifecycleListenersMap::iterator it = + connection_lifecycle_listeners_.find(endpoint_id); + if (it != connection_lifecycle_listeners_.end()) { + if (notify) { + it->second->onDisconnected( + MakeConstPtr(new OnDisconnectedParams(endpoint_id))); + } + it->second.destroy(); + connection_lifecycle_listeners_.erase(it); + } +} + +template +bool ClientProxy::isConnectedToEndpoint( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.find(endpoint_id); + if (it == connection_establishment_statuses_.end()) { + return false; + } + const ConnectionMetadata& metadata = it->second; + return metadata.status == ConnectionEstablishmentStatus::CONNECTED; +} + +template +std::vector ClientProxy::getConnectedEndpoints() { + Synchronized s(lock_.get()); + + std::vector connected_endpoints; + + for (typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.begin(); + it != connection_establishment_statuses_.end(); it++) { + const std::string& endpoint_id = it->first; + const ConnectionMetadata& metadata = it->second; + if (ConnectionEstablishmentStatus::CONNECTED == metadata.status) { + connected_endpoints.push_back(endpoint_id); + } + } + return connected_endpoints; +} + +template +std::vector ClientProxy::getPendingConnectedEndpoints() { + Synchronized s(lock_.get()); + + std::vector pending_connected_endpoints; + + for (typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.begin(); + it != connection_establishment_statuses_.end(); it++) { + const std::string& endpoint_id = it->first; + const ConnectionMetadata& metadata = it->second; + if (ConnectionEstablishmentStatus::CONNECTED != metadata.status) { + pending_connected_endpoints.push_back(endpoint_id); + } + } + return pending_connected_endpoints; +} + +template +std::int32_t ClientProxy::getNumOutgoingConnections() { + Synchronized s(lock_.get()); + + std::int32_t num_outgoing_connections = 0; + + for (typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.begin(); + it != connection_establishment_statuses_.end(); it++) { + const ConnectionMetadata& metadata = it->second; + if (ConnectionEstablishmentStatus::CONNECTED == metadata.status && + !metadata.is_incoming) { + num_outgoing_connections++; + } + } + return num_outgoing_connections; +} + +template +std::int32_t ClientProxy::getNumIncomingConnections() { + Synchronized s(lock_.get()); + + std::int32_t num_incoming_connections = 0; + + for (typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.begin(); + it != connection_establishment_statuses_.end(); it++) { + const ConnectionMetadata& metadata = it->second; + if (ConnectionEstablishmentStatus::CONNECTED == metadata.status && + metadata.is_incoming) { + num_incoming_connections++; + } + } + return num_incoming_connections; +} + +template +bool ClientProxy::hasPendingConnectionToEndpoint( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.find(endpoint_id); + if (it == connection_establishment_statuses_.end()) { + return false; + } + const ConnectionMetadata& metadata = it->second; + return metadata.status != ConnectionEstablishmentStatus::CONNECTED; +} + +template +bool ClientProxy::hasLocalEndpointResponded( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + return connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED) || + connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED); +} + +template +bool ClientProxy::hasRemoteEndpointResponded( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + return connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED) || + connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED); +} + +template +void ClientProxy::localEndpointAcceptedConnection( + const std::string& endpoint_id, Ptr payload_listener) { + Synchronized s(lock_.get()); + + if (hasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + appendConnectionEstablishmentStatus( + endpoint_id, ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED); + payload_listeners_.insert(std::make_pair(endpoint_id, payload_listener)); +} + +template +void ClientProxy::localEndpointRejectedConnection( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + if (hasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + appendConnectionEstablishmentStatus( + endpoint_id, ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED); +} + +template +void ClientProxy::remoteEndpointAcceptedConnection( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + if (hasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + appendConnectionEstablishmentStatus( + endpoint_id, ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED); +} + +template +void ClientProxy::remoteEndpointRejectedConnection( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + if (hasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + appendConnectionEstablishmentStatus( + endpoint_id, ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED); +} + +template +bool ClientProxy::isConnectionAccepted( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + return connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED) && + connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED); +} + +template +bool ClientProxy::isConnectionRejected( + const std::string& endpoint_id) { + Synchronized s(lock_.get()); + + return connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED) || + connectionEstablishmentStatusesContains( + endpoint_id, + ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED); +} + +template +void ClientProxy::onPayloadReceived(const std::string& endpoint_id, + ConstPtr payload) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> scoped_payload(payload); + + if (isConnectedToEndpoint(endpoint_id)) { + payload_listeners_.find(endpoint_id) + ->second->onPayloadReceived(MakeConstPtr(new OnPayloadReceivedParams( + endpoint_id, scoped_payload.release()))); + } +} + +template +void ClientProxy::onPayloadTransferUpdate( + const std::string& endpoint_id, + const PayloadTransferUpdate& payload_transfer_update) { + Synchronized s(lock_.get()); + + if (isConnectedToEndpoint(endpoint_id)) { + payload_listeners_.find(endpoint_id) + ->second->onPayloadTransferUpdate( + MakeConstPtr(new OnPayloadTransferUpdateParams( + endpoint_id, payload_transfer_update))); + } +} + +template +bool ClientProxy::operator==(const ClientProxy& rhs) { + return this->getClientId() == rhs.getClientId(); +} + +template +bool ClientProxy::operator<(const ClientProxy& rhs) { + return this->getClientId() < rhs.getClientId(); +} + +template +void ClientProxy::removeAllEndpoints() { + Synchronized s(lock_.get()); + + // Note: we may want to notify the client of onDisconnected() for each + // endpoint, in the case when this is called from stopAllEndpoints(). For now, + // just remove without notifying. + for (ConnectionLifecycleListenersMap::iterator it = + connection_lifecycle_listeners_.begin(); + it != connection_lifecycle_listeners_.end(); it++) { + it->second.destroy(); + } + connection_lifecycle_listeners_.clear(); + + for (PayloadListenersMap::iterator it = payload_listeners_.begin(); + it != payload_listeners_.end(); it++) { + it->second.destroy(); + } + payload_listeners_.clear(); + + connection_establishment_statuses_.clear(); +} + +template +bool ClientProxy::connectionEstablishmentStatusesContains( + const std::string& endpoint_id, + typename ConnectionEstablishmentStatus::Value status_to_match) { + typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.find(endpoint_id); + if (it == connection_establishment_statuses_.end()) { + return false; + } + const ConnectionMetadata& metadata = it->second; + return (metadata.status & status_to_match) != 0; +} + +template +void ClientProxy::appendConnectionEstablishmentStatus( + const std::string& endpoint_id, + typename ConnectionEstablishmentStatus::Value status_to_append) { + typename ConnectionEstablishmentStatusesMap::iterator it = + connection_establishment_statuses_.find(endpoint_id); + if (it == connection_establishment_statuses_.end()) { + return; + } + ConnectionMetadata& metadata = it->second; + metadata.status = static_cast( + metadata.status | status_to_append); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/client_proxy.h b/cpp/core/internal/client_proxy.h new file mode 100644 index 00000000..98e76fbb --- /dev/null +++ b/cpp/core/internal/client_proxy.h @@ -0,0 +1,241 @@ +#ifndef CORE_INTERNAL_CLIENT_PROXY_H_ +#define CORE_INTERNAL_CLIENT_PROXY_H_ + +#include +#include +#include +#include + +#include "core/listeners.h" +#include "core/strategy.h" +#include "platform/api/lock.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class ClientProxy { + public: + static const std::int32_t kEndpointIdLength; + + ClientProxy(); + ~ClientProxy(); + + std::int64_t getClientId() const; + + std::string generateLocalEndpointId(); + + // Clears all the runtime state of this client. + void reset(); + + // Marks this client as advertising with the given callbacks. + void startedAdvertising( + const std::string& service_id, const Strategy& strategy, + Ptr connection_lifecycle_listener, + const std::vector& mediums); + // Marks this client as not advertising. + void stoppedAdvertising(); + bool isAdvertising(); + std::string getAdvertisingServiceId(); + + // Marks this client as discovering with the given callback. + void startedDiscovery(const std::string& service_id, const Strategy& strategy, + Ptr discovery_listener, + const std::vector& mediums); + // Marks this client as not discovering at all. + void stoppedDiscovery(); + bool isDiscoveringServiceId(const std::string& service_id); + bool isDiscovering(); + std::string getDiscoveryServiceId(); + + // Proxies to the client's DiscoveryListener.onEndpointFound() callback. + void onEndpointFound(const std::string& endpoint_id, + const std::string& service_id, + const std::string& endpoint_name, + proto::connections::Medium medium); + // Proxies to the client's DiscoveryListener.onEndpointLost() callback. + void onEndpointLost(const std::string& service_id, + const std::string& endpoint_id); + + // Proxies to the client's ConnectionLifecycleListener.onConnectionInitiated() + // callback. + void onConnectionInitiated( + const std::string& endpoint_id, const std::string& endpoint_name, + const std::string& authentication_token, + ConstPtr raw_authentication_token, bool is_incoming_connection, + Ptr connection_lifecycle_listener); + // Proxies to the client's ConnectionLifecycleListener.onConnectionResult() + // callback. + void onConnectionResult(const std::string& endpoint_id, Status::Value status); + + void onBandwidthChanged(const std::string& endpoint_id, std::int32_t quality); + + // Removes the endpoint from this client's list of connected endpoints. If + // notify is true, also calls the client's + // ConnectionLifecycleListener.onDisconnected() callback. + void onDisconnected(const std::string& endpoint_id, bool notify); + + // Returns true if it's safe to send payloads to this endpoint. + bool isConnectedToEndpoint(const std::string& endpoint_id); + // Returns all endpoints that can safely be sent payloads. + std::vector getConnectedEndpoints(); + // Returns all endpoints that are still awaiting acceptance. + std::vector getPendingConnectedEndpoints(); + // Returns the number of endpoints that are connected and outgoing. + std::int32_t getNumOutgoingConnections(); + // Returns the number of endpoints that are connected and incoming. + std::int32_t getNumIncomingConnections(); + // If true, then we're in the process of approving (or rejecting) a + // connection. No payloads should be sent until isConnectedToEndpoint() + // returns true. + bool hasPendingConnectionToEndpoint(const std::string& endpoint_id); + // Returns true if the local endpoint has already marked itself as + // accepted/rejected. + bool hasLocalEndpointResponded(const std::string& endpoint_id); + // Returns true if the remote endpoint has already marked themselves as + // accepted/rejected. + bool hasRemoteEndpointResponded(const std::string& endpoint_id); + // Marks the local endpoint as having accepted the connection. + void localEndpointAcceptedConnection(const std::string& endpoint_id, + Ptr payload_listener); + // Marks the local endpoint as having rejected the connection. + void localEndpointRejectedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having accepted the connection. + void remoteEndpointAcceptedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having rejected the connection. + void remoteEndpointRejectedConnection(const std::string& endpoint_id); + // Returns true if both the local endpoint and the remote endpoint have + // accepted the connection. + bool isConnectionAccepted(const std::string& endpoint_id); + // Returns true if either the local endpoint or the remote endpoint has + // rejected the connection. + bool isConnectionRejected(const std::string& endpoint_id); + + // Proxies to the client's PayloadListener.onPayloadReceived() callback. + void onPayloadReceived(const std::string& endpoint_id, + ConstPtr payload); + // Proxies to the client's PayloadListener.onPayloadTransferUpdate() callback. + void onPayloadTransferUpdate( + const std::string& endpoint_id, + const PayloadTransferUpdate& payload_transfer_update); + + // Operator overloads when comparing Ptr. + bool operator==(const ClientProxy& rhs); + bool operator<(const ClientProxy& rhs); + + private: + struct ConnectionEstablishmentStatus { + enum Value { + PENDING = 0, + LOCAL_ENDPOINT_ACCEPTED = 1 << 0, + LOCAL_ENDPOINT_REJECTED = 1 << 1, + REMOTE_ENDPOINT_ACCEPTED = 1 << 2, + REMOTE_ENDPOINT_REJECTED = 1 << 3, + CONNECTED = 1 << 4, + }; + }; + + struct AdvertisingInfo { + const std::string service_id; + Ptr connection_lifecycle_listener; + + AdvertisingInfo( + const std::string& service_id, + Ptr connection_lifecycle_listener) + : service_id(service_id), + connection_lifecycle_listener(connection_lifecycle_listener) {} + }; + + struct DiscoveryInfo { + const std::string service_id; + ScopedPtr > discovery_listener; + + DiscoveryInfo(const std::string& service_id, + Ptr discovery_listener) + : service_id(service_id), discovery_listener(discovery_listener) {} + }; + + struct ConnectionMetadata { + const bool is_incoming; + typename ConnectionEstablishmentStatus::Value status; + + explicit ConnectionMetadata(bool is_incoming) + : is_incoming(is_incoming), + status(ConnectionEstablishmentStatus::PENDING) {} + }; + + void removeAllEndpoints(); + + bool connectionEstablishmentStatusesContains( + const std::string& endpoint_id, + typename ConnectionEstablishmentStatus::Value status_to_match); + void appendConnectionEstablishmentStatus( + const std::string& endpoint_id, + typename ConnectionEstablishmentStatus::Value status_to_append); + + ScopedPtr > lock_; + const std::int64_t client_id_; + + // If set, we are currently advertising and accepting connection requests for + // the given service_id. + Ptr advertising_info_; + + // If set, we are currently discovering for the given service_id. + Ptr discovery_info_; + + /** + * Map of endpoint_ids -> ConnectionMetadata. ConnectionMetadata.status may be + * either ConnectionEstablishmentStatus::PENDING, a combination of + * ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED: + * ConnectionEstablishmentStatus::LOCAL_ENDPOINT_REJECTED and + * ConnectionEstablishmentStatus::REMOTE_ENDPOINT_ACCEPTED: + * ConnectionEstablishmentStatus::REMOTE_ENDPOINT_REJECTED, or + * ConnectionEstablishmentStatus::CONNECTED. Only when this is set to + * CONNECTED should you allow payload transfers. + */ + typedef std::map + ConnectionEstablishmentStatusesMap; + ConnectionEstablishmentStatusesMap connection_establishment_statuses_; + + /** + * Map of endpoint_ids -> ConnectionLifecycleListeners. Every endpoint in here + * is guaranteed to at least be in + * ConnectionEstablishmentStatus::PENDING -- the precise status can be found + * from the corresponding entry in connection_establishment_statuses. + */ + typedef std::map > + ConnectionLifecycleListenersMap; + ConnectionLifecycleListenersMap connection_lifecycle_listeners_; + + /** + * Map of endpoint_ids -> PayloadListeners. Every endpoint in here is + * guaranteed to at least be in + * ConnectionEstablishmentStatus::LOCAL_ENDPOINT_ACCEPTED -- the + * precise status can be found from the corresponding entry in + * connection_establishment_statuses. + */ + typedef std::map > PayloadListenersMap; + PayloadListenersMap payload_listeners_; + + /** + * A cache of endpoint ids that we've already notified the discoverer of. We + * check this cache before calling onEndpointFound() so that we don't notify + * the client multiple times for the same endpoint. This would otherwise + * happen because some mediums (like Bluetooth) repeatedly give us the same + * endpoints after each scan. + */ + std::set discovered_endpoint_ids_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/client_proxy.cc" + +#endif // CORE_INTERNAL_CLIENT_PROXY_H_ diff --git a/cpp/core/internal/encryption_runner.cc b/cpp/core/internal/encryption_runner.cc new file mode 100644 index 00000000..ccd5f8d5 --- /dev/null +++ b/cpp/core/internal/encryption_runner.cc @@ -0,0 +1,437 @@ +#include "core/internal/encryption_runner.h" + +#include +#include + +#include "platform/base64_utils.h" +#include "platform/byte_array.h" +#include "platform/cancelable_alarm.h" +#include "platform/exception.h" +#include "platform/logging.h" +#include "absl/strings/ascii.h" + +namespace { + +std::int64_t kTimeoutMillis = 15 * 1000; // 15 seconds +std::int32_t kMaxUkey2VerificationStringLength = 32; +std::int32_t kTokenLength = 5; +securegcm::UKey2Handshake::HandshakeCipher kCipher = + securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512; + +} // namespace + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +// Transforms a raw UKEY2 token (which is a random ByteArray that's +// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only +// uses A-Z0-9 for each character. +string toHumanReadableString(ConstPtr token) { + string result = Base64Utils::encode(token).substr(0, kTokenLength); + absl::AsciiStrToUpper(&result); + return result; +} + +template +bool handleEncryptionSuccess( + const string& endpoint_id, Ptr ukey2_handshake, + Ptr::ResultListener> result_listener) { + ScopedPtr> scoped_ukey2_handshake( + ukey2_handshake); + + std::unique_ptr verification_string = + scoped_ukey2_handshake->GetVerificationString( + kMaxUkey2VerificationStringLength); + if (verification_string == nullptr) { + return false; + } + + ScopedPtr> raw_authentication_token(MakeConstPtr( + new ByteArray(verification_string->data(), verification_string->size()))); + + result_listener->onEncryptionSuccess( + endpoint_id, scoped_ukey2_handshake.release(), + toHumanReadableString(raw_authentication_token.get()), + raw_authentication_token.release()); + + return true; +} + +template +class CancelableAlarmRunnable : public Runnable { + public: + CancelableAlarmRunnable(Ptr> client_proxy, + const string& endpoint_id, + Ptr endpoint_channel) + : client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + endpoint_channel_(endpoint_channel) {} + + void run() override { + NEARBY_LOG(INFO, + "Timing out encryption for client %" PRId64 + " to endpoint %s after %" PRId64 " ms", + client_proxy_->getClientId(), endpoint_id_.c_str(), + kTimeoutMillis); + endpoint_channel_->close(); + } + + private: + Ptr> client_proxy_; + const string endpoint_id_; + Ptr endpoint_channel_; +}; + +template +class ServerRunnable : public Runnable { + public: + ServerRunnable(Ptr> client_proxy, + Ptr alarm_executor, + const string& endpoint_id, + Ptr endpoint_channel, + Ptr::ResultListener> + encryption_result_listener) + : client_proxy_(client_proxy), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + endpoint_channel_(endpoint_channel), + encryption_result_listener_(encryption_result_listener) {} + + void run() override { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startServer() timeout", + MakePtr(new CancelableAlarmRunnable( + client_proxy_, endpoint_id_, endpoint_channel_)), + kTimeoutMillis, alarm_executor_); + + std::unique_ptr server = + securegcm::UKey2Handshake::ForResponder(kCipher); + // Java code throws a HandshakeException. + if (server == nullptr) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + + // Message 1 (Client Init) + ExceptionOr> client_init = endpoint_channel_->read(); + if (!client_init.ok()) { + if (Exception::IO == client_init.exception()) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + ScopedPtr> scoped_client_init(client_init.result()); + + securegcm::UKey2Handshake::ParseResult parse_result = + server->ParseHandshakeMessage( + string(scoped_client_init->getData(), scoped_client_init->size())); + + // Java code throws a HandshakeException / AlertException. + if (!parse_result.success) { + logException(); + if (parse_result.alert_to_send != nullptr) { + handleAlertException(parse_result); + } + handleHandshakeOrIOException(timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 1 from endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + std::unique_ptr server_init = server->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (server_init == nullptr) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + + Exception::Value write_exception = endpoint_channel_->write( + MakeConstPtr(new ByteArray(server_init->data(), server_init->size()))); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + NEARBY_LOG(INFO, "In startServer(), wrote UKEY2 Message 2 to endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + ExceptionOr> client_finish = endpoint_channel_->read(); + + if (!client_finish.ok()) { + if (Exception::IO == client_finish.exception()) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + ScopedPtr> scoped_client_finish(client_finish.result()); + parse_result = server->ParseHandshakeMessage( + string(scoped_client_finish->getData(), scoped_client_finish->size())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + logException(); + if (parse_result.alert_to_send != nullptr) { + handleAlertException(parse_result); + } + handleHandshakeOrIOException(timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 3 from endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.cancel(); + + if (!handleEncryptionSuccess(endpoint_id_, + MakePtr(server.release()), + encryption_result_listener_.get())) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + private: + void logException() { + NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { + timeout_alarm.cancel(); + encryption_result_listener_->onEncryptionFailure(endpoint_id_, + endpoint_channel_); + } + + void handleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) { + Exception::Value write_exception = endpoint_channel_->write( + MakeConstPtr(new ByteArray(parse_result.alert_to_send->data(), + parse_result.alert_to_send->size()))); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + NEARBY_LOG(WARNING, + "In startServer(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_proxy_->getClientId(), endpoint_id_.c_str()); + } + } + } + + Ptr> client_proxy_; + Ptr alarm_executor_; + const string endpoint_id_; + Ptr endpoint_channel_; + ScopedPtr::ResultListener>> + encryption_result_listener_; +}; + +template +class ClientRunnable : public Runnable { + public: + ClientRunnable(Ptr> client_proxy, + Ptr alarm_executor, + const string& endpoint_id, + Ptr endpoint_channel, + Ptr::ResultListener> + encryption_result_listener) + : client_proxy_(client_proxy), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + endpoint_channel_(endpoint_channel), + encryption_result_listener_(encryption_result_listener) {} + + void run() override { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startClient() timeout", + MakePtr(new CancelableAlarmRunnable( + client_proxy_, endpoint_id_, endpoint_channel_)), + kTimeoutMillis, alarm_executor_); + + std::unique_ptr client = + securegcm::UKey2Handshake::ForInitiator(kCipher); + + // Java code throws a HandshakeException. + if (client == nullptr) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + + // Message 1 (Client Init) + std::unique_ptr client_init = client->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_init == nullptr) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + + Exception::Value write_init_exception = endpoint_channel_->write( + MakeConstPtr(new ByteArray(client_init->data(), client_init->size()))); + if (Exception::NONE != write_init_exception) { + if (Exception::IO == write_init_exception) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 1 to endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + ExceptionOr> server_init = endpoint_channel_->read(); + + if (!server_init.ok()) { + if (Exception::IO == server_init.exception()) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + ScopedPtr> scoped_server_init(server_init.result()); + securegcm::UKey2Handshake::ParseResult parse_result = + client->ParseHandshakeMessage( + string(scoped_server_init->getData(), scoped_server_init->size())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + logException(); + if (parse_result.alert_to_send != nullptr) { + handleAlertException(parse_result); + } + handleHandshakeOrIOException(timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), read UKEY2 Message 2 from endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + std::unique_ptr client_finish = client->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_finish == nullptr) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + + Exception::Value write_finish_exception = + endpoint_channel_->write(MakeConstPtr( + new ByteArray(client_finish->data(), client_finish->size()))); + if (Exception::NONE != write_finish_exception) { + if (Exception::IO == write_finish_exception) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 3 to endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.cancel(); + + if (!handleEncryptionSuccess(endpoint_id_, + MakePtr(client.release()), + encryption_result_listener_.get())) { + logException(); + handleHandshakeOrIOException(timeout_alarm); + return; + } + } + + private: + void logException() { + NEARBY_LOG(ERROR, "In startClient(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { + timeout_alarm.cancel(); + encryption_result_listener_->onEncryptionFailure(endpoint_id_, + endpoint_channel_); + } + + void handleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) { + Exception::Value write_exception = endpoint_channel_->write( + MakeConstPtr(new ByteArray(parse_result.alert_to_send->data(), + parse_result.alert_to_send->size()))); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + NEARBY_LOG(WARNING, + "In startClient(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_proxy_->getClientId(), endpoint_id_.c_str()); + } + } + } + + Ptr> client_proxy_; + Ptr alarm_executor_; + const string endpoint_id_; + Ptr endpoint_channel_; + ScopedPtr::ResultListener>> + encryption_result_listener_; +}; + +} // namespace + +template +EncryptionRunner::EncryptionRunner() + : alarm_executor_(Platform::createScheduledExecutor()), + server_executor_(Platform::createSingleThreadExecutor()), + client_executor_(Platform::createSingleThreadExecutor()) {} + +template +EncryptionRunner::~EncryptionRunner() { + // Stop all the ongoing Runnables (as gracefully as possible). + client_executor_->shutdown(); + server_executor_->shutdown(); + alarm_executor_->shutdown(); +} + +template +void EncryptionRunner::startServer( + Ptr> client_proxy, const string& endpoint_id, + Ptr endpoint_channel, + Ptr result_listener) { + server_executor_->execute(MakePtr(new ServerRunnable( + client_proxy, alarm_executor_.get(), endpoint_id, endpoint_channel, + result_listener))); +} + +template +void EncryptionRunner::startClient( + Ptr> client_proxy, const string& endpoint_id, + Ptr endpoint_channel, + Ptr result_listener) { + client_executor_->execute(MakePtr(new ClientRunnable( + client_proxy, alarm_executor_.get(), endpoint_id, endpoint_channel, + result_listener))); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/encryption_runner.h b/cpp/core/internal/encryption_runner.h new file mode 100644 index 00000000..3a2373f8 --- /dev/null +++ b/cpp/core/internal/encryption_runner.h @@ -0,0 +1,73 @@ +#ifndef CORE_INTERNAL_ENCRYPTION_RUNNER_H_ +#define CORE_INTERNAL_ENCRYPTION_RUNNER_H_ + +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "securegcm/ukey2_handshake.h" + +namespace location { +namespace nearby { +namespace connections { + +// Encrypts a connection over UKEY2. +// +//

NOTE: Stalled EndpointChannels will be disconnected after {TIMEOUT_MILLIS} +// milliseconds. This is to prevent unverified endpoints from maintaining an +// indefinite connection to us. +template +class EncryptionRunner { + public: + EncryptionRunner(); + ~EncryptionRunner(); + + class ResultListener { + public: + virtual ~ResultListener() {} + + // @EncryptionRunnerThread + virtual void onEncryptionSuccess( + const string& endpoint_id, + Ptr ukey2_handshake, + const string& authentication_token, + ConstPtr raw_authentication_token) = 0; + + // Encryption has failed. The remote_endpoint_id and channel are given so + // that any pending state can be cleaned up. + // + //

We return the EndpointChannel because, at this stage, simultaneous + // connections are a possibility. Use this channel to verify that the state + // you're cleaning up is for this EndpointChannel, and not state for another + // channel to the same endpoint. + // + // @EncryptionRunnerThread + virtual void onEncryptionFailure(const string& endpoint_id, + Ptr channel) = 0; + }; + + // @AnyThread + void startServer(Ptr > client_proxy, + const string& endpoint_id, + Ptr endpoint_channel, + Ptr result_listener); + // @AnyThread + void startClient(Ptr > client_proxy, + const string& endpoint_id, + Ptr endpoint_channel, + Ptr result_listener); + + private: + ScopedPtr > alarm_executor_; + ScopedPtr > server_executor_; + ScopedPtr > client_executor_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/encryption_runner.cc" + +#endif // CORE_INTERNAL_ENCRYPTION_RUNNER_H_ diff --git a/cpp/core/internal/endpoint_channel.h b/cpp/core/internal/endpoint_channel.h new file mode 100644 index 00000000..b7e2e52b --- /dev/null +++ b/cpp/core/internal/endpoint_channel.h @@ -0,0 +1,69 @@ +#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_ENDPOINT_CHANNEL_H_ + +#include + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" + +namespace location { +namespace nearby { +namespace connections { + +class EndpointChannel { + public: + virtual ~EndpointChannel() {} + + virtual ExceptionOr > + read() = 0; // throws Exception::IO, Exception::INTERRUPTED + + virtual Exception::Value write( + ConstPtr data) = 0; // throws Exception::IO + + // Closes this EndpointChannel, without tracking the closure in analytics. + virtual void close() = 0; + + // Closes this EndpointChannel and records the closure with the given reason. + virtual void close(proto::connections::DisconnectionReason reason) = 0; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, WIFI. + virtual string getType() = 0; + + // Returns the name of the EndpointChannel. + virtual string getName() = 0; + + // Returns the analytics enum representing the medium of this EndpointChannel. + virtual proto::connections::Medium getMedium() = 0; + + // Enables encryption on the EndpointChannel. + // + // This method takes ownership of the passed-in 'connection_context'. + virtual void enableEncryption( + Ptr connection_context) = 0; + + // True if the EndpointChannel is currently pausing all writes. + virtual bool isPaused() = 0; + + // Pauses all writes on this EndpointChannel until resume() is called. + virtual void pause() = 0; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + virtual void resume() = 0; + + // Returns the timestamp of the last read from this endpoint, or -1 if no + // reads have occurred. + // TODO(tracyzhou): Clarify units of timestamp. + virtual std::int64_t getLastReadTimestamp() = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc new file mode 100644 index 00000000..745c41b1 --- /dev/null +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -0,0 +1,299 @@ +#include "core/internal/endpoint_channel_manager.h" + +#include "core/internal/ble_endpoint_channel.h" +#include "core/internal/bluetooth_endpoint_channel.h" +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { + +template +EndpointChannelManager::EndpointChannelManager( + Ptr > medium_manager) + : lock_(Platform::createLock()), + medium_manager_(medium_manager), + channel_state_(new ChannelState()) {} + +template +EndpointChannelManager::~EndpointChannelManager() { + Synchronized s(lock_.get()); + + // TODO(tracyzhou): logger.atDebug().log("Initiating shutdown of + // EndpointChannelManager.") + channel_state_.destroy(); + // TODO(tracyzhou): logger.atDebug().log("EndpointChannelManager has shut + // down."); +} + +template +Ptr +EndpointChannelManager::createOutgoingBluetoothEndpointChannel( + const string& channel_name, Ptr bluetooth_socket) { + return BluetoothEndpointChannel::createOutgoing( + medium_manager_, channel_name, bluetooth_socket); +} + +template +Ptr +EndpointChannelManager::createIncomingBluetoothEndpointChannel( + const string& channel_name, Ptr bluetooth_socket) { + return BluetoothEndpointChannel::createIncoming( + medium_manager_, channel_name, bluetooth_socket); +} + +template +Ptr +EndpointChannelManager::createOutgoingBLEEndpointChannel( + const string& channel_name, Ptr ble_socket) { + return BLEEndpointChannel::createOutgoing(medium_manager_, + channel_name, ble_socket); +} + +template +Ptr +EndpointChannelManager::createIncomingBLEEndpointChannel( + const string& channel_name, Ptr ble_socket) { + return BLEEndpointChannel::createIncoming(medium_manager_, + channel_name, ble_socket); +} + +template +void EndpointChannelManager::registerChannelForEndpoint( + Ptr > client_proxy, const string& endpoint_id, + Ptr endpoint_channel) { + Synchronized s(lock_.get()); + + // Just in case there was a previous channel, unregister (and, thus, close) it + // now. + unregisterChannelForEndpoint(endpoint_id); + + setActiveEndpointChannel(client_proxy, endpoint_id, endpoint_channel); + + // TODO(tracyzhou): Add logging. +} + +#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED +template +Ptr +EndpointChannelManager::replaceChannelForEndpoint( + Ptr > client_proxy, const string& endpoint_id, + Ptr endpoint_channel) { + Synchronized s(lock_.get()); + + ScopedPtr > scoped_previous_endpoint_channel( + channel_state_->getChannelForEndpoint(endpoint_id)); + if (scoped_previous_endpoint_channel.isNull()) { + // TODO(tracyzhou): Add logging. + return Ptr(); + } + + setActiveEndpointChannel(client_proxy, endpoint_id, endpoint_channel); + + // TODO(tracyzhou): Add logging. + + return scoped_previous_endpoint_channel.release(); +} +#endif + +template +bool EndpointChannelManager::encryptChannelForEndpoint( + const string& endpoint_id, + Ptr encryption_context) { + Synchronized s(lock_.get()); + + ScopedPtr > scoped_endpoint_channel( + channel_state_->getChannelForEndpoint(endpoint_id)); + if (scoped_endpoint_channel.isNull()) { + // TODO(tracyzhou): Add logging. + return false; + } + + // We found the requested EndpointChannel, so encrypt it. + encryptChannel(endpoint_id, scoped_endpoint_channel.get(), + encryption_context); + + // Then update 'endpoint_id' to use this new 'encryption_context' here + // onwards. + // + // Remember to manage the memory of the returned + // Ptr responsibly, even though we don't + // need what's returned. + ScopedPtr >( + channel_state_->updateEncryptionContextForEndpoint(endpoint_id, + encryption_context)); + return true; +} + +template +Ptr EndpointChannelManager::getChannelForEndpoint( + const string& endpoint_id) { + Synchronized s(lock_.get()); + + return channel_state_->getChannelForEndpoint(endpoint_id); +} + +template +void EndpointChannelManager::setActiveEndpointChannel( + Ptr > client_proxy, const string& endpoint_id, + Ptr endpoint_channel) { +#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED + // If the endpoint is currently encrypted, encrypt this new + // 'endpoint_channel'. + if (channel_state_->isEndpointEncrypted(endpoint_id)) { + encryptChannel( + endpoint_id, endpoint_channel, + channel_state_->getEncryptionContextForEndpoint(endpoint_id)); + } +#endif + + // Then update 'endpoint_id' to use this new 'endpoint_channel' here onwards. + // + // Remember to manage the memory of the returned Ptr + // responsibly, even though we don't need what's returned. + ScopedPtr >( + channel_state_->updateChannelForEndpoint(endpoint_id, endpoint_channel)); +} + +template +void EndpointChannelManager::encryptChannel( + const string& endpoint_id, Ptr endpoint_channel, + Ptr encryption_context) { + // TODO(tracyzhou): Add logging. + endpoint_channel->enableEncryption(encryption_context); +} + +///////////////////////////////// ChannelState ///////////////////////////////// + +template +EndpointChannelManager::ChannelState::~ChannelState() { + while (!endpoint_id_to_metadata_.empty()) { + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.begin(); + // TODO(tracyzhou): Add logging. + removeEndpoint(it->first, + proto::connections::DisconnectionReason::SHUTDOWN); + } +} + +template +bool EndpointChannelManager::ChannelState::isEndpointEncrypted( + const string& endpoint_id) { + return !getEncryptionContextForEndpoint(endpoint_id).isNull(); +} + +template +Ptr +EndpointChannelManager::ChannelState::updateChannelForEndpoint( + const string& endpoint_id, Ptr endpoint_channel) { + Ptr previous_endpoint_channel; + Ptr endpoint_metadata; + + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.find(endpoint_id); + if (it == endpoint_id_to_metadata_.end()) { + endpoint_metadata = MakePtr(new EndpointMetaData()); + } else { + endpoint_metadata = it->second; + previous_endpoint_channel = endpoint_metadata->endpoint_channel; + } + // Avoid leaks. + ScopedPtr > scoped_previous_endpoint_channel( + previous_endpoint_channel); + + // Upgrade endpoint_channel to be reference-counted before starting to track + // it (and make it clear that endpoint_channel no longer owns the raw + // pointer). + endpoint_metadata->endpoint_channel = MakeRefCountedPtr(&(*endpoint_channel)); + endpoint_channel.clear(); + endpoint_id_to_metadata_[endpoint_id] = endpoint_metadata; + + return scoped_previous_endpoint_channel.release(); +} + +template +Ptr EndpointChannelManager:: + ChannelState::updateEncryptionContextForEndpoint( + const string& endpoint_id, + Ptr encryption_context) { + Ptr previous_encryption_context; + Ptr endpoint_metadata; + + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.find(endpoint_id); + if (it == endpoint_id_to_metadata_.end()) { + endpoint_metadata = MakePtr(new EndpointMetaData()); + } else { + endpoint_metadata = it->second; + previous_encryption_context = endpoint_metadata->encryption_context; + } + // Avoid leaks. + ScopedPtr > + scoped_previous_encryption_context(previous_encryption_context); + + endpoint_metadata->encryption_context = encryption_context; + endpoint_id_to_metadata_[endpoint_id] = endpoint_metadata; + + return scoped_previous_encryption_context.release(); +} + +template +bool EndpointChannelManager::ChannelState::removeEndpoint( + const string& endpoint_id, proto::connections::DisconnectionReason reason) { + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.find(endpoint_id); + if (it == endpoint_id_to_metadata_.end()) { + return false; + } + + it->second->endpoint_channel->close(reason); + it->second.destroy(); + endpoint_id_to_metadata_.erase(it); + return true; +} + +template +Ptr +EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( + const string& endpoint_id) { + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.find(endpoint_id); + if (it == endpoint_id_to_metadata_.end()) { + return Ptr(); + } + + return it->second->encryption_context; +} + +template +Ptr +EndpointChannelManager::ChannelState::getChannelForEndpoint( + const string& endpoint_id) { + typename EndpointIdToMetadataMap::iterator it = + endpoint_id_to_metadata_.find(endpoint_id); + if (it == endpoint_id_to_metadata_.end()) { + return Ptr(); + } + + return it->second->endpoint_channel; +} + +template +bool EndpointChannelManager::unregisterChannelForEndpoint( + const string& endpoint_id) { + Synchronized s(lock_.get()); + + if (!channel_state_->removeEndpoint( + endpoint_id, + proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) { + return false; + } + + // TODO(tracyzhou): Add logging. + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/endpoint_channel_manager.h b/cpp/core/internal/endpoint_channel_manager.h new file mode 100644 index 00000000..059085b7 --- /dev/null +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -0,0 +1,143 @@ +#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ +#define CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ + +#include + +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/ble.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/lock.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "securegcm/d2d_connection_context_v1.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages the communication channels to all the remote endpoints with which we +// are interacting, including serving as a factory for creating said channels. +// +// The factory methods would be static, but for the fact that they need to use +// the MediumManager. +template +class EndpointChannelManager { + public: + explicit EndpointChannelManager(Ptr > medium_manager); + ~EndpointChannelManager(); + + Ptr createOutgoingBluetoothEndpointChannel( + const string& channel_name, Ptr bluetooth_socket); + Ptr createIncomingBluetoothEndpointChannel( + const string& channel_name, Ptr bluetooth_socket); + + Ptr createOutgoingBLEEndpointChannel( + const string& channel_name, Ptr ble_socket); + Ptr createIncomingBLEEndpointChannel( + const string& channel_name, Ptr ble_socket); + + // Registers the initial EndpointChannel to be associated with an endpoint; + // if there already exists a previously-associated EndpointChannel, that will + // be closed before continuing the registration. + void registerChannelForEndpoint(Ptr > client_proxy, + const string& endpoint_id, + Ptr endpoint_channel); + +#ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED + // Replaces the EndpointChannel to be associated with an endpoint from here on + // in, transferring the encryption context from the previous EndpointChannel + // to the newly-provided EndpointChannel. + // + // Returns the previous EndpointChannel, or null Ptr object if called out of + // order. + Ptr replaceChannelForEndpoint( + Ptr > client_proxy, const string& endpoint_id, + Ptr endpoint_channel); +#endif + + bool encryptChannelForEndpoint( + const string& endpoint_id, + Ptr encryption_context); + + // The returned Ptr will be owned (and destroyed) by the caller. + Ptr getChannelForEndpoint(const string& endpoint_id); + + // Returns true if 'endpoint_id' actually had a registered EndpointChannel. + // IOW, a return of false signifies a no-op. + bool unregisterChannelForEndpoint(const string& endpoint_id); + + private: + // Tracks channel state for all endpoints. This includes what EndpointChannel + // the endpoint is currently using and whether or not the EndpointChannel has + // been encrypted yet. + class ChannelState { + public: + ~ChannelState(); + + // True if we have an 'encryption_context' for the endpoint. + bool isEndpointEncrypted(const string& endpoint_id); + + // Stores a new EndpointChannel for the endpoint, returning the previous + // one (if it existed). + Ptr updateChannelForEndpoint( + const string& endpoint_id, Ptr endpoint_channel); + // Stores a new D2DConnectionContextV1 for the endpoint, returning the + // previous one (if it existed). + Ptr updateEncryptionContextForEndpoint( + const string& endpoint_id, + Ptr encryption_context); + + // Removes all knowledge of this endpoint, cleaning up as necessary. + // Returns false if the endpoint was not found. + bool removeEndpoint(const string& endpoint_id, + proto::connections::DisconnectionReason reason); + + // Gets the 'encryption_context' for the endpoint. Null if the endpoint was + // not found, or if there is no 'encryption_context' yet. + Ptr getEncryptionContextForEndpoint( + const string& endpoint_id); + // Gets the 'endpoint_channel' for the endpoint. Null if the endpoint was + // not found. + // + // The returned Ptr will be owned (and destroyed) by the caller. + Ptr getChannelForEndpoint(const string& endpoint_id); + + private: + struct EndpointMetaData { + ~EndpointMetaData() { + encryption_context.destroy(); + endpoint_channel.destroy(); + } + + Ptr endpoint_channel; + Ptr encryption_context; + }; + + // Endpoint ID -> EndpointMetadata. Contains everything we know about the + // endpoint. + typedef std::map > EndpointIdToMetadataMap; + EndpointIdToMetadataMap endpoint_id_to_metadata_; + }; + + void setActiveEndpointChannel(Ptr > client_proxy, + const string& endpoint_id, + Ptr endpoint_channel); + void encryptChannel( + const string& endpoint_id, Ptr endpoint_channel, + Ptr encryption_context); + + ScopedPtr > lock_; + + Ptr > medium_manager_; + Ptr channel_state_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/endpoint_channel_manager.cc" + +#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc new file mode 100644 index 00000000..ce63e36a --- /dev/null +++ b/cpp/core/internal/endpoint_manager.cc @@ -0,0 +1,749 @@ +#include "core/internal/endpoint_manager.h" + +#include + +#include "core/internal/offline_frames.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace endpoint_manager { + +// A Runnable that continuously grabs the most recent EndpointChannel available +// for an endpoint. Override +// EndpointChannelLoopRunnable.execute(EndpointChannel) to interact with the +// EndpointChannel. +template +class EndpointChannelLoopRunnable : public Runnable { + public: + EndpointChannelLoopRunnable(Ptr> endpoint_manager, + const string& runnable_name, + Ptr> client_proxy, + const string& endpoint_id) + : endpoint_manager_(endpoint_manager), + runnable_name_(runnable_name), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id) {} + ~EndpointChannelLoopRunnable() override {} + + void run() override { + // The implication of using the EndpointChannel's medium to identify it is + // that this loop will break if we ever allow creating multiple + // EndpointChannels to the same endpoint over the same medium. + proto::connections::Medium last_failed_endpoint_channel_medium = + proto::connections::UNKNOWN_MEDIUM; + while (true) { + // It's important to keep re-fetching the EndpointChannel for an endpoint + // because it can be changed out from under us (for example, when we + // upgrade from Bluetooth to Wifi). + ScopedPtr> scoped_endpoint_channel( + endpoint_manager_->endpoint_channel_manager_->getChannelForEndpoint( + endpoint_id_)); + if (scoped_endpoint_channel.isNull()) { + // TODO(tracyzhou): Add logging. + break; + } + + // If we're looping back around after a failure, and there's not a new + // EndpointChannel for this endpoint, there's nothing more to do here. + if ((last_failed_endpoint_channel_medium != + proto::connections::UNKNOWN_MEDIUM) && + (scoped_endpoint_channel->getMedium() == + last_failed_endpoint_channel_medium)) { + // TODO(tracyzhou): Add logging. + break; + } + + ExceptionOr keep_using_channel = + useHealthyEndpointChannel(scoped_endpoint_channel.get()); + + if (!keep_using_channel.ok()) { + Exception::Value exception = keep_using_channel.exception(); + if (Exception::IO == exception) { + last_failed_endpoint_channel_medium = + scoped_endpoint_channel->getMedium(); + // TODO(tracyzhou): Add logging. + continue; + } + if (Exception::INTERRUPTED == exception) { + // Thread.currentThread().interrupt(); + // TODO(tracyzhou): Add logging. + break; + } + } + + if (!keep_using_channel.result()) { + // TODO(tracyzhou): Add logging. + break; + } + } + + // Always clear out all state related to this endpoint before terminating + // this thread. + endpoint_manager_->discardEndpoint(client_proxy_, endpoint_id_); + } + + // Called whenever an EndpointChannel is available for endpointId. + // Implementations are expected to read/write freely to the EndpointChannel + // until an Exception::IO is thrown. Once an Exception::IO occurs, a check + // will be performed to see if another EndpointChannel is available for the + // given endpoint and, if so, useHealthyEndpointChannel(EndpointChannel) will + // be called again. + // + //

Return false to exit the loop. + virtual ExceptionOr useHealthyEndpointChannel( + Ptr endpoint_channel) = 0; // throws Exception::IO, + // Exception::INTERRUPTED + + protected: + Ptr> endpoint_manager_; + const string runnable_name_; + Ptr> client_proxy_; + const string endpoint_id_; +}; + +template +class ReaderRunnable : public EndpointChannelLoopRunnable { + public: + ReaderRunnable(Ptr> endpoint_manager, + Ptr> client_proxy, + const string& endpoint_id) + : EndpointChannelLoopRunnable(endpoint_manager, "Read", + client_proxy, endpoint_id) {} + + // @EndpointManagerReaderThread + ExceptionOr useHealthyEndpointChannel( + Ptr endpoint_channel) override { + // Read as much as we can from the healthy EndpointChannel - when it is no + // longer in good shape (i.e. our read from it throws an Exception), our + // super class will loop back around and try our luck in case there's been + // a replacement for this endpoint since we last checked with the + // EndpointChannelManager. + while (true) { + ExceptionOr> read_bytes = endpoint_channel->read(); + if (!read_bytes.ok()) { + if (Exception::INVALID_PROTOCOL_BUFFER == read_bytes.exception()) { + // TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager + // failed to decode message from endpoint %s on channel %s, + // discarding.", endpointId, endpointChannel.getType()); + continue; + } else if (Exception::IO == read_bytes.exception()) { + return ExceptionOr(read_bytes.exception()); + } + } + ScopedPtr> scoped_read_bytes(read_bytes.result()); + + ExceptionOr> offline_frame = + OfflineFrames::fromBytes(scoped_read_bytes.get()); + if (!offline_frame.ok()) { + if (Exception::INVALID_PROTOCOL_BUFFER == offline_frame.exception()) { + // TODO(reznor): logger.atDebug().withCause(e).log("EndpointManager + // received an invalid OfflineFrame from endpoint %s on channel %s, + // discarding.", endpointId, endpointChannel.getType()); + continue; + } + } + ScopedPtr> scoped_offline_frame( + offline_frame.result()); + + // Route the incoming offlineFrame to its registered processor. + V1Frame::FrameType frame_type = + OfflineFrames::getFrameType(scoped_offline_frame.get()); + Ptr::IncomingOfflineFrameProcessor> + incoming_offline_frame_processor = + this->endpoint_manager_->getOfflineFrameProcessor(frame_type); + if (incoming_offline_frame_processor.isNull()) { + // TODO(tracyzhou): Add logging. + continue; + } + + incoming_offline_frame_processor->processIncomingOfflineFrame( + scoped_offline_frame.release(), this->endpoint_id_, + this->client_proxy_, endpoint_channel->getMedium()); + } + } +}; + +template +class KeepAliveManagerRunnable : public EndpointChannelLoopRunnable { + public: + KeepAliveManagerRunnable(Ptr> endpoint_manager, + Ptr> client_proxy, + const string& endpoint_id) + : EndpointChannelLoopRunnable( + endpoint_manager, "KeepAliveManager", client_proxy, endpoint_id) {} + + // @EndpointManagerKeepAliveThread + ExceptionOr useHealthyEndpointChannel( + Ptr endpoint_channel) override { + // Check if it has been too long since we received a frame from our + // endpoint. + if ((endpoint_channel->getLastReadTimestamp() != -1) && + ((endpoint_channel->getLastReadTimestamp() + + EndpointManager::kKeepAliveReadTimeoutMillis) < + this->endpoint_manager_->system_clock_->elapsedRealtime())) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(false); + } + + // Attempt to send the KeepAlive frame over the endpoint channel - if the + // write fails, our super class will loop back around and try our luck again + // in case there's been a replacement for this endpoint. + Exception::Value write_exception = + endpoint_channel->write(OfflineFrames::forKeepAlive()); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + return ExceptionOr(write_exception); + } + } + + // We sleep as the very last step because we want to minimize the caching of + // the EndpointChannel. If we do hold on to the EndpointChannel, and it's + // switched out from under us in BandwidthUpgradeManager, our write will + // trigger an erroneous write to the encryption context that will cascade + // into all our remote endpoint's future reads failing. + Exception::Value sleep_exception = + this->endpoint_manager_->thread_utils_->sleep( + EndpointManager::kKeepAliveWriteIntervalMillis); + if (Exception::NONE != sleep_exception) { + if (Exception::INTERRUPTED == sleep_exception) { + return ExceptionOr(sleep_exception); + } + } + + return ExceptionOr(true); + } +}; + +template +class RegisterIncomingOfflineFrameProcessorRunnable : public Runnable { + public: + RegisterIncomingOfflineFrameProcessorRunnable( + Ptr> endpoint_manager, + V1Frame::FrameType frame_type, + Ptr::IncomingOfflineFrameProcessor> + processor) + : endpoint_manager_(endpoint_manager), + frame_type_(frame_type), + processor_(processor) {} + + void run() override { + typename EndpointManager< + Platform>::IncomingOfflineFrameProcessorsMap::iterator it = + endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); + if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) { + // TODO(tracyzhou): Add logging. + it->second = processor_; + } else { + endpoint_manager_->incoming_offline_frame_processors_.insert( + std::make_pair(frame_type_, processor_)); + } + } + + private: + Ptr> endpoint_manager_; + const V1Frame::FrameType frame_type_; + Ptr::IncomingOfflineFrameProcessor> + processor_; +}; + +template +class UnregisterIncomingOfflineFrameProcessorRunnable : public Runnable { + public: + UnregisterIncomingOfflineFrameProcessorRunnable( + Ptr> endpoint_manager, + V1Frame::FrameType frame_type, + Ptr::IncomingOfflineFrameProcessor> + processor) + : endpoint_manager_(endpoint_manager), + frame_type_(frame_type), + processor_(processor) {} + + void run() override { + typename EndpointManager< + Platform>::IncomingOfflineFrameProcessorsMap::iterator it = + endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); + if (it != endpoint_manager_->incoming_offline_frame_processors_.end()) { + if (it->second != processor_) { + // TODO(tracyzhou): Add logging. + return; + } + + endpoint_manager_->incoming_offline_frame_processors_.erase(it); + } + } + + private: + Ptr> endpoint_manager_; + const V1Frame::FrameType frame_type_; + Ptr::IncomingOfflineFrameProcessor> + processor_; +}; + +template +class RegisterEndpointRunnable : public Runnable { + public: + RegisterEndpointRunnable( + Ptr> endpoint_manager, + Ptr> client_proxy, const string& endpoint_id, + const string& endpoint_name, const string& authentication_token, + ConstPtr raw_authentication_token, bool is_incoming, + Ptr endpoint_channel, + Ptr connection_lifecycle_listener, + Ptr latch) + : endpoint_manager_(endpoint_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + authentication_token_(authentication_token), + raw_authentication_token_(raw_authentication_token), + is_incoming_(is_incoming), + endpoint_channel_(endpoint_channel), + connection_lifecycle_listener_(connection_lifecycle_listener), + latch_(latch) {} + + void run() override { + endpoint_manager_->endpoint_channel_manager_->registerChannelForEndpoint( + client_proxy_, endpoint_id_, endpoint_channel_); + + // For every endpoint, there's one Reader instance running on the + // EndpointManagerReaderThread. This instance reads from the endpoint and + // delegates incoming frames to various IncomingOfflineFrameProcessors. + // Once the frame has been properly handled, it starts reading again for the + // next frame. If the Reader fails its read and no other EndpointChannels + // are available for this endpoint, a disconnection will be initiated. + endpoint_manager_->startEndpointReader(MakePtr(new ReaderRunnable( + endpoint_manager_, client_proxy_, endpoint_id_))); + + // For every endpoint, there's one KeepAliveManager instance running on the + // EndpointManagerKeepAliveThread. This instance will periodically + // send out a ping* to the endpoint while listening for an incoming pong**. + // If it fails to send the ping, or if no pong is heard within + // kKeepAliveReadTimeoutMillis milliseconds, it initiates a + // disconnection. + // + // (*) Bluetooth requires a constant outgoing stream of messages. If there's + // silence, Android will break the socket. This is why we ping. + // (**) Wifi Hotspots can fail to notice a connection has been lost, and + // they will happily keep writing to /dev/null. This is why we listen for + // the pong. + endpoint_manager_->startEndpointKeepAliveManager( + MakePtr(new KeepAliveManagerRunnable( + endpoint_manager_, client_proxy_, endpoint_id_))); + // TODO(tracyzhou): Add logging. + + // It's now time to let the client know of this new connection so that they + // can accept or reject it. + client_proxy_->onConnectionInitiated( + endpoint_id_, endpoint_name_, authentication_token_, + raw_authentication_token_.release(), is_incoming_, + connection_lifecycle_listener_.release()); + latch_->countDown(); + } + + private: + Ptr> endpoint_manager_; + Ptr> client_proxy_; + const string endpoint_id_; + const string endpoint_name_; + const string authentication_token_; + ScopedPtr> raw_authentication_token_; + const bool is_incoming_; + Ptr endpoint_channel_; + ScopedPtr> connection_lifecycle_listener_; + Ptr latch_; +}; + +template +class UnregisterEndpointRunnable : public Runnable { + public: + UnregisterEndpointRunnable(Ptr> endpoint_manager, + Ptr> client_proxy, + const string& endpoint_id, + Ptr latch) + : endpoint_manager_(endpoint_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + latch_(latch) {} + + void run() override { + endpoint_manager_->removeEndpoint( + client_proxy_, endpoint_id_, /*send_disconnection_notification=*/false); + + latch_->countDown(); + } + + private: + Ptr> endpoint_manager_; + Ptr> client_proxy_; + const string endpoint_id_; + Ptr latch_; +}; + +template +class DiscardEndpointRunnable : public Runnable { + public: + DiscardEndpointRunnable(Ptr> endpoint_manager, + Ptr> client_proxy, + const string& endpoint_id) + : endpoint_manager_(endpoint_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id) {} + + void run() override { + endpoint_manager_->removeEndpoint( + client_proxy_, endpoint_id_, + /*send_disconnection_notification=*/ + client_proxy_->isConnectedToEndpoint(endpoint_id_)); + } + + private: + Ptr> endpoint_manager_; + Ptr> client_proxy_; + const string endpoint_id_; +}; + +template +class GetOfflineFrameProcessorCallable + : public Callable::IncomingOfflineFrameProcessor>> { + public: + typedef Ptr::IncomingOfflineFrameProcessor> + ReturnType; + + GetOfflineFrameProcessorCallable( + Ptr> endpoint_manager, + V1Frame::FrameType frame_type) + : endpoint_manager_(endpoint_manager), frame_type_(frame_type) {} + + ExceptionOr call() override { + typename EndpointManager< + Platform>::IncomingOfflineFrameProcessorsMap::iterator it = + endpoint_manager_->incoming_offline_frame_processors_.find(frame_type_); + if (it == endpoint_manager_->incoming_offline_frame_processors_.end()) { + return ExceptionOr(ReturnType()); + } + return ExceptionOr(it->second); + } + + private: + Ptr> endpoint_manager_; + const V1Frame::FrameType frame_type_; +}; + +} // namespace endpoint_manager + +template +bool EndpointManager::IncomingOfflineFrameProcessor::operator==( + const EndpointManager::IncomingOfflineFrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return this == &rhs; +} + +template +bool EndpointManager::IncomingOfflineFrameProcessor::operator<( + const EndpointManager::IncomingOfflineFrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return this < &rhs; +} + +template +const std::int32_t EndpointManager::kKeepAliveWriteIntervalMillis = + 5000; +template +const std::int32_t EndpointManager::kKeepAliveReadTimeoutMillis = + 30000; +template +const std::int32_t + EndpointManager::kProcessEndpointDisconnectionTimeoutMillis = + 2000; +template +const std::int32_t EndpointManager::kMaxConcurrentEndpoints = 50; + +template +EndpointManager::EndpointManager( + Ptr> endpoint_channel_manager) + : thread_utils_(Platform::createThreadUtils()), + system_clock_(Platform::createSystemClock()), + endpoint_channel_manager_(endpoint_channel_manager), + incoming_offline_frame_processors_(), + endpoint_keep_alive_manager_thread_pool_( + Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)), + endpoint_readers_thread_pool_( + Platform::createMultiThreadExecutor(kMaxConcurrentEndpoints)), + serial_executor_(Platform::createSingleThreadExecutor()) {} + +template +EndpointManager::~EndpointManager() { + // TODO(tracyzhou): Add logging. + // Stop all the ongoing Runnables (as gracefully as possible). + serial_executor_->shutdown(); + endpoint_readers_thread_pool_->shutdown(); + endpoint_keep_alive_manager_thread_pool_->shutdown(); + + // 'incoming_offline_frame_processors' does not own the processors. + incoming_offline_frame_processors_.clear(); + // TODO(tracyzhou): Add logging. +} + +template +void EndpointManager::registerIncomingOfflineFrameProcessor( + V1Frame::FrameType frame_type, + Ptr::IncomingOfflineFrameProcessor> + processor) { + runOnEndpointManagerThread(MakePtr( + new endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable< + Platform>(MakePtr(this), frame_type, processor))); +} + +template +void EndpointManager::unregisterIncomingOfflineFrameProcessor( + V1Frame::FrameType frame_type, + Ptr::IncomingOfflineFrameProcessor> + processor) { + runOnEndpointManagerThread(MakePtr( + new endpoint_manager::UnregisterIncomingOfflineFrameProcessorRunnable< + Platform>(MakePtr(this), frame_type, processor))); +} + +template +Ptr::IncomingOfflineFrameProcessor> +EndpointManager::getOfflineFrameProcessor( + V1Frame::FrameType frame_type) { + typedef Ptr::IncomingOfflineFrameProcessor> + PtrIncomingOfflineFrameProcessor; + typedef Ptr> ResultType; + + ScopedPtr future_result( + runOnEndpointManagerThread(MakePtr( + new endpoint_manager::GetOfflineFrameProcessorCallable( + MakePtr(this), frame_type)))); + + return waitForResult("getOfflineFrameProcessor", future_result.get()); +} + +template +void EndpointManager::registerEndpoint( + Ptr> client_proxy, const string& endpoint_id, + const string& endpoint_name, const string& authentication_token, + ConstPtr raw_authentication_token, bool is_incoming, + Ptr endpoint_channel, + Ptr connection_lifecycle_listener) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnEndpointManagerThread( + MakePtr(new endpoint_manager::RegisterEndpointRunnable( + MakePtr(this), client_proxy, endpoint_id, endpoint_name, + authentication_token, raw_authentication_token, is_incoming, + endpoint_channel, connection_lifecycle_listener, latch.get()))); + waitForLatch("registerEndpoint", latch.get()); +} + +template +void EndpointManager::unregisterEndpoint( + Ptr> client_proxy, const string& endpoint_id) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnEndpointManagerThread( + MakePtr(new endpoint_manager::UnregisterEndpointRunnable( + MakePtr(this), client_proxy, endpoint_id, latch.get()))); + waitForLatch("unregisterEndpoint", latch.get()); +} + +template +void EndpointManager::discardEndpoint( + Ptr> client_proxy, const string& endpoint_id) { + runOnEndpointManagerThread( + MakePtr(new endpoint_manager::DiscardEndpointRunnable( + MakePtr(this), client_proxy, endpoint_id))); +} + +template +std::vector EndpointManager::sendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids) { + ConstPtr payload_transfer_frame_bytes = + OfflineFrames::forDataPayloadTransferFrame(payload_header, payload_chunk); + + return sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes, + payload_header.id(), + /*offset=*/payload_chunk.offset(), + /*packet_type=*/"DATA"); +} + +template +void EndpointManager::sendControlMessage( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::ControlMessage& control_message, + const std::vector& endpoint_ids) { + ConstPtr payload_transfer_frame_bytes = + OfflineFrames::forControlPayloadTransferFrame(payload_header, + control_message); + + sendTransferFrameBytes(endpoint_ids, payload_transfer_frame_bytes, + payload_header.id(), + /*offset=*/control_message.offset(), + /*packet_type=*/"CONTROL"); +} + +template +void EndpointManager::waitForLatch(const string& method_name, + Ptr latch) { + Exception::Value await_exception = latch->await(); + if (Exception::NONE != await_exception) { + if (Exception::INTERRUPTED == await_exception) { + // TODO(tracyzhou): Add logging. + // Thread.currentThread().interrupt(); + } + } +} + +template +void EndpointManager::waitForLatch(const string& method_name, + Ptr latch, + std::int32_t timeout_millis) { + ExceptionOr await_succeeded = latch->await(timeout_millis); + + if (!await_succeeded.ok()) { + // TODO(tracyzhou): Add logging. + if (Exception::INTERRUPTED == await_succeeded.exception()) { + // TODO(tracyzhou): Add logging. + // Thread.currentThread().interrupt(); + return; + } + } + + if (!await_succeeded.result()) { + // TODO(tracyzhou): Add logging. + } +} + +template +template +T EndpointManager::waitForResult(const string& method_name, + Ptr> result_future) { + ExceptionOr result = result_future->get(); + + if (!result.ok()) { + Exception::Value exception = result.exception(); + if (Exception::INTERRUPTED == exception || + Exception::EXECUTION == exception) { + // TODO(tracyzhou): Add logging. + if (Exception::INTERRUPTED == exception) { + // Thread.currentThread().interrupt(); + } + return T(); + } + } + + return result.result(); +} + +// @EndpointManagerThread +template +void EndpointManager::removeEndpoint( + Ptr> client_proxy, const string& endpoint_id, + bool send_disconnection_notification) { + // Unregistering from endpoint_channel_manager_ will also serve to terminate + // the dedicated reader and KeepAlive threads we started when we registered + // this endpoint. + if (endpoint_channel_manager_->unregisterChannelForEndpoint(endpoint_id)) { + // Notify all frame processors of the disconnection immediately and wait + // for them to clean up state. Only once all processors are done cleaning + // up, we can remove the endpoint from ClientProxy after which there + // should be no further interactions with the endpoint. + // (See b/37352254 for history) + waitForEndpointDisconnectionProcessing(client_proxy, endpoint_id); + + client_proxy->onDisconnected(endpoint_id, send_disconnection_notification); + // TODO(tracyzhou): Add logging. + } +} + +// @EndpointManagerThread +template +void EndpointManager::waitForEndpointDisconnectionProcessing( + Ptr> client_proxy, const string& endpoint_id) { + ScopedPtr> process_disconnection_barrier( + Platform::createCountDownLatch(static_cast( + incoming_offline_frame_processors_.size()))); + + for (typename IncomingOfflineFrameProcessorsMap::iterator it = + incoming_offline_frame_processors_.begin(); + it != incoming_offline_frame_processors_.end(); it++) { + it->second->processEndpointDisconnection( + client_proxy, endpoint_id, process_disconnection_barrier.get()); + } + + waitForLatch("waitForEndpointDisconnectionProcessing", + process_disconnection_barrier.get(), + kProcessEndpointDisconnectionTimeoutMillis); +} + +template +std::vector EndpointManager::sendTransferFrameBytes( + const std::vector& endpoint_ids, + ConstPtr payload_transfer_frame_bytes, std::int64_t payload_id, + std::int64_t offset, const string& packet_type) { + ScopedPtr> scoped_payload_transfer_frame_bytes( + payload_transfer_frame_bytes); + std::vector failed_endpoint_ids; + for (std::vector::const_iterator it = endpoint_ids.begin(); + it != endpoint_ids.end(); it++) { + const string& endpoint_id = *it; + + ScopedPtr> scoped_endpoint_channel( + endpoint_channel_manager_->getChannelForEndpoint(endpoint_id)); + + if (scoped_endpoint_channel.isNull()) { + // We no longer know about this endpoint (it was either explicitly + // unregistered, or a read/write error made us unregister it internally). + // TODO(tracyzhou): Add logging. + failed_endpoint_ids.push_back(endpoint_id); + continue; + } + + Exception::Value write_exception = scoped_endpoint_channel->write( + scoped_payload_transfer_frame_bytes.release()); + if (Exception::NONE != write_exception) { + if (Exception::IO == write_exception) { + // TODO(tracyzhou): Add logging. + failed_endpoint_ids.push_back(endpoint_id); + continue; + } + } + } + + return failed_endpoint_ids; +} + +template +void EndpointManager::startEndpointReader(Ptr runnable) { + endpoint_readers_thread_pool_->execute(runnable); +} + +template +void EndpointManager::startEndpointKeepAliveManager( + Ptr runnable) { + endpoint_keep_alive_manager_thread_pool_->execute(runnable); +} + +template +void EndpointManager::runOnEndpointManagerThread( + Ptr runnable) { + serial_executor_->execute(runnable); +} + +template +template +Ptr> EndpointManager::runOnEndpointManagerThread( + Ptr> callable) { + return serial_executor_->submit(callable); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h new file mode 100644 index 00000000..ae176b1d --- /dev/null +++ b/cpp/core/internal/endpoint_manager.h @@ -0,0 +1,232 @@ +#ifndef CORE_INTERNAL_ENDPOINT_MANAGER_H_ +#define CORE_INTERNAL_ENDPOINT_MANAGER_H_ + +#include + +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel.h" +#include "core/internal/endpoint_channel_manager.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/submittable_executor.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace endpoint_manager { + +template +class ReaderRunnable; +template +class KeepAliveManagerRunnable; +template +class EndpointChannelLoopRunnable; +template +class RegisterIncomingOfflineFrameProcessorRunnable; +template +class UnregisterIncomingOfflineFrameProcessorRunnable; +template +class RegisterEndpointRunnable; +template +class UnregisterEndpointRunnable; +template +class DiscardEndpointRunnable; +template +class GetOfflineFrameProcessorCallable; + +} // namespace endpoint_manager + +// Manages all operations related to the remote endpoints with which we are +// interacting. +// +//

All processing of incoming and outgoing payloads is spread across this and +// the PayloadManager as described below. +// +//

The sending of outgoing payloads originates in +// PayloadManager.sendPayload() before control is transferred over to +// EndpointManager.sendPayloadChunk(). This work happens on one of three +// dedicated writer threads belonging to the PayloadManager. The writer thread +// that is used depends on the PayloadType. +// +//

The EndpointManager has one dedicated reader thread for each registered +// endpoint, and the receiving of every incoming payload (and its subsequent +// chunks) originates on one of those threads before control is transferred over +// to PayloadManager.processIncomingOfflineFrame() (still running on that +// same dedicated reader thread). +template +class EndpointManager { + public: + class IncomingOfflineFrameProcessor { + public: + virtual ~IncomingOfflineFrameProcessor() {} + + // This function takes full ownership of offline_frame. + // @EndpointManagerReaderThread + virtual void processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) = 0; + + // Implementations must call process_disconnection_barrier.countDown() once + // they're done. This parallelizes the disconnection event across all frame + // processors. + // + // @EndpointManagerThread + virtual void processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) = 0; + + // Operator overloads when comparing Ptr. + bool operator==( + const typename EndpointManager::IncomingOfflineFrameProcessor& + rhs); + bool operator<( + const typename EndpointManager::IncomingOfflineFrameProcessor& + rhs); + }; + + explicit EndpointManager( + Ptr > endpoint_channel_manager); + ~EndpointManager(); + + // Invoked from the constructors of the various *Manager components that make + // up the OfflineServiceController implementation. + void registerIncomingOfflineFrameProcessor( + V1Frame::FrameType frame_type, + Ptr processor); + void unregisterIncomingOfflineFrameProcessor( + V1Frame::FrameType frame_type, + Ptr processor); + + // Invoked from the different PCPHandler implementations (of which there can + // be only one at a time). + void registerEndpoint( + Ptr > client_proxy, const string& endpoint_id, + const string& endpoint_name, const string& authentication_token, + ConstPtr raw_authentication_token, bool is_incoming, + Ptr endpoint_channel, + Ptr connection_lifecycle_listener); + // Called when a client explicitly asks to disconnect from this endpoint. In + // this case, we do not notify the client of onDisconnected(). + void unregisterEndpoint(Ptr > client_proxy, + const string& endpoint_id); + // Called when we internally want to get rid of the endpoint, without the + // client directly telling us to. For example... + // a) We failed to read from the endpoint in its dedicated reader thread. + // b) We failed to write to the endpoint in PayloadManager. + // c) The connection was rejected in PCPHandler. + // d) The dedicated KeepAlive thread exceeded its period of inactivity. + // Or in the numerous other cases where a failure occurred and we no longer + // believe the endpoint is in a healthy state. + // + // Note: This must not block. Otherwise we can get into a deadlock where we + // ask everyone who's registered an IncomingOfflineFrameProcessor to + // processEndpointDisconnection() while the caller of discardEndpoint() is + // blocked here. + void discardEndpoint(Ptr > client_proxy, + const string& endpoint_id); + + Ptr getOfflineFrameProcessor( + V1Frame::FrameType frame_type); + + // Returns the list of endpoints to which sending this chunk failed. + // + // Invoked from the PayloadManager's sendPayload() method. + std::vector sendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids); + void sendControlMessage( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::ControlMessage& control_message, + const std::vector& endpoint_ids); + + private: + template + friend class endpoint_manager::ReaderRunnable; + template + friend class endpoint_manager::KeepAliveManagerRunnable; + template + friend class endpoint_manager::EndpointChannelLoopRunnable; + template + friend class endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable; + template + friend class endpoint_manager:: + UnregisterIncomingOfflineFrameProcessorRunnable; + template + friend class endpoint_manager::RegisterEndpointRunnable; + template + friend class endpoint_manager::UnregisterEndpointRunnable; + template + friend class endpoint_manager::DiscardEndpointRunnable; + template + friend class endpoint_manager::GetOfflineFrameProcessorCallable; + + static void waitForLatch(const string& method_name, + Ptr latch); + static void waitForLatch(const string& method_name, Ptr latch, + std::int32_t timeout_millis); + template + static T waitForResult(const string& method_name, + Ptr > result_future); + + static const std::int32_t kKeepAliveWriteIntervalMillis; + static const std::int32_t kKeepAliveReadTimeoutMillis; + static const std::int32_t kProcessEndpointDisconnectionTimeoutMillis; + static const std::int32_t kMaxConcurrentEndpoints; + static const std::int32_t kEndpointIdLength; + + // It should be noted that this method may be called multiple times (because + // invoking this method closes the endpoint channel, which causes the + // dedicated reader and KeepAlive threads to terminate, which in turn leads to + // this method being called), but that's alright because the implementation of + // this method is idempotent. + void removeEndpoint(Ptr > client_proxy, + const string& endpoint_id, + bool send_disconnection_notification); + + void waitForEndpointDisconnectionProcessing( + Ptr > client_proxy, const string& endpoint_id); + + std::vector sendTransferFrameBytes( + const std::vector& endpoint_ids, + ConstPtr payload_transfer_frame_bytes, std::int64_t payload_id, + std::int64_t offset, const string& packet_type); + + void startEndpointReader(Ptr runnable); + void startEndpointKeepAliveManager(Ptr runnable); + void runOnEndpointManagerThread(Ptr runnable); + template + Ptr > runOnEndpointManagerThread(Ptr > callable); + + ScopedPtr > thread_utils_; + ScopedPtr > system_clock_; + + Ptr > endpoint_channel_manager_; + + typedef std::map > + IncomingOfflineFrameProcessorsMap; + IncomingOfflineFrameProcessorsMap incoming_offline_frame_processors_; + + ScopedPtr > + endpoint_keep_alive_manager_thread_pool_; + ScopedPtr > + endpoint_readers_thread_pool_; + ScopedPtr > serial_executor_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/endpoint_manager.cc" + +#endif // CORE_INTERNAL_ENDPOINT_MANAGER_H_ diff --git a/cpp/core/internal/internal_payload.cc b/cpp/core/internal/internal_payload.cc new file mode 100644 index 00000000..ca485783 --- /dev/null +++ b/cpp/core/internal/internal_payload.cc @@ -0,0 +1,20 @@ +#include "core/internal/internal_payload.h" + +namespace location { +namespace nearby { +namespace connections { + +InternalPayload::InternalPayload(ConstPtr payload) + : payload_(payload), payload_id_(payload_->getId()) {} + +InternalPayload::~InternalPayload() {} + +ConstPtr InternalPayload::releasePayload() { + return payload_.release(); +} + +std::int64_t InternalPayload::getId() const { return payload_id_; } + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/internal_payload.h b/cpp/core/internal/internal_payload.h new file mode 100644 index 00000000..33f11860 --- /dev/null +++ b/cpp/core/internal/internal_payload.h @@ -0,0 +1,82 @@ +#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_H_ +#define CORE_INTERNAL_INTERNAL_PAYLOAD_H_ + +#include + +#include "core/payload.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the operations layered atop a Payload, for use inside the +// OfflineServiceController. +// +//

There will be an extension of this abstract base class per type of +// Payload. +class InternalPayload { + public: + explicit InternalPayload(ConstPtr payload); + virtual ~InternalPayload(); + + ConstPtr releasePayload(); + + std::int64_t 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 ExceptionOr > 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::Value attachNextChunk(ConstPtr chunk) = 0; + + // Cleans up any resources used by this Payload. Called when we're stopping + // early, e.g. after being cancelled or having no more recipients left. + virtual void close() {} + + protected: + ScopedPtr > payload_; + // We're caching the payload ID here because the backing payload will be + // released to another owner during the lifetime of an incoming + // InternalPayload. + const std::int64_t payload_id_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_H_ diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc new file mode 100644 index 00000000..4deb40b7 --- /dev/null +++ b/cpp/core/internal/internal_payload_factory.cc @@ -0,0 +1,312 @@ +#include "core/internal/internal_payload_factory.h" + +#include + +#include "core/payload.h" +#include "platform/api/condition_variable.h" +#include "platform/api/lock.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/file_impl.h" +#include "platform/pipe.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +class BytesInternalPayload : public InternalPayload { + public: + explicit BytesInternalPayload(ConstPtr payload) + : InternalPayload(payload), + total_size_(payload_->asBytes()->size()), + detached_only_chunk_(false) {} + + PayloadTransferFrame::PayloadHeader::PayloadType getType() const override { + return PayloadTransferFrame::PayloadHeader::BYTES; + } + + std::int64_t getTotalSize() const override { return total_size_; } + + ExceptionOr > detachNextChunk() override { + if (detached_only_chunk_) { + return ExceptionOr >(ConstPtr()); + } + + detached_only_chunk_ = true; + return ExceptionOr >(payload_->releaseBytes()); + } + + Exception::Value attachNextChunk(ConstPtr chunk) override { + // Avoid leaks. + ScopedPtr > scoped_chunk(chunk); + + // Nothing to do - this method makes sense for other, more long-running + // InternalPayload concrete implementations. + return Exception::NONE; + } + + private: + // We're caching the total size here because the backing payload will be + // released to another owner during the lifetime of an incoming + // InternalPayload. + const std::int64_t total_size_; + bool detached_only_chunk_; +}; + +template +class OutgoingStreamInternalPayload : public InternalPayload { + public: + explicit OutgoingStreamInternalPayload(ConstPtr payload) + : InternalPayload(payload) {} + + PayloadTransferFrame::PayloadHeader::PayloadType getType() const override { + return PayloadTransferFrame::PayloadHeader::STREAM; + } + + std::int64_t getTotalSize() const override { return -1; } + + ExceptionOr > detachNextChunk() override { + Ptr input_stream(payload_->asStream()->asInputStream()); + + ExceptionOr > bytes_read = + input_stream->read(kChunkSize); + if (!bytes_read.ok()) { + if (Exception::IO == bytes_read.exception()) { + // Ignore the potential Exception returned by close(), as a counterpart + // to Java's closeQuietly(). + input_stream->close(); + return bytes_read; + } + } + + // Avoid leaks. + ScopedPtr > scoped_bytes_read(bytes_read.result()); + + if (scoped_bytes_read.isNull()) { + // TODO(reznor): logger.atVerbose().log("No more data for outgoing payload + // %s, closing InputStream.", this); + + // Ignore the potential Exception returned by close(), as a counterpart + // to Java's closeQuietly(). + input_stream->close(); + return ExceptionOr >(ConstPtr()); + } + + return ExceptionOr >(scoped_bytes_read.release()); + } + + Exception::Value attachNextChunk(ConstPtr chunk) override { + return Exception::IO; + } + + void close() override { + // Ignore the potential Exception returned by close(), as a counterpart + // to Java's closeQuietly(). + payload_->asStream()->asInputStream()->close(); + } + + private: + static const std::int64_t kChunkSize = 64 * 1024; +}; + +template +class IncomingStreamInternalPayload : public InternalPayload { + public: + IncomingStreamInternalPayload(ConstPtr payload, + Ptr output_stream) + : InternalPayload(payload), output_stream_(output_stream) {} + + PayloadTransferFrame::PayloadHeader::PayloadType getType() const override { + return PayloadTransferFrame::PayloadHeader::STREAM; + } + + std::int64_t getTotalSize() const override { return -1; } + + ExceptionOr > detachNextChunk() override { + return ExceptionOr >(Exception::IO); + } + + Exception::Value attachNextChunk(ConstPtr chunk) override { + ScopedPtr > scoped_chunk(chunk); + + if (scoped_chunk.isNull()) { + output_stream_->close(); + return Exception::NONE; + } + + return output_stream_->write(scoped_chunk.release()); + } + + void close() override { + output_stream_->close(); + } + + private: + ScopedPtr > output_stream_; +}; + +class OutgoingFileInternalPayload : public InternalPayload { + public: + explicit OutgoingFileInternalPayload(ConstPtr payload) + : InternalPayload(std::move(payload)) {} + + PayloadTransferFrame::PayloadHeader::PayloadType getType() const override { + return PayloadTransferFrame::PayloadHeader::FILE; + } + + std::int64_t getTotalSize() const override { + return payload_->asFile()->asInputFile()->getTotalSize(); + } + + ExceptionOr> detachNextChunk() override { + Ptr input_file(payload_->asFile()->asInputFile()); + + ExceptionOr> bytes_read = input_file->read(kChunkSize); + if (!bytes_read.ok()) { + if (Exception::IO == bytes_read.exception()) { + input_file->close(); + return bytes_read; + } + } + + // Avoid leaks. + ScopedPtr> scoped_bytes_read(bytes_read.result()); + + if (scoped_bytes_read.isNull()) { + // No more data for outgoing payload. + + input_file->close(); + return ExceptionOr>(ConstPtr()); + } + + return ExceptionOr>(scoped_bytes_read.release()); + } + + Exception::Value attachNextChunk(ConstPtr chunk) override { + return Exception::IO; + } + + void close() override { payload_->asFile()->asInputFile()->close(); } + + private: + static const std::int64_t kChunkSize = 64 * 1024; +}; + +class IncomingFileInternalPayload : public InternalPayload { + public: + IncomingFileInternalPayload(ConstPtr payload, + const Ptr& output_file, + std::int64_t total_size) + : InternalPayload(std::move(payload)), + output_file_(output_file), + total_size_(total_size) {} + + PayloadTransferFrame::PayloadHeader::PayloadType getType() const override { + return PayloadTransferFrame::PayloadHeader::FILE; + } + + std::int64_t getTotalSize() const override { return total_size_; } + + ExceptionOr> detachNextChunk() override { + return ExceptionOr>(Exception::IO); + } + + Exception::Value attachNextChunk(ConstPtr chunk) override { + ScopedPtr> scoped_chunk(chunk); + + if (scoped_chunk.isNull()) { + // Received null last chunk for incoming payload. + output_file_->close(); + return Exception::NONE; + } + + return output_file_->write(scoped_chunk.release()); + } + + void close() override { output_file_->close(); } + + private: + ScopedPtr> output_file_; + const std::int64_t total_size_; +}; + +} // namespace + +template +Ptr InternalPayloadFactory::createOutgoing( + ConstPtr payload) { + // Avoid leaks. + ScopedPtr > scoped_payload(payload); + + switch (scoped_payload->getType()) { + case Payload::Type::BYTES: + return MakePtr(new BytesInternalPayload(scoped_payload.release())); + + case Payload::Type::FILE: + return MakePtr(new OutgoingFileInternalPayload(scoped_payload.release())); + + case Payload::Type::STREAM: + return MakePtr(new OutgoingStreamInternalPayload( + scoped_payload.release())); + + default: {} + // Fall through + } + + // This should never be reached since the ServiceControllerRouter has already + // checked whether or not we can work with this Payload type. + return Ptr(); +} + +template +Ptr InternalPayloadFactory::createIncoming( + const PayloadTransferFrame& payload_transfer_frame) { + if (PayloadTransferFrame::DATA != payload_transfer_frame.packet_type()) { + return Ptr(); + } + + const int64_t payload_id = payload_transfer_frame.payload_header().id(); + switch (payload_transfer_frame.payload_header().type()) { + case PayloadTransferFrame::PayloadHeader::BYTES: { + const string& body = payload_transfer_frame.payload_chunk().body(); + return MakePtr(new BytesInternalPayload(MakeConstPtr(new Payload( + payload_id, MakeConstPtr(new ByteArray(body.data(), body.size())))))); + } + + case PayloadTransferFrame::PayloadHeader::STREAM: { + // pipe will be auto-destroyed when it is no longer referenced. + auto pipe = MakeRefCountedPtr(new Pipe()); + + return MakePtr(new IncomingStreamInternalPayload( + MakeConstPtr(new Payload( + payload_id, + MakeConstPtr(new Payload::Stream( + Pipe::createInputStream(pipe))))), + Pipe::createOutputStream(pipe))); + } + + case PayloadTransferFrame::PayloadHeader::FILE: { + const std::string payload_path = Platform::getPayloadPath(payload_id); + Ptr input_file = MakePtr(new InputFileImpl( + payload_path, payload_transfer_frame.payload_header().total_size())); + Ptr output_file = MakePtr(new OutputFileImpl(payload_path)); + ConstPtr payload = MakeConstPtr( + new Payload(payload_id, MakeConstPtr(new Payload::File(input_file)))); + return MakePtr(new IncomingFileInternalPayload( + payload, output_file, + payload_transfer_frame.payload_header().total_size())); + } + default: {} + // Fall through. + } + + // This should never be reached since the ServiceControllerRouter has + // already checked whether or not we can work with this Payload type. + return Ptr(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/internal_payload_factory.h b/cpp/core/internal/internal_payload_factory.h new file mode 100644 index 00000000..0b7086e6 --- /dev/null +++ b/cpp/core/internal/internal_payload_factory.h @@ -0,0 +1,34 @@ +#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ +#define CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ + +#include "core/internal/internal_payload.h" +#include "core/payload.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class InternalPayloadFactory { + public: + // Creates an InternalPayload representing an outgoing Payload. + // + // The returned Ptr will take ownership of the passed-in + // 'payload'. + Ptr createOutgoing(ConstPtr payload); + + // Creates an InternalPayload representing an incoming Payload from a remote + // endpoint. + Ptr createIncoming( + const PayloadTransferFrame& payload_transfer_frame); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/internal_payload_factory.cc" + +#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ diff --git a/cpp/core/internal/loop_runner.cc b/cpp/core/internal/loop_runner.cc new file mode 100644 index 00000000..39414857 --- /dev/null +++ b/cpp/core/internal/loop_runner.cc @@ -0,0 +1,54 @@ +#include "core/internal/loop_runner.h" + +#include "platform/exception.h" + +namespace location { +namespace nearby { +namespace connections { + +LoopRunner::LoopRunner(const std::string& name) : name_(name) {} + +bool LoopRunner::loop(Ptr > callable) { + ScopedPtr > > scoped_callable(callable); + + onEnterLoop(); + while (true) { + onEnterIteration(); + ExceptionOr should_continue = scoped_callable->call(); + if (!should_continue.ok()) { + onExceptionExitLoop(should_continue.exception()); + break; + } + + onExitIteration(); + if (!should_continue.result()) { + onExitLoop(); + return true; + } + } + return false; +} + +void LoopRunner::onEnterLoop() { + // TODO(tracyzhou): Add logging. +} + +void LoopRunner::onEnterIteration() { + // TODO(tracyzhou): Add logging. +} + +void LoopRunner::onExitIteration() { + // TODO(tracyzhou): Add logging. +} + +void LoopRunner::onExitLoop() { + // TODO(tracyzhou): Add logging. +} + +void LoopRunner::onExceptionExitLoop(Exception::Value exception) { + // TODO(tracyzhou): Add logging. +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/loop_runner.h b/cpp/core/internal/loop_runner.h new file mode 100644 index 00000000..af18a5c9 --- /dev/null +++ b/cpp/core/internal/loop_runner.h @@ -0,0 +1,42 @@ +#ifndef CORE_INTERNAL_LOOP_RUNNER_H_ +#define CORE_INTERNAL_LOOP_RUNNER_H_ + +#include "platform/callable.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Construct to run a loop repeatedly. This class is useful to increase +// testability for multi-threaded code that runs loops; it shouldn't be used for +// general purpose loops unless tests require fine-grained control over the +// looping procedure. +class LoopRunner { + public: + explicit LoopRunner(const std::string& name); + + // Runs the provided callable repeatedly until it returns false. + // + // @return true if the loop completed successfully, false if an exception was + // encountered. + bool loop(Ptr > callable); + + protected: + void onEnterLoop(); + void onEnterIteration(); + void onExitIteration(); + void onExitLoop(); + void onExceptionExitLoop(Exception::Value exception); + + private: + const std::string name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_LOOP_RUNNER_H_ diff --git a/cpp/core/internal/medium_manager.cc b/cpp/core/internal/medium_manager.cc new file mode 100644 index 00000000..be6ca370 --- /dev/null +++ b/cpp/core/internal/medium_manager.cc @@ -0,0 +1,361 @@ +#include "core/internal/medium_manager.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { + +template +MediumManager::MediumManager() + : mediums_(new Mediums()), + bluetooth_classic_lock_(Platform::createLock()), + ble_lock_(Platform::createLock()) {} + +template +MediumManager::~MediumManager() { + // TODO(reznor): log.atDebug().log("Initiating shutdown of MediumManager."); + Synchronized s1(bluetooth_classic_lock_.get()); + Synchronized s2(ble_lock_.get()); + + mediums_.destroy(); + // TODO(reznor): log.atDebug().log("MediumManager has shut down."); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~ BLUETOOTH ~~~~~~~~~~~~~~~~~~~~~~~~ + +template +bool MediumManager::isBluetoothAvailable() { + Synchronized s(bluetooth_classic_lock_.get()); + + return mediums_->bluetoothClassic()->isAvailable(); +} + +template +bool MediumManager::turnOnBluetoothDiscoverability( + const string& device_name) { + Synchronized s(bluetooth_classic_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && + mediums_->bluetoothClassic()->turnOnDiscoverability(device_name); +} + +template +void MediumManager::turnOffBluetoothDiscoverability() { + Synchronized s(bluetooth_classic_lock_.get()); + + mediums_->bluetoothClassic()->turnOffDiscoverability(); +} + +template +class DiscoveredDeviceCallback + : public BluetoothClassic::DiscoveredDeviceCallback { + public: + typedef typename MediumManager::FoundBluetoothDeviceProcessor + FoundBluetoothDeviceProcessor; + + explicit DiscoveredDeviceCallback( + Ptr found_bluetooth_device_processor) + : found_bluetooth_device_processor_(found_bluetooth_device_processor) {} + + void onDeviceDiscovered(Ptr device) override { + found_bluetooth_device_processor_->onFoundBluetoothDevice(device); + } + + void onDeviceNameChanged(Ptr device) override { + found_bluetooth_device_processor_->onFoundBluetoothDevice(device); + } + + void onDeviceLost(Ptr device) override { + found_bluetooth_device_processor_->onLostBluetoothDevice(device); + } + + private: + ScopedPtr > + found_bluetooth_device_processor_; +}; + +template +bool MediumManager::startScanningForBluetoothDevices( + Ptr found_bluetooth_device_processor) { + Synchronized s(bluetooth_classic_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && + mediums_->bluetoothClassic()->startDiscovery( + MakePtr(new DiscoveredDeviceCallback( + found_bluetooth_device_processor))); +} + +template +void MediumManager::stopScanningForBluetoothDevices() { + Synchronized s(bluetooth_classic_lock_.get()); + + mediums_->bluetoothClassic()->stopDiscovery(); +} + +template +bool MediumManager::isListeningForIncomingBluetoothConnections( + const string& service_name) { + Synchronized s(bluetooth_classic_lock_.get()); + + return mediums_->bluetoothClassic()->isAcceptingConnections(service_name); +} + +template +class BluetoothAcceptedConnectionCallback + : public BluetoothClassic::AcceptedConnectionCallback { + public: + typedef typename MediumManager::IncomingBluetoothConnectionProcessor + IncomingBluetoothConnectionProcessor; + + explicit BluetoothAcceptedConnectionCallback( + Ptr + incoming_bluetooth_connection_processor) + : incoming_bluetooth_connection_processor_( + incoming_bluetooth_connection_processor) {} + + void onConnectionAccepted(Ptr socket) override { + incoming_bluetooth_connection_processor_->onIncomingBluetoothConnection( + socket); + } + + private: + ScopedPtr > + incoming_bluetooth_connection_processor_; +}; + +template +bool MediumManager::startListeningForIncomingBluetoothConnections( + const string& service_name, Ptr + incoming_bluetooth_connection_processor) { + Synchronized s(bluetooth_classic_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && + mediums_->bluetoothClassic()->startAcceptingConnections( + service_name, + MakePtr(new BluetoothAcceptedConnectionCallback( + incoming_bluetooth_connection_processor))); +} + +template +void MediumManager::stopListeningForIncomingBluetoothConnections( + const string& service_name) { + Synchronized s(bluetooth_classic_lock_.get()); + + mediums_->bluetoothClassic()->stopAcceptingConnections(service_name); +} + +template +Ptr MediumManager::connectToBluetoothDevice( + Ptr bluetooth_device, const string& service_name) { + Synchronized s(bluetooth_classic_lock_.get()); + + if (!mediums_->bluetoothRadio()->enable()) { + return Ptr(); + } + + return mediums_->bluetoothClassic()->connect(bluetooth_device, service_name); +} + +// ~~~~~~~~~~~~~~~~~~~~~~~~ BLE ~~~~~~~~~~~~~~~~~~~~~~~~ +template +bool MediumManager::isBleAvailable() { + Synchronized s(ble_lock_.get()); + +#if BLE_V2_IMPLEMENTED + return mediums_->bleV2()->isAvailable(); +#else + return mediums_->ble()->isAvailable(); +#endif +} + +// TODO(ahlee): Add nearbyNotificationsBeaconData for phase 2 of implementation. +// TODO(ahlee): Add fast_advertisement_service_uuid and power_level to +// AdvertisingOptions and pass it through. +template +bool MediumManager::startBleAdvertising( + const string& service_id, ConstPtr advertisement_data) { + Synchronized s(ble_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->startAdvertising( + service_id, advertisement_data, BLEMediumV2::PowerMode::HIGH, + /* fast_advertisement_service_uuid= */ ""); +#else + mediums_->ble()->startAdvertising(service_id, advertisement_data); +#endif +} + +template +void MediumManager::stopBleAdvertising(const string& service_id) { + Synchronized s(ble_lock_.get()); + +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->stopAdvertising(); +#else + mediums_->ble()->stopAdvertising(); +#endif +} + +#if BLE_V2_IMPLEMENTED +template +class BLEAcceptedConnectionCallback + : public mediums::BLEV2::AcceptedConnectionCallback { + public: + BLEAcceptedConnectionCallback() {} +}; +#else +template +class BLEAcceptedConnectionCallback + : public BLE::AcceptedConnectionCallback { + public: + typedef typename MediumManager::IncomingBleConnectionProcessor + IncomingBleConnectionProcessor; + + explicit BLEAcceptedConnectionCallback( + Ptr incoming_ble_connection_processor) + : incoming_ble_connection_processor_(incoming_ble_connection_processor) {} + + void onConnectionAccepted(Ptr socket, + const string& service_id) override { + incoming_ble_connection_processor_->onIncomingBleConnection(socket, + service_id); + } + + private: + ScopedPtr > + incoming_ble_connection_processor_; +}; +#endif + +template +bool MediumManager::isListeningForIncomingBleConnections( + const string& service_id) { + Synchronized s(ble_lock_.get()); + +#if BLE_V2_IMPLEMENTED + return mediums_->bleV2()->isAcceptingConnections(); +#else + return mediums_->ble()->isAcceptingConnections(); +#endif +} + +template +bool MediumManager::startListeningForIncomingBleConnections( + const string& service_id, + Ptr incoming_ble_connection_processor) { + Synchronized s(ble_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->startAcceptingConnections( + service_id, + MakePtr(new BLEAcceptedConnectionCallback())); +#else + mediums_->ble()->startAcceptingConnections( + service_id, MakePtr(new BLEAcceptedConnectionCallback( + incoming_ble_connection_processor))); +#endif +} + +template +void MediumManager::stopListeningForIncomingBleConnections( + const string& service_id) { + Synchronized s(ble_lock_.get()); + +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->stopAcceptingConnections(); +#else + mediums_->ble()->stopAcceptingConnections(); +#endif +} + +template +class DiscoveredPeripheralCallback : public DISCOVERED_PERIPHERAL_CALLBACK { + public: + typedef typename MediumManager::FoundBlePeripheralProcessor + FoundBlePeripheralProcessor; + + explicit DiscoveredPeripheralCallback( + Ptr found_ble_peripheral_processor) + : found_ble_peripheral_processor_(found_ble_peripheral_processor) {} + + void onPeripheralDiscovered(Ptr ble_peripheral, + const string& service_id, +#if BLE_V2_IMPLEMENTED + ConstPtr advertisement_data, + // TODO(ahlee): Add is_fast_advertisement to + // FoundBlePeripheralProcessor. + bool is_fast_advertisement) override { +#else + ConstPtr advertisement_data) { +#endif + found_ble_peripheral_processor_->onFoundBlePeripheral( + ble_peripheral, service_id, advertisement_data); + } + + void onPeripheralLost(Ptr ble_peripheral, + const string& service_id) override { + found_ble_peripheral_processor_->onLostBlePeripheral(ble_peripheral, + service_id); + } + + private: + ScopedPtr > found_ble_peripheral_processor_; +}; + +// TODO(ahlee): Add fast_advertisement_service_uuid and power_level to +// DiscoveryOptions and pass it through. +template +bool MediumManager::startBleScanning( + const string& service_id, + Ptr found_ble_peripheral_processor) { + Synchronized s(ble_lock_.get()); + + return mediums_->bluetoothRadio()->enable() && +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->startScanning( + service_id, + MakePtr(new DiscoveredPeripheralCallback( + found_ble_peripheral_processor)), + BLEMediumV2::PowerMode::HIGH, + /* fast_advertisement_service_uuid= */ ""); +#else + mediums_->ble()->startScanning( + service_id, MakePtr(new DiscoveredPeripheralCallback( + found_ble_peripheral_processor))); +#endif +} + +template +void MediumManager::stopBleScanning(const string& service_id) { + Synchronized s(ble_lock_.get()); + +#if BLE_V2_IMPLEMENTED + mediums_->bleV2()->stopScanning(); +#else + mediums_->ble()->stopScanning(); +#endif +} + +template +Ptr MediumManager::connectToBlePeripheral( + Ptr ble_peripheral, const string& service_id) { + Synchronized s(ble_lock_.get()); + + if (!mediums_->bluetoothRadio()->enable()) { + return Ptr(); + } + +#if BLE_V2_IMPLEMENTED + // TODO(ahlee): Replace when connecting logic is implemented. + return Ptr(); +#else + return mediums_->ble()->connect(ble_peripheral, service_id); +#endif +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/medium_manager.h b/cpp/core/internal/medium_manager.h new file mode 100644 index 00000000..93ba82af --- /dev/null +++ b/cpp/core/internal/medium_manager.h @@ -0,0 +1,140 @@ +#ifndef CORE_INTERNAL_MEDIUM_MANAGER_H_ +#define CORE_INTERNAL_MEDIUM_MANAGER_H_ + +#include "core/internal/ble_compat.h" +#include "core/internal/mediums/mediums.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/lock.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +/** + * Manages everything related to the mediums used by Nearby Connections, acting + * as a simplifying layer around the different APIs used for said management. + * + *

An overview of thread safety: + * + *

    + *
  • Methods are synchronized at a per-medium level. For example, all + * Bluetooth Classic calls are synchronized under the same + * 'bluetooth_classic_lock_'. This ensures work on a particular medium is + * well-ordered without blocking other mediums from running. Nearby + * Mediums as a whole is already threadsafe, which is why we don't need to + * synchronize at a per-radio level. + *
  • All calls are guarded by the flag 'mediums_are_available_', which + * defaults to true and is set to false in shutdown(). This flag ensures + * that no further work is done after shutdown() has been called. + * Note: shutdown() is the one and only time we grab every + * medium-specific lock, to ensure everything stops at once. + *
+ * + *

Note: For methods that start an action (eg. startAdvertising()), the radio + * is first enabled. This is a prerequisite before doing any work on a medium; + * they will otherwise fail if the radio is off. Calls that stop an action (eg. + * stopAdvertising()) do not attempt to enable the radio because, if the radio + * was off, there is no work for them to stop. + */ +template +class MediumManager { + public: + MediumManager(); + ~MediumManager(); + + // ~~~~~~~~~~~~~~~~~~~~~~~~ BLUETOOTH ~~~~~~~~~~~~~~~~~~~~~~~~ + bool isBluetoothAvailable(); + + bool turnOnBluetoothDiscoverability(const string& device_name); + void turnOffBluetoothDiscoverability(); + + class FoundBluetoothDeviceProcessor { + public: + virtual ~FoundBluetoothDeviceProcessor() {} + + virtual void onFoundBluetoothDevice( + Ptr bluetooth_device) = 0; + virtual void onLostBluetoothDevice( + Ptr bluetooth_device) = 0; + }; + + bool startScanningForBluetoothDevices( + Ptr found_bluetooth_device_processor); + void stopScanningForBluetoothDevices(); + + class IncomingBluetoothConnectionProcessor { + public: + virtual ~IncomingBluetoothConnectionProcessor() {} + + virtual void onIncomingBluetoothConnection( + Ptr bluetooth_socket) = 0; + }; + + bool isListeningForIncomingBluetoothConnections(const string& service_name); + bool startListeningForIncomingBluetoothConnections( + const string& service_name, Ptr + incoming_bluetooth_connection_processor); + void stopListeningForIncomingBluetoothConnections(const string& service_name); + + Ptr connectToBluetoothDevice( + Ptr bluetooth_device, const string& service_name); + + // ~~~~~~~~~~~~~~~~~~~~~~~~ BLE ~~~~~~~~~~~~~~~~~~~~~~~~ + + bool isBleAvailable(); + + bool startBleAdvertising(const string& service_id, + ConstPtr advertisement_data); + void stopBleAdvertising(const string& service_id); + + class IncomingBleConnectionProcessor { + public: + virtual ~IncomingBleConnectionProcessor() {} + + virtual void onIncomingBleConnection(Ptr ble_socket, + const string& service_id) = 0; + }; + + bool isListeningForIncomingBleConnections(const string& service_id); + bool startListeningForIncomingBleConnections( + const string& service_id, + Ptr incoming_ble_connection_processor); + void stopListeningForIncomingBleConnections(const string& service_id); + + class FoundBlePeripheralProcessor { + public: + virtual ~FoundBlePeripheralProcessor() {} + + virtual void onFoundBlePeripheral( + Ptr ble_peripheral, const string& service_id, + ConstPtr advertisement_data) = 0; + virtual void onLostBlePeripheral(Ptr ble_peripheral, + const string& service_id) = 0; + }; + + bool startBleScanning( + const string& service_id, + Ptr found_ble_peripheral_processor); + void stopBleScanning(const string& service_id); + + Ptr connectToBlePeripheral(Ptr ble_peripheral, + const string& service_id); + + private: + // The destructor for this needs to be manually invoked after the locks below + // are acquired, so it cannot be a ScopedPtr. + Ptr > mediums_; + + ScopedPtr > bluetooth_classic_lock_; + ScopedPtr > ble_lock_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/medium_manager.cc" + +#endif // CORE_INTERNAL_MEDIUM_MANAGER_H_ diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD new file mode 100644 index 00000000..b0fca5fa --- /dev/null +++ b/cpp/core/internal/mediums/BUILD @@ -0,0 +1,107 @@ +cc_library( + name = "mediums", + srcs = [ + "ble_advertisement.cc", + "ble_advertisement_header.cc", + "ble_packet.cc", + "ble_peripheral.cc", + "utils.cc", + "utils.h", + ], + hdrs = [ + "advertisement_read_result.cc", + "advertisement_read_result.h", + "ble.cc", + "ble.h", + "ble_advertisement.h", + "ble_advertisement_header.h", + "ble_packet.h", + "ble_peripheral.h", + "ble_v2.cc", + "ble_v2.h", + "bloom_filter.cc", + "bloom_filter.h", + "bluetooth_classic.cc", + "bluetooth_classic.h", + "bluetooth_radio.cc", + "bluetooth_radio.h", + "discovered_peripheral_callback.h", + "discovered_peripheral_tracker.cc", + "discovered_peripheral_tracker.h", + "lost_entity_tracker.cc", + "lost_entity_tracker.h", + "mediums.cc", + "mediums.h", + "uuid.cc", + "uuid.h", + ], + visibility = ["//core/internal:__pkg__"], + deps = [ + "//platform:logging", + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/numeric:int128", + "//absl/strings", + "//smhasher:libmurmur3", + ], +) + +cc_test( + name = "advertisement_read_result_test", + srcs = ["advertisement_read_result_test.cc"], + deps = [ + ":mediums", + "//platform/impl/default", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) + +cc_test( + name = "ble_advertisement_header_test", + srcs = ["ble_advertisement_header_test.cc"], + deps = [ + ":mediums", + "//platform:utils", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "ble_advertisement_test", + srcs = ["ble_advertisement_test.cc"], + deps = [ + ":mediums", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "ble_packet_test", + srcs = ["ble_packet_test.cc"], + deps = [ + ":mediums", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "bloom_filter_test", + srcs = ["bloom_filter_test.cc"], + deps = [ + ":mediums", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "lost_entity_tracker_test", + srcs = ["lost_entity_tracker_test.cc"], + deps = [ + ":mediums", + "//platform/impl/default", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/core/internal/mediums/advertisement_read_result.cc b/cpp/core/internal/mediums/advertisement_read_result.cc new file mode 100644 index 00000000..12cf2e1d --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result.cc @@ -0,0 +1,186 @@ +#include "core/internal/mediums/advertisement_read_result.h" + +#include + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +template +void eraseOwnedPtrFromMap(std::map >& m, const K& k) { + typename std::map >::iterator it = m.find(k); + if (it != m.end()) { + it->second.destroy(); + m.erase(it); + } +} + +} // namespace + +// How much to multiply the backoff duration by with every failure to read +// from the advertisement GATT server. This should never be below 1! +template +const float AdvertisementReadResult::kAdvertisementBackoffMultiplier = + 2.0; + +// The initial backoff duration when we fail to read from an advertisement +// GATT server. +template +const std::int64_t + AdvertisementReadResult::kAdvertisementBaseBackoffDurationMillis = + 1 * 1000; // 1 second + +// The maximum backoff duration allowed between advertisement GATT server +// reads. +template +const std::int64_t + AdvertisementReadResult::kAdvertisementMaxBackoffDurationMillis = + 5 * 60 * 1000; // 5 minutes + +template +AdvertisementReadResult::AdvertisementReadResult() + : lock_(Platform::createLock()), + system_clock_(Platform::createSystemClock()), + advertisements_(), + backoff_duration_millis_(kAdvertisementBaseBackoffDurationMillis), + // We need a long enough duration such that we always trigger a read + // retry AND we always connect to it without delay. The former case + // helps us initialize an AdvertisementReadResult so that we + // unconditionally try reading on the first sighting. And the latter + // case helps us connect immediately when we initialize a dummy read + // result for fast advertisements (which don't use the GATT server). + last_read_timestamp_millis_(system_clock_->elapsedRealtime() - + kAdvertisementMaxBackoffDurationMillis), + result_(Result::Value::UNKNOWN) {} + +template +AdvertisementReadResult::~AdvertisementReadResult() { + Synchronized s(lock_.get()); + + for (AdvertisementMap::iterator it = advertisements_.begin(); + it != advertisements_.end(); ++it) { + it->second.destroy(); + } + advertisements_.clear(); +} + +// Adds a successfully read advertisement for the specified slot to this read +// result. This is fundamentally different from +// {@link #recordLastReadStatus(boolean)} because we can report a read +// failure, but still manage to read some advertisements. +// Note: advertisement should be passed in as a RefCounted Ptr. It is not the +// responsibility of AdvertisementReadResult to make it RefCounted. +template +void AdvertisementReadResult::addAdvertisement( + std::int32_t slot, /* RefCounted */ ConstPtr advertisement) { + Synchronized s(lock_.get()); + + ScopedPtr> scoped_advertisement(advertisement); + + // Blindly remove from the advertisements map to make sure any existing + // key-value pair is destroyed. + eraseOwnedPtrFromMap(advertisements_, slot); + + advertisements_.insert(std::make_pair(slot, scoped_advertisement.release())); +} + +// Determines whether or not an advertisement was successfully read at the +// specified slot. +template +bool AdvertisementReadResult::hasAdvertisement(std::int32_t slot) { + Synchronized s(lock_.get()); + + return advertisements_.find(slot) != advertisements_.end(); +} + +// Retrieves all raw advertisements that were successfully read. +template +std::set> +AdvertisementReadResult::getAdvertisements() { + Synchronized s(lock_.get()); + + std::set> all_advertisements; + for (AdvertisementMap::iterator it = advertisements_.begin(); + it != advertisements_.end(); ++it) { + all_advertisements.insert(it->second); + } + + return all_advertisements; +} + +// Determines what stage we're in for retrying a read from an advertisement +// GATT server. +template +typename AdvertisementReadResult::RetryStatus::Value +AdvertisementReadResult::evaluateRetryStatus() { + Synchronized s(lock_.get()); + + // Check if we have already succeeded reading this advertisement. + if (result_ == Result::SUCCESS) { + return RetryStatus::PREVIOUSLY_SUCCEEDED; + } + + // Check if we have recently failed to read this advertisement. + if (getDurationSinceReadMillis() < backoff_duration_millis_) { + return RetryStatus::TOO_SOON; + } + + return RetryStatus::RETRY; +} + +// Records the status of the latest read, and updates the next backoff +// duration for subsequent reads. Be sure to also call +// {@link #addAdvertisement(int, byte[])} if any advertisements were read. +template +void AdvertisementReadResult::recordLastReadStatus(bool is_success) { + Synchronized s(lock_.get()); + + // Update the last read timestamp. + last_read_timestamp_millis_ = system_clock_->elapsedRealtime(); + + // Update the backoff duration. + if (is_success) { + // Reset the backoff duration now that we had a successful read. + backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis; + } else { + // Determine whether or not we were already failing before. If we were, we + // should increase the backoff duration. + if (result_ == Result::FAILURE) { + // Use exponential backoff to determine the next backoff duration. This + // simply involves multiplying our current backoff duration by some + // multiplier. + std::int64_t next_backoff_duration = + kAdvertisementBackoffMultiplier * backoff_duration_millis_; + // Update the backoff duration, making sure not to blow past the + // ceiling. + backoff_duration_millis_ = std::min( + next_backoff_duration, kAdvertisementMaxBackoffDurationMillis); + } else { + // This is our first time failing, so we should only backoff for the + // initial duration. + backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis; + } + } + + // Update the internal result. + result_ = is_success ? Result::SUCCESS : Result::FAILURE; +} + +// Returns how much time has passed since we last tried reading from an +// advertisement GATT server. +template +std::int64_t AdvertisementReadResult::getDurationSinceReadMillis() { + Synchronized s(lock_.get()); + + return system_clock_->elapsedRealtime() - last_read_timestamp_millis_; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/advertisement_read_result.h b/cpp/core/internal/mediums/advertisement_read_result.h new file mode 100644 index 00000000..9fde9598 --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result.h @@ -0,0 +1,73 @@ +#ifndef CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#define CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ + +#include +#include +#include + +#include "platform/api/lock.h" +#include "platform/api/system_clock.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Representation of a GATT advertisement read result. This object helps us +// determine whether or not we need to retry GATT reads. +template +class AdvertisementReadResult { + public: + AdvertisementReadResult(); + ~AdvertisementReadResult(); + + struct RetryStatus { + enum Value { + UNKNOWN = 0, + RETRY = 1, + PREVIOUSLY_SUCCEEDED = 2, + TOO_SOON = 3, + }; + }; + + void addAdvertisement(std::int32_t slot, ConstPtr advertisement); + bool hasAdvertisement(std::int32_t slot); + std::set> getAdvertisements(); + typename RetryStatus::Value evaluateRetryStatus(); + void recordLastReadStatus(bool is_success); + std::int64_t getDurationSinceReadMillis(); + + private: + struct Result { + enum Value { UNKNOWN = 0, SUCCESS = 1, FAILURE = 2 }; + }; + + static const float kAdvertisementBackoffMultiplier; + static const std::int64_t kAdvertisementBaseBackoffDurationMillis; + static const std::int64_t kAdvertisementMaxBackoffDurationMillis; + + // ------------ GENERAL ------------ + ScopedPtr> lock_; + ScopedPtr> system_clock_; + + // ------ ADVERTISEMENTREADRESULT STATE ------ + // Maps slot numbers to the GATT advertisement found in that slot. + typedef std::map> + AdvertisementMap; + AdvertisementMap advertisements_; + + std::int64_t backoff_duration_millis_; + std::int64_t last_read_timestamp_millis_; + typename Result::Value result_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/advertisement_read_result.cc" + +#endif // CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc new file mode 100644 index 00000000..dd3e7c8b --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -0,0 +1,148 @@ +#include "core/internal/mediums/advertisement_read_result.h" + +#include "platform/impl/default/default_platform.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +class SampleSystemClock : public SystemClock { + public: + SampleSystemClock() {} + ~SampleSystemClock() override {} + + std::int64_t elapsedRealtime() override { + return absl::ToUnixMillis(absl::Now()); + } +}; + +class SamplePlatform { + public: + static Ptr createLock() { return DefaultPlatform::createLock(); } + static Ptr createSystemClock() { + return MakePtr(new SampleSystemClock()); + } +}; + +// We keep a copy of these constants because this is an old-school test (so we +// can't delare it as a friend class of AdvertisementReadResult). +const absl::Duration kAdvertisementBaseBackoffDuration = + absl::Milliseconds(1000); // 1 second +const absl::Duration kAdvertisementMaxBackoffDuration = + absl::Milliseconds(6000); // 6 seconds +const char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; + +TEST(AdvertisementReadResultTest, AdvertisementExists) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + advertisement_read_result.addAdvertisement( + slot, + MakeConstPtr(new ByteArray(kAdvertisementBytes, + sizeof(kAdvertisementBytes) / sizeof(char)))); + + ASSERT_TRUE(advertisement_read_result.hasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + + ASSERT_FALSE(advertisement_read_result.hasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { + AdvertisementReadResult advertisement_read_result; + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::RETRY); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ true); + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult< + SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + + // Sleep for some time, but not long enough to warrant a retry. + absl::SleepFor(absl::Milliseconds( + absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2)); + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::TOO_SOON); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + + // Sleep long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::RETRY); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + + // Record an additional failure so our backoff duration increases. + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + + // Sleep for the backoff duration. We shouldn't trigger a retry because the + // backoff should have increased from failing a second time. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::TOO_SOON); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + + // Record an absurd amount of failures so we hit the maximum backoff duration. + for (std::int32_t i = 0; i < 1000; i++) { + advertisement_read_result.recordLastReadStatus(/* is_success= */ false); + } + + // Sleep for the maximum backoff duration. This should be enough to warrant a + // retry. + absl::SleepFor(kAdvertisementMaxBackoffDuration); + + ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::RETRY); +} + +TEST(AdvertisementReadResultTest, GetDurationSinceRead) { + AdvertisementReadResult advertisement_read_result; + advertisement_read_result.recordLastReadStatus(/* is_success= */ true); + + std::int64_t sleepTime = 420; + absl::SleepFor(absl::Milliseconds(sleepTime)); + + ASSERT_GE(advertisement_read_result.getDurationSinceReadMillis(), sleepTime); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble.cc b/cpp/core/internal/mediums/ble.cc new file mode 100644 index 00000000..ebcffbf4 --- /dev/null +++ b/cpp/core/internal/mediums/ble.cc @@ -0,0 +1,281 @@ +#include "core/internal/mediums/ble.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { + +template +const std::int32_t BLE::kMaxAdvertisementLength = 512; + +template +BLE::BLE(Ptr> bluetooth_radio) + : lock_(Platform::createLock()), + bluetooth_radio_(bluetooth_radio), + bluetooth_adapter_(Platform::createBluetoothAdapter()), + ble_medium_(Platform::createBLEMedium()), + scanning_info_(), + advertising_info_(), + accepting_connections_info_() {} + +template +BLE::~BLE() { + stopAdvertising(); + stopAcceptingConnections(); + stopScanning(); +} + +template +bool BLE::isAvailable() { + Synchronized s(lock_.get()); + + return !ble_medium_.isNull() && !bluetooth_adapter_.isNull(); +} + +// TODO(ahlee): Add fastPairData for phase 2 of C++ implementation. +template +bool BLE::startAdvertising(const string& service_id, + ConstPtr advertisement) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> scoped_advertisement(advertisement); + if (scoped_advertisement.isNull() || service_id.empty()) { + // TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising + // because a null parameter was passed in."); + return false; + } + + if (scoped_advertisement->size() > kMaxAdvertisementLength) { + // TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising + // because the advertisement was too long. Expected at most %d bytes but + // received %d.", kMaxAdvertisementLength, advertisement->size()); + return false; + } + + if (isAdvertising()) { + // TODO(ahlee): logger.atSevere().log("Failed to BLE advertise because we're + // already advertising."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because + // Bluetooth isn't enabled."); + return false; + } + + if (!isAvailable()) { + // TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because + // BLE isn't enabled."); + return false; + } + + if (!ble_medium_->startAdvertising(service_id, + scoped_advertisement.release())) { + // TODO(ahlee) logger.atSevere().log("Failed to start BLE advertising"); + return false; + } + + advertising_info_ = MakePtr(new AdvertisingInfo(service_id)); + return true; +} + +template +void BLE::stopAdvertising() { + Synchronized s(lock_.get()); + + if (!isAdvertising()) { + // TODO(ahlee): logger.atDebug().log("Can't turn off BLE advertising because + // it never started."); + return; + } + + ble_medium_->stopAdvertising(advertising_info_->service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.destroy(); + + // TODO(ahlee): logger.atVerbose().log("Turned BLE advertising off"); +} + +template +bool BLE::isAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.isNull(); +} + +template +bool BLE::startScanning( + const string& service_id, + Ptr discovered_peripheral_callback) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> + scoped_discovered_peripheral_callback(discovered_peripheral_callback); + if (scoped_discovered_peripheral_callback.isNull() || service_id.empty()) { + // TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning + // because a null parameter was passed in."); + return false; + } + + if (isScanning()) { + // TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning + // because we are already scanning."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because + // Bluetooth was never turned on"); + return false; + } + + if (!isAvailable()) { + // TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because + // BLE isn't available."); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_ble_discovered_peripheral_callback( + new BLEDiscoveredPeripheralCallback( + scoped_discovered_peripheral_callback.release())); + if (!ble_medium_->startScanning( + service_id, scoped_ble_discovered_peripheral_callback.get())) { + // TODO(ahlee): logger.atSevere().log("Failed to start BLE scanning."); + return false; + } + + scanning_info_ = MakePtr(new ScanningInfo( + service_id, scoped_ble_discovered_peripheral_callback.release())); + return true; +} + +template +void BLE::stopScanning() { + Synchronized s(lock_.get()); + + if (!isScanning()) { + // TODO(ahlee): logger.atDebug().log("Can't turn off BLE scanning because we + // never started scanning."); + return; + } + + ble_medium_->stopScanning(scanning_info_->service_id); + // Reset our bundle of scanning state to mark that we're no longer scanning. + scanning_info_.destroy(); +} + +template +bool BLE::isScanning() { + Synchronized s(lock_.get()); + + return !scanning_info_.isNull(); +} + +template +bool BLE::startAcceptingConnections( + const string& service_id, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> + scoped_accepted_connection_callback(accepted_connection_callback); + if (scoped_accepted_connection_callback.isNull() || service_id.empty()) { + // TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE + // connections because a null parameter was passed in."); + return false; + } + + if (isAcceptingConnections()) { + // TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE + // connections for %s because another BLE server socket is already + // in-progress.", service_id); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections + // for %s because Bluetooth isn't enabled.", serviceId); + return false; + } + + if (!isAvailable()) { + // TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections + // for %s because BLE isn't available.", serviceId); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_ble_accepted_connection_callback(new BLEAcceptedConnectionCallback( + scoped_accepted_connection_callback.release())); + if (!ble_medium_->startAcceptingConnections( + service_id, scoped_ble_accepted_connection_callback.get())) { + return false; + } + + accepting_connections_info_ = MakePtr(new AcceptingConnectionsInfo( + service_id, scoped_ble_accepted_connection_callback.release())); + return true; +} + +template +void BLE::stopAcceptingConnections() { + Synchronized s(lock_.get()); + + if (!isAcceptingConnections()) { + // TODO(ahlee): logger.atDebug().log("Can't stop accepting BLE connections + // because it was never started."); + return; + } + + ble_medium_->stopAcceptingConnections( + accepting_connections_info_->service_id); + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.destroy(); +} + +template +bool BLE::isAcceptingConnections() { + Synchronized s(lock_.get()); + + return !accepting_connections_info_.isNull(); +} + +template +Ptr BLE::connect(Ptr ble_peripheral, + const string& service_id) { + Synchronized s(lock_.get()); + + if (ble_peripheral.isNull() || service_id.empty()) { + // TODO(ahlee): logger.atSevere().log("Refusing to create client BLE socket + // because at least one of blePeripheral or serviceId is null."); + return Ptr(); + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s + // because Bluetooth isn't enabled.", blePeripheral); + return Ptr(); + } + + if (!isAvailable()) { + // TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s + // because BLE isn't available.", blePeripheral); + return Ptr(); + } + + return ble_medium_->connect(ble_peripheral, service_id); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble.h b/cpp/core/internal/mediums/ble.h new file mode 100644 index 00000000..e7db1336 --- /dev/null +++ b/cpp/core/internal/mediums/ble.h @@ -0,0 +1,197 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_H_ + +#include + +#include "core/internal/mediums/bluetooth_radio.h" +#include "platform/api/ble.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/lock.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class BLE { + public: + explicit BLE(Ptr> bluetooth_radio); + ~BLE(); + + bool isAvailable(); + + bool startAdvertising(const string& service_id, + ConstPtr advertisement); + void stopAdvertising(); + + class DiscoveredPeripheralCallback { + public: + virtual ~DiscoveredPeripheralCallback() {} + + virtual void onPeripheralDiscovered(Ptr ble_peripheral, + const string& service_id, + ConstPtr advertisement) = 0; + virtual void onPeripheralLost(Ptr ble_peripheral, + const string& service_id) = 0; + }; + + bool startScanning( + const string& service_id, + Ptr discovered_peripheral_callback); + void stopScanning(); + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() {} + + virtual void onConnectionAccepted(Ptr socket, + const string& service_id) = 0; + }; + + bool startAcceptingConnections( + const string& service_id, + Ptr accepted_connection_callback); + void stopAcceptingConnections(); + bool isAcceptingConnections(); + + Ptr connect(Ptr ble_peripheral, + const string& service_id); + + private: + // TODO(ahlee): Rename to DiscoveredPeripheralCallbackBridge + class BLEDiscoveredPeripheralCallback + : public BLEMedium::DiscoveredPeripheralCallback { + public: + explicit BLEDiscoveredPeripheralCallback( + Ptr discovered_peripheral_callback) + : discovered_peripheral_callback_(discovered_peripheral_callback) {} + ~BLEDiscoveredPeripheralCallback() override { + // Nothing to do. + } + + void onPeripheralDiscovered(Ptr ble_peripheral, + const string& service_id, + ConstPtr advertisement) override { + discovered_peripheral_callback_->onPeripheralDiscovered( + ble_peripheral, service_id, advertisement); + } + void onPeripheralLost(Ptr ble_peripheral, + const string& service_id) override { + discovered_peripheral_callback_->onPeripheralLost(ble_peripheral, + service_id); + } + + private: + ScopedPtr> + discovered_peripheral_callback_; + }; + + // TODO(ahlee): Rename to AcceptedConnectionCallbackBridge + class BLEAcceptedConnectionCallback + : public BLEMedium::AcceptedConnectionCallback { + public: + explicit BLEAcceptedConnectionCallback( + Ptr accepted_connection_callback) + : accepted_connection_callback_(accepted_connection_callback) {} + ~BLEAcceptedConnectionCallback() override { + // Nothing to do. + } + + void onConnectionAccepted(Ptr ble_socket, + const string& service_id) override { + accepted_connection_callback_->onConnectionAccepted(ble_socket, + service_id); + } + + private: + ScopedPtr> + accepted_connection_callback_; + }; + + struct ScanningInfo { + ScanningInfo( + const string& service_id, + Ptr ble_discovered_peripheral_callback) + : service_id(service_id), + ble_discovered_peripheral_callback( + ble_discovered_peripheral_callback) {} + ~ScanningInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + const string service_id; + ScopedPtr> + ble_discovered_peripheral_callback; + }; + + struct AdvertisingInfo { + explicit AdvertisingInfo(const string& service_id) + : service_id(service_id) {} + ~AdvertisingInfo() {} + + const string service_id; + }; + + struct AcceptingConnectionsInfo { + AcceptingConnectionsInfo( + const string& service_id, + Ptr ble_accepted_connection_callback) + : service_id(service_id), + ble_accepted_connection_callback(ble_accepted_connection_callback) {} + ~AcceptingConnectionsInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + const string service_id; + ScopedPtr> + ble_accepted_connection_callback; + }; + + static const std::int32_t kMaxAdvertisementLength; + + bool isAdvertising(); + bool isScanning(); + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + + // ------------ CORE BLE ------------ + + Ptr> bluetooth_radio_; + ScopedPtr> bluetooth_adapter_; + // The underlying, per-platform implementation. + ScopedPtr> ble_medium_; + + // ------------ DISCOVERY ------------ + + // A bundle of state required to start/stop BLE scanning. When non-null, + // we are currently performing a BLE scan. + // In the Java code this maps to the bleListener and + // bleScanningMediumOperation. + Ptr scanning_info_; + + // ------------ ADVERTISING ------------ + + // A bundle of state required to start/stop BLE advertising. When non-null, + // we are currently advertising over BLE. + // In the Java code this maps to bleAdvertiser, advertiseCallback, and + // bleAdvertisingMediumOperation. + Ptr advertising_info_; + + // A bundle of state required to start/stop accepting BLE connections. When + // non-null, we are currently accepting BLE connections. + // In the Java code this maps to the bleServerSocket. + Ptr accepting_connections_info_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/ble.cc" + +#endif // CORE_INTERNAL_MEDIUMS_BLE_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement.cc b/cpp/core/internal/mediums/ble_advertisement.cc new file mode 100644 index 00000000..050c51a1 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement.cc @@ -0,0 +1,288 @@ +#include "core/internal/mediums/ble_advertisement.h" + +#include "platform/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const std::uint32_t BLEAdvertisement::kServiceIdHashLength = 3; + +const std::uint32_t BLEAdvertisement::kVersionLength = 1; +// Length of one int. Be sure to re-evaluate how we compute data size in this +// class if this constant ever changes! +const std::uint32_t BLEAdvertisement::kDataSizeLength = 4; +const std::uint32_t BLEAdvertisement::kMinAdvertisementLength = + kVersionLength + kServiceIdHashLength + kDataSizeLength; +// The maximum length for a GATT characteristic value is 512 bytes, so make sure +// the entire advertisement is less than that. The data can take up whatever +// space is remaining after the bytes preceding it. +const std::uint32_t BLEAdvertisement::kMaxDataSize = + 512 - kMinAdvertisementLength; +const std::uint16_t BLEAdvertisement::kVersionBitmask = 0x0E0; +const std::uint16_t BLEAdvertisement::kSocketVersionBitmask = 0x01C; + +ConstPtr BLEAdvertisement::fromBytes( + ConstPtr ble_advertisement_bytes) { + if (ble_advertisement_bytes.isNull()) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: null bytes passed in"); + return ConstPtr(); + } + + if (ble_advertisement_bytes->size() < kMinAdvertisementLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: expecting min %u raw " + "bytes, got %zu", + kMinAdvertisementLength, ble_advertisement_bytes->size()); + return ConstPtr(); + } + + // Now, time to read the bytes! + const char *ble_advertisement_bytes_read_ptr = + ble_advertisement_bytes->getData(); + + // 1. Version. + Version::Value version = parseVersionFromByte( + static_cast(*ble_advertisement_bytes_read_ptr)); + if (!isSupportedVersion(version)) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: unsupported Version %u", + version); + return ConstPtr(); + } + + // 2. Socket Version. + SocketVersion::Value socket_version = parseSocketVersionFromByte( + static_cast(*ble_advertisement_bytes_read_ptr)); + if (!isSupportedSocketVersion(socket_version)) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u", + socket_version); + return ConstPtr(); + } + ble_advertisement_bytes_read_ptr += kVersionLength; + + // 3. Service ID hash. + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength))); + ble_advertisement_bytes_read_ptr += kServiceIdHashLength; + + // 4.1. Data size. + size_t expected_data_size = + deserializeDataSize(ble_advertisement_bytes_read_ptr); + if (expected_data_size < 0) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: negative data size %zu", + expected_data_size); + return ConstPtr(); + } + ble_advertisement_bytes_read_ptr += kDataSizeLength; + + // Check that the stated data size is the same as what we received. + size_t actual_data_size = computeDataSize(ble_advertisement_bytes); + if (actual_data_size < expected_data_size) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: expected data to be %zu " + "bytes, got %zu bytes", + expected_data_size, actual_data_size); + return ConstPtr(); + } + + // 4.2. Data. + ScopedPtr > scoped_data(MakeConstPtr( + new ByteArray(ble_advertisement_bytes_read_ptr, expected_data_size))); + ble_advertisement_bytes_read_ptr += expected_data_size; + + return MakeRefCountedConstPtr(new BLEAdvertisement( + version, socket_version, scoped_service_id_hash.release(), + scoped_data.release())); +} + +ConstPtr BLEAdvertisement::toBytes( + Version::Value version, SocketVersion::Value socket_version, + ConstPtr service_id_hash, ConstPtr data) { + // Check that the given input is valid. + if (!isSupportedVersion(version)) { + NEARBY_LOG(INFO, + "Cannot serialize BLEAdvertisement: unsupported Version %u", + version); + return ConstPtr(); + } + + if (!isSupportedSocketVersion(socket_version)) { + NEARBY_LOG( + INFO, "Cannot serialize BLEAdvertisement: unsupported SocketVersion %u", + socket_version); + return ConstPtr(); + } + + if (service_id_hash->size() != kServiceIdHashLength) { + NEARBY_LOG(INFO, + "Cannot serialize BLEAdvertisement: expected a service_id_hash " + "of %u bytes, but got %zu", + kServiceIdHashLength, service_id_hash->size()); + return ConstPtr(); + } + + if (data->size() > kMaxDataSize) { + NEARBY_LOG(INFO, + "Cannot serialize BLEAdvertisement: expected data of at most %u " + "bytes, but got %zu", + kMaxDataSize, data->size()); + return ConstPtr(); + } + + // Initialize the bytes. + size_t advertisement_length = computeAdvertisementLength(data); + Ptr advertisement_bytes{new ByteArray{advertisement_length}}; + char *advertisement_bytes_write_ptr = advertisement_bytes->getData(); + + // 1. Version. + serializeVersionByte(advertisement_bytes_write_ptr, version); + + // 2. SocketVersion. + serializeSocketVersionByte(advertisement_bytes_write_ptr, socket_version); + advertisement_bytes_write_ptr += kVersionLength; + + // 3. Service ID hash. + memcpy(advertisement_bytes_write_ptr, service_id_hash->getData(), + kServiceIdHashLength); + advertisement_bytes_write_ptr += kServiceIdHashLength; + + // 4.1. Data length. + serializeDataSize(advertisement_bytes_write_ptr, data->size()); + advertisement_bytes_write_ptr += kDataSizeLength; + + // 4.2. Data. + memcpy(advertisement_bytes_write_ptr, data->getData(), data->size()); + advertisement_bytes_write_ptr += data->size(); + + return ConstifyPtr(advertisement_bytes); +} + +bool BLEAdvertisement::isSupportedVersion(Version::Value version) { + return version >= Version::V1 && version <= Version::V2; +} + +bool BLEAdvertisement::isSupportedSocketVersion( + SocketVersion::Value socket_version) { + return socket_version >= SocketVersion::V1 && + socket_version <= SocketVersion::V2; +} + +BLEAdvertisement::Version::Value BLEAdvertisement::parseVersionFromByte( + std::uint16_t byte) { + return static_cast( + (byte & kVersionBitmask) >> 5); +} + +BLEAdvertisement::SocketVersion::Value +BLEAdvertisement::parseSocketVersionFromByte(std::uint16_t byte) { + return static_cast((byte & kSocketVersionBitmask) >> 2); +} + +size_t BLEAdvertisement::deserializeDataSize( + const char *data_size_bytes_read_ptr) { + // Allocate a chunk of memory to store our deserialized size. + char data_size_bytes[kDataSizeLength]; + + // Assign the bits of our size from the given raw bytes, keeping in mind that + // we need to convert from Big Endian to Little Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1]; + } + + // Interpret the char array as a single int. + return static_cast( + *(reinterpret_cast(&data_size_bytes))); +} + +size_t BLEAdvertisement::computeDataSize( + ConstPtr ble_advertisement_bytes) { + return ble_advertisement_bytes->size() - kMinAdvertisementLength; +} + +size_t BLEAdvertisement::computeAdvertisementLength(ConstPtr data) { + // The advertisement length is the minimum length + the length of the data. + return kMinAdvertisementLength + data->size(); +} + +void BLEAdvertisement::serializeVersionByte(char *version_byte_write_ptr, + Version::Value version) { + *version_byte_write_ptr |= + static_cast((version << 5) & kVersionBitmask); +} + +void BLEAdvertisement::serializeSocketVersionByte( + char *socket_version_byte_write_ptr, SocketVersion::Value socket_version) { + *socket_version_byte_write_ptr |= + static_cast((socket_version << 2) & kSocketVersionBitmask); +} + +void BLEAdvertisement::serializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) { + // Get a raw representation of the data size bytes in memory. + char *data_size_bytes = reinterpret_cast(&data_size); + + // Append these raw bytes to advertisement bytes, keeping in mind that we need + // to convert from Little Endian to Big Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1]; + } +} + +BLEAdvertisement::BLEAdvertisement(Version::Value version, + SocketVersion::Value socket_version, + ConstPtr service_id_hash, + ConstPtr data) + : version_(version), + socket_version_(socket_version), + service_id_hash_(service_id_hash), + data_(data) {} + +BLEAdvertisement::~BLEAdvertisement() { + // Nothing to do. +} + +BLEAdvertisement::Version::Value BLEAdvertisement::getVersion() const { + return version_; +} + +BLEAdvertisement::SocketVersion::Value BLEAdvertisement::getSocketVersion() + const { + return socket_version_; +} + +ConstPtr BLEAdvertisement::getServiceIdHash() const { + return service_id_hash_.get(); +} + +ConstPtr BLEAdvertisement::getData() const { return data_.get(); } + +bool BLEAdvertisement::operator==(const BLEAdvertisement &rhs) const { + return this->getVersion() == rhs.getVersion() && + this->getSocketVersion() == rhs.getSocketVersion() && + *(this->getServiceIdHash()) == *(rhs.getServiceIdHash()) && + *(this->getData()) == *(rhs.getData()); +} + +bool BLEAdvertisement::operator<(const BLEAdvertisement &rhs) const { + if (this->getVersion() != rhs.getVersion()) { + return this->getVersion() < rhs.getVersion(); + } + if (this->getSocketVersion() != rhs.getSocketVersion()) { + return this->getSocketVersion() < rhs.getSocketVersion(); + } + if (*(this->getServiceIdHash()) != *(rhs.getServiceIdHash())) { + return *(this->getServiceIdHash()) < *(rhs.getServiceIdHash()); + } + return *(this->getData()) < *(rhs.getData()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_advertisement.h b/cpp/core/internal/mediums/ble_advertisement.h new file mode 100644 index 00000000..75209336 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement.h @@ -0,0 +1,100 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ + +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums BLE Advertisement used in advertising +// and discovery. +// +// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA] +// +// See go/nearby-ble-design for more information. +class BLEAdvertisement { + public: + // Versions of the BLEAdvertisement. + struct Version { + enum Value { + UNKNOWN = 0, + V1 = 1, + V2 = 2, + // Version is only allocated 3 bits in the BLEAdvertisement, so this can + // never go beyond V7. + }; + }; + + // Versions of the BLESocket. + struct SocketVersion { + enum Value { + UNKNOWN = 0, + V1 = 1, + V2 = 2, + // SocketVersion is only allocated 3 bits in the BLEAdvertisement, so this + // can never go beyond V7. + }; + }; + + static ConstPtr fromBytes( + ConstPtr ble_advertisement_bytes); + + static ConstPtr toBytes(Version::Value version, + SocketVersion::Value socket_version, + ConstPtr service_id_hash, + ConstPtr data); + + static const std::uint32_t kServiceIdHashLength; + + ~BLEAdvertisement(); + + Version::Value getVersion() const; + SocketVersion::Value getSocketVersion() const; + ConstPtr getServiceIdHash() const; + ConstPtr getData() const; + + // Operator overloads when comparing ConstPtr. + bool operator==(const BLEAdvertisement &rhs) const; + bool operator<(const BLEAdvertisement &rhs) const; + + private: + static bool isSupportedVersion(Version::Value version); + static bool isSupportedSocketVersion(SocketVersion::Value socket_version); + static Version::Value parseVersionFromByte(std::uint16_t byte); + static SocketVersion::Value parseSocketVersionFromByte(std::uint16_t byte); + static size_t deserializeDataSize(const char *data_size_bytes_read_ptr); + static size_t computeDataSize(ConstPtr ble_advertisement_bytes); + static size_t computeAdvertisementLength(ConstPtr data); + static void serializeVersionByte(char *version_byte_write_ptr, + Version::Value version); + static void serializeSocketVersionByte(char *socket_version_byte_write_ptr, + SocketVersion::Value socket_version); + static void serializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size); + + static const std::uint32_t kVersionLength; + static const std::uint32_t kDataSizeLength; + static const std::uint32_t kMinAdvertisementLength; + static const std::uint32_t kMaxDataSize; + static const std::uint16_t kVersionBitmask; + static const std::uint16_t kSocketVersionBitmask; + + BLEAdvertisement(Version::Value version, SocketVersion::Value socket_version, + ConstPtr service_id_hash, + ConstPtr data); + + const Version::Value version_; + const SocketVersion::Value socket_version_; + ScopedPtr > service_id_hash_; + ScopedPtr > data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement_header.cc b/cpp/core/internal/mediums/ble_advertisement_header.cc new file mode 100644 index 00000000..e433877d --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -0,0 +1,208 @@ +#include "core/internal/mediums/ble_advertisement_header.h" + +#include "platform/base64_utils.h" +#include "platform/byte_array.h" +#include "platform/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// The following IfThisThenThat is for BloomFilter length in +// ble_v2.createAdvertisementHeader +// LINT.IfChange +const std::uint32_t BLEAdvertisementHeader::kServiceIdBloomFilterLength = 10; +// LINT.ThenChange(//depot/google3/core/internal/\ +// mediums/ble_v2.h) +const std::uint32_t BLEAdvertisementHeader::kAdvertisementHashLength = 4; + +const std::uint32_t BLEAdvertisementHeader::kVersionAndNumSlotsLength = 1; +const std::uint32_t BLEAdvertisementHeader::kMinAdvertisementHeaderLength = + kVersionAndNumSlotsLength + kServiceIdBloomFilterLength + + kAdvertisementHashLength; +const std::uint16_t BLEAdvertisementHeader::kVersionBitmask = 0x0E0; +const std::uint16_t BLEAdvertisementHeader::kNumSlotsBitmask = 0x01F; + +ConstPtr BLEAdvertisementHeader::fromString( + const std::string &ble_advertisement_header_string) { + ScopedPtr > scoped_ble_advertisement_header_bytes( + Base64Utils::decode(ble_advertisement_header_string)); + if (scoped_ble_advertisement_header_bytes.isNull()) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding"); + return ConstPtr(); + } + + if (scoped_ble_advertisement_header_bytes->size() < + kMinAdvertisementHeaderLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisementHeader: expecting min %u " + "raw bytes, got %zu instead", + kMinAdvertisementHeaderLength, + scoped_ble_advertisement_header_bytes->size()); + return ConstPtr(); + } + + // Now, time to read the bytes! + const char *ble_advertisement_header_read_ptr = + scoped_ble_advertisement_header_bytes->getData(); + + // 1. Version. + // The first 3 bits of the first byte represent the version. + Version::Value version = parseVersionFromByte( + static_cast(*ble_advertisement_header_read_ptr)); + if (version != Version::V2) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisementHeader, unsupported version %u", + version); + return ConstPtr(); + } + + // 2. Number of slots. + // The last 5 bits of the first byte represent the number of slots. + std::uint32_t num_slots = parseNumSlotsFromByte( + static_cast(*ble_advertisement_header_read_ptr)); + ble_advertisement_header_read_ptr += kVersionAndNumSlotsLength; + + // 3. Service ID bloom filter. + ScopedPtr > scoped_service_id_bloom_filter( + MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr, + kServiceIdBloomFilterLength))); + ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength; + + // 4. Advertisement hash. + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr, + kAdvertisementHashLength))); + ble_advertisement_header_read_ptr += kAdvertisementHashLength; + + return MakeRefCountedConstPtr(new BLEAdvertisementHeader( + version, num_slots, scoped_service_id_bloom_filter.release(), + scoped_advertisement_hash.release())); +} + +std::string BLEAdvertisementHeader::asString( + Version::Value version, std::uint32_t num_slots, + ConstPtr service_id_bloom_filter, + ConstPtr advertisement_hash) { + // Check that the given input is valid. + if (version != Version::V2) { + NEARBY_LOG( + INFO, "Cannot serialize BLEAdvertisementHeader: unsupported Version %u", + version); + return ""; + } + + if (service_id_bloom_filter->size() != kServiceIdBloomFilterLength) { + NEARBY_LOG(INFO, + "Cannot serialize BLEAdvertisementHeader: expected " + "service_id_bloom_filter of %u bytes, but got %zu", + kServiceIdBloomFilterLength, service_id_bloom_filter->size()); + return ""; + } + + if (advertisement_hash->size() != kAdvertisementHashLength) { + NEARBY_LOG(INFO, + "Cannot serialize BLEAdvertisementHeader: expected " + "advertisement_hash of %u bytes, but got %zu", + kAdvertisementHashLength, advertisement_hash->size()); + return ""; + } + + // Initialize the bytes. + ByteArray advertisement_header_bytes{kMinAdvertisementHeaderLength}; + char *advertisement_header_bytes_write_ptr = + advertisement_header_bytes.getData(); + + // 1. Version. + serializeVersionByte(advertisement_header_bytes_write_ptr, version); + + // 2. Number of slots. + serializeNumSlots(advertisement_header_bytes_write_ptr, num_slots); + advertisement_header_bytes_write_ptr += kVersionAndNumSlotsLength; + + // 3. Service ID bloom filter. + memcpy(advertisement_header_bytes_write_ptr, + service_id_bloom_filter->getData(), kServiceIdBloomFilterLength); + advertisement_header_bytes_write_ptr += kServiceIdBloomFilterLength; + + // 4. Advertisement hash. + memcpy(advertisement_header_bytes_write_ptr, advertisement_hash->getData(), + kAdvertisementHashLength); + advertisement_header_bytes_write_ptr += kAdvertisementHashLength; + + // Header needs to be binary safe, so apply a Base64 encoding. + return Base64Utils::encode(advertisement_header_bytes); +} + +BLEAdvertisementHeader::Version::Value +BLEAdvertisementHeader::parseVersionFromByte(std::uint16_t byte) { + return static_cast((byte & kVersionBitmask) >> 5); +} + +std::uint32_t BLEAdvertisementHeader::parseNumSlotsFromByte( + std::uint16_t byte) { + return static_cast((byte & kNumSlotsBitmask)); +} + +void BLEAdvertisementHeader::serializeVersionByte(char *version_byte_write_ptr, + Version::Value version) { + *version_byte_write_ptr |= + static_cast((version << 5) & kVersionBitmask); +} + +void BLEAdvertisementHeader::serializeNumSlots(char *num_slots_byte_write_ptr, + std::uint32_t num_slots) { + *num_slots_byte_write_ptr |= static_cast(num_slots & kNumSlotsBitmask); +} + +BLEAdvertisementHeader::BLEAdvertisementHeader( + BLEAdvertisementHeader::Version::Value version, std::uint32_t num_slots, + ConstPtr service_id_bloom_filter, + ConstPtr advertisement_hash) + : version_(version), + num_slots_(num_slots), + service_id_bloom_filter_(service_id_bloom_filter), + advertisement_hash_(advertisement_hash) {} + +BLEAdvertisementHeader::~BLEAdvertisementHeader() { + // Nothing to do. +} + +BLEAdvertisementHeader::Version::Value BLEAdvertisementHeader::getVersion() + const { + return version_; +} + +std::uint32_t BLEAdvertisementHeader::getNumSlots() const { return num_slots_; } + +ConstPtr BLEAdvertisementHeader::getServiceIdBloomFilter() const { + return service_id_bloom_filter_.get(); +} + +ConstPtr BLEAdvertisementHeader::getAdvertisementHash() const { + return advertisement_hash_.get(); +} + +bool BLEAdvertisementHeader::operator<( + const BLEAdvertisementHeader &rhs) const { + if (this->getVersion() != rhs.getVersion()) { + return this->getVersion() < rhs.getVersion(); + } + if (this->getNumSlots() != rhs.getNumSlots()) { + return this->getNumSlots() < rhs.getNumSlots(); + } + if (*(this->getServiceIdBloomFilter()) != *(rhs.getServiceIdBloomFilter())) { + return *(this->getServiceIdBloomFilter()) < + *(rhs.getServiceIdBloomFilter()); + } + return *(this->getAdvertisementHash()) < *(rhs.getAdvertisementHash()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_advertisement_header.h b/cpp/core/internal/mediums/ble_advertisement_header.h new file mode 100644 index 00000000..3cf70e5a --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header.h @@ -0,0 +1,91 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ + +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums BLE Advertisement Header used in +// Advertising + Discovery. +// +// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH] +// +// See go/nearby-ble-design for more information. +class BLEAdvertisementHeader { + public: + // Versions of the BLEAdvertisementHeader. + struct Version { + enum Value { + V2 = 2, + // Version is only allocated 3 bits in the BLEAdvertisementHeader, so this + // can never go beyond V7. + // + // V1 is not present because it's an old format used in Nearby Connections + // before this logic was pushed down into Nearby Mediums. V1 put + // everything in the service data, while V2 puts the data inside a GATT + // characteristic so the two are not compatible. + }; + }; + + static ConstPtr fromString( + const std::string &ble_advertisement_header_string); + + static std::string asString(Version::Value version, std::uint32_t num_slots, + ConstPtr service_id_bloom_filter, + ConstPtr advertisement_hash); + + static const std::uint32_t kServiceIdBloomFilterLength; + static const std::uint32_t kAdvertisementHashLength; + + ~BLEAdvertisementHeader(); + + Version::Value getVersion() const; + std::uint32_t getNumSlots() const; + ConstPtr getServiceIdBloomFilter() const; + ConstPtr getAdvertisementHash() const; + + // Operator overloads when comparing ConstPtr. + bool operator<(const BLEAdvertisementHeader &rhs) const; + + private: + // DiscoveredPeripheralTracker needs to be a friend of this class because it + // directly calls the constructor (the Java code keeps the constructor package + // private). + // Calling the constuctor directly allows us to avoid the unnessary extra + // calls to parse and decode to get the BLEAdvertisementHeader. + template + friend class DiscoveredPeripheralTracker; + + static Version::Value parseVersionFromByte(std::uint16_t byte); + static std::uint32_t parseNumSlotsFromByte(std::uint16_t byte); + + static const std::uint32_t kVersionAndNumSlotsLength; + static const std::uint32_t kMinAdvertisementHeaderLength; + static const std::uint16_t kVersionBitmask; + static const std::uint16_t kNumSlotsBitmask; + + BLEAdvertisementHeader(Version::Value version, std::uint32_t num_slots, + ConstPtr service_id_bloom_filter, + ConstPtr advertisement_hash); + + static void serializeVersionByte(char *version_byte_write_ptr, + Version::Value version); + static void serializeNumSlots(char *num_slots_byte_write_ptr, + std::uint32_t num_slots); + + const Version::Value version_; + const uint32_t num_slots_; + ScopedPtr > service_id_bloom_filter_; + ScopedPtr > advertisement_hash_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement_header_test.cc b/cpp/core/internal/mediums/ble_advertisement_header_test.cc new file mode 100644 index 00000000..df9267c6 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header_test.cc @@ -0,0 +1,221 @@ +#include "core/internal/mediums/ble_advertisement_header.h" + +#include "platform/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const BLEAdvertisementHeader::Version::Value kVersion = + BLEAdvertisementHeader::Version::V2; +const std::uint32_t kNumSlots = 2; +const char kServiceIDBloomFilter[] = {0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0A}; +const char kAdvertisementHash[] = {0x0A, 0x0B, 0x0C, 0x0D}; +const size_t kAdvertisementHeaderLength = 15; +const size_t kLongAdvertisementHeaderLength = kAdvertisementHeaderLength + 1; +const size_t kShortAdvertisementHeaderLength = kAdvertisementHeaderLength - 1; + +TEST(BLEAdvertisementHeader, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + kVersion, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + ScopedPtr > scoped_ble_advertisement_header( + BLEAdvertisementHeader::fromString(ble_advertisement_header_string)); + + ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion()); + ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots()); + ASSERT_EQ( + 0, + memcmp( + kServiceIDBloomFilter, + scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(), + scoped_ble_advertisement_header->getServiceIdBloomFilter()->size())); + ASSERT_EQ( + 0, + memcmp(kAdvertisementHash, + scoped_ble_advertisement_header->getAdvertisementHash()->getData(), + scoped_ble_advertisement_header->getAdvertisementHash()->size())); +} + +TEST(BLEAdvertisementHeader, SerializationFailsWithBadVersion) { + BLEAdvertisementHeader::Version::Value bad_version = + static_cast(666); + + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + bad_version, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + + ASSERT_EQ("", ble_advertisement_header_string); +} + +TEST(BLEAdvertisementHeader, SerializationFailsWithShortServiceIdBloomFilter) { + char short_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09}; + + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(short_service_id_bloom_filter, + sizeof(short_service_id_bloom_filter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + kVersion, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + + ASSERT_EQ("", ble_advertisement_header_string); +} + +TEST(BLEAdvertisementHeader, SerializationFailsWithLongServiceIdBloomFilter) { + char long_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B}; + + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(long_service_id_bloom_filter, + sizeof(long_service_id_bloom_filter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + kVersion, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + + ASSERT_EQ("", ble_advertisement_header_string); +} + +TEST(BLEAdvertisementHeader, SerializationFailsWithShortAdvertisementHash) { + char short_advertisement_hash[] = {0x0A, 0x0B, 0x0C}; + + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash(MakeConstPtr( + new ByteArray(short_advertisement_hash, + sizeof(short_advertisement_hash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + kVersion, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + + ASSERT_EQ("", ble_advertisement_header_string); +} + +TEST(BLEAdvertisementHeader, SerializationFailsWithLongAdvertisementHash) { + char long_advertisement_hash[] = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E}; + + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash(MakeConstPtr( + new ByteArray(long_advertisement_hash, + sizeof(long_advertisement_hash) / sizeof(char)))); + + std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString( + kVersion, kNumSlots, scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get())); + + ASSERT_EQ("", ble_advertisement_header_string); +} + +TEST(BLEAdvertisementHeader, DeserializationWorksWithExtraBytes) { + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + std::string ble_advertisement_header_string = + BLEAdvertisementHeader::asString(kVersion, kNumSlots, + scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get()); + + // Base64 decode the string, add a character, and then re-encode it. We must + // explicitly define how long our array is because we can't use variable + // length arrays. + ScopedPtr > scoped_ble_advertisement_header_bytes( + Base64Utils::decode(ble_advertisement_header_string)); + char raw_long_ble_advertisement_header_bytes[kLongAdvertisementHeaderLength]; + memcpy(raw_long_ble_advertisement_header_bytes, + scoped_ble_advertisement_header_bytes->getData(), + kLongAdvertisementHeaderLength); + ScopedPtr > scoped_long_ble_advertisement_header_bytes( + MakeConstPtr(new ByteArray(raw_long_ble_advertisement_header_bytes, + kLongAdvertisementHeaderLength))); + std::string long_ble_advertisement_header_string = + Base64Utils::encode(scoped_long_ble_advertisement_header_bytes.get()); + + ScopedPtr > scoped_ble_advertisement_header( + BLEAdvertisementHeader::fromString(long_ble_advertisement_header_string)); + + ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion()); + ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots()); + ASSERT_EQ( + 0, + memcmp( + kServiceIDBloomFilter, + scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(), + scoped_ble_advertisement_header->getServiceIdBloomFilter()->size())); + ASSERT_EQ( + 0, + memcmp(kAdvertisementHash, + scoped_ble_advertisement_header->getAdvertisementHash()->getData(), + scoped_ble_advertisement_header->getAdvertisementHash()->size())); +} + +TEST(BLEAdvertisementHeader, DeserializationFailsWithShortLength) { + ScopedPtr > scoped_service_id_bloom_filter(MakeConstPtr( + new ByteArray(kServiceIDBloomFilter, + sizeof(kServiceIDBloomFilter) / sizeof(char)))); + ScopedPtr > scoped_advertisement_hash( + MakeConstPtr(new ByteArray(kAdvertisementHash, + sizeof(kAdvertisementHash) / sizeof(char)))); + std::string ble_advertisement_header_string = + BLEAdvertisementHeader::asString(kVersion, kNumSlots, + scoped_service_id_bloom_filter.get(), + scoped_advertisement_hash.get()); + + // Base64 decode the string, remove a character, and then re-encode it. We + // must explicitly define how long our array is because we can't use variable + // length arrays. + ScopedPtr > scoped_ble_advertisement_header_bytes( + Base64Utils::decode(ble_advertisement_header_string)); + char + raw_short_ble_advertisement_header_bytes[kShortAdvertisementHeaderLength]; + memcpy(raw_short_ble_advertisement_header_bytes, + scoped_ble_advertisement_header_bytes->getData(), + kShortAdvertisementHeaderLength); + ScopedPtr > scoped_short_ble_advertisement_header_bytes( + MakeConstPtr(new ByteArray(raw_short_ble_advertisement_header_bytes, + kShortAdvertisementHeaderLength))); + std::string short_ble_advertisement_header_string = + Base64Utils::encode(scoped_short_ble_advertisement_header_bytes.get()); + + ScopedPtr > scoped_ble_advertisement_header( + BLEAdvertisementHeader::fromString( + short_ble_advertisement_header_string)); + + ASSERT_TRUE(scoped_ble_advertisement_header.isNull()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_advertisement_test.cc b/cpp/core/internal/mediums/ble_advertisement_test.cc new file mode 100644 index 00000000..22200fa3 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_test.cc @@ -0,0 +1,319 @@ +#include "core/internal/mediums/ble_advertisement.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const BLEAdvertisement::Version::Value kVersion = BLEAdvertisement::Version::V2; +const BLEAdvertisement::SocketVersion::Value kSocketVersion = + BLEAdvertisement::SocketVersion::V2; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +const char kData[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +// This corresponds to the length of a specific BLEAdvertisement packed with the +// kData given above. Be sure to update this if kData ever changes. +const size_t kAdvertisementLength = 77; +const size_t kLongAdvertisementLength = kAdvertisementLength + 1000; + +TEST(BLEAdvertisementTest, SerializationDeserializationWorksV1) { + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes( + BLEAdvertisement::Version::V1, BLEAdvertisement::SocketVersion::V1, + scoped_service_id_hash.get(), scoped_data.get())); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(BLEAdvertisement::Version::V1, + scoped_ble_advertisement->getVersion()); + ASSERT_EQ(BLEAdvertisement::SocketVersion::V1, + scoped_ble_advertisement->getSocketVersion()); + ASSERT_EQ(scoped_service_id_hash->size(), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(kServiceIDHashBytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size()); + ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(), + scoped_ble_advertisement->getData()->size())); +} + +TEST(BLEAdvertisementTest, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion()); + ASSERT_EQ(scoped_service_id_hash->size(), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(kServiceIDHashBytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size()); + ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(), + scoped_ble_advertisement->getData()->size())); +} + +TEST(BLEAdvertisementTest, SerializationDeserializationWorksWithEmptyData) { + char empty_data[0]; + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data(MakeConstPtr( + new ByteArray(empty_data, sizeof(empty_data) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion()); + ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion()); + ASSERT_EQ(scoped_service_id_hash->size(), + scoped_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, memcmp(kServiceIDHashBytes, + scoped_ble_advertisement->getServiceIdHash()->getData(), + scoped_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size()); + ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(), + scoped_ble_advertisement->getData()->size())); +} + +TEST(BLEAdvertisementTest, SerializationDeserializationFailsWithLargeData) { + // Create data that's larger than the allowed size. + char large_data[513]; + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data(MakeConstPtr( + new ByteArray(large_data, sizeof(large_data) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get())); + + ASSERT_TRUE(scoped_ble_advertisement.isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithBadVersion) { + BLEAdvertisement::Version::Value bad_version = + static_cast(666); + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(bad_version, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithBadSocketVersion) { + BLEAdvertisement::SocketVersion::Value bad_socket_version = + static_cast(666); + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, bad_socket_version, + scoped_service_id_hash.get(), + scoped_data.get())); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull()); +} + +TEST(BLEAdvertisementTest, SerializationFailsWithLongData) { + // BLEAdvertisement shouldn't be able to support data with the max GATT + // attribute length because it needs some room for the preceding fields. + char long_data[512]; + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(long_data, sizeof(long_data) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationWorksWithExtraBytes) { + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + // Copy the bytes into a new array with extra bytes. We must explicitly + // define how long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kLongAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, scoped_ble_advertisement_bytes->getData(), + kLongAdvertisementLength); + + // Re-parse the BLE advertisement using our extra long advertisement bytes. + ScopedPtr > scoped_long_ble_advertisement_bytes( + MakeConstPtr(new ByteArray(raw_ble_advertisement_bytes, + kLongAdvertisementLength))); + ScopedPtr > scoped_long_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_long_ble_advertisement_bytes.get())); + + ASSERT_EQ(kVersion, scoped_long_ble_advertisement->getVersion()); + ASSERT_EQ(kSocketVersion, scoped_long_ble_advertisement->getSocketVersion()); + ASSERT_EQ(scoped_service_id_hash->size(), + scoped_long_ble_advertisement->getServiceIdHash()->size()); + ASSERT_EQ(0, + memcmp(kServiceIDHashBytes, + scoped_long_ble_advertisement->getServiceIdHash()->getData(), + scoped_long_ble_advertisement->getServiceIdHash()->size())); + ASSERT_EQ(scoped_data->size(), + scoped_long_ble_advertisement->getData()->size()); + ASSERT_EQ(0, + memcmp(kData, scoped_long_ble_advertisement->getData()->getData(), + scoped_long_ble_advertisement->getData()->size())); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithNullBytes) { + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(ConstPtr())); + + ASSERT_TRUE(scoped_ble_advertisement.isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithShortLength) { + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + // Cut off the advertisement so that it's too short. + ScopedPtr > scoped_short_ble_advertisement_bytes( + MakeConstPtr( + new ByteArray(scoped_ble_advertisement_bytes->getData(), 7))); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes(scoped_short_ble_advertisement_bytes.get())); + + ASSERT_TRUE(scoped_ble_advertisement.isNull()); +} + +TEST(BLEAdvertisementTest, DeserializationFailsWithInvalidDataLength) { + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kVersion, kSocketVersion, + scoped_service_id_hash.get(), + scoped_data.get())); + + // Corrupt the DATA_SIZE bits. Start by making a raw copy of the BLE + // advertisement bytes so we can modify it. We must explicitly define how long + // our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, scoped_ble_advertisement_bytes->getData(), + kAdvertisementLength); + + // The data size field lives in indices 4-7. Corrupt it. + memset(raw_ble_advertisement_bytes + 4, 0xFF, 4); + + // Try to parse the BLE advertisement using our corrupted advertisement bytes. + ScopedPtr > scoped_corrupted_ble_advertisement_bytes( + MakeConstPtr( + new ByteArray(raw_ble_advertisement_bytes, kAdvertisementLength))); + ScopedPtr > scoped_ble_advertisement( + BLEAdvertisement::fromBytes( + scoped_corrupted_ble_advertisement_bytes.get())); + + ASSERT_TRUE(scoped_ble_advertisement.isNull()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_packet.cc b/cpp/core/internal/mediums/ble_packet.cc new file mode 100644 index 00000000..3f5fad02 --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet.cc @@ -0,0 +1,112 @@ +#include "core/internal/mediums/ble_packet.h" + +#include + +#include "platform/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const std::uint32_t BLEPacket::kServiceIdHashLength = 3; + +const std::uint32_t BLEPacket::kMinPacketLength = kServiceIdHashLength; +const std::uint32_t BLEPacket::kMaxDataSize = + std::numeric_limits::max() - kMinPacketLength; + +ConstPtr BLEPacket::fromBytes(ConstPtr ble_packet_bytes) { + if (ble_packet_bytes.isNull()) { + NEARBY_LOG(INFO, "Cannot deserialize BLEPacket: null bytes passed in"); + return ConstPtr(); + } + + if (ble_packet_bytes->size() < kMinPacketLength) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEPacket: expecting min %u raw bytes, got %zu", + kMinPacketLength, ble_packet_bytes->size()); + return ConstPtr(); + } + + // Now, time to read the bytes! + const char *ble_packet_bytes_read_ptr = ble_packet_bytes->getData(); + + // 1. Service ID hash. + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength))); + ble_packet_bytes_read_ptr += kServiceIdHashLength; + + // 2. Data. + size_t data_size = computeDataSize(ble_packet_bytes); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(ble_packet_bytes_read_ptr, data_size))); + ble_packet_bytes_read_ptr += data_size; + + return MakeConstPtr( + new BLEPacket(scoped_service_id_hash.release(), scoped_data.release())); +} + +ConstPtr BLEPacket::toBytes(ConstPtr service_id_hash, + ConstPtr data) { + if (service_id_hash->size() != kServiceIdHashLength) { + NEARBY_LOG( + INFO, + "Cannot serialize BLEPacket: expected a service_id_hash of %u bytes, " + "but got %zu", + kServiceIdHashLength, service_id_hash->size()); + return ConstPtr(); + } + + if (data->size() > kMaxDataSize) { + NEARBY_LOG(INFO, + "Cannot serialize BLEPacket: expected data of at most %u bytes, " + "but got %zu", + kMaxDataSize, data->size()); + return ConstPtr(); + } + + // Initialize the bytes. + size_t packet_length = computePacketLength(data); + Ptr packet_bytes{new ByteArray{packet_length}}; + char *packet_bytes_write_ptr = packet_bytes->getData(); + + // 1. Service ID hash. + memcpy(packet_bytes_write_ptr, service_id_hash->getData(), + kServiceIdHashLength); + packet_bytes_write_ptr += kServiceIdHashLength; + + // 2. Data. + memcpy(packet_bytes_write_ptr, data->getData(), data->size()); + packet_bytes_write_ptr += data->size(); + + return ConstifyPtr(packet_bytes); +} + +size_t BLEPacket::computeDataSize(ConstPtr ble_packet_bytes) { + return ble_packet_bytes->size() - kMinPacketLength; +} + +size_t BLEPacket::computePacketLength(ConstPtr data) { + // The packet length is the minimum length + the length of the data. + return kMinPacketLength + data->size(); +} + +BLEPacket::BLEPacket(ConstPtr service_id_hash, + ConstPtr data) + : service_id_hash_(service_id_hash), data_(data) {} + +BLEPacket::~BLEPacket() { + // Nothing to do. +} + +ConstPtr BLEPacket::getServiceIdHash() const { + return service_id_hash_.get(); +} + +ConstPtr BLEPacket::getData() const { return data_.get(); } + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_packet.h b/cpp/core/internal/mediums/ble_packet.h new file mode 100644 index 00000000..14b218be --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet.h @@ -0,0 +1,49 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_ + +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of data sent over BLE sockets. +// +// [SERVICE_ID_HASH][DATA] +// +// See go/nearby-ble-design for more information. +class BLEPacket { + public: + static ConstPtr fromBytes(ConstPtr ble_packet_bytes); + + static ConstPtr toBytes(ConstPtr service_id_hash, + ConstPtr data); + + static const std::uint32_t kServiceIdHashLength; + + ~BLEPacket(); + + ConstPtr getServiceIdHash() const; + ConstPtr getData() const; + + private: + static size_t computeDataSize(ConstPtr ble_packet_bytes); + static size_t computePacketLength(ConstPtr data); + + static const std::uint32_t kMinPacketLength; + static const std::uint32_t kMaxDataSize; + + BLEPacket(ConstPtr service_id_hash, ConstPtr data); + + ScopedPtr > service_id_hash_; + ScopedPtr > data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_ diff --git a/cpp/core/internal/mediums/ble_packet_test.cc b/cpp/core/internal/mediums/ble_packet_test.cc new file mode 100644 index 00000000..90c0d06b --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet_test.cc @@ -0,0 +1,108 @@ +#include "core/internal/mediums/ble_packet.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const char kServiceIDHash[] = {0x0A, 0x0B, 0x0C}; +const char kData[] = {0x00, 0x01, 0x02, 0x03, 0x04}; + +TEST(BLEPacket, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_packet_bytes( + BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get())); + ScopedPtr > scoped_ble_packet( + BLEPacket::fromBytes(scoped_ble_packet_bytes.get())); + + ASSERT_EQ(0, memcmp(kServiceIDHash, + scoped_ble_packet->getServiceIdHash()->getData(), + scoped_ble_packet->getServiceIdHash()->size())); + ASSERT_EQ(0, memcmp(kData, scoped_ble_packet->getData()->getData(), + scoped_ble_packet->getData()->size())); +} + +TEST(BLEPacket, SerializationDeserializationWorksWithEmptyData) { + char empty_data[] = {}; + + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char)))); + ScopedPtr > scoped_data(MakeConstPtr( + new ByteArray(empty_data, sizeof(empty_data) / sizeof(char)))); + + ScopedPtr > scoped_ble_packet_bytes( + BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get())); + ScopedPtr > scoped_ble_packet( + BLEPacket::fromBytes(scoped_ble_packet_bytes.get())); + + ASSERT_EQ(0, memcmp(kServiceIDHash, + scoped_ble_packet->getServiceIdHash()->getData(), + scoped_ble_packet->getServiceIdHash()->size())); + ASSERT_EQ(0, memcmp(empty_data, scoped_ble_packet->getData()->getData(), + scoped_ble_packet->getData()->size())); +} + +TEST(BLEPacket, SerializationFailsWithShortServiceIdHash) { + char short_service_id_hash[] = {0x0A, 0x0B}; + + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(short_service_id_hash, + sizeof(short_service_id_hash) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_packet_bytes( + BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get())); + + ASSERT_TRUE(scoped_ble_packet_bytes.isNull()); +} + +TEST(BLEPacket, SerializationFailsWithLongServiceIdHash) { + char long_service_id_hash[]{0x0A, 0x0B, 0x0C, 0x0D}; + + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(long_service_id_hash, + sizeof(long_service_id_hash) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + + ScopedPtr > scoped_ble_packet_bytes( + BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get())); + + ASSERT_TRUE(scoped_ble_packet_bytes.isNull()); +} + +TEST(BLEPacket, DeserializationFailsWithNullBytes) { + ScopedPtr > scoped_ble_packet( + BLEPacket::fromBytes(ConstPtr())); + + ASSERT_TRUE(scoped_ble_packet.isNull()); +} + +TEST(BLEPacket, DeserializationFailsWithShortLength) { + ScopedPtr > scoped_service_id_hash(MakeConstPtr( + new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char)))); + ScopedPtr > scoped_data( + MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char)))); + ScopedPtr > scoped_ble_packet_bytes( + BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get())); + + // Cut off the packet so that it's too short + ScopedPtr > scoped_short_ble_packet_bytes( + MakeConstPtr(new ByteArray(scoped_ble_packet_bytes->getData(), 2))); + ScopedPtr > scoped_ble_packet( + BLEPacket::fromBytes(scoped_short_ble_packet_bytes.get())); + + ASSERT_TRUE(scoped_ble_packet.isNull()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_peripheral.cc b/cpp/core/internal/mediums/ble_peripheral.cc new file mode 100644 index 00000000..ef54ec8c --- /dev/null +++ b/cpp/core/internal/mediums/ble_peripheral.cc @@ -0,0 +1,19 @@ +#include "core/internal/mediums/ble_peripheral.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BLEPeripheral::BLEPeripheral(ConstPtr id) : id_(id) {} + +BLEPeripheral::~BLEPeripheral() { + // Nothing to do. +} + +ConstPtr BLEPeripheral::getId() const { return id_.get(); } + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_peripheral.h b/cpp/core/internal/mediums/ble_peripheral.h new file mode 100644 index 00000000..c305171f --- /dev/null +++ b/cpp/core/internal/mediums/ble_peripheral.h @@ -0,0 +1,30 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ + +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class BLEPeripheral { + public: + explicit BLEPeripheral(ConstPtr id); + ~BLEPeripheral(); + + ConstPtr getId() const; + + private: + // A unique identifier for this peripheral. It can be the BLE advertisement it + // was found on, or even simply the BLE MAC address. + ScopedPtr> id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc new file mode 100644 index 00000000..145a9904 --- /dev/null +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -0,0 +1,831 @@ +#include "core/internal/mediums/ble.h" +#include "core/internal/mediums/ble_advertisement_header.h" +#include "core/internal/mediums/bloom_filter.h" +#include "core/internal/mediums/utils.h" +#include "core/internal/mediums/uuid.h" +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace ble_v2 { + +template +class ProcessOnLostRunnable : public Runnable { + public: + explicit ProcessOnLostRunnable(Ptr> ble_v2) + : ble_v2_(ble_v2) {} + + void run() override { ble_v2_->processOnLostTimeout(); } + + private: + Ptr> ble_v2_; +}; + +template +class OnAdvertisementFoundRunnable : public Runnable { + public: + OnAdvertisementFoundRunnable( + Ptr> ble_v2, Ptr peripheral, + ConstPtr advertisement_data) + : ble_v2_(ble_v2), + peripheral_(peripheral), + advertisement_data_(advertisement_data) {} + + // This method is synchronized because it affects class state, but is called + // from a separate thread that fires whenever a BLE advertisement is seen. + void run() override { + Synchronized s(ble_v2_->lock_.get()); + + ble_v2_->discovered_peripheral_tracker_->processFoundBleAdvertisement( + peripheral_, advertisement_data_.release(), + MakePtr(new typename BLEV2::GATTAdvertisementFetcherFacade( + ble_v2_))); + } + + private: + Ptr> ble_v2_; + Ptr peripheral_; + ScopedPtr> advertisement_data_; +}; + +} // namespace ble_v2 + +template +const std::int32_t BLEV2::kNumAdvertisementSlots = 2; + +template +const std::int32_t BLEV2::kMaxAdvertisementLength = 512; + +template +const std::int32_t BLEV2::kDummyServiceIdLength = 512; + +template +const char* BLEV2::kCopresenceServiceUuid = + "0000FEF3-0000-1000-8000-00805F9B34FB"; + +template +const std::int64_t BLEV2::kOnLostTimeoutMillis = 15000; + +template +const std::int64_t BLEV2::kGattAdvertisementOperationTimeoutMillis = + 5000; + +template +const std::int64_t + BLEV2::kMinConnectionAttemptRecoveryDurationMillis = 1000; + +template +const std::int32_t + BLEV2::kMaxConnectionAttemptRecoveryFuzzDurationMillis = 10000; + +template +const std::uint32_t BLEV2::kDefaultMtu = 512; + +// These two values make up the base UUID we use when advertising a slot. The +// base is an all zero Version-3 name-based UUID. To turn this into an +// advertisement slot UUID, we simply OR the least significant bits with the +// slot number. +// +// More info about the format can be found here: +// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) +template +const std::int64_t BLEV2::kAdvertisementUuidMsb = 0x0000000000003000; + +template +const std::int64_t BLEV2::kAdvertisementUuidLsb = 0x8000000000000000; + +template +BLEV2::BLEV2(Ptr> bluetooth_radio) + : lock_(Platform::createLock()), + platform_thread_offloader_(Platform::createSingleThreadExecutor()), + prng_(MakePtr(new Prng())), + hash_utils_(Platform::createHashUtils()), + bluetooth_radio_(bluetooth_radio), + bluetooth_adapter_(Platform::createBluetoothAdapter()), + ble_medium_(Platform::createBLEMediumV2()), + scanning_info_(), + discovered_peripheral_tracker_( + new DiscoveredPeripheralTracker()), + on_lost_executor_(Platform::createScheduledExecutor()), + advertising_info_(), + gatt_server_info_(), + accepting_connections_info_() {} + +template +BLEV2::~BLEV2() { + Synchronized s(lock_.get()); + + on_lost_executor_->shutdown(); + platform_thread_offloader_->shutdown(); + stopAdvertising(); + stopAdvertisementGattServer(); + stopAcceptingConnections(); + stopScanning(); + // discovered_peripheral_tracker is a ScopedPtr member and will take care of + // itself. +} + +template +bool BLEV2::isAvailable() { + // This is purposefully left un-synchronized like its java counterpart. + // Callers should be able to query this without waiting for other operations + // to complete first and this should be safe to call after shutdown. We would + // have made it static, but it relies on variables from the constructor (like + // ble_medium_ and bluetooth_adapter_). + return !ble_medium_.isNull() && !bluetooth_adapter_.isNull(); +} + +// Returns true if currently scanning for BLE advertisements. +template +bool BLEV2::isAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.isNull(); +} + +// Starts BLE advertising, delivering additional information through a GATT +// server. +template +bool BLEV2::startAdvertising( + const string& service_id, ConstPtr advertisement_bytes, + BLEMediumV2::PowerMode::Value power_mode, + const string& fast_advertisement_service_uuid) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> scoped_advertisement_bytes( + advertisement_bytes); + + if (service_id.empty() || scoped_advertisement_bytes.isNull()) { + // logger.atSevere().log("Refusing to start BLE advertising because a null + // parameter was passed in."); + return false; + } + + if (scoped_advertisement_bytes->size() > kMaxAdvertisementLength) { + // logger.atSevere().log("Refusing to start BLE advertising because the + // advertisement was too long. Expected at most %d bytes but received %d.", + // kMaxAdvertisementLength, scoped_advertisement_bytes->size()); + return false; + } + + // Note: We don't include logic checking/using the fast_pair_model_id because + // that is a java-only concept for now. + + if (isAdvertising()) { + // logger.atSevere().log("Failed to BLE advertise because we're already + // advertising."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // logger.atSevere().log("Can't start BLE advertising because Bluetooth + // isn't enabled."); + return false; + } + + if (!isAvailable()) { + // logger.atSevere().log("Can't start BLE advertising because BLE is not + // available."); + return false; + } + + // TODO(ahlee): Remove this check here and in the java code (redundant) + // Stop any existing advertisement GATT servers. We don't stop it in + // stopAdvertising() to avoid GATT issues with BLE sockets. + if (isAdvertisementGattServerRunning()) { + stopAdvertisementGattServer(); + } + + // Start a GATT server to deliver the full advertisement data. If we fail to + // advertise the header, we must shut this down before the method returns. + bool is_fast_advertisement = !fast_advertisement_service_uuid.empty(); + if (!is_fast_advertisement) { + if (!startAdvertisementGattServer(service_id, + scoped_advertisement_bytes.get())) { + // logger.atSevere().log("Failed to to BLE advertise because the + // advertisement GATT server failed to start"); + return false; + } + } + + ScopedPtr> advertisement_header_bytes( + createAdvertisementHeader(service_id, scoped_advertisement_bytes.get(), + is_fast_advertisement)); + if (advertisement_header_bytes.isNull()) { + // logger.atSevere().log("Failed to to BLE advertise because we could not + // create an advertisement header"); + // We failed to start BLE advertising, so stop the advertisement GATT + // server. + stopAdvertisementGattServer(); + return false; + } + + ScopedPtr> advertisement( + new BLEAdvertisementData()); + advertisement->is_connectable = true; + advertisement->tx_power_level = + BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL; + + ScopedPtr> scan_response( + new BLEAdvertisementData()); + scan_response->is_connectable = true; + scan_response->tx_power_level = + BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL; + scan_response->service_uuids.insert(kCopresenceServiceUuid); + scan_response->service_data.insert(std::make_pair( + kCopresenceServiceUuid, advertisement_header_bytes.release())); + + // Note: We don't use fast pair data because that is java-only for now. + + // TODO(ahlee): Fix this if check in the java code. + if (is_fast_advertisement) { + ScopedPtr> service_id_hash( + generateServiceIdHash(BLEAdvertisement::Version::V2, service_id)); + ScopedPtr> fast_advertisement_bytes( + BLEAdvertisement::toBytes( + BLEAdvertisement::Version::V2, BLEAdvertisement::SocketVersion::V2, + service_id_hash.get(), scoped_advertisement_bytes.get())); + if (fast_advertisement_bytes.isNull()) { + // logger.atSevere().log("Failed to BLE advertise because we could not + // create a fast advertisement for service UUID %s.", + // fast_advertisement_service_uuid); + + // We shouldn't have started an advertisement GATT server in the first + // place if we are using fast advertisements. However, to avoid careless + // leaks, try shutting down the server anyway. + stopAdvertisementGattServer(); + return false; + } + advertisement->service_data.insert(std::make_pair( + fast_advertisement_service_uuid, fast_advertisement_bytes.release())); + scan_response->service_uuids.insert(fast_advertisement_service_uuid); + } + + if (!ble_medium_->startAdvertising(ConstifyPtr(advertisement.release()), + ConstifyPtr(scan_response.release()), + power_mode)) { + // If BLE advertising was not successful, stop the advertisement GATT + // server. + stopAdvertisementGattServer(); + return false; + } + + // logger.atVerbose().flog("Started BLE advertising with advertisement %s for + // serviceID %s.", advertisement_header, service_id); + advertising_info_ = MakePtr(new AdvertisingInfo(service_id)); + return true; +} + +template +ConstPtr BLEV2::createAdvertisementHeader( + const string& service_id, ConstPtr advertisement_bytes, + bool is_fast_advertisement) { + // Create a randomized dummy service ID to anonymize our header with. + string dummy_service_id; + dummy_service_id.reserve(kDummyServiceIdLength); + for (int i = 0; i < kDummyServiceIdLength; i++) { + dummy_service_id[i] = static_cast(prng_->nextInt32() & 0x000000FF); + } + + // Put the service ID along with the dummy service ID into our bloom filter + // Note: BloomFilter length should always match + // BLEAdvertisementHeader::kServiceIdBloomFilterLength + ScopedPtr>> bloom_filter(new BloomFilter<10>()); + bloom_filter->add(dummy_service_id); + + // Only add the service ID to our bloom filter if it's not a fast + // advertisement. Fast advertisements want discoverers to avoid reading our + // GATT advertisement. + if (!is_fast_advertisement) { + bloom_filter->add(service_id); + } + + // Create a hash seeded from dummy_service_id + advertisementBytes + // + // First, populate advertisement_bodies with the dummy_service_id and + // advertisement_bytes. + string advertisement_bodies; + advertisement_bodies.reserve(dummy_service_id.size() + + advertisement_bytes->size()); + advertisement_bodies.append(dummy_service_id.data(), dummy_service_id.size()); + advertisement_bodies.append(advertisement_bytes->getData(), + advertisement_bytes->size()); + + // Then, generate the advertisement hash from the populated + // advertisement_bodies string. + ScopedPtr> advertisement_bodies_byte_array(MakeConstPtr( + new ByteArray(advertisement_bodies.data(), advertisement_bodies.size()))); + ScopedPtr> advertisement_hash( + generateAdvertisementHash(advertisement_bodies_byte_array.get())); + + ScopedPtr> bloom_filter_bytes(bloom_filter->asBytes()); + string ble_advertisement_header_string = BLEAdvertisementHeader::asString( + BLEAdvertisementHeader::Version::V2, kNumAdvertisementSlots, + bloom_filter_bytes.get(), advertisement_hash.get()); + + return MakeConstPtr(new ByteArray(ble_advertisement_header_string.data(), + ble_advertisement_header_string.size())); +} + +// Stops BLE advertising. +template +void BLEV2::stopAdvertising() { + Synchronized s(lock_.get()); + + if (!isAdvertising()) { + // logger.atDebug().log("Can't turn off BLE advertising because it never + // started."); + return; + } + + ble_medium_->stopAdvertising(); + // Reset advertising_info_to mark that we're no longer advertising. + advertising_info_.destroy(); + + // Do NOT stop the advertisement GATT server here. Doing so will cause any + // other existing GATT connections to stop receiving callbacks. This affects + // our BLE sockets. Therefore, we only stop it in shutdown() and + // startAdvertising(), where it is safe to do so. At those two points, we + // shouldn't expect any BLE sockets to be connected. + + // logger.atVerbose().log("Turned BLE advertising off"); +} + +// Returns true if currently scanning for BLE advertisements. +template +bool BLEV2::isScanning() { + Synchronized s(lock_.get()); + + return !scanning_info_.isNull(); +} + +// Starts scanning for BLE advertisements (if it is possible for the device). +template +bool BLEV2::startScanning( + const string& service_id, + Ptr discovered_peripheral_callback, + BLEMediumV2::PowerMode::Value power_mode, + const string& fast_advertisement_service_uuid) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> + scoped_discovered_peripheral_callback(discovered_peripheral_callback); + + if (service_id.empty() || scoped_discovered_peripheral_callback.isNull()) { + // logger.atSevere().log("Refusing to start BLE scanning because at least + // one of workSource, serviceId, or discoveredPeripheralCallback is null."); + return false; + } + + if (isScanning()) { + // logger.atSevere().log("Refusing to start BLE scanning because we are + // already scanning."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // logger.atSevere().log("Can't start BLE scanning because Bluetooth was + // never turned on"); + return false; + } + + if (!isAvailable()) { + // logger.atSevere().log("Can't start BLE scanning because BLE is not + // available."); + return false; + } + + discovered_peripheral_tracker_->startTracking( + service_id, scoped_discovered_peripheral_callback.release(), + fast_advertisement_service_uuid); + // Avoid leaks. + ScopedPtr> scan_callback_facade( + new ScanCallbackFacade(MakePtr(this))); + std::set service_uuids; + service_uuids.insert(kCopresenceServiceUuid); + if (!ble_medium_->startScanning(service_uuids, power_mode, + scan_callback_facade.get())) { + discovered_peripheral_tracker_->stopTracking(service_id); + return false; + } + + // logger.atVerbose().log("Started BLE scanning for serviceID %s.", + // service_id); + scanning_info_ = MakePtr(new ScanningInfo( + service_id, scan_callback_facade.release(), createOnLostAlarm())); + return true; +} + +template +void BLEV2::onAdvertisementFoundImpl( + Ptr ble_peripheral, + ConstPtr advertisement_data) { + offloadFromPlatformThread( + MakePtr(new ble_v2::OnAdvertisementFoundRunnable( + MakePtr(this), ble_peripheral, advertisement_data))); +} + +// This method is synchronized because it affects class state, but is called +// from a separate thread that has a recurring alarm running on it. +template +void BLEV2::processOnLostTimeout() { + Synchronized s(lock_.get()); + + discovered_peripheral_tracker_->processLostGattAdvertisements(); +} + +// Stops scanning for BLE advertisements. +template +void BLEV2::stopScanning() { + Synchronized s(lock_.get()); + + if (!isScanning()) { + // logger.atDebug().log("Can't turn off BLE scanning because we never + // started scanning."); + return; + } + + scanning_info_->on_lost_alarm->cancel(); + + ble_medium_->stopScanning(); + discovered_peripheral_tracker_->stopTracking(scanning_info_->service_id); + // Reset our bundle of scanning state to mark that we're no longer scanning. + scanning_info_.destroy(); +} + +// TODO(b/112199086) Change to RecurringCancelableAlarm +template +Ptr> BLEV2::createOnLostAlarm() { + // return MakePtr(new CancelableAlarm( + // "BluetoothLowEnergy.startScanning() onLost", + // MakePtr(new + // ble_v2::ProcessOnLostRunnable(MakePtr(this))), + // kOnLostTimeoutMillis, on_lost_executor_.get())); + return Ptr>(); +} + +// Returns true if the device is currently accepting incoming BLE socket +// connections. +template +bool BLEV2::isAcceptingConnections() { + Synchronized s(lock_.get()); + + return !accepting_connections_info_.isNull(); +} + +// Starts accepting incoming BLE socket connections. +template +bool BLEV2::startAcceptingConnections( + const string& service_id, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> + scoped_accepted_connection_callback(accepted_connection_callback); + if (service_id.empty() || scoped_accepted_connection_callback.isNull()) { + // logger.atSevere().log("Refusing to start accepting BLE connections + // because at least one of serviceId or acceptedConnectionCallback is + // null."); + return false; + } + + if (isAcceptingConnections()) { + // logger.atSevere().log("Refusing to start accepting BLE connections for %s + // because another BLE server socket is already in-progress.", service_id); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // logger.atSevere().log("Can't start accepting BLE connections for %s + // because Bluetooth isn't enabled.", service_id); + return false; + } + + if (!isAvailable()) { + // logger.atSevere().log("Can't start accepting BLE connections for %s + // because BLE is not available.", service_id); + return false; + } + + // TODO(ahlee): Implement w/ the rest of the connecting logic. + // Default to returning true and creating accepting_connections_info_ so we + // can test the advertising and discovery flow fully. + accepting_connections_info_ = + MakePtr(new AcceptingConnectionsInfo(service_id)); + return true; +} + +// Stops accepting incoming BLE socket connections. +template +void BLEV2::stopAcceptingConnections() { + Synchronized s(lock_.get()); + + if (!isAcceptingConnections()) { + // logger.atDebug().log("Can't stop accepting BLE connections because it was + // never started."); + return; + } + + ble_medium_->stopListeningForIncomingBLESockets(); + + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.destroy(); +} + +// Note: getGattConnectionBackoffPeriodMillis is only used in the java version +// of reliablyConnect() for now. + +// Returns true if the advertisement GATT server is currently running. +template +bool BLEV2::isAdvertisementGattServerRunning() { + return !gatt_server_info_.isNull(); +} + +// Starts a GATT server to deliver additional advertisement data. Returns true +// if the server was started successfully. +template +bool BLEV2::startAdvertisementGattServer( + const string& service_id, ConstPtr advertisement) { + // advertisement is not being wrapped in a ScopedPtr because ownership is not + // passed on from startAdvertising(). + + if (isAdvertisementGattServerRunning()) { + // logger.atSevere().log("Refusing to start an advertisement GATT server + // because one is already running."); + return false; + } + + // Create a BleAdvertisement to wrap over the passed in advertisement. + ScopedPtr> legacy_service_id_hash( + generateServiceIdHash(BLEAdvertisement::Version::V1, service_id)); + ScopedPtr> legacy_ble_advertisement_bytes( + BLEAdvertisement::toBytes(BLEAdvertisement::Version::V1, + BLEAdvertisement::SocketVersion::V1, + legacy_service_id_hash.get(), advertisement)); + if (legacy_ble_advertisement_bytes.isNull()) { + // logger.atSevere().log("Refusing to start an advertisement GATT server + // because creating a legacy BleAdvertisement with service ID %s failed.", + // service_id); + return false; + } + + ScopedPtr> service_id_hash( + generateServiceIdHash(BLEAdvertisement::Version::V2, service_id)); + ScopedPtr> ble_advertisement_bytes( + BLEAdvertisement::toBytes(BLEAdvertisement::Version::V2, + BLEAdvertisement::SocketVersion::V2, + service_id_hash.get(), advertisement)); + if (ble_advertisement_bytes.isNull()) { + // logger.atSevere().log("Refusing to start an advertisement GATT server + // because creating a BleAdvertisement with service ID %s failed.", + // service_id); + return false; + } + + return internalStartAdvertisementGattServer( + legacy_ble_advertisement_bytes.release(), + ble_advertisement_bytes.release()); +} + +template +bool BLEV2::internalStartAdvertisementGattServer( + ConstPtr legacy_ble_advertisement_bytes, + ConstPtr ble_advertisement_bytes) { + // Avoid leaks. + ScopedPtr> scoped_legacy_ble_advertisement_bytes( + legacy_ble_advertisement_bytes); + ScopedPtr> scoped_ble_advertisement_bytes( + ble_advertisement_bytes); + + ScopedPtr> + connection_lifecycle_callback( + new ServerGATTConnectionLifecycleCallbackFacade(MakePtr(this))); + ScopedPtr> gatt_server( + ble_medium_->startGATTServer(connection_lifecycle_callback.get())); + if (gatt_server.isNull()) { + // logger.atSevere().withCause(e).log("Unable to start an advertisement GATT + // server."); + return false; + } + + if (!generateAdvertisementCharacteristic( + /* slot= */ 0, scoped_legacy_ble_advertisement_bytes.release(), + gatt_server.get())) { + gatt_server->stop(); + return false; + } + + if (!generateAdvertisementCharacteristic( + /* slot= */ 1, scoped_ble_advertisement_bytes.release(), + gatt_server.get())) { + gatt_server->stop(); + return false; + } + + // GattCharacteristic is not included in GATTServerInfo because we don't need + // it after it's been updated. + gatt_server_info_ = MakePtr(new GATTServerInfo( + gatt_server.release(), connection_lifecycle_callback.release())); + return true; +} + +template +bool BLEV2::generateAdvertisementCharacteristic( + std::int32_t slot, ConstPtr advertisement, + Ptr gatt_server) { + // Avoid leaks. + ScopedPtr> scoped_advertisement(advertisement); + + std::set permissions; + permissions.insert(GATTCharacteristic::Permission::READ); + std::set properties; + properties.insert(GATTCharacteristic::Property::READ); + Ptr gatt_characteristic(gatt_server->createCharacteristic( + kCopresenceServiceUuid, generateAdvertisementUuid(slot), permissions, + properties)); + + if (gatt_characteristic.isNull()) { + // logger.atSevere().withCause(e).log("Unable to create and add a + // characterstic to the gatt server for the advertisement."); + return false; + } + + if (!gatt_server->updateCharacteristic(gatt_characteristic, + scoped_advertisement.release())) { + // logger.atSevere().withCause(e).log("Unable to write a value to the GATT + // characteristic."); + return false; + } + + return true; +} + +// Note: In the java counterpart this in a utils class. +// Generates a characteristic UUID for an advertisement at the given slot. +template +string BLEV2::generateAdvertisementUuid(std::int32_t slot) { + return UUID(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot) + .str(); +} + +// Stops a GATT server used for additional advertisement data. +template +void BLEV2::stopAdvertisementGattServer() { + Synchronized s(lock_.get()); + + if (!isAdvertisementGattServerRunning()) { + // logger.atSevere().log("Unable to stop the advertisement GATT server + // because it's not running."); + return; + } + + gatt_server_info_->gatt_server->stop(); + gatt_server_info_.destroy(); +} + +// Connects to a GATT server, reads advertisement data, and then disconnects +// from the GATT server. This method blocks until all advertisements are read, +// or a connection error occurs. +template +Ptr> +BLEV2::processFetchGattAdvertisementsRequest( + Ptr peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result) { + Synchronized s(lock_.get()); + + if (advertisement_read_result.isNull()) { + advertisement_read_result = + MakeRefCountedPtr(new AdvertisementReadResult()); + } + + if (peripheral.isNull()) { + // logger.atSevere().log("Can't read from an advertisement GATT server + // because ble peripheral is null."); + return advertisement_read_result; + } + + if (!bluetooth_radio_->isEnabled()) { + // logger.atSevere().log("Can't read from an advertisement GATT server + // because Bluetooth was never turned on."); + return advertisement_read_result; + } + + if (!isAvailable()) { + // logger.atSevere().log("Can't read from an advertisement GATT server + // because BLE is not available."); + return advertisement_read_result; + } + + return internalReadFromAdvertisementGattServer(peripheral, num_slots, + advertisement_read_result); +} + +template +Ptr> +BLEV2::internalReadFromAdvertisementGattServer( + Ptr peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result) { + // Attempt to connect and read some GATT characteristics. + bool read_success = true; + + ScopedPtr> + connection_lifecycle_callback( + new ClientGATTConnectionLifecycleCallbackFacade(MakePtr(this))); + ScopedPtr> gatt_connection( + ble_medium_->connectToGATTServer(peripheral, kDefaultMtu, + BLEMediumV2::PowerMode::HIGH, + connection_lifecycle_callback.get())); + if (!gatt_connection.isNull() && gatt_connection->discoverServices()) { + // Read all advertisements from all slots that we haven't read from yet. + for (std::int32_t slot = 0; slot < num_slots; ++slot) { + // Make sure we haven't already read this advertisement before. + if (advertisement_read_result->hasAdvertisement(slot)) { + continue; + } + + // Make sure the characteristic even exists for this slot number. If the + // characteristic doesn't exist, we shouldn't count the fetch as a + // failure because there's nothing we could've done about a non-existent + // characteristic. + Ptr gatt_characteristic( + gatt_connection->getCharacteristic(kCopresenceServiceUuid, + generateAdvertisementUuid(slot))); + if (/* !advertisementSlotExists()= */ gatt_characteristic.isNull()) { + continue; + } + + // Read advertisement data from the characteristic associated with this + // slot. + ScopedPtr> characteristic_value( + gatt_connection->readCharacteristic(gatt_characteristic)); + if (!characteristic_value.isNull()) { + advertisement_read_result->addAdvertisement( + slot, characteristic_value.release()); + // logger.atVerbose().log("Successfully read advertisement at slot %d + // on peripheral %s.", slot, peripheral); + } else { + // logger.atWarning().withCause(characteristicReadException).log("Can't + // read advertisement for slot %d on peripheral %s.", slot, + // peripheral); + read_success = false; + } + // Whether or not we succeeded with this slot, we should try reading the + // other slots to get as many advertisements as possible before + // returning a success or failure. + } + + gatt_connection->disconnect(); + } else { + // logger.atWarning().withCause(connectException).log("Can't connect to an + // advertisement GATT server for peripheral %s.", peripheral); + read_success = false; + } + + advertisement_read_result->recordLastReadStatus(read_success); + return advertisement_read_result; +} + +template +void BLEV2::offloadFromPlatformThread(Ptr runnable) { + platform_thread_offloader_->execute(runnable); +} + +template +ConstPtr BLEV2::generateAdvertisementHash( + ConstPtr advertisement_bytes) { + return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes, + BLEAdvertisementHeader::kAdvertisementHashLength); +} + +template +ConstPtr BLEV2::generateServiceIdHash( + BLEAdvertisement::Version::Value version, const string& service_id) { + ScopedPtr> service_id_bytes( + MakeConstPtr(new ByteArray(service_id.data(), service_id.size()))); + switch (version) { + case BLEAdvertisement::Version::V1: + return Utils::legacySha256HashOnlyForPrinting( + hash_utils_.get(), service_id_bytes.get(), + BLEAdvertisement::kServiceIdHashLength); + case BLEAdvertisement::Version::V2: + // Fall through. + case BLEAdvertisement::Version::UNKNOWN: + // Fall through. + default: + // Use the latest known hashing scheme. + return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(), + BLEAdvertisement::kServiceIdHashLength); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h new file mode 100644 index 00000000..fc568d10 --- /dev/null +++ b/cpp/core/internal/mediums/ble_v2.h @@ -0,0 +1,312 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_V2_H_ + +#include + +#include "core/internal/mediums/advertisement_read_result.h" +#include "core/internal/mediums/ble_advertisement.h" +#include "core/internal/mediums/bluetooth_radio.h" +#include "core/internal/mediums/discovered_peripheral_callback.h" +#include "core/internal/mediums/discovered_peripheral_tracker.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/byte_array.h" +#include "platform/cancelable_alarm.h" +#include "platform/port/string.h" +#include "platform/prng.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace ble_v2 { + +template +class ProcessOnLostRunnable; + +template +class OnAdvertisementFoundRunnable; + +} // namespace ble_v2 + +template +class BLEV2 { + public: + explicit BLEV2(Ptr> bluetooth_radio); + ~BLEV2(); + + bool isAvailable(); + // While the start* functions for each action (advertising, scanning, + // accepting connections) take in a service_id, the stop* and is* functions do + // not. This is because the service_id isn't used. In the java code, shutdown + // calls all the stop* functions w/ a null service_id. The service_id is just + // passed through to the corresponding is* function, which ignores it. + // service_id should be added back in when C++ supports multi-client. + bool startAdvertising(const string& service_id, + ConstPtr advertisement, + BLEMediumV2::PowerMode::Value power_mode, + const string& fast_advertisement_service_uuid); + void stopAdvertising(); + + bool startScanning( + const string& service_id, + Ptr discovered_peripheral_callback, + BLEMediumV2::PowerMode::Value power_mode, + const string& fast_advertisement_service_uuid); + void stopScanning(); + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() {} + + // TODO(ahlee): Add in connecting logic. + }; + + bool isAcceptingConnections(); + bool startAcceptingConnections( + const string& service_id, + Ptr accepted_connection_callback); + void stopAcceptingConnections(); + + private: + template + friend class ble_v2::ProcessOnLostRunnable; + template + friend class ble_v2::OnAdvertisementFoundRunnable; + + class GATTAdvertisementFetcherFacade + : public DiscoveredPeripheralTracker::GattAdvertisementFetcher { + public: + explicit GATTAdvertisementFetcherFacade(Ptr> impl) + : impl_(impl) {} + ~GATTAdvertisementFetcherFacade() override {} + + Ptr> fetchGattAdvertisements( + Ptr ble_peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result) + override { + return impl_->processFetchGattAdvertisementsRequest( + ble_peripheral, num_slots, advertisement_read_result); + } + + private: + Ptr> impl_; + }; + + class ScanCallbackFacade : public BLEMediumV2::ScanCallback { + public: + explicit ScanCallbackFacade(Ptr> impl) : impl_(impl) {} + ~ScanCallbackFacade() override {} + + void onAdvertisementFound( + Ptr peripheral, + ConstPtr advertisement_data) override { + impl_->onAdvertisementFoundImpl(peripheral, advertisement_data); + } + + private: + Ptr> impl_; + }; + + class ClientGATTConnectionLifecycleCallbackFacade + : public ClientGATTConnectionLifecycleCallback { + public: + explicit ClientGATTConnectionLifecycleCallbackFacade( + Ptr> impl) + : impl_(impl) {} + ~ClientGATTConnectionLifecycleCallbackFacade() override {} + + void onDisconnected(Ptr connection) override { + // Avoid leaks. + ScopedPtr> scoped_connection(connection); + + // Nothing else to do for now. + } + + private: + Ptr> impl_; + }; + + class ServerGATTConnectionLifecycleCallbackFacade + : public ServerGATTConnectionLifecycleCallback { + public: + explicit ServerGATTConnectionLifecycleCallbackFacade( + Ptr> impl) + : impl_(impl) {} + ~ServerGATTConnectionLifecycleCallbackFacade() override {} + + void onCharacteristicSubscription( + Ptr connection, + Ptr characteristic) override { + // Avoid leaks. Do not scope the characteristic because it is ref counted + // by the per-platform ble_v2 implementation. + ScopedPtr> scoped_connection(connection); + + // Nothing else to do for now. + } + + void onCharacteristicUnsubscription( + Ptr connection, + Ptr characteristic) override { + // Avoid leaks. Do not scope the characteristic because it is ref counted + // by the per-platform ble_v2 implementation. + ScopedPtr> scoped_connection(connection); + + // Nothing else to do for now. + } + + private: + Ptr> impl_; + }; + + struct ScanningInfo { + ScanningInfo(const string& service_id, + Ptr scan_callback_facade, + Ptr> on_lost_alarm) + : service_id(service_id), + scan_callback_facade(scan_callback_facade), + on_lost_alarm(on_lost_alarm) {} + ~ScanningInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + const string service_id; + ScopedPtr> scan_callback_facade; + // TODO(ahlee): Change to recurring cancelable alarm + ScopedPtr>> on_lost_alarm; + }; + + struct AdvertisingInfo { + explicit AdvertisingInfo(const string& service_id) + : service_id(service_id) {} + ~AdvertisingInfo() {} + + const string service_id; + }; + + struct GATTServerInfo { + GATTServerInfo(Ptr gatt_server, + Ptr + connection_lifecycle_callback) + : gatt_server(gatt_server), + connection_lifecycle_callback(connection_lifecycle_callback) {} + ~GATTServerInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + ScopedPtr> gatt_server; + ScopedPtr> + connection_lifecycle_callback; + }; + + struct AcceptingConnectionsInfo { + explicit AcceptingConnectionsInfo(const string& service_id) + : service_id(service_id) {} + ~AcceptingConnectionsInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + const string service_id; + // TODO(ahlee): Fill in. + }; + + static const std::int32_t kNumAdvertisementSlots; + static const std::int32_t kMaxAdvertisementLength; + static const std::int32_t kDummyServiceIdLength; + static const char* kCopresenceServiceUuid; + static const std::int64_t kOnLostTimeoutMillis; + static const std::int64_t kGattAdvertisementOperationTimeoutMillis; + static const std::int64_t kMinConnectionAttemptRecoveryDurationMillis; + static const std::int32_t kMaxConnectionAttemptRecoveryFuzzDurationMillis; + static const std::uint32_t kDefaultMtu; + static const std::int64_t kAdvertisementUuidMsb; + static const std::int64_t kAdvertisementUuidLsb; + + bool isAdvertising(); + ConstPtr createAdvertisementHeader( + const string& service_id, ConstPtr advertisement_bytes, + bool is_fast_advertisement); + + bool isScanning(); + void onAdvertisementFoundImpl( + Ptr ble_peripheral, + ConstPtr advertisement_data); + void processOnLostTimeout(); + Ptr> createOnLostAlarm(); + + bool isAdvertisementGattServerRunning(); + bool startAdvertisementGattServer(const string& service_id, + ConstPtr advertisement); + bool internalStartAdvertisementGattServer( + ConstPtr legacy_ble_advertisement_bytes, + ConstPtr ble_advertisement_bytes); + bool generateAdvertisementCharacteristic( + std::int32_t slot, ConstPtr advertisement, + Ptr gatt_server); + void stopAdvertisementGattServer(); + + Ptr> processFetchGattAdvertisementsRequest( + Ptr peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result); + Ptr> + internalReadFromAdvertisementGattServer( + Ptr ble_peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result); + + void offloadFromPlatformThread(Ptr runnable); + // TODO(ahlee): Move these out to utils (also used by + // DiscoveredPeripheralTracker). + ConstPtr generateAdvertisementHash( + ConstPtr advertisement_bytes); + ConstPtr generateServiceIdHash( + BLEAdvertisement::Version::Value version, const string& service_id); + + // This maps to a helper function found in bluetoothlowenergy/Utils.java. In + // the C++ code we moved it because it's only used here. + string generateAdvertisementUuid(std::int32_t slot); + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + // Where we throw potentially blocking work off of the platform thread. + ScopedPtr> + platform_thread_offloader_; + ScopedPtr> prng_; + ScopedPtr> hash_utils_; + + // ------------ CORE BLE ------------ + + Ptr> bluetooth_radio_; + ScopedPtr> bluetooth_adapter_; + // The underlying, per-platform implementation. + ScopedPtr> ble_medium_; + + // ------------ DISCOVERY ------------ + + // scanning_info_ is not scoped because it's nullable. + Ptr scanning_info_; + ScopedPtr>> + discovered_peripheral_tracker_; + ScopedPtr> on_lost_executor_; + + // ------------ ADVERTISING ------------ + + // advertising_info_, gatt_server_info_, and accepting_connections_info_ are + // not scoped because they are nullable. + Ptr advertising_info_; + Ptr gatt_server_info_; + Ptr accepting_connections_info_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/ble_v2.cc" + +#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_H_ diff --git a/cpp/core/internal/mediums/bloom_filter.cc b/cpp/core/internal/mediums/bloom_filter.cc new file mode 100644 index 00000000..835ed205 --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter.cc @@ -0,0 +1,109 @@ +#include "core/internal/mediums/bloom_filter.h" + +#include "absl/numeric/int128.h" +#include "absl/strings/numbers.h" +#include "smhasher/MurmurHash3.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +template +const std::int32_t BloomFilter::kHasherNumberOfRepetitions = 5; + +template +BloomFilter::BloomFilter() : bits_() {} + +template +BloomFilter::BloomFilter(ConstPtr bytes) : bits_() { + const char* bytes_read_ptr = bytes->getData(); + for (size_t byte_index = 0; byte_index < bytes->size(); byte_index++) { + for (size_t bit_index = 0; bit_index < 8; bit_index++) { + bits_.set((byte_index * 8) + bit_index, + (*bytes_read_ptr >> bit_index) & 0x01); + } + bytes_read_ptr++; + } +} + +template +BloomFilter::~BloomFilter() { + // Nothing to do. +} + +template +ConstPtr BloomFilter::asBytes() { + // Gets a binary string representation of the bitset where the leftmost + // character corresponds to bitset position (total size) - 1. + // + // If the bitset's internal representation is: + // [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11] + // The string representation will be outputted like this: + // "1 0 1 0 1 0 0 0 1 1 0 0" + std::string bitset_binary_string = bits_.to_string(); + + Ptr result_bytes{new ByteArray{CapacityInBytes}}; + char* result_bytes_write_ptr = result_bytes->getData(); + // We go through the string backwards because the rightmost character + // corresponds to position 0 in the bitset. + for (size_t i = bits_.size(); i > 0; i -= 8) { + std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8); + std::uint32_t byte_value; + absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value, + /* base= */ 2); + *result_bytes_write_ptr = static_cast(byte_value & 0x000000FF); + result_bytes_write_ptr++; + } + return ConstifyPtr(result_bytes); +} + +template +void BloomFilter::add(const std::string& s) { + std::vector hashes = getHashes(s); + for (std::vector::iterator it = hashes.begin(); + it != hashes.end(); ++it) { + size_t position = static_cast(*it) % bits_.size(); + bits_.set(position); + } +} + +template +bool BloomFilter::possiblyContains(const std::string& s) { + std::vector hashes = getHashes(s); + for (std::vector::iterator i = hashes.begin(); + i != hashes.end(); ++i) { + size_t position = static_cast(*i) % bits_.size(); + if (!bits_.test(position)) { + return false; + } + } + return true; +} + +template +std::vector BloomFilter::getHashes( + const std::string& s) { + std::vector hashes(kHasherNumberOfRepetitions, 0); + + absl::uint128 hash128; + MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128); + std::uint64_t hash64 = + absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash + std::int32_t hash1 = static_cast( + hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash + std::int32_t hash2 = static_cast( + (hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash + for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) { + std::int32_t combinedHash = static_cast(hash1 + (i * hash2)); + // Flip all the bits if it's negative (guaranteed positive number) + if (combinedHash < 0) combinedHash = ~combinedHash; + hashes[i - 1] = combinedHash; + } + return hashes; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/bloom_filter.h b/cpp/core/internal/mediums/bloom_filter.h new file mode 100644 index 00000000..d358cb25 --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter.h @@ -0,0 +1,54 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ +#define CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ + +#include +#include +#include + +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +/** + * A bloom filter that gives access to the underlying BitSet. The implementation + * is copied from our Java version of Bloom filter, which in turn copies from + * Guava's BloomFilter. + * + * BloomFilter is templatized on the size of the byte array and not the size of + * the bit set to ensure the bit set's length is a multiple of 8 (and can + * neatly be returned as a ByteArray). + */ +template +class BloomFilter { + public: + BloomFilter(); + explicit BloomFilter(ConstPtr bytes); + ~BloomFilter(); + + ConstPtr asBytes(); + + void add(const std::string& s); + + bool possiblyContains(const std::string& s); + + private: + static const std::int32_t kHasherNumberOfRepetitions; + + std::vector getHashes(const std::string& s); + + std::bitset bits_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/bloom_filter.cc" + +#endif // CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ diff --git a/cpp/core/internal/mediums/bloom_filter_test.cc b/cpp/core/internal/mediums/bloom_filter_test.cc new file mode 100644 index 00000000..fe3fbfe1 --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter_test.cc @@ -0,0 +1,162 @@ +#include "core/internal/mediums/bloom_filter.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const size_t kByteArrayLength = 100; + +TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + + ScopedPtr> scoped_bloom_filter_bytes( + scoped_bloom_filter->asBytes()); + std::string empty_string(kByteArrayLength, '\0'); + ASSERT_EQ(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(), + empty_string.size())); +} + +TEST(BloomFilterTest, EmptyFilterNeverContains) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1")); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2")); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddSuccess) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1")); + + scoped_bloom_filter->add("ELEMENT_1"); + ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1")); +} + +TEST(BloomFilterTest, AddOnlyGivenArg) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + scoped_bloom_filter->add("ELEMENT_1"); + + ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1")); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2")); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddMultipleArgs) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + scoped_bloom_filter->add("ELEMENT_1"); + scoped_bloom_filter->add("ELEMENT_2"); + + ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1")); + ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_2")); + ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) { + ScopedPtr>> scoped_bloom_filter(new BloomFilter<10>()); + scoped_bloom_filter->add("ELEMENT_1"); + scoped_bloom_filter->add("ELEMENT_2"); + scoped_bloom_filter->add("ELEMENT_3"); + + ScopedPtr> scoped_bloom_filter_bytes( + scoped_bloom_filter->asBytes()); + std::string empty_string(kByteArrayLength, '\0'); + ASSERT_NE(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(), + empty_string.size())); +} + +/** + * This test was added because of a bug where the BloomFilter doesn't utilize + * all bits given. Functionally, the filter still works, but we just have a much + * higher false positive rate. The bug was caused by confusing bit length and + * byte length, which made our BloomFilter only set bits on the first byteLength + * (bitLength / 8) bits rather than the whole bitLength bits. + * + *

Here, we're verifying that the bits set are somewhat scattered. So instead + * of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting + * something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0]. + */ +TEST(BloomFilterTest, RandomnessNoEndBias) { + ScopedPtr>> scoped_bloom_filter( + new BloomFilter()); + // Add one element to our BloomFilter. + scoped_bloom_filter->add("ELEMENT_1"); + + std::int32_t non_zero_count = 0; + std::int32_t longest_zero_streak = 0; + std::int32_t current_zero_streak = 0; + + // Record the amount of non-zero bytes and the longest streak of zero bytes in + // the resulting BloomFilter. This is an approximation of reasonable + // distribution since we're recording by bytes instead of bits. + ScopedPtr> scoped_bloom_filter_bytes( + scoped_bloom_filter->asBytes()); + const char* bloom_filter_bytes_read_ptr = + scoped_bloom_filter_bytes->getData(); + for (int i = 0; i < scoped_bloom_filter_bytes->size(); i++) { + if (*bloom_filter_bytes_read_ptr == '\0') { + current_zero_streak++; + } else { + // Increment the number of non-zero bytes we've seen, update the longest + // zero streak, and then reset the current zero streak. + non_zero_count++; + longest_zero_streak = std::max(longest_zero_streak, current_zero_streak); + current_zero_streak = 0; + } + bloom_filter_bytes_read_ptr++; + } + // Update the longest zero streak again for the tail case. + longest_zero_streak = std::min(longest_zero_streak, current_zero_streak); + + // Since randomness is hard to measure within one unit test, we instead do a + // sanity check. All non-zero bytes should not be packed into one end of the + // array. + // + // In this case, the size of one end is approximated to be: + // kByteArrayLength / nonZeroCount. + // Therefore, the longest zero streak should be less than: + // kByteArrayLength - one end of the array. + std::int32_t longest_acceptable_zero_streak = + kByteArrayLength - (kByteArrayLength / non_zero_count); + ASSERT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak); +} + +TEST(BloomFilterTest, RandomnessFalsePositiveRate) { + ScopedPtr>> scoped_bloom_filter(new BloomFilter<10>()); + // Add 5 distinct elements to the BloomFilter. + scoped_bloom_filter->add("ELEMENT_1"); + scoped_bloom_filter->add("ELEMENT_2"); + scoped_bloom_filter->add("ELEMENT_3"); + scoped_bloom_filter->add("ELEMENT_4"); + scoped_bloom_filter->add("ELEMENT_5"); + + std::int32_t false_positives = 0; + // Now test 100 other elements and record the number of false positives. + for (int i = 5; i < 105; i++) { + false_positives += + scoped_bloom_filter->possiblyContains("ELEMENT_" + std::to_string(i)) + ? 1 + : 0; + } + + // We expect the false positive rate to be 3% with 5 elements in a 10 byte + // filter. Thus, we give a little leeway and verify that the false positive + // rate is no more than 5%. + ASSERT_LE(false_positives, 5); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/bluetooth_classic.cc b/cpp/core/internal/mediums/bluetooth_classic.cc new file mode 100644 index 00000000..c49348c5 --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_classic.cc @@ -0,0 +1,468 @@ +#include "core/internal/mediums/bluetooth_classic.h" + +#include + +#include "core/internal/mediums/uuid.h" +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { + +template +const std::int32_t BluetoothClassic::kMaxConcurrentAcceptLoops = 5; + +template +BluetoothClassic::BluetoothClassic( + Ptr> bluetooth_radio) + : lock_(Platform::createLock()), + bluetooth_radio_(bluetooth_radio), + bluetooth_adapter_(Platform::createBluetoothAdapter()), + bluetooth_classic_medium_(Platform::createBluetoothClassicMedium()), + scan_info_(), + original_scan_mode_(BluetoothAdapter::ScanMode::UNKNOWN), + original_device_name_(), + accept_loops_thread_pool_( + Platform::createMultiThreadExecutor(kMaxConcurrentAcceptLoops)), + bluetooth_server_sockets_() {} + +template +BluetoothClassic::~BluetoothClassic() { + stopDiscovery(); + for (BluetoothServerSocketMap::iterator it = + bluetooth_server_sockets_.begin(); + it != bluetooth_server_sockets_.end(); ++it) { + stopAcceptingConnections(it->first); + } + turnOffDiscoverability(); + + // All the AcceptLoopRunnable objects in here should already have gotten an + // opportunity to shut themselves down cleanly in the calls to + // stopAcceptingConnections() above. + accept_loops_thread_pool_->shutdown(); + + original_device_name_.destroy(); + scan_info_.destroy(); +} + +template +bool BluetoothClassic::isAvailable() { + Synchronized s(lock_.get()); + + return !bluetooth_classic_medium_.isNull() && !bluetooth_adapter_.isNull(); +} + +template +bool BluetoothClassic::turnOnDiscoverability( + const string& device_name) { + Synchronized s(lock_.get()); + + if (device_name.empty()) { + // TODO(ahlee): logger.atSevere().log("Refusing to turn on Bluetooth + // discoverability because a null deviceName was passed in."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability + // because Bluetooth isn't enabled."); + return false; + } + + if (!isAvailable()) { + // TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability + // because Bluetooth isn't available."); + return false; + } + + if (isDiscoverable()) { + // TODO(reznor): log.atSevere().log("Refusing to turn on Bluetooth + // discoverability with device name %s because we're already discoverable + // with device name %s.", deviceName, bluetoothAdapter.getName()); + return false; + } + + if (!modifyDeviceName(device_name)) { + // TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth + // discoverability because we couldn't set the device name to %s", + // deviceName); + return false; + } + + if (!modifyScanMode(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE)) { + // TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth + // discoverability because we couldn't set the scan mode to %d", + // BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE); + + // Don't forget to perform this rollback of the partial state changes we've + // made til now. + restoreDeviceName(); + return false; + } + + // TODO(reznor): log.atVerbose().log("Turned on Bluetooth discoverability with + // deviceName %s", deviceName); + return true; +} + +template +void BluetoothClassic::turnOffDiscoverability() { + Synchronized s(lock_.get()); + + if (!isDiscoverable()) { + // TODO(reznor): log.atDebug().log("Can't turn off Bluetooth discoverability + // because it was never turned on."); + return; + } + + restoreScanMode(); + restoreDeviceName(); + + // TODO(reznor): log.atVerbose().log("Turned Bluetooth discoverability off"); +} + +template +bool BluetoothClassic::isDiscoverable() const { + return ((!original_device_name_.isNull()) && + (BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE == + bluetooth_adapter_->getScanMode())); +} + +template +bool BluetoothClassic::modifyDeviceName(const string& device_name) { + original_device_name_ = bluetooth_adapter_->getName(); + + if (!bluetooth_adapter_->setName(device_name)) { + original_device_name_.destroy(); + return false; + } + + return true; +} + +template +bool BluetoothClassic::modifyScanMode( + BluetoothAdapter::ScanMode::Value scan_mode) { + original_scan_mode_ = bluetooth_adapter_->getScanMode(); + + if (!bluetooth_adapter_->setScanMode(scan_mode)) { + original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN; + return false; + } + + return true; +} + +template +void BluetoothClassic::restoreScanMode() { + if (!bluetooth_adapter_->setScanMode(original_scan_mode_)) { + // TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth + // scan mode to %d", originalScanMode); + } + + // Regardless of whether or not we could actually restore the Bluetooth scan + // mode, reset our relevant state. + original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN; +} + +template +void BluetoothClassic::restoreDeviceName() { + if (!bluetooth_adapter_->setName(*original_device_name_)) { + // TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth + // device name to %s", originalDeviceName); + } + + // Regardless of whether or not we could actually restore the Bluetooth device + // name, reset the marker that opens us up for business for the next time + // 'round. + original_device_name_.destroy(); +} + +template +bool BluetoothClassic::startDiscovery( + Ptr discovered_device_callback) { + Synchronized s(lock_.get()); + + if (discovered_device_callback.isNull()) { + // TODO(reznor): log.atSevere().log("Refusing to start discovery of + // Bluetooth devices because a null discoveredDeviceCallback was passed + // in."); + return false; + } + // Avoid leaks. + ScopedPtr> scoped_discovered_device_callback( + discovered_device_callback); + + if (!bluetooth_radio_->isEnabled()) { + // TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices + // because Bluetooth isn't enabled."); + return false; + } + + if (!isAvailable()) { + // TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices + // because Bluetooth isn't available."); + return false; + } + + if (isDiscovering()) { + // TODO(reznor): log.atSevere().log("Refusing to start discovery of + // Bluetooth devices because another discovery is already in-progress."); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_bluetooth_discovery_callback(new BluetoothDiscoveryCallback( + scoped_discovered_device_callback.get())); + + if (!bluetooth_classic_medium_->startDiscovery( + scoped_bluetooth_discovery_callback.get())) { + // TODO(reznor): log.atSevere().log("Failed to start discovery of Bluetooth + // devices."); + return false; + } + + // Mark the fact that we're currently performing a Bluetooth scan. + scan_info_ = + MakePtr(new ScanInfo(scoped_discovered_device_callback.release(), + scoped_bluetooth_discovery_callback.release())); + return true; +} + +template +void BluetoothClassic::stopDiscovery() { + Synchronized s(lock_.get()); + + if (!isDiscovering()) { + // TODO(reznor): log.atDebug().log("Can't stop discovery of Bluetooth + // devices because it never started."); + return; + } + + if (!bluetooth_classic_medium_->stopDiscovery()) { + // TODO(reznor): log.atWarning().log("Failed to stop discovery of Bluetooth + // devices."); + } + // Regardless of whether or not stopDiscovery() succeeded, destroy scan_info_ + // to: + // + // a) Avoid a leak. + // b) Mark the fact that we're no longer performing a Bluetooth discovery. + scan_info_.destroy(); +} + +template +bool BluetoothClassic::isDiscovering() const { + return !scan_info_.isNull(); +} + +template +class AcceptLoopRunnable : public Runnable { + public: + AcceptLoopRunnable( + Ptr::AcceptedConnectionCallback> + accepted_connection_callback, + Ptr listening_socket, const string& service_name) + : accepted_connection_callback_(accepted_connection_callback), + listening_socket_(listening_socket), + service_name_(service_name) {} + + void run() override { + while (true) { + ExceptionOr> bluetooth_socket = + listening_socket_->accept(); + if (!bluetooth_socket.ok()) { + if (Exception::IO == bluetooth_socket.exception()) { + Utils::closeSocket(listening_socket_, "Bluetooth", service_name_); + } + break; + } + + accepted_connection_callback_->onConnectionAccepted( + bluetooth_socket.result()); + } + } + + private: + ScopedPtr< + Ptr::AcceptedConnectionCallback>> + accepted_connection_callback_; + Ptr listening_socket_; + const string service_name_; +}; + +template +bool BluetoothClassic::startAcceptingConnections( + const string& service_name, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> + scoped_accepted_connection_callback(accepted_connection_callback); + if (scoped_accepted_connection_callback.isNull() || service_name.empty()) { + // TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth + // connections because at least one of serviceName or + // acceptedConnectionCallback is null."); + return false; + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(reznor): log.atSevere().log("Can't create Bluetooth server socket + // for %s because Bluetooth isn't enabled.", serviceName); + return false; + } + + if (!isAvailable()) { + // TODO(reznor): log.atSevere().log("Can't start accepting BLuetooth + // connections for %s because Bluetooth isn't available.", serviceName); + return false; + } + + if (isAcceptingConnections(service_name)) { + // TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth + // connections for %s because a Bluetooth server is already in-progress for + // that service name.", serviceName); + return false; + } + + ExceptionOr> listening_socket = + bluetooth_classic_medium_->listenForService( + service_name, generateUUIDFromString(service_name)); + if (!listening_socket.ok()) { + if (Exception::IO == listening_socket.exception()) { + // TODO(reznor): log.atSevere().withCause(e).log("Failed to start + // accepting Bluetooth connections for %s.", serviceName); + return false; + } + } + + // Start the accept loop on a dedicated thread - this stays alive and + // listening for new incoming connections until stopAcceptingConnections() is + // invoked. + accept_loops_thread_pool_->execute(MakePtr(new AcceptLoopRunnable( + scoped_accepted_connection_callback.release(), listening_socket.result(), + service_name))); + + // Mark the fact that there's an in-progress Bluetooth server accepting + // connections. + bluetooth_server_sockets_.insert( + std::make_pair(service_name, listening_socket.result())); + return true; +} + +template +bool BluetoothClassic::isAcceptingConnections( + const string& service_name) { + Synchronized s(lock_.get()); + + return bluetooth_server_sockets_.find(service_name) != + bluetooth_server_sockets_.end(); +} + +template +void BluetoothClassic::stopAcceptingConnections( + const string& service_name) { + Synchronized s(lock_.get()); + + if (service_name.empty()) { + // TODO(ahlee): logger.atSevere().log("Unable to stop accepting Bluetooth + // connections because the serviceName is empty."); + return; + } + + if (!isAcceptingConnections(service_name)) { + // TODO(reznor): log.atDebug().log("Can't stop accepting Bluetooth + // connections for %s because it was never started.", serviceName); + return; + } + + // Closing the BluetoothServerSocket will kick off the suicide of the thread + // in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept(). + // That may take some time to complete, but there's no particular reason to + // wait around for it. + BluetoothServerSocketMap::iterator listening_socket_iter = + bluetooth_server_sockets_.find(service_name); + + // Store a handle to the BluetoothServerSocket, so we can use it after + // removing the entry from bluetooth_server_sockets_; making it scoped + // is a bonus that takes care of deallocation before we leave this method. + ScopedPtr> scoped_listening_socket( + listening_socket_iter->second); + + // Regardless of whether or not we fail to close the existing + // BluetoothServerSocket, remove it from bluetooth_server_sockets_ so that it + // frees up this service for another round. + bluetooth_server_sockets_.erase(listening_socket_iter); + + // Finally, close the BluetoothServerSocket. + Exception::Value e = scoped_listening_socket->close(); + if (Exception::NONE != e) { + if (Exception::IO == e) { + // TODO(reznor): log.atSevere().withCause(e).log("Failed to close + // Bluetooth server socket for %s.", serviceName); + } + } +} + +template +Ptr BluetoothClassic::connect( + Ptr bluetooth_device, const string& service_name) { + Synchronized s(lock_.get()); + + if (bluetooth_device.isNull() || service_name.empty()) { + // TODO(reznor): log.atSevere().log("Refusing to create client Bluetooth + // socket because at least one of bluetoothDevice or serviceName is null."); + return Ptr(); + } + + if (!bluetooth_radio_->isEnabled()) { + // TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to + // %s because Bluetooth isn't enabled.", bluetoothSocketName); + return Ptr(); + } + + if (!isAvailable()) { + // TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to + // %s because Bluetooth isn't available.", bluetoothSocketName); + return Ptr(); + } + + // WARNING WARNING WARNING + // + // This block deviates from the corresponding Java code. + // + // In Java, we pause an in-progress discovery before attempting this + // connection, and then resume it after, but the memory management of the + // DiscoveredDeviceCallback is complicated in C++, and would need a severe + // deviation from the Java code, so we're choosing the lesser of 2 evils, and + // introducing this (simplifying) deviation instead -- also, this deviation is + // fairly inconsequential since we don't yet have a use-case that needs a + // device that: + // + // a) uses the C++ code, + // b) has Bluetooth Classic support, and + // c) plays the role of Discoverer. + ExceptionOr> bluetooth_socket = + bluetooth_classic_medium_->connectToService( + bluetooth_device, generateUUIDFromString(service_name)); + if (!bluetooth_socket.ok()) { + if (Exception::IO == bluetooth_socket.exception()) { + // TODO(reznor): log.atSevere().log("Failed to connect via Bluetooth + // socket to %s.", bluetoothSocketName); + } + return Ptr(); + } + + return bluetooth_socket.result(); +} + +template +string BluetoothClassic::generateUUIDFromString(const string& data) { + return UUID(data).str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/bluetooth_classic.h b/cpp/core/internal/mediums/bluetooth_classic.h new file mode 100644 index 00000000..dddf3993 --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_classic.h @@ -0,0 +1,169 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ +#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "core/internal/mediums/bluetooth_radio.h" +#include "core/internal/mediums/utils.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/lock.h" +#include "platform/api/multi_thread_executor.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class BluetoothClassic { + public: + explicit BluetoothClassic(Ptr> bluetooth_radio); + ~BluetoothClassic(); + + bool isAvailable(); + + bool turnOnDiscoverability(const string& device_name); + void turnOffDiscoverability(); + + // Callback that is invoked when a nearby Bluetooth device is discovered. + class DiscoveredDeviceCallback { + public: + virtual ~DiscoveredDeviceCallback() {} + + virtual void onDeviceDiscovered(Ptr device) = 0; + virtual void onDeviceNameChanged(Ptr device) = 0; + virtual void onDeviceLost(Ptr device) = 0; + }; + + bool startDiscovery(Ptr discovered_device_callback); + void stopDiscovery(); + + // Callback that is invoked when a new connection is accepted. + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() {} + + virtual void onConnectionAccepted(Ptr socket) = 0; + }; + + bool startAcceptingConnections( + const string& service_name, + Ptr accepted_connection_callback); + bool isAcceptingConnections(const string& service_name); + void stopAcceptingConnections(const string& service_name); + + Ptr connect(Ptr bluetooth_device, + const string& service_name); + + private: + class BluetoothDiscoveryCallback + : public BluetoothClassicMedium::DiscoveryCallback { + public: + explicit BluetoothDiscoveryCallback( + Ptr discovered_device_callback) + : discovered_device_callback_(discovered_device_callback) {} + ~BluetoothDiscoveryCallback() override { + // Nothing to do. + } + + void onDeviceDiscovered(Ptr bluetooth_device) override { + discovered_device_callback_->onDeviceDiscovered(bluetooth_device); + } + void onDeviceNameChanged(Ptr bluetooth_device) override { + discovered_device_callback_->onDeviceNameChanged(bluetooth_device); + } + void onDeviceLost(Ptr bluetooth_device) override { + discovered_device_callback_->onDeviceLost(bluetooth_device); + } + + private: + // This could well have been a ScopedPtr, with BluetoothDiscoveryCallback in + // turn being owned by ScanInfo (and it would have been cleaner overall, + // since the chain of wrapped callbacks starting from + // BluetoothDiscoveryCallback would then destruct like a stack of dominoes + // falling, triggered by the destruction of ScanInfo), but we instead give + // ownership of this DiscoveredDeviceCallback *and* + // BluetoothDiscoveryCallback to ScanInfo, to maintain compatibility with + // the Java code. + Ptr discovered_device_callback_; + }; + + struct ScanInfo { + ScanInfo(Ptr discovered_device_callback, + Ptr bluetooth_discovery_callback) + : discovered_device_callback(discovered_device_callback), + bluetooth_discovery_callback(bluetooth_discovery_callback) {} + ~ScanInfo() { + // Nothing to do (the ScopedPtr members take care of themselves). + } + + // Stores the DiscoveredDeviceCallback passed in to startDiscovery() by + // clients so that we can internally stop and start Bluetooth scans + // transparently as needed (for example, when a call to connect() is + // invoked). + ScopedPtr> discovered_device_callback; + // The ordering of bluetooth_discovery_callback_ coming after + // discovered_device_callback_ is very deliberate -- + // bluetooth_discovery_callback_ contains a reference to + // discovered_device_callback_, so it should be destroyed first. + ScopedPtr> bluetooth_discovery_callback; + }; + + static string generateUUIDFromString(const string& data); + + static const std::int32_t kMaxConcurrentAcceptLoops; + + bool isDiscoverable() const; + bool modifyDeviceName(const string& device_name); + bool modifyScanMode(BluetoothAdapter::ScanMode::Value scan_mode); + void restoreScanMode(); + void restoreDeviceName(); + bool isDiscovering() const; + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + + // ------------ CORE BLUETOOTH ------------ + + Ptr> bluetooth_radio_; + ScopedPtr> bluetooth_adapter_; + // The underlying, per-platform implementation. + ScopedPtr> bluetooth_classic_medium_; + + // ------------ DISCOVERY ------------ + + // A bundle of state required to do a Bluetooth Classic scan. When non-null, + // we are currently performing a Bluetooth scan. + Ptr scan_info_; + + // ------------ ADVERTISING ------------ + + // The original scan mode (that controls visibility to scanners) of the device + // before we modified it. Restored when we stop advertising. + BluetoothAdapter::ScanMode::Value original_scan_mode_; + // The original Bluetooth device name, before we modified it. If non-null, we + // are currently Bluetooth discoverable. Restored when we stop advertising. + Ptr original_device_name_; + // A thread pool dedicated to running all the accept loops from + // startAcceptingConnections(). + ScopedPtr> + accept_loops_thread_pool_; + // A map of service name -> ServerSocket. While this map is non-empty, we + // are currently listening for incoming connections. + typedef std::map> BluetoothServerSocketMap; + BluetoothServerSocketMap bluetooth_server_sockets_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/bluetooth_classic.cc" + +#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/core/internal/mediums/bluetooth_radio.cc b/cpp/core/internal/mediums/bluetooth_radio.cc new file mode 100644 index 00000000..9edaa777 --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_radio.cc @@ -0,0 +1,122 @@ +#include "core/internal/mediums/bluetooth_radio.h" + +#include "platform/exception.h" + +namespace location { +namespace nearby { +namespace connections { + +template +std::int64_t BluetoothRadio::kPauseBetweenToggleDurationMillis = 3000; + +template +BluetoothRadio::BluetoothRadio() + : bluetooth_adapter_(Platform::createBluetoothAdapter()), + thread_utils_(Platform::createThreadUtils()), + originally_enabled_() { + if (bluetooth_adapter_.isNull()) { + // TODO(reznor): log.atSevere().log("Failed to retrieve default + // BluetoothAdapter, Bluetooth is unsupported."); + } +} + +template +BluetoothRadio::~BluetoothRadio() { + // We never enabled Bluetooth, nothing to do. + if (originally_enabled_.isNull()) { + return; + } + + // Make sure we cleanup the one non-ScopedPtr member before we leave the + // destructor. + ScopedPtr > scoped_originally_enabled(originally_enabled_); + + // Toggle Bluetooth regardless of our original state. Some devices/chips can + // start to freak out after some time (e.g. b/37775337), and this helps to + // ensure BT resets properly. + toggle(); + + if (!setBluetoothState(originally_enabled_->get())) { + // TODO(reznor): log.atWarning().log("Failed to turn Bluetooth back to its + // original state."); + } +} + +template +bool BluetoothRadio::enable() { + if (!saveOriginalState()) { + return false; + } + + return setBluetoothState(true); +} + +template +bool BluetoothRadio::disable() { + if (!saveOriginalState()) { + return false; + } + + return setBluetoothState(false); +} + +template +bool BluetoothRadio::isEnabled() { + return !bluetooth_adapter_.isNull() && isInDesiredState(true); +} + +template +void BluetoothRadio::toggle() { + if (!saveOriginalState()) { + return; + } + + if (!setBluetoothState(false)) { + // TODO(reznor): log.atWarning().log("Failed to turn Bluetooth off while + // toggling state."); + } + + if (Exception::INTERRUPTED == + thread_utils_->sleep(kPauseBetweenToggleDurationMillis)) { + // TODO(reznor): log.atSevere().withCause(e).log("Interrupted while waiting + // in between a Bluetooth toggle."); + return; + } + + if (!setBluetoothState(true)) { + // TODO(reznor): log.atWarning().log("Failed to turn Bluetooth on while + // toggling state."); + } +} + +template +bool BluetoothRadio::setBluetoothState(bool enable) { + return bluetooth_adapter_->setStatus( + enable ? BluetoothAdapter::Status::ENABLED + : BluetoothAdapter::Status::DISABLED); +} + +template +bool BluetoothRadio::isInDesiredState(bool should_be_enabled) const { + return ((should_be_enabled && bluetooth_adapter_->isEnabled()) || + (!should_be_enabled && !bluetooth_adapter_->isEnabled())); +} + +template +bool BluetoothRadio::saveOriginalState() { + if (bluetooth_adapter_.isNull()) { + return false; + } + + // If we haven't saved the original state of the radio, save it. + if (originally_enabled_.isNull()) { + originally_enabled_ = + Platform::createAtomicBoolean(bluetooth_adapter_->isEnabled()); + } + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/bluetooth_radio.h b/cpp/core/internal/mediums/bluetooth_radio.h new file mode 100644 index 00000000..14dd611b --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_radio.h @@ -0,0 +1,69 @@ +#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ +#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/thread_utils.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Provides the operations that can be performed on the Bluetooth radio. +template +class BluetoothRadio { + public: + BluetoothRadio(); + // Reverts the Bluetooth radio to its original state. + ~BluetoothRadio(); + + // Enables Bluetooth. + // + // This must be called before attempting to invoke any other methods of + // this class. + // + // Returns true if enabled successfully. + bool enable(); + // Disables Bluetooth. + // + // Returns true if disabled successfully. + bool disable(); + // Returns true if the Bluetooth radio is currently enabled. + bool isEnabled(); + + void toggle(); + + private: + static std::int64_t kPauseBetweenToggleDurationMillis; + + bool setBluetoothState(bool enable); + bool isInDesiredState(bool should_be_enabled) const; + // To be called in enable(), disable(), and toggle(). This will remember the + // original state of the radio before any radio state has been modified. + // Returns false if Bluetooth doesn't exist on the device and the state cannot + // be obtained. + bool saveOriginalState(); + + // Null if the device does not support Bluetooth. + ScopedPtr> bluetooth_adapter_; + ScopedPtr> thread_utils_; + // The Bluetooth radio's original state, before we modified it. True if + // originally enabled, false if originally disabled, null if we never modified + // the radio state. We restore the radio to its original state in the + // destructor. + // + // This is a Ptr instead of a ScopedPtr because it's lazily initialized + // (and ScopedPtr doesn't support re-assignment). + Ptr originally_enabled_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/bluetooth_radio.cc" + +#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ diff --git a/cpp/core/internal/mediums/discovered_peripheral_callback.h b/cpp/core/internal/mediums/discovered_peripheral_callback.h new file mode 100644 index 00000000..1e3fe35f --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_callback.h @@ -0,0 +1,32 @@ +#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_ +#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_ + +#include "core/internal/mediums/ble_peripheral.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +/** Callback that is invoked when a {@link BLEPeripheral} is discovered. */ +class DiscoveredPeripheralCallback { + public: + virtual ~DiscoveredPeripheralCallback() {} + + virtual void onPeripheralDiscovered(Ptr ble_peripheral, + const string& service_id, + ConstPtr advertisement, + bool is_fast_advertisement) = 0; + virtual void onPeripheralLost(Ptr ble_peripheral, + const string& service_id); +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_ diff --git a/cpp/core/internal/mediums/discovered_peripheral_tracker.cc b/cpp/core/internal/mediums/discovered_peripheral_tracker.cc new file mode 100644 index 00000000..276ec2cf --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.cc @@ -0,0 +1,744 @@ +#include "core/internal/mediums/discovered_peripheral_tracker.h" + +#include "core/internal/mediums/ble_packet.h" +#include "core/internal/mediums/bloom_filter.h" +#include "core/internal/mediums/utils.h" +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace dpt { + +template +void eraseOwnedPtrFromMap(std::map& m, const K& k) { + typename std::map::iterator it = m.find(k); + if (it != m.end()) { + it->second.destroy(); + m.erase(it); + } +} + +template +void eraseAllOwnedPtrsFromMap(std::map>& m) { + for (typename std::map>::iterator it = m.begin(); it != m.end(); + ++it) { + it->second.destroy(); + } + m.clear(); +} + +template +V removeOwnedPtrFromMap(std::map& m, const K& k) { + V removed_ptr; + typename std::map::iterator it = m.find(k); + if (it != m.end()) { + removed_ptr = it->second; + m.erase(it); + } + return removed_ptr; +} + +} // namespace dpt + +// The maximum number of advertisement slots to assume if we don't know the +// exact number. +template +const std::int32_t DiscoveredPeripheralTracker::kMaxSlots = 10; + +// Amount of time to wait before attempting a connection. This is needed to +// prevent the GATT server from operation overload if we just came from a GATT +// discovery. +template +const std::int64_t + DiscoveredPeripheralTracker::kMinConnectionDelayMillis = + 5 * 1000; // 5 seconds + +template +const char* DiscoveredPeripheralTracker::kCopresenceServiceUuid = + "0000FEF3-0000-1000-8000-00805F9B34FB"; + +template +DiscoveredPeripheralTracker::DiscoveredPeripheralTracker() + : lock_(Platform::createLock()), + thread_utils_(Platform::createThreadUtils()), + system_clock_(Platform::createSystemClock()), + hash_utils_(Platform::createHashUtils()), + discovered_peripheral_callbacks_(), + lost_entity_trackers_(), + fast_advertisement_service_uuids_(), + advertisement_read_results_(), + gatt_advertisements_(), + advertisement_service_ids_(), + advertisement_headers_(), + mac_addresses_() {} + +template +DiscoveredPeripheralTracker::~DiscoveredPeripheralTracker() { + Synchronized s(lock_.get()); + + mac_addresses_.clear(); + advertisement_headers_.clear(); + advertisement_service_ids_.clear(); + // gatt_advertisements_ maps a string to a Ptr to a set of ConstPtrs. We do + // not go and iterate through every set because those values are RefCounted. + dpt::eraseAllOwnedPtrsFromMap(gatt_advertisements_); + dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_); + fast_advertisement_service_uuids_.clear(); + dpt::eraseAllOwnedPtrsFromMap(lost_entity_trackers_); + dpt::eraseAllOwnedPtrsFromMap(discovered_peripheral_callbacks_); +} + +// Starts tracking discoveries for a particular service ID. +template +void DiscoveredPeripheralTracker::startTracking( + const string& service_id, + Ptr discovered_peripheral_callback, + const string& fast_advertisement_service_uuid) { + Synchronized s(lock_.get()); + + dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id); + discovered_peripheral_callbacks_.insert( + std::make_pair(service_id, discovered_peripheral_callback)); + + // We create a new LostEntityTracker because any pre-existing ones only + // contain stale advertisements. LostEntityTracker also doesn't provide a + // reset method, so creating a new one is the right way to go. + dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id); + lost_entity_trackers_.insert(std::make_pair( + service_id, + MakePtr(new LostEntityTracker()))); + + if (!fast_advertisement_service_uuid.empty()) { + fast_advertisement_service_uuids_.erase(service_id); + fast_advertisement_service_uuids_.insert( + std::make_pair(service_id, fast_advertisement_service_uuid)); + } + + // Clear all of the GATT read results. With this cleared, we will now attempt + // to reconnect to every peripheral we see, giving us a chance to search for + // the new service we're now tracking. + // See the documentation of advertisementReadResults for more information. + dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_); + + // Remove stale data from any previous sessions. + clearDataForServiceId(service_id); +} + +// Stops tracking discoveries for a particular service ID. +template +void DiscoveredPeripheralTracker::stopTracking( + const string& service_id) { + Synchronized s(lock_.get()); + + fast_advertisement_service_uuids_.erase(service_id); + dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id); + dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id); +} + +// Processes a found BLE advertisement. +template +void DiscoveredPeripheralTracker::processFoundBleAdvertisement( + Ptr ble_peripheral, + ConstPtr advertisement_data, + Ptr gatt_advertisement_fetcher) { + Synchronized s(lock_.get()); + + // Avoid leaks. + ScopedPtr> scoped_advertisement_data( + advertisement_data); + ScopedPtr> scoped_gatt_advertisement_fetcher( + gatt_advertisement_fetcher); + + if (getTrackedServiceIds().empty()) { + // TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header + // because we are not tracking any service IDs."); + return; + } + + if (ble_peripheral.isNull() || scoped_advertisement_data.isNull()) { + // TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header + // because the given BleSighting is null or incomplete."); + return; + } + + handleFastAdvertisement(ble_peripheral, scoped_advertisement_data.get()); + handleAdvertisementHeader(ble_peripheral, scoped_advertisement_data.get(), + scoped_gatt_advertisement_fetcher.get()); +} + +// Processes the set of lost GATT advertisements and notifies the client of any +// lost peripherals. +template +void DiscoveredPeripheralTracker::processLostGattAdvertisements() { + Synchronized s(lock_.get()); + + std::set tracked_service_ids = getTrackedServiceIds(); + for (typename std::set::iterator tsi_it = tracked_service_ids.begin(); + tsi_it != tracked_service_ids.end(); ++tsi_it) { + BLEAdvertisementSet lost_gatt_advertisements = + lost_entity_trackers_.find(*tsi_it)->second->computeLostEntities(); + + // Clear the map state for each lost GATT advertisement and report it to the + // client. + for (BLEAdvertisementSet::iterator lga_it = + lost_gatt_advertisements.begin(); + lga_it != lost_gatt_advertisements.end(); ++lga_it) { + clearGattAdvertisement(*lga_it); + discovered_peripheral_callbacks_.find(*tsi_it)->second->onPeripheralLost( + generateBlePeripheral(*lga_it), *tsi_it); + } + } +} + +template +Ptr DiscoveredPeripheralTracker::generateBlePeripheral( + ConstPtr gatt_advertisement) { + // TODO(ahlee): Reminder to port over deviceToken change. + return MakePtr(new BLEPeripheral(BLEAdvertisement::toBytes( + gatt_advertisement->getVersion(), gatt_advertisement->getSocketVersion(), + gatt_advertisement->getServiceIdHash(), gatt_advertisement->getData()))); +} + +template +std::set DiscoveredPeripheralTracker::getTrackedServiceIds() { + std::set tracked_service_ids; + for (DiscoveredPeripheralCallbackMap::iterator dpc_it = + discovered_peripheral_callbacks_.begin(); + dpc_it != discovered_peripheral_callbacks_.end(); ++dpc_it) { + tracked_service_ids.insert(dpc_it->first); + } + return tracked_service_ids; +} + +// Note: There is no C++ equivalent for getTrackedGattAdvertisements() because +// we make a copy of the subset of the keys in directly in +// clearDataForServiceId(). + +template +void DiscoveredPeripheralTracker::clearDataForServiceId( + const string& service_id) { + BLEAdvertisementSet gatt_advertisements_to_clear; + for (AdvertisementServiceIdMap::iterator it = + advertisement_service_ids_.begin(); + it != advertisement_service_ids_.end(); ++it) { + if (it->second != service_id) { + continue; + } + gatt_advertisements_to_clear.insert(it->first); + } + + for (BLEAdvertisementSet::iterator it = gatt_advertisements_to_clear.begin(); + it != gatt_advertisements_to_clear.end(); ++it) { + clearGattAdvertisement(*it); + } +} + +// Clears out all data related to the provided GATT advertisement. This +// includes: +// 1. Removing the GATT advertisement from GATT advertisement keyed maps. This +// includes advertisementServiceIds, AdvertisementHeaders, and +// macAddresses. +// 2. Removing the corresponding advertisement header from +// advertisementReadResults. +// 3. Removing the corresponding advertisement header from gattAdvertisements, +// only if there are no remaining GATT advertisements related to that +// header. +template +void DiscoveredPeripheralTracker::clearGattAdvertisement( + ConstPtr gatt_advertisement) { + // BLEAdvertisement is RefCounted, so it does not need to be scoped. + advertisement_service_ids_.erase(gatt_advertisement); + mac_addresses_.erase(gatt_advertisement); + + ConstPtr advertisement_header = + dpt::removeOwnedPtrFromMap(advertisement_headers_, gatt_advertisement); + typename GattAdvertisementMap::iterator ga_it = + gatt_advertisements_.find(advertisement_header); + if (ga_it != gatt_advertisements_.end()) { + // Remove the GATT advertisement from the advertisement header it's + // associated with. + Ptr header_gatt_advertisements = ga_it->second; + header_gatt_advertisements->erase(gatt_advertisement); + + // Unconditionally remove the header from advertisementReadResults so we + // can attempt to reread the GATT advertisement if they return. + dpt::eraseOwnedPtrFromMap(advertisement_read_results_, + advertisement_header); + + // If there are no more tracked GATT advertisements under this header, go + // ahead and remove it from gattAdvertisements. + if (header_gatt_advertisements->empty()) { + dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header); + } + } +} + +template +void DiscoveredPeripheralTracker::handleFastAdvertisement( + Ptr ble_peripheral, + ConstPtr advertisement_data) { + // Extract the fast advertisement bytes, if any. + ScopedPtr> fast_advertisement_bytes( + extractFastAdvertisementBytes(advertisement_data)); + if (fast_advertisement_bytes.isNull()) { + return; + } + + // Create a header tied to this fast advertisement. This helps us track the + // advertisement when reporting it as lost or connecting. + /* RefCounted */ ConstPtr fast_advertisement_header = + createFastAdvertisementHeader(fast_advertisement_bytes.get()); + + // Process the fast advertisement like we would a GATT advertisement and + // insert a placeholder AdvertisementReadResult. + dpt::eraseOwnedPtrFromMap(advertisement_read_results_, + fast_advertisement_header); + advertisement_read_results_.insert( + std::make_pair(fast_advertisement_header, + MakePtr(new AdvertisementReadResult()))); + + std::set> fast_advertisement_bytes_set; + fast_advertisement_bytes_set.insert(fast_advertisement_bytes.get()); + handleRawGattAdvertisements(fast_advertisement_header, + fast_advertisement_bytes_set, + /* are_fast_advertisements= */ true); + updateCommonStateForFoundBleAdvertisement(fast_advertisement_header, + ble_peripheral->getId()); +} + +template +void DiscoveredPeripheralTracker::handleAdvertisementHeader( + Ptr ble_peripheral, + ConstPtr advertisement_data, + Ptr gatt_advertisement_fetcher) { + // Attempt to parse the advertisement header. + /* RefCounted */ ConstPtr advertisement_header = + BLEAdvertisementHeader::fromString( + extractAdvertisementHeaderBytes(ble_peripheral, advertisement_data)); + if (advertisement_header.isNull()) { + // TODO(ahlee) logger.atVerbose().log("Failed to deserialize BLE + // advertisement header %s. Ignoring.", + // bytesToString(advertisementHeaderBytes)); + return; + } + + // Check if the advertisement header contains a service ID we're tracking. + if (!isInterestingAdvertisementHeader(advertisement_header)) { + // TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header %s + // because it does not contain any service IDs we're interested in.", + // advertisementHeader); + return; + } + + // Determine whether or not we need to read a fresh GATT advertisement. + if (shouldReadFromAdvertisementGattServer(advertisement_header)) { + // Determine whether or not we need to read a fresh GATT advertisement. + std::set> raw_gatt_advertisements = + fetchRawGattAdvertisements(ble_peripheral, advertisement_header, + gatt_advertisement_fetcher); + if (!raw_gatt_advertisements.empty()) { + handleRawGattAdvertisements(advertisement_header, raw_gatt_advertisements, + /* are_fast_advertisements= */ false); + } + } + + // Regardless of whether or not we read a new GATT advertisement, the maps + // should now be up-to-date. With this information, do some general + // housekeeping. + updateCommonStateForFoundBleAdvertisement( + advertisement_header, /* mac_address= */ ble_peripheral->getId()); +} + +template +string DiscoveredPeripheralTracker::extractAdvertisementHeaderBytes( + Ptr ble_peripheral, + ConstPtr advertisement_data) { + ConstPtr service_data; + std::map>::const_iterator sd_it = + advertisement_data->service_data.find(kCopresenceServiceUuid); + if (sd_it != advertisement_data->service_data.end()) { + service_data = sd_it->second; + } + const string& local_name = advertisement_data->local_name; // alias + + // A valid advertisement header lives in either the local name (iOS) or the + // service data (Android). + if (!service_data.isNull()) { + // TODO(ahlee) logger.atVerbose().log("Service data found on possible + // Android BLE peripheral at address %s", + // bleSighting.getDevice().getAddress()); + return string(service_data->getData(), service_data->size()); + } else if (!local_name.empty()) { + // TODO(ahlee) logger.atVerbose().log("Local name found on possible iOS BLE + // peripheral at address %s", bleSighting.getDevice().getAddress()); + return local_name; + } else { + // iOS peripherals have a bug where the local name sometimes doesn't appear. + // In that case, we should still take a look at the advertisement in case + // there's something valuable on the peripheral's GATT server. + + // TODO(ahlee) logger.atVerbose().log("BLE advertisement found with no + // service data or local name from BLE peripheral at address %s (could be a + // buggy iOS peripheral with a missing local name).", + // bleSighting.getDevice().getAddress()); + + // Create a phony BloomFilter that always contains the service ID we're + // looking for. + return createDummyAdvertisementHeaderBytes(ble_peripheral); + } +} + +template +ConstPtr +DiscoveredPeripheralTracker::extractFastAdvertisementBytes( + ConstPtr advertisement_data) { + ConstPtr fast_advertisement_bytes; + // Iterate through all tracked service IDs to see if any of their fast + // advertisements are contained within this BLE advertisement. + std::set tracked_service_ids = getTrackedServiceIds(); + for (typename std::set::iterator tsi_it = tracked_service_ids.begin(); + tsi_it != tracked_service_ids.end(); ++tsi_it) { + // First, check if a service UUID is tied to this service ID. + typename FastAdvertisementServiceUUIDMap::iterator fasu_it = + fast_advertisement_service_uuids_.find(*tsi_it); + if (fasu_it != fast_advertisement_service_uuids_.end()) { + const string& fast_advertisement_service_uuid = fasu_it->second; // alias + + // Then, check if there's service data for this fast advertisement + // service UUID. If so, we can short-circuit since all BLE + // advertisements can contain at most ONE fast advertisement. + typename std::map>::const_iterator sd_it = + advertisement_data->service_data.find( + fast_advertisement_service_uuid); + if (sd_it != advertisement_data->service_data.end()) { + // TODO(b/117432693): Remove this copy once Ptr is fully RefCounted. + fast_advertisement_bytes = MakeConstPtr( + new ByteArray(sd_it->second->getData(), sd_it->second->size())); + break; + } + } + } + return fast_advertisement_bytes; +} + +// Creates an advertisement header that's purely a hash of the fast +// advertisement, since they come with no header. +template +/* RefCounted */ ConstPtr +DiscoveredPeripheralTracker::createFastAdvertisementHeader( + ConstPtr fast_advertisement_bytes) { + // Our end goal is to have a fully zeroed-out byte array of the correct length + // representing an empty bloom filter. + // TODO(b/149938110): remove ScopedPtr. + ScopedPtr> bloom_filter_bytes{ConstPtr{ + new ByteArray{BLEAdvertisementHeader::kServiceIdBloomFilterLength}}}; + + ScopedPtr> advertisement_hash( + generateAdvertisementHash(fast_advertisement_bytes)); + return MakeRefCountedConstPtr(new BLEAdvertisementHeader( + BLEAdvertisementHeader::Version::V2, /* num_slots= */ 1, + bloom_filter_bytes.get(), advertisement_hash.get())); +} + +// Creates a dummy advertisement header that possibly contains all tracked +// service IDs. +template +string +DiscoveredPeripheralTracker::createDummyAdvertisementHeaderBytes( + Ptr ble_peripheral) { + // Put the service ID along with the dummy service ID into our bloom filter + // Note: BloomFilter length should always match + // BLEAdvertisementHeader::kServiceIdBloomFilterLength + ScopedPtr>> bloom_filter(new BloomFilter<10>()); + + std::set tracked_service_ids = getTrackedServiceIds(); + for (typename std::set::iterator tsi_it = tracked_service_ids.begin(); + tsi_it != tracked_service_ids.end(); ++tsi_it) { + bloom_filter->add(*tsi_it); + } + + const string& ble_peripheral_id = ble_peripheral->getId(); // alias + ScopedPtr> ble_peripheral_id_bytes(MakeConstPtr( + new ByteArray(ble_peripheral_id.data(), ble_peripheral_id.size()))); + ScopedPtr> advertisement_hash( + generateAdvertisementHash(ble_peripheral_id_bytes.get())); + return BLEAdvertisementHeader::asString(BLEAdvertisementHeader::Version::V2, + kMaxSlots, bloom_filter->asBytes(), + advertisement_hash.get()); +} + +template +bool DiscoveredPeripheralTracker::isInterestingAdvertisementHeader( + /* RefCounted */ ConstPtr advertisement_header) { + ScopedPtr>> bloom_filter( + new BloomFilter<10>(advertisement_header->getServiceIdBloomFilter())); + std::set tracked_service_ids = getTrackedServiceIds(); + for (typename std::set::iterator tsi_it = tracked_service_ids.begin(); + tsi_it != tracked_service_ids.end(); ++tsi_it) { + if (bloom_filter->possiblyContains(*tsi_it)) { + return true; + } + } + return false; +} + +template +bool DiscoveredPeripheralTracker:: + shouldReadFromAdvertisementGattServer( + /* RefCounted */ ConstPtr + advertisement_header) { + // Check if we have never seen this header. New headers should always be read. + typename AdvertisementReadResultMap::iterator arr_it = + advertisement_read_results_.find(advertisement_header); + if (arr_it == advertisement_read_results_.end()) { + // TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but + // we have never seen it before. Will try reading its GATT advertisement.", + // advertisementHeader); + return true; + } + + // Extract the last read result for this particular header. + Ptr> advertisement_read_result = + arr_it->second; // alias + + // Now evaluate if we should retry reading. + switch (advertisement_read_result->evaluateRetryStatus()) { + case AdvertisementReadResult::RetryStatus::RETRY: + // TODO(ahlee) logger.atDebug().log("Received advertisement header %s. + // Will retry reading its GATT advertisement.", advertisementHeader); + return true; + case AdvertisementReadResult::RetryStatus::PREVIOUSLY_SUCCEEDED: + // TODO(ahlee) logger.atVerbose().log("Received advertisement header %s, + // but we have already read its GATT advertisement.", + // advertisementHeader); + return false; + case AdvertisementReadResult::RetryStatus::TOO_SOON: + // TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but + // we have recently failed to read its GATT advertisement.", + // advertisementHeader); + return false; + case AdvertisementReadResult::RetryStatus::UNKNOWN: + // Fall through. + break; + } + + // TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but we + // do not know whether or not to retry reading its GATT advertisement. Will + // retry to be safe.", advertisementHeader); + return true; +} + +template +std::set> +DiscoveredPeripheralTracker::fetchRawGattAdvertisements( + Ptr ble_peripheral, + /* RefCounted */ ConstPtr advertisement_header, + Ptr gatt_advertisement_fetcher) { + Ptr> old_advertisement_read_result; + typename AdvertisementReadResultMap::iterator arr_it = + advertisement_read_results_.find(advertisement_header); + if (arr_it != advertisement_read_results_.end()) { + old_advertisement_read_result = arr_it->second; // alias + } + + /* RefCounted */ Ptr> + advertisement_read_result = + gatt_advertisement_fetcher->fetchGattAdvertisements( + ble_peripheral, advertisement_header->getNumSlots(), + old_advertisement_read_result); + + dpt::eraseOwnedPtrFromMap(advertisement_read_results_, advertisement_header); + arr_it = advertisement_read_results_ + .insert(std::make_pair(advertisement_header, + advertisement_read_result)) + .first; + + return arr_it->second->getAdvertisements(); +} + +template +void DiscoveredPeripheralTracker::handleRawGattAdvertisements( + /* RefCounted */ ConstPtr advertisement_header, + const std::set>& raw_gatt_advertisements, + bool are_fast_advertisements) { + typedef std::map> BLEAdvertisementMap; + // Parse the raw GATT advertisements. The output of this method is a mapping + // of service ID -> GATT advertisement. + BLEAdvertisementMap parsed_gatt_advertisements = + parseRawGattAdvertisements(raw_gatt_advertisements); + ScopedPtr> parsed_gatt_advertisement_values( + new BLEAdvertisementSet()); + + // Update state for each GATT advertisement. + for (BLEAdvertisementMap::iterator pga_it = + parsed_gatt_advertisements.begin(); + pga_it != parsed_gatt_advertisements.end(); ++pga_it) { + const string& service_id = pga_it->first; // alias + ConstPtr gatt_advertisement = pga_it->second; // alias + parsed_gatt_advertisement_values->insert(gatt_advertisement); + + // TODO(ahlee): Update the java code to create old_advertisement_header + // within the if/else block. + AdvertisementHeaderMap::iterator ah_it = + advertisement_headers_.find(gatt_advertisement); + if (ah_it == advertisement_headers_.end()) { + discovered_peripheral_callbacks_.find(service_id) + ->second->onPeripheralDiscovered( + generateBlePeripheral(gatt_advertisement), service_id, + gatt_advertisement->getData(), are_fast_advertisements); + } else { + ConstPtr old_advertisement_header = + ah_it->second; // alias + dpt::eraseOwnedPtrFromMap(advertisement_read_results_, + old_advertisement_header); + dpt::eraseOwnedPtrFromMap(gatt_advertisements_, old_advertisement_header); + } + + dpt::eraseOwnedPtrFromMap(advertisement_headers_, gatt_advertisement); + advertisement_headers_.insert( + std::make_pair(gatt_advertisement, advertisement_header)); + + advertisement_service_ids_.erase(gatt_advertisement); + advertisement_service_ids_.insert( + std::make_pair(gatt_advertisement, service_id)); + } + + // Insert the list of read GATT advertisements for this advertisement header. + dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header); + gatt_advertisements_.insert(std::make_pair( + advertisement_header, parsed_gatt_advertisement_values.release())); +} + +// Returns a map of service IDs to GATT advertisements who belong to a tracked +// service ID. +template +std::map> +DiscoveredPeripheralTracker::parseRawGattAdvertisements( + const std::set>& raw_gatt_advertisements) { + std::set tracked_service_ids = getTrackedServiceIds(); + typedef std::map> BLEAdvertisementMap; + BLEAdvertisementMap parsed_gatt_advertisements; + for (std::set>::iterator rga_it = + raw_gatt_advertisements.begin(); + rga_it != raw_gatt_advertisements.end(); ++rga_it) { + /* RefCounted */ ConstPtr gatt_advertisement = + BLEAdvertisement::fromBytes(*rga_it); + if (gatt_advertisement.isNull()) { + // logger.atDebug().log("Unable to parse raw GATT advertisement %s", + // *rga_it); + continue; + } + + // Make sure the advertisement belongs to a service ID we're tracking. + for (typename std::set::iterator tsi_it = + tracked_service_ids.begin(); + tsi_it != tracked_service_ids.end(); ++tsi_it) { + // If we already found a higher version advertisement for this service ID, + // there's no point in comparing this advertisement against it. + BLEAdvertisementMap::iterator pga_it = + parsed_gatt_advertisements.find(*tsi_it); + if (pga_it != parsed_gatt_advertisements.end()) { + if (pga_it->second->getVersion() > gatt_advertisement->getVersion()) { + continue; + } + } + + // Map the service ID to the advertisement if the service ID hashes match. + ScopedPtr> service_id_hash( + generateServiceIdHash(gatt_advertisement->getVersion(), *tsi_it)); + if (*service_id_hash == *(gatt_advertisement->getServiceIdHash())) { + // logger.atDebug().log("Matched service ID %s to GATT advertisement + // %s.", serviceId, gattAdvertisement); + parsed_gatt_advertisements.insert( + std::make_pair(*tsi_it, gatt_advertisement)); + break; + } + } + } + + return parsed_gatt_advertisements; +} + +template +void DiscoveredPeripheralTracker:: + updateCommonStateForFoundBleAdvertisement( + /* RefCounted */ ConstPtr advertisement_header, + const string& mac_address) { + typename GattAdvertisementMap::iterator ga_it = + gatt_advertisements_.find(advertisement_header); + if (ga_it == gatt_advertisements_.end()) { + // logger.atDebug().log("No GATT advertisements found for advertisement + // header %s.", advertisementHeader); + return; + } + + Ptr saved_gatt_advertisements = ga_it->second; // alias + for (BLEAdvertisementSet::iterator sga_it = + saved_gatt_advertisements->begin(); + sga_it != saved_gatt_advertisements->end(); ++sga_it) { + ConstPtr gatt_advertisement = *sga_it; // alias + + AdvertisementServiceIdMap::iterator asi_it = + advertisement_service_ids_.find(gatt_advertisement); + if (asi_it == advertisement_service_ids_.end()) { + continue; + } + const string& service_id = asi_it->second; // alias + + // Make sure the stored GATT advertisement is still being tracked. + std::set tracked_service_ids = getTrackedServiceIds(); + if (tracked_service_ids.find(service_id) == tracked_service_ids.end()) { + continue; + } + + // The iterator returned from find() is guaranteed to be valid because it's + // tied to discovered_peripheral_callbacks_, whose keyset is checked through + // getTrackedServiceIds() above. + lost_entity_trackers_.find(service_id) + ->second->recordFoundEntity(gatt_advertisement); + + // The iterator returned from find() is guaranteed to be valid because it's + // tied to advertisement_service_ids_ which is checked at the beginning of + // the for loop. + mac_addresses_.erase(gatt_advertisement); + mac_addresses_.insert(std::make_pair(gatt_advertisement, mac_address)); + } +} + +template +ConstPtr +DiscoveredPeripheralTracker::generateAdvertisementHash( + ConstPtr advertisement_bytes) { + return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes, + BLEAdvertisementHeader::kAdvertisementHashLength); +} + +template +ConstPtr +DiscoveredPeripheralTracker::generateServiceIdHash( + BLEAdvertisement::Version::Value version, const string& service_id) { + ScopedPtr> service_id_bytes( + MakeConstPtr(new ByteArray(service_id.data(), service_id.size()))); + switch (version) { + case BLEAdvertisement::Version::V1: + return Utils::legacySha256HashOnlyForPrinting( + hash_utils_.get(), service_id_bytes.get(), + BLEPacket::kServiceIdHashLength); + case BLEAdvertisement::Version::V2: + // Fall through. + case BLEAdvertisement::Version::UNKNOWN: + // Fall through. + default: + // Use the latest known hashing scheme. + return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(), + BLEPacket::kServiceIdHashLength); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/discovered_peripheral_tracker.h b/cpp/core/internal/mediums/discovered_peripheral_tracker.h new file mode 100644 index 00000000..7c23a3d8 --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.h @@ -0,0 +1,218 @@ +#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_ +#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_ + +#include +#include +#include + +#include "core/internal/mediums/advertisement_read_result.h" +#include "core/internal/mediums/ble_advertisement.h" +#include "core/internal/mediums/ble_advertisement_header.h" +#include "core/internal/mediums/ble_peripheral.h" +#include "core/internal/mediums/discovered_peripheral_callback.h" +#include "core/internal/mediums/lost_entity_tracker.h" +#include "platform/api/ble_v2.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Manages all discovered peripheral logic for {@link BluetoothLowEnergy}. This +// includes tracking found peripherals, lost peripherals, and MAC addresses +// associated with those peripherals. +// +// See go/ble-on-lost for more information. It includes the algorithms used to +// compute found and lost peripherals. +template +class DiscoveredPeripheralTracker { + public: + DiscoveredPeripheralTracker(); + ~DiscoveredPeripheralTracker(); + + void startTracking( + const string& service_id, + Ptr discovered_peripheral_callback, + const string& fast_advertisement_service_uuid); + void stopTracking(const string& service_id); + + // GATT advertisement fetcher. + class GattAdvertisementFetcher { + public: + virtual ~GattAdvertisementFetcher() {} + + // Fetches relevant GATT advertisements for the peripheral found in {@link + // DiscoveredPeripheralTracker#processFoundBleAdvertisement(BleSighting, + // GattAdvertisementFetcher)}. + virtual Ptr> fetchGattAdvertisements( + Ptr ble_peripheral, std::int32_t num_slots, + Ptr> advertisement_read_result) = 0; + }; + void processFoundBleAdvertisement( + Ptr ble_peripheral, + ConstPtr advertisement_data, + Ptr gatt_advertisement_fetcher); + void processLostGattAdvertisements(); + + // TODO(ahlee): Add connecting logic. + + private: + static Ptr generateBlePeripheral( + ConstPtr gatt_advertisement); + + static const std::int32_t kMaxSlots; + static const std::int64_t kMinConnectionDelayMillis; + static const char* kCopresenceServiceUuid; + + std::set getTrackedServiceIds(); + void clearDataForServiceId(const string& service_id); + void clearGattAdvertisement(ConstPtr gatt_advertisement); + void handleFastAdvertisement( + Ptr ble_peripheral, + ConstPtr advertisement_data); + void handleAdvertisementHeader( + Ptr ble_peripheral, + ConstPtr advertisement_data, + Ptr gatt_advertisement_fetcher); + string extractAdvertisementHeaderBytes( + Ptr ble_peripheral, + ConstPtr advertisement_data); + ConstPtr extractFastAdvertisementBytes( + ConstPtr advertisement_data); + /*RefCounted */ ConstPtr + createFastAdvertisementHeader(ConstPtr fast_advertisement_bytes); + string createDummyAdvertisementHeaderBytes( + Ptr ble_peripheral); + bool isInterestingAdvertisementHeader( + /* RefCounted */ ConstPtr advertisement_header); + bool shouldReadFromAdvertisementGattServer( + /* RefCounted */ ConstPtr advertisement_header); + std::set> fetchRawGattAdvertisements( + Ptr ble_peripheral, + /* RefCounted */ ConstPtr advertisement_header, + Ptr gatt_advertisement_fetcher); + void handleRawGattAdvertisements( + /* RefCounted */ ConstPtr advertisement_header, + const std::set>& raw_gatt_advertisements, + bool are_fast_advertisements); + std::map> parseRawGattAdvertisements( + const std::set>& raw_gatt_advertisements); + void updateCommonStateForFoundBleAdvertisement( + /* RefCounted */ ConstPtr advertisement_header, + const string& mac_address); + + // TODO(ahlee): Add in connecting logic. + + // TODO(ahlee): Move these out to utils (also used by BLE V2). + ConstPtr generateAdvertisementHash( + ConstPtr advertisement_bytes); + ConstPtr generateServiceIdHash( + BLEAdvertisement::Version::Value version, const string& service_id); + + // ------------ GENERAL ------------ + ScopedPtr> lock_; + ScopedPtr> thread_utils_; + ScopedPtr> system_clock_; + ScopedPtr> hash_utils_; + + // ------------ SERVICE ID MAPS ------------ + // Entries in these maps all follow the same lifecycle. Entries are added in + // startTracking, and removed in stopTracking. + + // Maps service IDs to DiscoveredPeripheralCallbacks. Tracks what service IDs + // are currently active and gives us client callbacks to call. + typedef std::map> + DiscoveredPeripheralCallbackMap; + DiscoveredPeripheralCallbackMap discovered_peripheral_callbacks_; + + // Maps service IDs to LostEntityTrackers. Used to periodically compute lost + // GATT advertisements, grouped by service ID. + typedef std::map>> + LostEntityTrackerMap; + LostEntityTrackerMap lost_entity_trackers_; + + // Maps service IDs to BLE service UUIDs. Used to check for fast + // advertisements delivered through BLE advertisement service data, under the + // given UUID. + // UUIDs are represented as strings in this map because they are coming from + // AdvertisingOptions and our UUID class is an internal concept that we don't + // want to expose to clients. + typedef std::map FastAdvertisementServiceUUIDMap; + FastAdvertisementServiceUUIDMap fast_advertisement_service_uuids_; + + // ------------ ADVERTISEMENT HEADER MAPS ------------ + + // Maps advertisement headers to AdvertisementReadResults. Tells us when to + // retry reading a GATT advertisement. If no entry exists for a particular + // header, we should try reading a GATT advertisement. Entries are added + // whenever a GATT advertisement read is attempted, and removed when GATT + // advertisements are lost. Entries are also removed whenever + // gattAdvertisements removes its entry. + // + // The map is also cleared whenever startTracking is called, due to client + // changes. For example, say clients A and B start scanning and discover + // advertisements A and B (for both clients) on advertisement header 1. Then, + // A restarts scanning, causing us to clear stale advertisement A. However, + // since B was still scanning, we don't remove advertisement header 1 from the + // map. This causes us to never re-read advertisement A. + typedef std::map, + Ptr>> + AdvertisementReadResultMap; + AdvertisementReadResultMap advertisement_read_results_; + + // Maps advertisement headers to a set of GATT advertisements from a single + // peripheral. Used to retrieve GATT advertisements that we need to reprocess + // every time a header is seen. Entries are added when GATT advertisements are + // read, removed when all associated GATT advertisements are lost or become + // stale, and replaced when the advertisement header is updated for a single + // remote peripheral. + typedef std::set> + BLEAdvertisementSet; + typedef std::map, + Ptr> + GattAdvertisementMap; + GattAdvertisementMap gatt_advertisements_; + + // ------------ GATT ADVERTISEMENT MAPS ------------ + // Entries in these maps all follow the same lifecycle. Entries are added when + // GATT advertisements are read, and removed when GATT advertisements are lost + // or become stale. + + // Maps GATT advertisements to the service ID it's associated with. Tracks + // what GATT advertisements are currently active. Used to determine which + // LostEntityTracker to invoke when advertisements are rediscovered. + typedef std::map, string> + AdvertisementServiceIdMap; + AdvertisementServiceIdMap advertisement_service_ids_; + + // Maps GATT advertisements to advertisement headers. Used to efficiently find + // advertisement headers to delete when GATT advertisements are updated. This + // is a reverse map of gatt_advertisements_. + typedef std::map, + /* RefCounted */ ConstPtr> + AdvertisementHeaderMap; + AdvertisementHeaderMap advertisement_headers_; + + // Maps GATT advertisements to MAC addresses. Used when we need to make a + // socket connection based off of the GATT advertisement alone. Entries are + // modified every time a GATT advertisement's advertisement header is seen. + typedef std::map, string> + MacAddressMap; + MacAddressMap mac_addresses_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/discovered_peripheral_tracker.cc" + +#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_ diff --git a/cpp/core/internal/mediums/lost_entity_tracker.cc b/cpp/core/internal/mediums/lost_entity_tracker.cc new file mode 100644 index 00000000..0122cb57 --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker.cc @@ -0,0 +1,56 @@ +#include "core/internal/mediums/lost_entity_tracker.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +template +LostEntityTracker::LostEntityTracker() + : lock_(Platform::createLock()), + current_entities_(), + previously_found_entities_() {} + +template +LostEntityTracker::~LostEntityTracker() { + previously_found_entities_.clear(); + current_entities_.clear(); +} + +template +void LostEntityTracker::recordFoundEntity( + ConstPtr entity) { + Synchronized s(lock_.get()); + + current_entities_.insert(entity); +} + +template +typename LostEntityTracker::EntitySet +LostEntityTracker::computeLostEntities() { + Synchronized s(lock_.get()); + + // The set of lost entities is the previously found set MINUS the currently + // found set. + for (typename EntitySet::iterator it = current_entities_.begin(); + it != current_entities_.end(); ++it) { + previously_found_entities_.erase(*it); + } + EntitySet lost_entities(previously_found_entities_.begin(), + previously_found_entities_.end()); + + // Update our previous and current sets. + previously_found_entities_.clear(); + previously_found_entities_.insert(current_entities_.begin(), + current_entities_.end()); + current_entities_.clear(); + + return lost_entities; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/lost_entity_tracker.h b/cpp/core/internal/mediums/lost_entity_tracker.h new file mode 100644 index 00000000..ad4fb7ae --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker.h @@ -0,0 +1,49 @@ +#ifndef CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ +#define CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ + +#include + +#include "platform/api/lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Tracks "lost" entities based on a manual update/compute model. Used by +// mediums that only report found devices. Lost entities are computed based off +// of whether a specific entity was rediscovered since the last call to +// computeLostEntities. +// +// Note: Entity must overload the < and == operators. +template +class LostEntityTracker { + public: + typedef std::set > EntitySet; + + LostEntityTracker(); + ~LostEntityTracker(); + + // Records the given entity as being recently found, whether or not this is + // our first time discovering the entity. + void recordFoundEntity(ConstPtr entity); + + // Computes and returns the set of entities considered lost since the last + // time this method was called. + EntitySet computeLostEntities(); + + private: + ScopedPtr > lock_; + EntitySet current_entities_; + EntitySet previously_found_entities_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/lost_entity_tracker.cc" + +#endif // CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ diff --git a/cpp/core/internal/mediums/lost_entity_tracker_test.cc b/cpp/core/internal/mediums/lost_entity_tracker_test.cc new file mode 100644 index 00000000..ce37d6e0 --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -0,0 +1,121 @@ +#include "core/internal/mediums/lost_entity_tracker.h" + +#include "platform/impl/default/default_platform.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +struct TestEntity { + int id; + + explicit TestEntity(int givenId) : id(givenId) {} + + bool operator<(const TestEntity &other) const { return id < other.id; } +}; + +TEST(LostEntityTracker, NoEntitiesLost) { + LostEntityTracker lost_entity_tracker; + ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); + ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); + ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); + + // Discover some entities. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + lost_entity_tracker.recordFoundEntity(entity_2.get()); + lost_entity_tracker.recordFoundEntity(entity_3.get()); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); + + // Rediscover the same entities. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + lost_entity_tracker.recordFoundEntity(entity_2.get()); + lost_entity_tracker.recordFoundEntity(entity_3.get()); + + // Make sure we still didn't lose any entities. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); +} + +TEST(LostEntityTracker, AllEntitiesLost) { + LostEntityTracker lost_entity_tracker; + ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); + ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); + ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); + + // Discover some entities. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + lost_entity_tracker.recordFoundEntity(entity_2.get()); + lost_entity_tracker.recordFoundEntity(entity_3.get()); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); + + // Go through a round without rediscovering any entities. + typename LostEntityTracker::EntitySet + lost_entities = lost_entity_tracker.computeLostEntities(); + ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); + ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); + ASSERT_TRUE(lost_entities.find(entity_3.get()) != lost_entities.end()); +} + +TEST(LostEntityTracker, SomeEntitiesLost) { + LostEntityTracker lost_entity_tracker; + ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); + ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); + ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); + + // Discover some entities. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + lost_entity_tracker.recordFoundEntity(entity_2.get()); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); + + // Go through the next round only rediscovering one of our entities and + // discovering an additional entity as well. Then, verify that only one entity + // was lost after the check. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + lost_entity_tracker.recordFoundEntity(entity_3.get()); + typename LostEntityTracker::EntitySet + lost_entities = lost_entity_tracker.computeLostEntities(); + ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end()); + ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); + ASSERT_TRUE(lost_entities.find(entity_3.get()) == lost_entities.end()); +} + +TEST(LostEntityTracker, SameEntityMultipleCopies) { + LostEntityTracker lost_entity_tracker; + ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); + ScopedPtr > entity_1_copy( + MakeConstPtr(new TestEntity(1))); + + // Discover an entity. + lost_entity_tracker.recordFoundEntity(entity_1.get()); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); + + // Rediscover the same entity, but through a copy of it. + lost_entity_tracker.recordFoundEntity(entity_1_copy.get()); + + // Make sure none are lost on the second round. + ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); + + // Go through a round without rediscovering any entities and verify that we + // lost an entity equivalent to both copies of it. + typename LostEntityTracker::EntitySet + lost_entities = lost_entity_tracker.computeLostEntities(); + ASSERT_EQ(lost_entities.size(), 1); + ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); + ASSERT_TRUE(lost_entities.find(entity_1_copy.get()) != lost_entities.end()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/mediums.cc b/cpp/core/internal/mediums/mediums.cc new file mode 100644 index 00000000..22638499 --- /dev/null +++ b/cpp/core/internal/mediums/mediums.cc @@ -0,0 +1,42 @@ +#include "core/internal/mediums/mediums.h" + +namespace location { +namespace nearby { +namespace connections { + +template +Mediums::Mediums() + : bluetooth_radio_(new BluetoothRadio()), + bluetooth_classic_( + new BluetoothClassic(bluetooth_radio_.get())), + ble_(new BLE(bluetooth_radio_.get())), + ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())) {} + +template +Mediums::~Mediums() { + // Nothing to do. +} + +template +Ptr > Mediums::bluetoothRadio() const { + return bluetooth_radio_.get(); +} + +template +Ptr > Mediums::bluetoothClassic() const { + return bluetooth_classic_.get(); +} + +template +Ptr > Mediums::ble() const { + return ble_.get(); +} + +template +Ptr > Mediums::bleV2() const { + return ble_v2_.get(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/mediums.h b/cpp/core/internal/mediums/mediums.h new file mode 100644 index 00000000..f6d57d75 --- /dev/null +++ b/cpp/core/internal/mediums/mediums.h @@ -0,0 +1,52 @@ +#ifndef CORE_INTERNAL_MEDIUMS_MEDIUMS_H_ +#define CORE_INTERNAL_MEDIUMS_MEDIUMS_H_ + +#include "core/internal/mediums/ble.h" +#include "core/internal/mediums/ble_v2.h" +#include "core/internal/mediums/bluetooth_classic.h" +#include "core/internal/mediums/bluetooth_radio.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Facilitates convenient and reliable usage of various wireless mediums. +template +class Mediums { + public: + Mediums(); + // Reverts all the mediums to their original state. + ~Mediums(); + + // Returns a handle to the Bluetooth radio. + Ptr > bluetoothRadio() const; + // Returns a handle to the Bluetooth Classic medium. + Ptr > bluetoothClassic() const; + // Returns a handle to the Bluetooth Low Energy (BLE) medium. + Ptr > ble() const; + // Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium. + Ptr > bleV2() const; + + private: + // The order of declaration is critical for both construction and + // destruction. + // + // 1) Construction: The individual mediums have a dependency on the + // corresponding radio, so the radio must be initialized first. + // + // 2) Destruction: The individual mediums should be shut down before the + // corresponding radio. + ScopedPtr > > bluetooth_radio_; + ScopedPtr > > bluetooth_classic_; + ScopedPtr > > ble_; + ScopedPtr > > ble_v2_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/mediums.cc" + +#endif // CORE_INTERNAL_MEDIUMS_MEDIUMS_H_ diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc new file mode 100644 index 00000000..125359c7 --- /dev/null +++ b/cpp/core/internal/mediums/utils.cc @@ -0,0 +1,73 @@ +#include "core/internal/mediums/utils.h" + +#include + +#include "platform/exception.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { + +void Utils::closeSocket(Ptr socket, + const std::string& type, const std::string& name) { + if (!socket.isNull()) { + Exception::Value e = socket->close(); + if (Exception::NONE != e) { + if (Exception::IO == e) { + // TODO(reznor): log.atWarning().withCause(e).log("Failed to close + // %sSocket %s", type, name); + } + return; + } + // TODO(reznor): log.atVerbose().log("Closed %sSocket %s", type, name); + } +} + +ConstPtr Utils::sha256Hash(Ptr hash_utils, + ConstPtr source, + size_t length) { + if (source.isNull()) { + return ConstPtr(); + } + + ScopedPtr> full_hash( + hash_utils->sha256(std::string(source->getData(), source->size()))); + return MakeConstPtr(new ByteArray(full_hash->getData(), length)); +} + +ConstPtr Utils::legacySha256HashOnlyForPrinting( + Ptr hash_utils, ConstPtr source, size_t length) { + if (source.isNull()) { + return ConstPtr(); + } + + std::string formatted_hex_string = Utils::bytesToPrintableHexString(source); + ScopedPtr> formatted_hex_byte_array(MakeConstPtr( + new ByteArray(formatted_hex_string.data(), formatted_hex_string.size()))); + return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length); +} + +std::string Utils::bytesToPrintableHexString(ConstPtr bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); + + // Print out the byte array as a space separated listing of hex bytes. + std::ostringstream formatted_hex_string_stream; + formatted_hex_string_stream << "[ "; + for (int i = 0; i < hex_string.size(); i += 2) { + formatted_hex_string_stream << "0x"; + // This is safe because we have the guarantee that hex_string is of even + // length (because a hex encoding will always be double the size of its + // input). + formatted_hex_string_stream << hex_string[i] << hex_string[i + 1]; + formatted_hex_string_stream << " "; + } + formatted_hex_string_stream << "]"; + + return formatted_hex_string_stream.str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/utils.h b/cpp/core/internal/mediums/utils.h new file mode 100644 index 00000000..665716a9 --- /dev/null +++ b/cpp/core/internal/mediums/utils.h @@ -0,0 +1,32 @@ +#ifndef CORE_INTERNAL_MEDIUMS_UTILS_H_ +#define CORE_INTERNAL_MEDIUMS_UTILS_H_ + +#include "platform/api/bluetooth_classic.h" +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +class Utils { + public: + static void closeSocket(Ptr socket, + const std::string& type, const std::string& name); + static ConstPtr sha256Hash(Ptr hash_utils, + ConstPtr source, + size_t length); + static ConstPtr legacySha256HashOnlyForPrinting( + Ptr hash_utils, ConstPtr source, size_t length); + + private: + static std::string bytesToPrintableHexString(ConstPtr bytes); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_UTILS_H_ diff --git a/cpp/core/internal/mediums/uuid.cc b/cpp/core/internal/mediums/uuid.cc new file mode 100644 index 00000000..df6bed62 --- /dev/null +++ b/cpp/core/internal/mediums/uuid.cc @@ -0,0 +1,100 @@ +#include "core/internal/mediums/uuid.h" + +#include +#include + +#include "platform/api/hash_utils.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +template +UUID::UUID(const string& data) { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162. + ScopedPtr > scoped_hash_utils(Platform::createHashUtils()); + ScopedPtr > scoped_md5_bytes( + scoped_hash_utils->md5(data)); + data_.assign(scoped_md5_bytes->getData(), scoped_md5_bytes->size()); + + data_[6] &= 0x0f; // Clear version. + data_[6] |= 0x30; // Set to version 3. + data_[8] &= 0x3f; // Clear variant. + data_[8] |= 0x80; // Set to IETF variant. +} + +template +UUID::UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits) { + // Base on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104. + data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits)); + + data_[0] = static_cast((most_sig_bits >> 56) & 0x0ff); + data_[1] = static_cast((most_sig_bits >> 48) & 0x0ff); + data_[2] = static_cast((most_sig_bits >> 40) & 0x0ff); + data_[3] = static_cast((most_sig_bits >> 32) & 0x0ff); + data_[4] = static_cast((most_sig_bits >> 24) & 0x0ff); + data_[5] = static_cast((most_sig_bits >> 16) & 0x0ff); + data_[6] = static_cast((most_sig_bits >> 8) & 0x0ff); + data_[7] = static_cast((most_sig_bits >> 0) & 0x0ff); + + data_[8] = static_cast((least_sig_bits >> 56) & 0x0ff); + data_[9] = static_cast((least_sig_bits >> 48) & 0x0ff); + data_[10] = static_cast((least_sig_bits >> 40) & 0x0ff); + data_[11] = static_cast((least_sig_bits >> 32) & 0x0ff); + data_[12] = static_cast((least_sig_bits >> 24) & 0x0ff); + data_[13] = static_cast((least_sig_bits >> 16) & 0x0ff); + data_[14] = static_cast((least_sig_bits >> 8) & 0x0ff); + data_[15] = static_cast((least_sig_bits >> 0) & 0x0ff); +} + +template +UUID::~UUID() {} + +template +string UUID::str() { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375. + + // The masking with 0x0ff is essential because we're taking 8-bit bytes and + // casting them to integers (which, depending on the platform, are 16- or + // 32-bits wide); without that, we get a leading FF (16-bit) or FFFFFF + // (32-bit) when the MSB of the 8-bit byte is 1. + // + // And the cast to an integer is required because std::hex only takes effect + // on integral types (and no, uint8_t doesn't activate it). +#define BYTE_TO_HEX(b) \ + std::setfill('0') << std::setw(2) << std::hex \ + << (static_cast(b) & 0x0ff) + + std::ostringstream md5_hex; + + md5_hex << BYTE_TO_HEX(data_[0]); + md5_hex << BYTE_TO_HEX(data_[1]); + md5_hex << BYTE_TO_HEX(data_[2]); + md5_hex << BYTE_TO_HEX(data_[3]); + md5_hex << "-"; + md5_hex << BYTE_TO_HEX(data_[4]); + md5_hex << BYTE_TO_HEX(data_[5]); + md5_hex << "-"; + md5_hex << BYTE_TO_HEX(data_[6]); + md5_hex << BYTE_TO_HEX(data_[7]); + md5_hex << "-"; + md5_hex << BYTE_TO_HEX(data_[8]); + md5_hex << BYTE_TO_HEX(data_[9]); + md5_hex << "-"; + md5_hex << BYTE_TO_HEX(data_[10]); + md5_hex << BYTE_TO_HEX(data_[11]); + md5_hex << BYTE_TO_HEX(data_[12]); + md5_hex << BYTE_TO_HEX(data_[13]); + md5_hex << BYTE_TO_HEX(data_[14]); + md5_hex << BYTE_TO_HEX(data_[15]); + + return md5_hex.str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/uuid.h b/cpp/core/internal/mediums/uuid.h new file mode 100644 index 00000000..5742e6c4 --- /dev/null +++ b/cpp/core/internal/mediums/uuid.h @@ -0,0 +1,39 @@ +#ifndef CORE_INTERNAL_MEDIUMS_UUID_H_ +#define CORE_INTERNAL_MEDIUMS_UUID_H_ + +#include + +#include "platform/port/string.h" + +namespace location { +namespace nearby { +namespace connections { + +// A type 3 name-based +// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) +// UUID. +// +// https://developer.android.com/reference/java/util/UUID.html +template +class UUID { + public: + explicit UUID(const string& data); + UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits); + ~UUID(); + + // Returns the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the + // UUID. + string str(); + + private: + string data_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/uuid.cc" + +#endif // CORE_INTERNAL_MEDIUMS_UUID_H_ diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc new file mode 100644 index 00000000..8004286a --- /dev/null +++ b/cpp/core/internal/offline_frames.cc @@ -0,0 +1,268 @@ +#include "core/internal/offline_frames.h" + +#include "platform/port/down_cast.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +template +T *downcastToRaw(Ptr message) { + return DOWN_CAST(message.operator->()); +} + +// This method takes ownership of the passed-in 'message'. +// +// This can be implemented more efficiently by taking in a reference to an +// OfflineFrame object created on the caller's stack, but we instead create it +// on the heap and return a Ptr to it for the sake of consistency. +ConstPtr newOfflineFrame(V1Frame::FrameType frame_type, + Ptr message) { + V1Frame *v1_frame = new V1Frame(); + v1_frame->set_type(frame_type); + + switch (frame_type) { + case V1Frame::CONNECTION_REQUEST: + v1_frame->set_allocated_connection_request( + downcastToRaw(message)); + break; + case V1Frame::CONNECTION_RESPONSE: + v1_frame->set_allocated_connection_response( + downcastToRaw(message)); + break; + case V1Frame::PAYLOAD_TRANSFER: + v1_frame->set_allocated_payload_transfer( + downcastToRaw(message)); + break; + case V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION: + v1_frame->set_allocated_bandwidth_upgrade_negotiation( + downcastToRaw(message)); + break; + case V1Frame::KEEP_ALIVE: + v1_frame->set_allocated_keep_alive( + downcastToRaw(message)); + break; + default: + break; + } + + Ptr offline_frame(new OfflineFrame()); + offline_frame->set_version(OfflineFrame::V1); + offline_frame->set_allocated_v1(v1_frame); + return ConstifyPtr(offline_frame); +} + +// This method takes ownership of the passed-in 'offline_frame' and destroys it +// before returning. +ConstPtr toBytes(ConstPtr offline_frame) { + ScopedPtr > scoped_offline_frame(offline_frame); + + size_t serialized_size = offline_frame->ByteSizeLong(); + Ptr bytes{new ByteArray{serialized_size}}; + + offline_frame->SerializeToArray(bytes->getData(), serialized_size); + return ConstifyPtr(bytes); +} + +} // namespace + +ExceptionOr > OfflineFrames::fromBytes( + ConstPtr offline_frame_bytes) { + ScopedPtr > offline_frame(new OfflineFrame()); + + if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(), + offline_frame_bytes->size())) { + return ExceptionOr >( + Exception::INVALID_PROTOCOL_BUFFER); + } + + return ExceptionOr >( + ConstifyPtr(offline_frame.release())); +} + +V1Frame::FrameType OfflineFrames::getFrameType( + ConstPtr offline_frame) { + if ((offline_frame->version() == OfflineFrame::V1) && + offline_frame->has_v1()) { + return offline_frame->v1().type(); + } + + return V1Frame::UNKNOWN_FRAME_TYPE; +} + +ConstPtr OfflineFrames::forConnectionRequest( + const std::string &endpoint_id, const std::string &endpoint_name, + std::int32_t nonce, + const std::vector &mediums) { + Ptr connection_request(new ConnectionRequestFrame()); + connection_request->set_endpoint_id(endpoint_id); + connection_request->set_endpoint_name(endpoint_name); + connection_request->set_nonce(nonce); + + for (std::vector::const_iterator it = + mediums.begin(); + it != mediums.end(); it++) { + connection_request->add_mediums(mediumToConnectionRequestMedium(*it)); + } + + return toBytes( + newOfflineFrame(V1Frame::CONNECTION_REQUEST, connection_request)); +} + +ConstPtr OfflineFrames::forConnectionResponse(std::int32_t status) { + Ptr connection_response( + new ConnectionResponseFrame()); + connection_response->set_status(status); + + return toBytes( + newOfflineFrame(V1Frame::CONNECTION_RESPONSE, connection_response)); +} + +ConstPtr OfflineFrames::forDataPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader &header, + const PayloadTransferFrame::PayloadChunk &chunk) { + Ptr payload_transfer(new PayloadTransferFrame()); + payload_transfer->set_packet_type(PayloadTransferFrame::DATA); + *payload_transfer->mutable_payload_header() = header; + *payload_transfer->mutable_payload_chunk() = chunk; + + return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer)); +} + +ConstPtr OfflineFrames::forControlPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader &header, + const PayloadTransferFrame::ControlMessage &control) { + Ptr payload_transfer(new PayloadTransferFrame()); + payload_transfer->set_packet_type(PayloadTransferFrame::CONTROL); + *payload_transfer->mutable_payload_header() = header; + *payload_transfer->mutable_control_message() = control; + + return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer)); +} + +ConstPtr OfflineFrames:: + forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent( + const std::string &ssid, const std::string &password, + std::int32_t port) { + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials + *wifi_hotspot_credentials = new BandwidthUpgradeNegotiationFrame:: + UpgradePathInfo::WifiHotspotCredentials(); + wifi_hotspot_credentials->set_ssid(ssid); + wifi_hotspot_credentials->set_password(password); + wifi_hotspot_credentials->set_port(port); + + BandwidthUpgradeNegotiationFrame::UpgradePathInfo *upgrade_path_info = + new BandwidthUpgradeNegotiationFrame::UpgradePathInfo(); + upgrade_path_info->set_medium( + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); + upgrade_path_info->set_allocated_wifi_hotspot_credentials( + wifi_hotspot_credentials); + + Ptr bandwidth_upgrade_negotiation( + new BandwidthUpgradeNegotiationFrame()); + bandwidth_upgrade_negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + bandwidth_upgrade_negotiation->set_allocated_upgrade_path_info( + upgrade_path_info); + + return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + bandwidth_upgrade_negotiation)); +} + +ConstPtr +OfflineFrames::forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent() { + Ptr bandwidth_upgrade_negotiation( + new BandwidthUpgradeNegotiationFrame()); + bandwidth_upgrade_negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL); + + return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + bandwidth_upgrade_negotiation)); +} + +ConstPtr +OfflineFrames::forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent() { + Ptr bandwidth_upgrade_negotiation( + new BandwidthUpgradeNegotiationFrame()); + bandwidth_upgrade_negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL); + + return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + bandwidth_upgrade_negotiation)); +} + +ConstPtr +OfflineFrames::forClientIntroductionBandwidthUpgradeNegotiationEvent( + const std::string &endpoint_id) { + BandwidthUpgradeNegotiationFrame::ClientIntroduction *client_introduction = + new BandwidthUpgradeNegotiationFrame::ClientIntroduction(); + client_introduction->set_endpoint_id(endpoint_id); + + Ptr bandwidth_upgrade_negotiation( + new BandwidthUpgradeNegotiationFrame()); + bandwidth_upgrade_negotiation->set_event_type( + BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION); + bandwidth_upgrade_negotiation->set_allocated_client_introduction( + client_introduction); + + return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + bandwidth_upgrade_negotiation)); +} + +ConstPtr OfflineFrames::forKeepAlive() { + Ptr keep_alive_frame(new KeepAliveFrame()); + return toBytes(newOfflineFrame(V1Frame::KEEP_ALIVE, keep_alive_frame)); +} + +ConnectionRequestFrame::Medium OfflineFrames::mediumToConnectionRequestMedium( + proto::connections::Medium medium) { + switch (medium) { + case proto::connections::MDNS: + return ConnectionRequestFrame::MDNS; + case proto::connections::BLUETOOTH: + return ConnectionRequestFrame::BLUETOOTH; + case proto::connections::WIFI_HOTSPOT: + return ConnectionRequestFrame::WIFI_HOTSPOT; + case proto::connections::BLE: + return ConnectionRequestFrame::BLE; + case proto::connections::WIFI_LAN: + return ConnectionRequestFrame::WIFI_LAN; + default: + return ConnectionRequestFrame::UNKNOWN_MEDIUM; + } +} + +proto::connections::Medium OfflineFrames::connectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium) { + switch (medium) { + case ConnectionRequestFrame::MDNS: + return proto::connections::Medium::MDNS; + case ConnectionRequestFrame::BLUETOOTH: + return proto::connections::Medium::BLUETOOTH; + case ConnectionRequestFrame::WIFI_HOTSPOT: + return proto::connections::Medium::WIFI_HOTSPOT; + case ConnectionRequestFrame::BLE: + return proto::connections::Medium::BLE; + case ConnectionRequestFrame::WIFI_LAN: + return proto::connections::Medium::WIFI_LAN; + default: + return proto::connections::Medium::UNKNOWN_MEDIUM; + } +} + +std::vector +OfflineFrames::connectionRequestMediumsToMediums( + const ConnectionRequestFrame &connection_request_frame) { + std::vector result; + for (size_t i = 0; i < connection_request_frame.mediums_size(); i++) { + result.push_back( + connectionRequestMediumToMedium(connection_request_frame.mediums(i))); + } + return result; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/offline_frames.h b/cpp/core/internal/offline_frames.h new file mode 100644 index 00000000..425bac06 --- /dev/null +++ b/cpp/core/internal/offline_frames.h @@ -0,0 +1,70 @@ +#ifndef CORE_INTERNAL_OFFLINE_FRAMES_H_ +#define CORE_INTERNAL_OFFLINE_FRAMES_H_ + +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +// Detects the right usage. +#include "google/protobuf/message_lite.h" +#define proto_ns google3_proto_compat + + +namespace location { +namespace nearby { +namespace connections { + +class OfflineFrames { + public: + static ExceptionOr > fromBytes( + ConstPtr + offline_frame_bytes); // throws Exception::INVALID_PROTOCOL_BUFFER + + static V1Frame::FrameType getFrameType(ConstPtr offline_frame); + + static ConstPtr forConnectionRequest( + const std::string& endpoint_id, const std::string& endpoint_name, + std::int32_t nonce, + const std::vector& mediums); + static ConstPtr forConnectionResponse(std::int32_t status); + + static ConstPtr forDataPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk); + static ConstPtr forControlPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control); + + static ConstPtr + forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent( + const std::string& ssid, const std::string& password, std::int32_t port); + static ConstPtr + forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent(); + static ConstPtr + forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent(); + static ConstPtr + forClientIntroductionBandwidthUpgradeNegotiationEvent( + const std::string& endpoint_id); + + static ConstPtr forKeepAlive(); + + static ConnectionRequestFrame::Medium mediumToConnectionRequestMedium( + proto::connections::Medium medium); + static proto::connections::Medium connectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium); + static std::vector + connectionRequestMediumsToMediums( + const ConnectionRequestFrame& connection_request_frame); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_OFFLINE_FRAMES_H_ diff --git a/cpp/core/internal/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc new file mode 100644 index 00000000..b5e45cfb --- /dev/null +++ b/cpp/core/internal/offline_service_controller.cc @@ -0,0 +1,110 @@ +#include "core/internal/offline_service_controller.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +template +OfflineServiceController::OfflineServiceController() + : ServiceController(), + medium_manager_(new MediumManager()), + endpoint_channel_manager_( + new EndpointChannelManager(medium_manager_.get())), + endpoint_manager_( + new EndpointManager(endpoint_channel_manager_.get())), + payload_manager_(new PayloadManager(endpoint_manager_.get())), + bandwidth_upgrade_manager_(new BandwidthUpgradeManager( + medium_manager_.get(), endpoint_channel_manager_.get(), + endpoint_manager_.get())), + pcp_manager_(new PCPManager( + medium_manager_.get(), endpoint_channel_manager_.get(), + endpoint_manager_.get(), bandwidth_upgrade_manager_.get())) {} + +template +OfflineServiceController::~OfflineServiceController() {} + +template +Status::Value OfflineServiceController::startAdvertising( + Ptr > client_proxy, const string& endpoint_name, + const string& service_id, const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) { + return pcp_manager_->startAdvertising(client_proxy, endpoint_name, service_id, + advertising_options, + connection_lifecycle_listener); +} + +template +void OfflineServiceController::stopAdvertising( + Ptr > client_proxy) { + pcp_manager_->stopAdvertising(client_proxy); +} + +template +Status::Value OfflineServiceController::startDiscovery( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) { + return pcp_manager_->startDiscovery(client_proxy, service_id, + discovery_options, discovery_listener); +} + +template +void OfflineServiceController::stopDiscovery( + Ptr > client_proxy) { + pcp_manager_->stopDiscovery(client_proxy); +} + +template +Status::Value OfflineServiceController::requestConnection( + Ptr > client_proxy, const string& endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener) { + return pcp_manager_->requestConnection( + client_proxy, endpoint_name, endpoint_id, connection_lifecycle_listener); +} + +template +Status::Value OfflineServiceController::acceptConnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr payload_listener) { + return pcp_manager_->acceptConnection(client_proxy, endpoint_id, + payload_listener); +} + +template +Status::Value OfflineServiceController::rejectConnection( + Ptr > client_proxy, const string& endpoint_id) { + return pcp_manager_->rejectConnection(client_proxy, endpoint_id); +} + +template +void OfflineServiceController::initiateBandwidthUpgrade( + Ptr > client_proxy, const string& endpoint_id) { + bandwidth_upgrade_manager_->initiateBandwidthUpgradeForEndpoint( + client_proxy, endpoint_id, pcp_manager_->getBandwidthUpgradeMedium()); +} + +template +void OfflineServiceController::sendPayload( + Ptr > client_proxy, + const std::vector& endpoint_ids, ConstPtr payload) { + payload_manager_->sendPayload(client_proxy, endpoint_ids, payload); +} + +template +Status::Value OfflineServiceController::cancelPayload( + Ptr > client_proxy, std::int64_t payload_id) { + return payload_manager_->cancelPayload(client_proxy, payload_id); +} + +template +void OfflineServiceController::disconnectFromEndpoint( + Ptr > client_proxy, const string& endpoint_id) { + endpoint_manager_->unregisterEndpoint(client_proxy, endpoint_id); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/offline_service_controller.h b/cpp/core/internal/offline_service_controller.h new file mode 100644 index 00000000..743e7852 --- /dev/null +++ b/cpp/core/internal/offline_service_controller.h @@ -0,0 +1,85 @@ +#ifndef CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ +#define CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ + +#include +#include + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "core/internal/payload_manager.h" +#include "core/internal/pcp_manager.h" +#include "core/internal/service_controller.h" +#include "core/listeners.h" +#include "core/options.h" +#include "core/payload.h" +#include "core/status.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class OfflineServiceController : public ServiceController { + public: + OfflineServiceController(); + ~OfflineServiceController() override; + + Status::Value startAdvertising( + Ptr > client_proxy, const string& endpoint_name, + const string& service_id, const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) override; + void stopAdvertising(Ptr > client_proxy) override; + + Status::Value startDiscovery( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) override; + void stopDiscovery(Ptr > client_proxy) override; + + Status::Value requestConnection( + Ptr > client_proxy, const string& endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener) override; + Status::Value acceptConnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr payload_listener) override; + Status::Value rejectConnection(Ptr > client_proxy, + const string& endpoint_id) override; + + void initiateBandwidthUpgrade(Ptr > client_proxy, + const string& endpoint_id) override; + + void sendPayload(Ptr > client_proxy, + const std::vector& endpoint_ids, + ConstPtr payload) override; + Status::Value cancelPayload(Ptr > client_proxy, + std::int64_t payload_id) override; + + void disconnectFromEndpoint(Ptr > client_proxy, + const string& endpoint_id) override; + + private: + // Note that the order of declaration of these is crucial, because we depend + // on the destructors running (strictly) in the reverse order; a deviation + // from that will lead to crashes at runtime. + ScopedPtr > > medium_manager_; + ScopedPtr > > endpoint_channel_manager_; + ScopedPtr > > endpoint_manager_; + ScopedPtr > > payload_manager_; + ScopedPtr > > + bandwidth_upgrade_manager_; + ScopedPtr > > pcp_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/offline_service_controller.cc" + +#endif // CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc new file mode 100644 index 00000000..36443bd0 --- /dev/null +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -0,0 +1,788 @@ +#include "core/internal/p2p_cluster_pcp_handler.h" + +#include "platform/api/hash_utils.h" + +namespace location { +namespace nearby { +namespace connections { + +template +const BluetoothDeviceName::Version::Value + P2PClusterPCPHandler::kBluetoothDeviceNameVersion = + BluetoothDeviceName::Version::V1; + +template +const BLEAdvertisement::Version::Value + P2PClusterPCPHandler::kBleAdvertisementVersion = + BLEAdvertisement::Version::V1; + +template +ConstPtr P2PClusterPCPHandler::generateHash( + const string& source, size_t size) { + // Initiazing a new HashUtils each time instead of making it a class member + // because FoundBluetoothAdvertisementProcessor uses generateHash in its + // constructor so this method has to be static. We *could* make a static + // ScopedPtr for HashUtils, but that can get into dangerous territory in terms + // of time of destruction of that object, so we'll avoid it for now, and stick + // with this. + ScopedPtr> hash_utils(Platform::createHashUtils()); + + ScopedPtr> scoped_hash(hash_utils->sha256(source)); + return MakeConstPtr(new ByteArray(scoped_hash->getData(), size)); +} + +template +P2PClusterPCPHandler::P2PClusterPCPHandler( + Ptr> medium_manager, + Ptr> endpoint_manager, + Ptr> endpoint_channel_manager, + Ptr> bandwidth_upgrade_manager) + : BasePCPHandler(endpoint_manager, endpoint_channel_manager, + bandwidth_upgrade_manager), + medium_manager_(medium_manager) {} + +template +P2PClusterPCPHandler::~P2PClusterPCPHandler() {} + +template +Strategy P2PClusterPCPHandler::getStrategy() { + return Strategy::kP2PCluster; +} + +template +PCP::Value P2PClusterPCPHandler::getPCP() { + return PCP::P2P_CLUSTER; +} + +template +std::vector +P2PClusterPCPHandler::getConnectionMediumsByPriority() { + std::vector mediums; + if (medium_manager_->isBluetoothAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + if (medium_manager_->isBleAvailable()) { + mediums.push_back(proto::connections::BLE); + } + return mediums; +} + +template +proto::connections::Medium +P2PClusterPCPHandler::getDefaultUpgradeMedium() { + return proto::connections::WIFI_LAN; +} + +template +Ptr::StartOperationResult> +P2PClusterPCPHandler::startAdvertisingImpl( + Ptr> client_proxy, const string& service_id, + const string& local_endpoint_id, const string& local_endpoint_name, + const AdvertisingOptions& options) { + std::vector mediums_started_successfully; + + ScopedPtr> scoped_bluetooth_service_id_hash( + generateHash(service_id, BluetoothDeviceName::kServiceIdHashLength)); + proto::connections::Medium bluetooth_medium = startBluetoothAdvertising( + client_proxy, service_id, scoped_bluetooth_service_id_hash.get(), + local_endpoint_id, local_endpoint_name); + if (proto::connections::UNKNOWN_MEDIUM != bluetooth_medium) { + mediums_started_successfully.push_back(bluetooth_medium); + } + + ScopedPtr> scoped_ble_service_id_hash( + generateHash(service_id, BLEAdvertisement::kServiceIdHashLength)); + proto::connections::Medium ble_medium = startBleAdvertising( + client_proxy, service_id, scoped_ble_service_id_hash.get(), + local_endpoint_id, local_endpoint_name); + if (proto::connections::UNKNOWN_MEDIUM != ble_medium) { + mediums_started_successfully.push_back(ble_medium); + } + + if (mediums_started_successfully.empty()) { + // TODO(tracyzhou): Add logging. + return BasePCPHandler::StartOperationResult::error( + Status::BLUETOOTH_ERROR); + } + + // The rest of the operations for startAdvertising() will continue + // asynchronously via + // IncomingBluetoothConnectionProcessor.onIncomingBluetoothConnection(), so + // leave it to that to signal any errors that may occur. + return BasePCPHandler::StartOperationResult::success( + mediums_started_successfully); +} + +template +Status::Value P2PClusterPCPHandler::stopAdvertisingImpl( + Ptr> client_proxy) { + medium_manager_->stopBleAdvertising(client_proxy->getAdvertisingServiceId()); + medium_manager_->turnOffBluetoothDiscoverability(); + medium_manager_->stopListeningForIncomingBleConnections( + client_proxy->getAdvertisingServiceId()); + medium_manager_->stopListeningForIncomingBluetoothConnections( + client_proxy->getAdvertisingServiceId()); + return Status::SUCCESS; +} + +template +Ptr::StartOperationResult> +P2PClusterPCPHandler::startDiscoveryImpl( + Ptr> client_proxy, const string& service_id, + const DiscoveryOptions& options) { + std::vector mediums_started_successfully; + + proto::connections::Medium bluetooth_medium = + startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor( + MakePtr(this), client_proxy, service_id)), + client_proxy, service_id); + if (proto::connections::UNKNOWN_MEDIUM != bluetooth_medium) { + mediums_started_successfully.push_back(bluetooth_medium); + } + + proto::connections::Medium ble_medium = startBleDiscovery( + MakePtr(new FoundBleAdvertisementProcessor(MakePtr(this), client_proxy)), + client_proxy, service_id); + if (proto::connections::UNKNOWN_MEDIUM != ble_medium) { + mediums_started_successfully.push_back(ble_medium); + } + + if (mediums_started_successfully.empty()) { + // TODO(tracyzhou): Add logging. + return BasePCPHandler::StartOperationResult::error( + Status::BLUETOOTH_ERROR); + } + + return BasePCPHandler::StartOperationResult::success( + mediums_started_successfully); +} + +template +Status::Value P2PClusterPCPHandler::stopDiscoveryImpl( + Ptr> client_proxy) { + medium_manager_->stopBleScanning(client_proxy->getDiscoveryServiceId()); + medium_manager_->stopScanningForBluetoothDevices(); + return Status::SUCCESS; +} + +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::connectImpl( + Ptr> client_proxy, + Ptr::DiscoveredEndpoint> endpoint) { + Ptr bluetooth_endpoint = + DowncastPtr(endpoint); + if (!bluetooth_endpoint.isNull()) { + return bluetoothConnectImpl(client_proxy, bluetooth_endpoint); + } + + Ptr ble_endpoint = DowncastPtr(endpoint); + if (!ble_endpoint.isNull()) { + return bleConnectImpl(client_proxy, ble_endpoint); + } + + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::UNKNOWN_MEDIUM, Status::ERROR); +} + +/////////////////// START IMPLEMENTATIONS FOR NESTED CLASSES /////////////////// + +///////// P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor ////////// +template +P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor:: + IncomingBluetoothConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + const string& local_endpoint_name) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name) {} + +template +void P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor:: + onIncomingBluetoothConnection(Ptr bluetooth_socket) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnIncomingBluetoothConnectionRunnable( + pcp_handler_, client_proxy_, bluetooth_socket))); +} + +template +P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor:: + OnIncomingBluetoothConnectionRunnable:: + OnIncomingBluetoothConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr bluetooth_socket) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + bluetooth_socket_(bluetooth_socket) {} + +template +void P2PClusterPCPHandler::IncomingBluetoothConnectionProcessor:: + OnIncomingBluetoothConnectionRunnable::run() { + string remote_device_name = bluetooth_socket_->getRemoteDevice()->getName(); + ScopedPtr> scoped_bluetooth_endpoint_channel( + pcp_handler_->endpoint_channel_manager_ + ->createIncomingBluetoothEndpointChannel(remote_device_name, + bluetooth_socket_)); + if (!scoped_bluetooth_endpoint_channel.isNull()) { + // TODO(tracyzhou): Add logging. + } else { + Exception::Value exception = bluetooth_socket_->close(); + bluetooth_socket_.destroy(); + if (Exception::NONE != exception) { + if (Exception::IO == exception) { + // TODO(tracyzhou): Add logging. + } + } + } + pcp_handler_->onIncomingConnection( + client_proxy_, remote_device_name, + scoped_bluetooth_endpoint_channel.release(), + proto::connections::Medium::BLUETOOTH); +} + +//////////// P2PClusterPCPHandler::IncomingBleConnectionProcessor ///////////// +template +P2PClusterPCPHandler::IncomingBleConnectionProcessor:: + IncomingBleConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + const string& local_endpoint_name) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name) {} + +template +void P2PClusterPCPHandler::IncomingBleConnectionProcessor:: + onIncomingBleConnection(Ptr ble_socket, + const string& service_id) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnIncomingBleConnectionRunnable(pcp_handler_, client_proxy_, + ble_socket))); +} + +template +P2PClusterPCPHandler::IncomingBleConnectionProcessor:: + OnIncomingBleConnectionRunnable::OnIncomingBleConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, Ptr ble_socket) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + ble_socket_(ble_socket) {} + +template +void P2PClusterPCPHandler::IncomingBleConnectionProcessor:: + OnIncomingBleConnectionRunnable::run() { + string remote_device_name = + ble_socket_->getRemotePeripheral()->getBluetoothDevice()->getName(); + ScopedPtr> scoped_ble_endpoint_channel( + pcp_handler_->endpoint_channel_manager_->createIncomingBLEEndpointChannel( + remote_device_name, ble_socket_)); + if (!scoped_ble_endpoint_channel.isNull()) { + // TODO(ahlee): Add logging. + } else { + Exception::Value exception = ble_socket_->close(); + ble_socket_.destroy(); + if (Exception::NONE != exception) { + if (Exception::IO == exception) { + // TODO(ahlee): Add logging. + } + } + } + pcp_handler_->onIncomingConnection(client_proxy_, remote_device_name, + scoped_ble_endpoint_channel.release(), + proto::connections::Medium::BLE); +} + +///////// P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor ////////// +template +P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + FoundBluetoothAdvertisementProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, const string& service_id) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + expected_service_id_hash_(generateHash( + service_id, BluetoothDeviceName::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + onFoundBluetoothDevice(Ptr bluetooth_device) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnFoundBluetoothDeviceRunnable(pcp_handler_, client_proxy_, + MakePtr(this), service_id_, + bluetooth_device))); +} + +template +void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + onLostBluetoothDevice(Ptr bluetooth_device) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBluetoothDeviceRunnable( + pcp_handler_, client_proxy_, MakePtr(this), service_id_, + bluetooth_device))); +} + +template +bool P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + isRecognizedBluetoothEndpoint( + const string& found_bluetooth_device_name, + Ptr bluetooth_device_name) { + if (bluetooth_device_name.isNull()) { + // TODO(tracyzhou): Add logging. + return false; + } + + if (bluetooth_device_name->getPCP() != pcp_handler_->getPCP()) { + // TODO(tracyzhou): Add logging. + return false; + } + + if (*(bluetooth_device_name->getServiceIdHash()) != + *(expected_service_id_hash_.get())) { + // TODO(tracyzhou): Add logging. + return false; + } + + return true; +} + +template +P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + OnFoundBluetoothDeviceRunnable::OnFoundBluetoothDeviceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr + found_bluetooth_advertisement_processor, + const string& service_id, Ptr bluetooth_device) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_bluetooth_advertisement_processor_( + found_bluetooth_advertisement_processor), + service_id_(service_id), + bluetooth_device_(bluetooth_device) {} + +template +void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + OnFoundBluetoothDeviceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(tracyzhou): Add logging. + return; + } + + // Parse the Bluetooth device name. + ScopedPtr> bluetooth_device_name( + BluetoothDeviceName::fromString(bluetooth_device_->getName())); + + // Make sure the Bluetooth device name points to a valid endpoint we're + // discovering. + if (!found_bluetooth_advertisement_processor_->isRecognizedBluetoothEndpoint( + bluetooth_device_->getName(), bluetooth_device_name.get())) { + return; + } + + // Report the discovered endpoint to the client. + // TODO(tracyzhou): Add logging. + pcp_handler_->onEndpointFound( + client_proxy_, + MakePtr(new BluetoothEndpoint( + bluetooth_device_.release(), bluetooth_device_name->getEndpointId(), + bluetooth_device_name->getEndpointName(), service_id_))); +} + +template +P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + OnLostBluetoothDeviceRunnable::OnLostBluetoothDeviceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr + found_bluetooth_advertisement_processor, + const string& service_id, Ptr bluetooth_device) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_bluetooth_advertisement_processor_( + found_bluetooth_advertisement_processor), + service_id_(service_id), + bluetooth_device_(bluetooth_device) {} + +template +void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + OnLostBluetoothDeviceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(tracyzhou): Add logging. + return; + } + + // Parse the Bluetooth device name. + ScopedPtr> bluetooth_device_name( + BluetoothDeviceName::fromString(bluetooth_device_->getName())); + + // Make sure the Bluetooth device name points to a valid endpoint we're + // discovering. + if (!found_bluetooth_advertisement_processor_->isRecognizedBluetoothEndpoint( + bluetooth_device_->getName(), bluetooth_device_name.get())) { + return; + } + + // Report the endpoint as lost to the client. + // TODO(tracyzhou): Add logging. + pcp_handler_->onEndpointLost( + client_proxy_, + MakePtr(new BluetoothEndpoint( + bluetooth_device_.release(), bluetooth_device_name->getEndpointId(), + bluetooth_device_name->getEndpointName(), service_id_))); +} + +//////////// P2PClusterPCPHandler::FoundBleAdvertisementProcessor ///////////// +template +P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + FoundBleAdvertisementProcessor( + Ptr> pcp_handler, + Ptr> client_proxy) + : pcp_handler_(pcp_handler), client_proxy_(client_proxy) {} + +template +void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + onFoundBlePeripheral(Ptr ble_peripheral, + const string& service_id, + ConstPtr advertisement_bytes) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundBlePeripheralRunnable( + pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral, + advertisement_bytes))); +} + +template +P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + OnFoundBlePeripheralRunnable::OnFoundBlePeripheralRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_ble_advertisement_processor, + const string& service_id, Ptr ble_peripheral, + ConstPtr advertisement_bytes) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_ble_advertisement_processor_(found_ble_advertisement_processor), + service_id_(service_id), + ble_peripheral_(ble_peripheral), + advertisement_bytes_(advertisement_bytes), + expected_service_id_hash_( + generateHash(service_id, BLEAdvertisement::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + OnFoundBlePeripheralRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(ahlee): logger.atWarning().log("Skipping discovery of + // BLEAdvertisement header %s because we are no longer discovering.", + // bytesToString(advertisementBytes)); + return; + } + + ScopedPtr> scoped_ble_advertisement( + BLEAdvertisement::fromBytes(advertisement_bytes_.get())); + if (scoped_ble_advertisement.isNull()) { + // TODO(ahlee): logger.atVerbose().log("%s doesn't conform to the + // BLEAdvertisement format, discarding.", + // bytesToSTring(advertisementBytes)); + return; + } + + if (scoped_ble_advertisement->getVersion() != BLEAdvertisement::Version::V1) { + // TODO(ahlee): logging + return; + } + + if (scoped_ble_advertisement->getPCP() != pcp_handler_->getPCP()) { + // TODO(ahlee): Add logging + return; + } + + if (*(scoped_ble_advertisement->getServiceIdHash()) != + *(expected_service_id_hash_.get())) { + // TODO(ahlee): Add logging + return; + } + + // TODO(ahlee): Add logging. + + // Store all the state we need to be able to re-create a BLEEndpoint in + // OnLostBlePeripheralRunnable::run(), since that isn't privy to the bytes of + // the BLE advertisement itself. + found_ble_advertisement_processor_->found_ble_endpoints_.insert( + std::make_pair( + getBlePeripheralId(ble_peripheral_.get()), + BLEEndpointState(scoped_ble_advertisement->getEndpointId(), + scoped_ble_advertisement->getEndpointName()))); + + pcp_handler_->onEndpointFound( + client_proxy_, + MakePtr(new BLEEndpoint( + ble_peripheral_.release(), scoped_ble_advertisement->getEndpointId(), + scoped_ble_advertisement->getEndpointName(), service_id_))); + + // TODO(b/75047971): Add functionality to connect over Bluetooth. +} + +template +void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + onLostBlePeripheral(Ptr ble_peripheral, + const string& service_id) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBlePeripheralRunnable( + pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral))); +} + +template +P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + OnLostBlePeripheralRunnable::OnLostBlePeripheralRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_ble_advertisement_processor, + const string& service_id, Ptr ble_peripheral) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_ble_advertisement_processor_(found_ble_advertisement_processor), + service_id_(service_id), + ble_peripheral_(ble_peripheral) {} + +template +void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: + OnLostBlePeripheralRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(reznor): logger.atWarning().log("Ignoring lost BlePeripheral %s + // because we are no longer discovering.", blePeripheral); + return; + } + + // Remove this BLEPeripheral from + // found_ble_advertisement_processor_->found_ble_endpoints_, and report the + // endpoint as lost to the client. + typename FoundBLEEndpointsMap::iterator it = + found_ble_advertisement_processor_->found_ble_endpoints_.find( + getBlePeripheralId(ble_peripheral_.get())); + if (it != found_ble_advertisement_processor_->found_ble_endpoints_.end()) { + // TODO(reznor): logger.atDebug().log("Lost BlePeripheral %s (with + // EndpointId %s and EndpointName %s)", blePeripheral, + // bleEndpoint.getEndpointId(), bleEndpoint.getEndpointName()); + + // Make a copy since it->second will get destroyed once we call erase() + // below. + BLEEndpointState ble_endpoint_state(it->second); + found_ble_advertisement_processor_->found_ble_endpoints_.erase(it); + + pcp_handler_->onEndpointLost( + client_proxy_, + MakePtr(new BLEEndpoint( + ble_peripheral_.release(), ble_endpoint_state.endpoint_id, + ble_endpoint_state.endpoint_name, service_id_))); + } +} + +//////////////////// END IMPLEMENTATIONS FOR NESTED CLASSES //////////////////// + +template +proto::connections::Medium +P2PClusterPCPHandler::startBluetoothAdvertising( + Ptr> client_proxy, const string& service_id, + ConstPtr service_id_hash, const string& local_endpoint_id, + const string& local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + if (!medium_manager_->isListeningForIncomingBluetoothConnections( + service_id)) { + if (!medium_manager_->startListeningForIncomingBluetoothConnections( + service_id, + MakePtr(new IncomingBluetoothConnectionProcessor( + MakePtr(this), client_proxy, local_endpoint_name)))) { + // TODO(tracyzhou): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(tracyzhou): Add logging. + } + + // Generate a BluetoothDeviceName with which to become Bluetooth discoverable. + const string bluetooth_device_name = BluetoothDeviceName::asString( + kBluetoothDeviceNameVersion, getPCP(), local_endpoint_id, service_id_hash, + local_endpoint_name); + if (bluetooth_device_name.empty()) { + // TODO(tracyzhou): Add logging. + medium_manager_->stopListeningForIncomingBluetoothConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(tracyzhou): Add logging. + } + + // Become Bluetooth discoverable. + if (!medium_manager_->turnOnBluetoothDiscoverability(bluetooth_device_name)) { + // TODO(tracyzhou): Add logging. + medium_manager_->stopListeningForIncomingBluetoothConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(tracyzhou): Add logging. + } + return proto::connections::BLUETOOTH; +} + +template +proto::connections::Medium +P2PClusterPCPHandler::startBluetoothDiscovery( + Ptr processor, + Ptr> client_proxy, const string& service_id) { + if (!medium_manager_->startScanningForBluetoothDevices(processor)) { + // TODO(tracyzhou): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(tracyzhou): Add logging. + } + + return proto::connections::BLUETOOTH; +} + +template +proto::connections::Medium P2PClusterPCPHandler::startBleAdvertising( + Ptr> client_proxy, const string& service_id, + ConstPtr service_id_hash, const string& local_endpoint_id, + const string& local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + if (!medium_manager_->isListeningForIncomingBleConnections(service_id)) { + if (!medium_manager_->startListeningForIncomingBleConnections( + service_id, + MakePtr(new IncomingBleConnectionProcessor( + MakePtr(this), client_proxy, local_endpoint_name)))) { + // TODO(ahlee): logger.atWarning().log("In startBleAdvertising(%s), client + // %d failed to start listening for incoming BLE connections to ServiceId + // %s", local_endpoint_name, clientProxy.getClientId(), service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(ahlee): Add logging. + } + + // TODO(b/75047971): Add functionality to connect over Bluetooth. + + // Create a BLEAdvertisement. + // TODO(b/75047971): Add a bluetooth_adapter method to get the mac address. + string bluetooth_mac_address; + ScopedPtr> scoped_ble_advertisement_bytes( + BLEAdvertisement::toBytes(kBleAdvertisementVersion, getPCP(), + service_id_hash, local_endpoint_id, + local_endpoint_name, bluetooth_mac_address)); + if (scoped_ble_advertisement_bytes.isNull()) { + // TODO(ahlee): Add logging + medium_manager_->stopListeningForIncomingBleConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(ahlee): Add logging + + if (!medium_manager_->startBleAdvertising( + service_id, scoped_ble_advertisement_bytes.release())) { + // TODO(ahlee): Add logging + medium_manager_->stopListeningForIncomingBleConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(ahlee): Add logging + return proto::connections::BLE; +} + +template +proto::connections::Medium P2PClusterPCPHandler::startBleDiscovery( + Ptr processor, + Ptr> client_proxy, const string& service_id) { + if (!medium_manager_->startBleScanning(service_id, processor)) { + // TODO(ahlee): logger.atDebug().log("In startBleDiscover(), client %d + // couldn't start scanning on BLE for service id %s.", + // client_proxy.getClientId(), service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(ahlee): logger.atVerbose().log("In startBleDiscovery(), client %d + // started scanning for BLE advertisements for serviceId %s.", + // client_proxy.getClietnId(), service_id); + + return proto::connections::BLE; +} + +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::bluetoothConnectImpl( + Ptr> client_proxy, + Ptr bluetooth_endpoint) { + Ptr remote_bluetooth_device = + bluetooth_endpoint->getBluetoothDevice(); + + Ptr bluetooth_socket = + medium_manager_->connectToBluetoothDevice( + remote_bluetooth_device, bluetooth_endpoint->getServiceId()); + if (bluetooth_socket.isNull()) { + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::BLUETOOTH, Status::BLUETOOTH_ERROR); + } + + ScopedPtr> scoped_bluetooth_endpoint_channel( + this->endpoint_channel_manager_->createOutgoingBluetoothEndpointChannel( + bluetooth_endpoint->getEndpointId(), bluetooth_socket)); + + if (scoped_bluetooth_endpoint_channel.isNull()) { + bluetooth_socket->close(); + bluetooth_socket.destroy(); // Avoid leaks. + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::BLUETOOTH, Status::ERROR); + } + + // TODO(tracyzhou): Add logging. + return typename BasePCPHandler::ConnectImplResult( + scoped_bluetooth_endpoint_channel.release()); +} + +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::bleConnectImpl( + Ptr> client_proxy, Ptr ble_endpoint) { + Ptr remote_ble_peripheral = ble_endpoint->getBlePeripheral(); + + Ptr ble_socket = medium_manager_->connectToBlePeripheral( + remote_ble_peripheral, ble_endpoint->getServiceId()); + + if (ble_socket.isNull()) { + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::BLE, Status::BLUETOOTH_ERROR); + } + + ScopedPtr> scoped_ble_endpoint_channel( + this->endpoint_channel_manager_->createOutgoingBLEEndpointChannel( + ble_endpoint->getEndpointId(), ble_socket)); + + if (scoped_ble_endpoint_channel.isNull()) { + ble_socket->close(); + ble_socket.destroy(); // Avoid leaks. + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::BLE, Status::ERROR); + } + + // TODO(tracyzhou): Add logging. + return typename BasePCPHandler::ConnectImplResult( + scoped_ble_endpoint_channel.release()); +} + +template +string P2PClusterPCPHandler::getBlePeripheralId( + Ptr ble_peripheral) { +#if BLE_V2_IMPLEMENTED + return string(ble_peripheral->getId()->getData(), + ble_peripheral->getId()->size()); +#else + return ble_peripheral->getBluetoothDevice()->getName(); +#endif +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h new file mode 100644 index 00000000..06e09a7e --- /dev/null +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -0,0 +1,378 @@ +#ifndef CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ +#define CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ + +#include + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/base_pcp_handler.h" +#include "core/internal/ble_advertisement.h" +#include "core/internal/ble_compat.h" +#include "core/internal/bluetooth_device_name.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "core/internal/pcp.h" +#include "core/options.h" +#include "core/strategy.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PCPHandler for the P2P_CLUSTER PCP. This PCP +// is reserved for mediums that can connect to multiple devices simultaneously +// and all devices are considered equal. For asymmetric mediums, where one +// device is a server and the others are clients, use P2PStarPCPHandler instead. +// +//

Currently, this implementation advertises/discovers over BLE and Bluetooth +// and connects over Bluetooth. +template +class P2PClusterPCPHandler : public BasePCPHandler { + public: + P2PClusterPCPHandler( + Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager); + ~P2PClusterPCPHandler() override; + + Strategy getStrategy() override; + PCP::Value getPCP() override; + + protected: + std::vector getConnectionMediumsByPriority() + override; + proto::connections::Medium getDefaultUpgradeMedium() override; + + // @PCPHandlerThread + Ptr::StartOperationResult> + startAdvertisingImpl(Ptr > client_proxy, + const string& service_id, + const string& local_endpoint_id, + const string& local_endpoint_name, + const AdvertisingOptions& options) override; + // @PCPHandlerThread + Status::Value stopAdvertisingImpl( + Ptr > client_proxy) override; + + // @PCPHandlerThread + Ptr::StartOperationResult> + startDiscoveryImpl(Ptr > client_proxy, + const string& service_id, + const DiscoveryOptions& options) override; + // @PCPHandlerThread + Status::Value stopDiscoveryImpl( + Ptr > client_proxy) override; + + // @PCPHandlerThread + typename BasePCPHandler::ConnectImplResult connectImpl( + Ptr > client_proxy, + Ptr::DiscoveredEndpoint> endpoint) + override; + + private: + template + friend class IncomingBluetoothConnectionProcessor; + template + friend class IncomingBleConnectionProcessor; + template + friend class FoundBluetoothAdvertisementProcessor; + template + friend class FoundBleAdvertisementProcessor; + + class IncomingBluetoothConnectionProcessor + : public MediumManager::IncomingBluetoothConnectionProcessor { + public: + IncomingBluetoothConnectionProcessor( + Ptr > pcp_handler, + Ptr > client_proxy, + const string& local_endpoint_name); + + void onIncomingBluetoothConnection( + Ptr bluetooth_socket) override; + + private: + class OnIncomingBluetoothConnectionRunnable : public Runnable { + public: + OnIncomingBluetoothConnectionRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, + Ptr bluetooth_socket); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr bluetooth_socket_; + }; + + Ptr > pcp_handler_; + Ptr > client_proxy_; + const string local_endpoint_name_; + }; + + class IncomingBleConnectionProcessor + : public MediumManager::IncomingBleConnectionProcessor { + public: + IncomingBleConnectionProcessor( + Ptr > pcp_handler, + Ptr > client_proxy, + const string& local_endpoint_name); + + void onIncomingBleConnection(Ptr ble_socket, + const string& service_id) override; + + private: + class OnIncomingBleConnectionRunnable : public Runnable { + public: + OnIncomingBleConnectionRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, Ptr ble_socket); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr ble_socket_; + }; + + Ptr > pcp_handler_; + Ptr > client_proxy_; + const string local_endpoint_name_; + }; + + class FoundBluetoothAdvertisementProcessor + : public MediumManager::FoundBluetoothDeviceProcessor { + public: + FoundBluetoothAdvertisementProcessor( + Ptr > pcp_handler, + Ptr > client_proxy, const string& service_id); + + void onFoundBluetoothDevice(Ptr bluetooth_device) override; + void onLostBluetoothDevice(Ptr bluetooth_device) override; + + private: + class OnFoundBluetoothDeviceRunnable : public Runnable { + public: + OnFoundBluetoothDeviceRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, + Ptr + found_bluetooth_advertisement_processor, + const string& service_id, Ptr bluetooth_device); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr + found_bluetooth_advertisement_processor_; + const string service_id_; + ScopedPtr > bluetooth_device_; + }; + + class OnLostBluetoothDeviceRunnable : public Runnable { + public: + OnLostBluetoothDeviceRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, + Ptr + found_bluetooth_advertisement_processor, + const string& service_id, Ptr bluetooth_device); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr + found_bluetooth_advertisement_processor_; + const string service_id_; + ScopedPtr > bluetooth_device_; + }; + + bool isRecognizedBluetoothEndpoint( + const string& found_bluetooth_device_name, + Ptr bluetooth_device_name); + + Ptr > pcp_handler_; + Ptr > client_proxy_; + const string service_id_; + ScopedPtr > expected_service_id_hash_; + }; + + class FoundBleAdvertisementProcessor + : public MediumManager::FoundBlePeripheralProcessor { + public: + FoundBleAdvertisementProcessor( + Ptr > pcp_handler, + Ptr > client_proxy); + + void onFoundBlePeripheral(Ptr ble_peripheral, + const string& service_id, + ConstPtr advertisement_bytes) override; + void onLostBlePeripheral(Ptr ble_peripheral, + const string& service_id) override; + + private: + class OnFoundBlePeripheralRunnable : public Runnable { + public: + OnFoundBlePeripheralRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, + Ptr found_ble_advertisement_processor, + const string& service_id, Ptr ble_peripheral, + ConstPtr advertisement_bytes); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr found_ble_advertisement_processor_; + const string service_id_; + ScopedPtr > ble_peripheral_; + ScopedPtr > advertisement_bytes_; + ScopedPtr > expected_service_id_hash_; + }; + + class OnLostBlePeripheralRunnable : public Runnable { + public: + OnLostBlePeripheralRunnable( + Ptr > pcp_handler, + Ptr > client_proxy, + Ptr found_ble_advertisement_processor, + const string& service_id, Ptr ble_peripheral); + + void run() override; + + private: + Ptr > pcp_handler_; + Ptr > client_proxy_; + Ptr found_ble_advertisement_processor_; + const string service_id_; + ScopedPtr > ble_peripheral_; + }; + + // Holds the state required to re-create a BLEEndpoint we see on a + // BLEPeripheral, so OnLostBlePeripheralRunnable::run() can call + // BasePCPHandler::onEndpointLost() with the same information as was passed + // in to BasePCPHandler::onEndpointFound(). + struct BLEEndpointState { + public: + BLEEndpointState(const string& endpoint_id, const string& endpoint_name) + : endpoint_id(endpoint_id), endpoint_name(endpoint_name) {} + + const string endpoint_id; + const string endpoint_name; + }; + + Ptr > pcp_handler_; + Ptr > client_proxy_; + // Maps a BLEPeripheral to its corresponding BLEEndpointState. + typedef std::map FoundBLEEndpointsMap; + FoundBLEEndpointsMap found_ble_endpoints_; + }; + + class BluetoothEndpoint + : public BasePCPHandler::DiscoveredEndpoint { + public: + Ptr getBluetoothDevice() { + return bluetooth_device_.get(); + } + string getEndpointId() override { return endpoint_id_; } + string getEndpointName() override { return endpoint_name_; } + string getServiceId() override { return service_id_; } + proto::connections::Medium getMedium() override { + return proto::connections::Medium::BLUETOOTH; + } + + private: + BluetoothEndpoint(Ptr bluetooth_device, + const string& endpoint_id, const string& endpoint_name, + const string& service_id) + : bluetooth_device_(bluetooth_device), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + service_id_(service_id) {} + + friend class FoundBluetoothAdvertisementProcessor; + + ScopedPtr > bluetooth_device_; + const string endpoint_id_; + const string endpoint_name_; + const string service_id_; + }; + + class BLEEndpoint : public BasePCPHandler::DiscoveredEndpoint { + public: + Ptr getBlePeripheral() { return ble_peripheral_.get(); } + string getEndpointId() override { return endpoint_id_; } + string getEndpointName() override { return endpoint_name_; } + string getServiceId() override { return service_id_; } + proto::connections::Medium getMedium() override { + return proto::connections::Medium::BLE; + } + + private: + BLEEndpoint(Ptr ble_peripheral, const string& endpoint_id, + const string& endpoint_name, const string& service_id) + : ble_peripheral_(ble_peripheral), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + service_id_(service_id) {} + + friend class FoundBleAdvertisementProcessor; + + ScopedPtr > ble_peripheral_; + const string endpoint_id_; + const string endpoint_name_; + const string service_id_; + }; + + static const BluetoothDeviceName::Version::Value kBluetoothDeviceNameVersion; + static const BLEAdvertisement::Version::Value kBleAdvertisementVersion; + + static ConstPtr generateHash(const string& source, size_t size); + static string getBlePeripheralId(Ptr ble_peripheral); + + proto::connections::Medium startBluetoothAdvertising( + Ptr > client_proxy, const string& service_id, + ConstPtr service_id_hash, const string& local_endpoint_id, + const string& local_endpoint_name); + proto::connections::Medium startBluetoothDiscovery( + Ptr processor, + Ptr > client_proxy, const string& service_id); + typename BasePCPHandler::ConnectImplResult bluetoothConnectImpl( + Ptr > client_proxy, + Ptr bluetooth_endpoint); + + proto::connections::Medium startBleAdvertising( + Ptr > client_proxy, const string& service_id, + ConstPtr service_id_hash, const string& local_endpoint_id, + const string& local_endpoint_name); + proto::connections::Medium startBleDiscovery( + Ptr processor, + Ptr > client_proxy, const string& service_id); + typename BasePCPHandler::ConnectImplResult bleConnectImpl( + Ptr > client_proxy, Ptr ble_endpoint); + + Ptr > medium_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/p2p_cluster_pcp_handler.cc" + +#endif // CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc new file mode 100644 index 00000000..4e48a42c --- /dev/null +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -0,0 +1,61 @@ +#include "core/internal/p2p_point_to_point_pcp_handler.h" + +namespace location { +namespace nearby { +namespace connections { + +template +P2PPointToPointPCPHandler::P2PPointToPointPCPHandler( + Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager) + : P2PStarPCPHandler(medium_manager, endpoint_manager, + endpoint_channel_manager, + bandwidth_upgrade_manager), + medium_manager_(medium_manager) {} + +template +Strategy P2PPointToPointPCPHandler::getStrategy() { + return Strategy::kP2PPointToPoint; +} + +template +PCP::Value P2PPointToPointPCPHandler::getPCP() { + return PCP::P2P_POINT_TO_POINT; +} + +template +std::vector +P2PPointToPointPCPHandler::getConnectionMediumsByPriority() { + std::vector mediums; + if (medium_manager_->isBluetoothAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + if (medium_manager_->isBleAvailable()) { + mediums.push_back(proto::connections::BLE); + } + return mediums; +} + +template +bool P2PPointToPointPCPHandler::canSendOutgoingConnection( + Ptr > client_proxy) { + // For point to point, we can only send an outgoing connection while we have + // no other connections. + return !this->hasOutgoingConnections(client_proxy) && + !this->hasIncomingConnections(client_proxy); +} + +template +bool P2PPointToPointPCPHandler::canReceiveIncomingConnection( + Ptr > client_proxy) { + // For point to point, we can only receive an incoming connection while we + // have no other connections. + return !this->hasOutgoingConnections(client_proxy) && + !this->hasIncomingConnections(client_proxy); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.h b/cpp/core/internal/p2p_point_to_point_pcp_handler.h new file mode 100644 index 00000000..56f7104b --- /dev/null +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -0,0 +1,55 @@ +#ifndef CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ +#define CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "core/internal/p2p_star_pcp_handler.h" +#include "core/internal/pcp.h" +#include "core/strategy.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PCPHandler for the P2P_POINT_TO_POINT. This +// PCP is for mediums that have limitations on the number of simultaneous +// connections; all mediums in P2P_STAR are valid for P2P_POINT_TO_POINT, but +// not all mediums in P2P_POINT_TO_POINT and valid for P2P_STAR. +// +//

Currently, this implementation advertises/discovers over BLE and Bluetooth +// and connects over Bluetooth, eventually upgrading to Wifi Hotspot. +template +class P2PPointToPointPCPHandler : public P2PStarPCPHandler { + public: + P2PPointToPointPCPHandler( + Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager); + + Strategy getStrategy() override; + PCP::Value getPCP() override; + + protected: + std::vector getConnectionMediumsByPriority() + override; + + bool canSendOutgoingConnection( + Ptr > client_proxy) override; + bool canReceiveIncomingConnection( + Ptr > client_proxy) override; + + private: + Ptr > medium_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/p2p_point_to_point_pcp_handler.cc" + +#endif // CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ diff --git a/cpp/core/internal/p2p_star_pcp_handler.cc b/cpp/core/internal/p2p_star_pcp_handler.cc new file mode 100644 index 00000000..a3bf50d6 --- /dev/null +++ b/cpp/core/internal/p2p_star_pcp_handler.cc @@ -0,0 +1,71 @@ +#include "core/internal/p2p_star_pcp_handler.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +template +P2PStarPCPHandler::P2PStarPCPHandler( + Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager) + : P2PClusterPCPHandler(medium_manager, endpoint_manager, + endpoint_channel_manager, + bandwidth_upgrade_manager), + medium_manager_(medium_manager) {} + +template +P2PStarPCPHandler::~P2PStarPCPHandler() {} + +template +Strategy P2PStarPCPHandler::getStrategy() { + return Strategy::kP2PStar; +} + +template +PCP::Value P2PStarPCPHandler::getPCP() { + return PCP::P2P_STAR; +} + +template +std::vector +P2PStarPCPHandler::getConnectionMediumsByPriority() { + std::vector mediums; + if (medium_manager_->isBluetoothAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } + if (medium_manager_->isBleAvailable()) { + mediums.push_back(proto::connections::BLE); + } + return mediums; +} + +template +proto::connections::Medium +P2PStarPCPHandler::getDefaultUpgradeMedium() { + return proto::connections::Medium::WIFI_HOTSPOT; +} + +template +bool P2PStarPCPHandler::canSendOutgoingConnection( + Ptr > client_proxy) { + // For star, we can only send an outgoing connection while we have no other + // connections. + return !this->hasOutgoingConnections(client_proxy) && + !this->hasIncomingConnections(client_proxy); +} + +template +bool P2PStarPCPHandler::canReceiveIncomingConnection( + Ptr > client_proxy) { + // For star, we can only receive an incoming connection if we've sent no + // outgoing connections. + return !this->hasOutgoingConnections(client_proxy); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/p2p_star_pcp_handler.h b/cpp/core/internal/p2p_star_pcp_handler.h new file mode 100644 index 00000000..4a7c110f --- /dev/null +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -0,0 +1,60 @@ +#ifndef CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_ +#define CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_ + +#include + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "core/internal/p2p_cluster_pcp_handler.h" +#include "core/internal/pcp.h" +#include "core/strategy.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Concrete implementation of the PCPHandler for the P2P_STAR PCP. This PCP is +// for mediums that have one server with (potentially) many clients; all mediums +// in P2P_CLUSTER are valid for P2P_STAR, but not all mediums in P2P_STAR and +// valid for P2P_CLUSTER. +// +//

Currently, this implementation advertises/discovers over BLE and Bluetooth +// and connects over Bluetooth, eventually upgrading to a Wifi Hotspot. +template +class P2PStarPCPHandler : public P2PClusterPCPHandler { + public: + P2PStarPCPHandler( + Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr > endpoint_channel_manager, + Ptr > bandwidth_upgrade_manager); + ~P2PStarPCPHandler() override; + + Strategy getStrategy() override; + PCP::Value getPCP() override; + + protected: + std::vector getConnectionMediumsByPriority() + override; + proto::connections::Medium getDefaultUpgradeMedium() override; + + bool canSendOutgoingConnection( + Ptr > client_proxy) override; + bool canReceiveIncomingConnection( + Ptr > client_proxy) override; + + private: + Ptr > medium_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/p2p_star_pcp_handler.cc" + +#endif // CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_ diff --git a/cpp/core/internal/payload_manager.cc b/cpp/core/internal/payload_manager.cc new file mode 100644 index 00000000..ada9ed74 --- /dev/null +++ b/cpp/core/internal/payload_manager.cc @@ -0,0 +1,1355 @@ +#include "core/internal/payload_manager.h" + +#include +#include + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace payload_manager { + +template +void eraseOwnedPtrFromMap(std::map >& m, const K& k) { + typename std::map >::iterator it = m.find(k); + if (it != m.end()) { + it->second.destroy(); + m.erase(it); + } +} + +template +class SendPayloadRunnable : public Runnable { + public: + SendPayloadRunnable(Ptr > payload_manager, + Ptr > client_proxy, + const std::vector& endpoint_ids, + ConstPtr payload) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + endpoint_ids_(endpoint_ids), + payload_(payload) {} + + void run() override { + // If successfully created, pending_payload is owned by + // PayloadManager::pending_payloads_ until + // PayloadManager::PendingPayloads::stopTrackingPayload() is invoked. + Ptr::PendingPayload> pending_payload( + createOutgoingPayload(payload_.release(), endpoint_ids_)); + if (pending_payload.isNull()) { + // TODO(tracyzhou): Add logging. + return; + } + + ScopedPtr > payload_header( + payload_manager_->createPayloadHeader( + ConstifyPtr(pending_payload->getInternalPayload()))); + + payload_manager_->send_payload_loop_runner_->loop( + MakePtr(new LoopCallable(payload_manager_, client_proxy_, + pending_payload, payload_header.get()))); + } + + private: + class LoopCallable : public Callable { + public: + LoopCallable( + Ptr > payload_manager, + Ptr > client_proxy, + Ptr::PendingPayload> pending_payload, + ConstPtr payload_header) + : next_chunk_offset_(0), + payload_manager_(payload_manager), + client_proxy_(client_proxy), + pending_payload_(pending_payload), + payload_header_(payload_header) {} + + ExceptionOr call() override { + AvailableAndUnavailableEndpoints available_and_unavailable_endpoints = + getAvailableAndUnavailableEndpoints(ConstifyPtr(pending_payload_)); + const UnavailableEndpoints& unavailable_endpoints = + available_and_unavailable_endpoints.second; + + // First, handle any non-available endpoints. + for (typename UnavailableEndpoints::const_iterator it = + unavailable_endpoints.begin(); + it != unavailable_endpoints.end(); it++) { + Ptr::EndpointInfo> endpoint_info = + *it; + payload_manager_->handleFinishedOutgoingPayload( + client_proxy_, std::vector(1, endpoint_info->getId()), + *payload_header_, next_chunk_offset_, + PayloadManager::endpointInfoStatusToPayloadStatus( + endpoint_info->getStatus())); + } + + // Update the still-active recipients of this payload. + const AvailableEndpointIds& available_endpoint_ids = + available_and_unavailable_endpoints.first; + if (available_endpoint_ids.empty()) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(false); + } + + // Check if the payload has been cancelled by the client and, if so, + // notify the remaining recipients. + if (pending_payload_->isLocallyCanceled()) { + // TODO(tracyzhou): Add logging. + payload_manager_->handleFinishedOutgoingPayload( + client_proxy_, available_endpoint_ids, *payload_header_, + next_chunk_offset_, + proto::connections::PayloadStatus::LOCAL_CANCELLATION); + return ExceptionOr(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 (AvailableEndpointIds::const_iterator it = + available_endpoint_ids.begin(); + it != available_endpoint_ids.end(); it++) { + const string& endpoint_id = *it; + pending_payload_->setOffsetForEndpoint(endpoint_id, next_chunk_offset_); + } + + ExceptionOr > next_chunk = + pending_payload_->getInternalPayload()->detachNextChunk(); + if (!next_chunk.ok()) { + if (Exception::IO == next_chunk.exception()) { + // TODO(tracyzhou): Add logging. + payload_manager_->handleFinishedOutgoingPayload( + client_proxy_, available_endpoint_ids, *payload_header_, + next_chunk_offset_, + proto::connections::PayloadStatus::LOCAL_ERROR); + return ExceptionOr(false); + } + } + + ScopedPtr > scoped_next_chunk(next_chunk.result()); + ScopedPtr > payload_chunk( + payload_manager_->createPayloadChunk(next_chunk_offset_, + scoped_next_chunk.get())); + std::vector failed_endpoint_ids = + payload_manager_->endpoint_manager_->sendPayloadChunk( + *payload_header_, *payload_chunk, available_endpoint_ids); + + // Check whether at least one endpoint failed. + if (!failed_endpoint_ids.empty()) { + payload_manager_->handleFinishedOutgoingPayload( + client_proxy_, 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 (std::vector::const_iterator it = + available_endpoint_ids.begin(); + it != available_endpoint_ids.end(); it++) { + const string& endpoint_id = *it; + if (std::find(failed_endpoint_ids.begin(), failed_endpoint_ids.end(), + endpoint_id) == failed_endpoint_ids.end()) { + payload_manager_->handleSuccessfulOutgoingChunk( + client_proxy_, endpoint_id, *payload_header_, + payload_chunk->flags(), payload_chunk->offset(), + payload_chunk->body().size()); + } + } + + // TODO(tracyzhou): Add logging. + if (scoped_next_chunk.isNull()) { + // That was the last chunk, we're outta here. + return ExceptionOr(false); + } + + next_chunk_offset_ += scoped_next_chunk->size(); + } + return ExceptionOr(true); + } + + private: + typedef std::vector AvailableEndpointIds; + typedef std::vector::EndpointInfo> > + UnavailableEndpoints; + typedef std::pair + AvailableAndUnavailableEndpoints; + + // Splits the endpoints for this payload by availability. Returns a pair of + // lists, with the first being the list of still-available endpoint IDs and + // the second the list of EndpointInfos for unavailable endpoints. + static AvailableAndUnavailableEndpoints getAvailableAndUnavailableEndpoints( + ConstPtr::PendingPayload> + pending_payload) { + AvailableEndpointIds available_endpoint_ids; + UnavailableEndpoints unavailable_endpoints; + std::vector::EndpointInfo> > + endpoints = pending_payload->getEndpoints(); + for (typename std::vector::EndpointInfo> >::const_iterator it = + endpoints.begin(); + it != endpoints.end(); it++) { + Ptr::EndpointInfo> endpoint_info = + *it; + if (PayloadManager::EndpointInfo::Status::AVAILABLE == + endpoint_info->getStatus()) { + available_endpoint_ids.push_back(endpoint_info->getId()); + } else { + unavailable_endpoints.push_back(endpoint_info); + } + } + return std::make_pair(available_endpoint_ids, unavailable_endpoints); + } + + // Keep track of the chunk offset across iterations. + std::int64_t next_chunk_offset_; + Ptr > payload_manager_; + Ptr > client_proxy_; + Ptr::PendingPayload> pending_payload_; + ConstPtr payload_header_; + }; + + // Creates and starts tracking a PendingPayload for this Payload. Returns null + // if unable to create the InternalPayload. + Ptr::PendingPayload> createOutgoingPayload( + ConstPtr payload, const std::vector& endpoint_ids) { + ScopedPtr > scoped_payload(payload); + + ScopedPtr > internal_payload( + payload_manager_->internal_payload_factory_->createOutgoing( + scoped_payload.release())); + if (internal_payload.isNull()) { + return Ptr::PendingPayload>(); + } + + std::int64_t payload_id = internal_payload->getId(); + ScopedPtr::PendingPayload> > + pending_payload( + PayloadManager::PendingPayload::createOutgoing( + internal_payload.release(), endpoint_ids)); + payload_manager_->pending_payloads_->startTrackingPayload( + payload_id, pending_payload.release()); + + return payload_manager_->pending_payloads_->getPayload(payload_id); + } + + Ptr > payload_manager_; + Ptr > client_proxy_; + std::vector endpoint_ids_; + ScopedPtr > payload_; +}; + +template +class ProcessEndpointDisconnectionRunnable : public Runnable { + public: + ProcessEndpointDisconnectionRunnable( + Ptr > payload_manager, + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + process_disconnection_barrier_(process_disconnection_barrier) {} + + void run() override { + std::vector endpoints_to_remove(1, endpoint_id_); + + // Iterate through all our payloads and look for payloads associated with + // this endpoint. + std::vector::PendingPayload> > + pending = payload_manager_->pending_payloads_->getAllPayloads(); + for (typename std::vector::PendingPayload> >::const_iterator it = pending.begin(); + it != pending.end(); it++) { + Ptr::PendingPayload> pending_payload = + *it; + Ptr::EndpointInfo> endpoint_info = + pending_payload->getEndpoint(endpoint_id_); + if (endpoint_info.isNull()) { + continue; + } + + // Stop tracking the endpoint for this payload. + pending_payload->removeEndpoints(endpoints_to_remove); + + std::int64_t payload_id = pending_payload->getId(); + std::int64_t payload_total_size = + pending_payload->getInternalPayload()->getTotalSize(); + + // If no endpoints are left for this payload, stop tracking it and close + // it. + if (pending_payload->getEndpoints().empty()) { + pending_payload = + payload_manager_->pending_payloads_->stopTrackingPayload( + pending_payload->getId()); + pending_payload->close(); + pending_payload.destroy(); + } + + // Create the payload transfer update. + PayloadTransferUpdate update( + payload_id, PayloadTransferUpdate::Status::FAILURE, + payload_total_size, endpoint_info->getOffset()); + + // Send a client notification of a payload transfer failure. + client_proxy_->onPayloadTransferUpdate(endpoint_id_, update); + } + + process_disconnection_barrier_->countDown(); + } + + private: + Ptr > payload_manager_; + Ptr > client_proxy_; + const string endpoint_id_; + Ptr process_disconnection_barrier_; +}; + +template +class SendClientCallbacksForFinishedOutgoingPayloadRunnable : public Runnable { + public: + SendClientCallbacksForFinishedOutgoingPayloadRunnable( + Ptr > payload_manager, + Ptr > client_proxy, + const std::vector& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + finished_endpoint_ids_(finished_endpoint_ids), + payload_header_(payload_header), + num_bytes_successfully_transferred_(num_bytes_successfully_transferred), + status_(status) {} + + void run() override { + // Make sure we're still tracking this payload. + Ptr::PendingPayload> pending_payload = + payload_manager_->pending_payloads_->getPayload(payload_header_.id()); + if (pending_payload.isNull()) { + return; + } + + PayloadTransferUpdate update( + payload_header_.id(), + PayloadManager::payloadStatusToTransferUpdateStatus(status_), + payload_header_.total_size(), num_bytes_successfully_transferred_); + for (std::vector::const_iterator it = + finished_endpoint_ids_.begin(); + it != finished_endpoint_ids_.end(); it++) { + const string& endpoint_id = *it; + + // Skip sending notifications if we have stopped tracking this endpoint. + if (pending_payload->getEndpoint(endpoint_id).isNull()) { + continue; + } + + // Notify the client. + client_proxy_->onPayloadTransferUpdate(endpoint_id, update); + } + + // Remove these endpoints from our tracking list for this payload. + pending_payload->removeEndpoints(finished_endpoint_ids_); + + // Close the payload and stop tracking it if no endpoints remain. + if (pending_payload->getEndpoints().empty()) { + pending_payload = + payload_manager_->pending_payloads_->stopTrackingPayload( + payload_header_.id()); + pending_payload->close(); + pending_payload.destroy(); + } + } + + private: + Ptr > payload_manager_; + Ptr > client_proxy_; + const std::vector finished_endpoint_ids_; + const PayloadTransferFrame::PayloadHeader payload_header_; + const std::int64_t num_bytes_successfully_transferred_; + const proto::connections::PayloadStatus status_; +}; + +template +class SendClientCallbacksForFinishedIncomingPayloadRunnable : public Runnable { + public: + SendClientCallbacksForFinishedIncomingPayloadRunnable( + Ptr > payload_manager, + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + payload_header_(payload_header), + offset_bytes_(offset_bytes), + status_(status) {} + + void run() override { + // Make sure we're still tracking this payload. + Ptr::PendingPayload> pending_payload = + payload_manager_->pending_payloads_->getPayload(payload_header_.id()); + if (pending_payload.isNull()) { + return; + } + + // Unless we never started tracking this payload (meaning we failed to even + // create the InternalPayload), notify the client (and close it). + PayloadTransferUpdate update( + payload_header_.id(), + PayloadManager::payloadStatusToTransferUpdateStatus(status_), + payload_header_.total_size(), offset_bytes_); + payload_manager_->notifyClientOfIncomingPayloadTransferUpdate( + client_proxy_, endpoint_id_, update, /*done_with_payload=*/true); + } + + private: + Ptr > payload_manager_; + Ptr > client_proxy_; + const string endpoint_id_; + const PayloadTransferFrame::PayloadHeader payload_header_; + const std::int64_t offset_bytes_; + const proto::connections::PayloadStatus status_; +}; + +template +class HandleSuccessfulOutgoingChunkRunnable : public Runnable { + public: + HandleSuccessfulOutgoingChunkRunnable( + Ptr > payload_manager, + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + payload_header_(payload_header), + payload_chunk_flags_(payload_chunk_flags), + payload_chunk_offset_(payload_chunk_offset), + payload_chunk_body_size_(payload_chunk_body_size) {} + + void run() override { + // Make sure we're still tracking this payload and its associated endpoint. + Ptr::PendingPayload> pending_payload = + payload_manager_->pending_payloads_->getPayload(payload_header_.id()); + if (pending_payload.isNull() || + pending_payload->getEndpoint(endpoint_id_).isNull()) { + return; + } + + // TODO(reznor): The fact that we've sent total_size bytes (which we will + // always know 1 frame before we get the SUCCESS frame), also tells us this + // is the last chunk - should we add those smarts, or just be simple and + // always have the last IN_PROGRESS have the same numbers as the following + // SUCCESS? I prefer the simplicity, but it'll look stupid if we send all + // the bytes and then remain hanging because the remote device disconnected + // at just that point, so at least consider injecting the smarts. + // TODO(reznor): Should we check whether payload_header.total_size == + // payload_chunk.offset? + bool is_last_chunk = (payload_chunk_flags_ & + PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; + PayloadTransferUpdate update( + payload_header_.id(), + is_last_chunk ? PayloadTransferUpdate::Status::SUCCESS + : PayloadTransferUpdate::Status::IN_PROGRESS, + payload_header_.total_size(), + is_last_chunk ? payload_chunk_offset_ + : payload_chunk_offset_ + payload_chunk_body_size_); + + // Notify the client. + client_proxy_->onPayloadTransferUpdate(endpoint_id_, update); + + if (is_last_chunk) { + // Stop tracking this endpoint. + pending_payload->removeEndpoints(std::vector(1, endpoint_id_)); + + // Close the payload and stop tracking it if no endpoints remain. + if (pending_payload->getEndpoints().empty()) { + pending_payload = + payload_manager_->pending_payloads_->stopTrackingPayload( + payload_header_.id()); + pending_payload->close(); + pending_payload.destroy(); + } + } + } + + private: + Ptr > payload_manager_; + Ptr > client_proxy_; + const string endpoint_id_; + const PayloadTransferFrame::PayloadHeader payload_header_; + const std::int32_t payload_chunk_flags_; + const std::int64_t payload_chunk_offset_; + const std::int64_t payload_chunk_body_size_; +}; + +template +class HandleSuccessfulIncomingChunkRunnable : public Runnable { + public: + HandleSuccessfulIncomingChunkRunnable( + Ptr > payload_manager, + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) + : payload_manager_(payload_manager), + client_proxy_(client_proxy), + endpoint_id_(endpoint_id), + payload_header_(payload_header), + payload_chunk_flags_(payload_chunk_flags), + payload_chunk_offset_(payload_chunk_offset), + payload_chunk_body_size_(payload_chunk_body_size) {} + + void run() override { + // Make sure we're still tracking this payload. + Ptr::PendingPayload> pending_payload = + payload_manager_->pending_payloads_->getPayload(payload_header_.id()); + if (pending_payload.isNull()) { + return; + } + + // TODO(reznor): The fact that we've received total_size bytes (which we + // will always know 1 frame before we get the SUCCESS frame), also tells us + // this is the last chunk - should we add those smarts, or just be simple + // and always have the last IN_PROGRESS have the same numbers as the + // following SUCCESS? I prefer the simplicity, but it'll look stupid if we + // get all the bytes and then remain hanging because the remote device + // disconnected at just that point, so at least consider injecting the + // smarts. + bool is_last_chunk = (payload_chunk_flags_ & + PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; + PayloadTransferUpdate update( + payload_header_.id(), + is_last_chunk ? PayloadTransferUpdate::Status::SUCCESS + : PayloadTransferUpdate::Status::IN_PROGRESS, + payload_header_.total_size(), + is_last_chunk ? payload_chunk_offset_ + : payload_chunk_offset_ + payload_chunk_body_size_); + + // Notify the client of this update. + payload_manager_->notifyClientOfIncomingPayloadTransferUpdate( + client_proxy_, endpoint_id_, update, is_last_chunk); + } + + private: + Ptr > payload_manager_; + Ptr > client_proxy_; + const string endpoint_id_; + const PayloadTransferFrame::PayloadHeader payload_header_; + const std::int32_t payload_chunk_flags_; + const std::int64_t payload_chunk_offset_; + const std::int64_t payload_chunk_body_size_; +}; + +template +class ProcessDataPacketRunnable : public Runnable { + public: + ProcessDataPacketRunnable(Ptr > to_client_proxy, + const string& from_endpoint_id, + ConstPtr payload) + : to_client_proxy_(to_client_proxy), + from_endpoint_id_(from_endpoint_id), + payload_(payload) {} + + void run() override { + to_client_proxy_->onPayloadReceived(from_endpoint_id_, payload_.release()); + } + + private: + Ptr > to_client_proxy_; + const string from_endpoint_id_; + ScopedPtr > payload_; +}; + +} // namespace payload_manager + +template +PayloadManager::PayloadManager( + Ptr > endpoint_manager) + : internal_payload_factory_(new InternalPayloadFactory()), + send_payload_loop_runner_(new LoopRunner("sendPayload")), + pending_payloads_(new PendingPayloads()), + bytes_payload_executor_(Platform::createSingleThreadExecutor()), + file_payload_executor_(Platform::createSingleThreadExecutor()), + stream_payload_executor_(Platform::createSingleThreadExecutor()), + payload_status_update_executor_(Platform::createSingleThreadExecutor()), + endpoint_manager_(endpoint_manager) { + endpoint_manager_->registerIncomingOfflineFrameProcessor( + V1Frame::PAYLOAD_TRANSFER, MakePtr(this)); +} + +template +PayloadManager::~PayloadManager() { + // TODO(reznor): + // logger.atDebug().log("Initiating shutdown of PayloadManager"); + + // Unregister ourselves from the IncomingOfflineFrameProcessors. + endpoint_manager_->unregisterIncomingOfflineFrameProcessor( + V1Frame::CONNECTION_RESPONSE, MakePtr(this)); + + // Stop all the ongoing Runnables (as gracefully as possible). + payload_status_update_executor_->shutdown(); + bytes_payload_executor_->shutdown(); + file_payload_executor_->shutdown(); + stream_payload_executor_->shutdown(); + + typedef Ptr::PendingPayload> + PtrPendingPayload; + + // Clear our tracked pending payloads. + std::vector pending = pending_payloads_->getAllPayloads(); + for (typename std::vector::const_iterator it = + pending.begin(); + it != pending.end(); it++) { + PtrPendingPayload pending_payload = + pending_payloads_->stopTrackingPayload((*it)->getId()); + pending_payload->close(); + pending_payload.destroy(); + } + + // TODO(reznor): + // logger.atVerbose().log("PayloadManager has shut down."); +} + +template +void PayloadManager::sendPayload( + Ptr > client_proxy, + const std::vector& endpoint_ids, ConstPtr payload) { + Ptr send_payload_executor = + getOutgoingPayloadExecutor(payload->getType()); + // The send_payload_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. + ScopedPtr > scoped_payload(payload); + if (send_payload_executor.isNull()) { + // TODO(tracyzhou): Add logging. + 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. + enqueueOutgoingPayload( + send_payload_executor, + MakePtr(new payload_manager::SendPayloadRunnable( + MakePtr(this), client_proxy, endpoint_ids, + scoped_payload.release()))); + // TODO(tracyzhou): Add logging. +} + +template +Status::Value PayloadManager::cancelPayload( + Ptr > client_proxy, std::int64_t payload_id) { + Ptr::PendingPayload> canceled_payload = + pending_payloads_->getPayload(payload_id); + if (canceled_payload.isNull()) { + // TODO(tracyzhou): Add logging. + return Status::PAYLOAD_UNKNOWN; + } + + // Mark the payload as canceled. + canceled_payload->markLocallyCanceled(); + // TODO(tracyzhou): Add logging. + + // Return SUCCESS immediately. Remaining cleanup and updates will be sent in + // sendPayload() or processIncomingOfflineFrame() + return Status::SUCCESS; +} + +template +void PayloadManager::processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) { + ScopedPtr > scoped_offline_frame(offline_frame); + const PayloadTransferFrame& payload_transfer_frame = + scoped_offline_frame->v1().payload_transfer(); + + switch (payload_transfer_frame.packet_type()) { + case PayloadTransferFrame::CONTROL: + processControlPacket(to_client_proxy, from_endpoint_id, + payload_transfer_frame); + break; + case PayloadTransferFrame::DATA: + processDataPacket(to_client_proxy, from_endpoint_id, + payload_transfer_frame); + break; + default: + // TODO(tracyzhou): Add logging. + break; + } +} + +template +void PayloadManager::processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) { + payload_status_update_executor_->execute(MakePtr( + new payload_manager::ProcessEndpointDisconnectionRunnable( + MakePtr(this), client_proxy, endpoint_id, + process_disconnection_barrier))); +} + +template +proto::connections::PayloadStatus +PayloadManager::endpointInfoStatusToPayloadStatus( + typename EndpointInfo::Status::Value status) { + switch (status) { + case EndpointInfo::Status::CANCELED: + return proto::connections::PayloadStatus::REMOTE_CANCELLATION; + case EndpointInfo::Status::ERROR: + return proto::connections::PayloadStatus::REMOTE_ERROR; + case EndpointInfo::Status::AVAILABLE: + return proto::connections::PayloadStatus::SUCCESS; + default: + // TODO(tracyzhou): Add logging. + return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; + } +} + +template +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: + // TODO(tracyzhou): Add logging. + return proto::connections::PayloadStatus::UNKNOWN_PAYLOAD_STATUS; + } +} + +template +PayloadTransferUpdate::Status::Value +PayloadManager::payloadStatusToTransferUpdateStatus( + proto::connections::PayloadStatus status) { + switch (status) { + case proto::connections::LOCAL_CANCELLATION: + case proto::connections::REMOTE_CANCELLATION: + return PayloadTransferUpdate::Status::CANCELED; + case proto::connections::SUCCESS: + return PayloadTransferUpdate::Status::SUCCESS; + default: + return PayloadTransferUpdate::Status::FAILURE; + } +} + +template +Ptr +PayloadManager::getOutgoingPayloadExecutor( + Payload::Type::Value payload_type) { + switch (payload_type) { + case Payload::Type::BYTES: + return bytes_payload_executor_.get(); + case Payload::Type::FILE: + return file_payload_executor_.get(); + case Payload::Type::STREAM: + return stream_payload_executor_.get(); + default: + return Ptr(); + } +} + +template +ConstPtr +PayloadManager::createPayloadHeader( + ConstPtr internal_payload) { + ScopedPtr > payload_header( + new PayloadTransferFrame::PayloadHeader()); + + payload_header->set_id(internal_payload->getId()); + payload_header->set_type(internal_payload->getType()); + payload_header->set_total_size(internal_payload->getTotalSize()); + + return ConstifyPtr(payload_header.release()); +} + +template +ConstPtr +PayloadManager::createPayloadChunk( + std::int64_t payload_chunk_offset, ConstPtr payload_chunk_body) { + ScopedPtr > payload_chunk( + new PayloadTransferFrame::PayloadChunk()); + + payload_chunk->set_offset(payload_chunk_offset); + if (!payload_chunk_body.isNull()) { + payload_chunk->set_body(payload_chunk_body->getData(), + payload_chunk_body->size()); + } + + // This is a null-initialized Integer, so it needs to be initialized to avoid + // inadvertent NPEs. + payload_chunk->set_flags(0); + if (payload_chunk_body.isNull()) { + payload_chunk->set_flags(payload_chunk->flags() | + PayloadTransferFrame::PayloadChunk::LAST_CHUNK); + } + + return ConstifyPtr(payload_chunk.release()); +} + +template +Ptr::PendingPayload> +PayloadManager::createIncomingPayload( + const PayloadTransferFrame& payload_transfer_frame, + const string& endpoint_id) { + ScopedPtr > internal_payload( + internal_payload_factory_->createIncoming(payload_transfer_frame)); + if (internal_payload.isNull()) { + return Ptr::PendingPayload>(); + } + + std::int64_t payload_id = internal_payload->getId(); + ScopedPtr::PendingPayload> > + pending_payload(PendingPayload::createIncoming(internal_payload.release(), + endpoint_id)); + pending_payloads_->startTrackingPayload(payload_id, + pending_payload.release()); + + return pending_payloads_->getPayload(payload_id); +} + +template +void PayloadManager::sendClientCallbacksForFinishedOutgoingPayload( + Ptr > client_proxy, + const std::vector& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status) { + payload_status_update_executor_->execute(MakePtr( + new payload_manager:: + SendClientCallbacksForFinishedOutgoingPayloadRunnable( + MakePtr(this), client_proxy, finished_endpoint_ids, + payload_header, num_bytes_successfully_transferred, status))); +} + +template +void PayloadManager::sendClientCallbacksForFinishedIncomingPayload( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status) { + payload_status_update_executor_->execute(MakePtr( + new payload_manager:: + SendClientCallbacksForFinishedIncomingPayloadRunnable( + MakePtr(this), client_proxy, endpoint_id, payload_header, + offset_bytes, status))); +} + +template +void PayloadManager::sendControlMessage( + const std::vector& 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); +} + +template +void PayloadManager::handleFinishedOutgoingPayload( + Ptr > client_proxy, + const std::vector& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status) { + sendClientCallbacksForFinishedOutgoingPayload( + client_proxy, 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: + 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 (std::vector::const_iterator it = + finished_endpoint_ids.begin(); + it != finished_endpoint_ids.end(); it++) { + endpoint_manager_->discardEndpoint(client_proxy, *it); + } + break; + case proto::connections::PayloadStatus::REMOTE_ERROR: + case proto::connections::PayloadStatus::REMOTE_CANCELLATION: + // No special handling needed for these. + break; + default: + // TODO(tracyzhou): Add logging. + break; + } +} + +template +void PayloadManager::handleFinishedIncomingPayload( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status) { + sendClientCallbacksForFinishedIncomingPayload( + client_proxy, endpoint_id, payload_header, offset_bytes, status); + + switch (status) { + case proto::connections::PayloadStatus::LOCAL_ERROR: + sendControlMessage(std::vector(1, endpoint_id), payload_header, + offset_bytes, + PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); + break; + case proto::connections::PayloadStatus::LOCAL_CANCELLATION: + sendControlMessage( + std::vector(1, endpoint_id), payload_header, offset_bytes, + PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + break; + default: + // TODO(tracyzhou): Add logging. + break; + } +} + +template +void PayloadManager::handleSuccessfulOutgoingChunk( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) { + payload_status_update_executor_->execute(MakePtr( + new payload_manager::HandleSuccessfulOutgoingChunkRunnable( + MakePtr(this), client_proxy, endpoint_id, payload_header, + payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); +} + +template +void PayloadManager::handleSuccessfulIncomingChunk( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size) { + payload_status_update_executor_->execute(MakePtr( + new payload_manager::HandleSuccessfulIncomingChunkRunnable( + MakePtr(this), client_proxy, endpoint_id, payload_header, + payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); +} + +template +void PayloadManager::processDataPacket( + Ptr > to_client_proxy, const string& from_endpoint_id, + const PayloadTransferFrame& payload_transfer_frame) { + const PayloadTransferFrame::PayloadHeader& payload_header = + payload_transfer_frame.payload_header(); + const PayloadTransferFrame::PayloadChunk& payload_chunk = + payload_transfer_frame.payload_chunk(); + // TODO(tracyzhou): Add logging. + + Ptr::PendingPayload> pending_payload; + if (payload_chunk.offset() == 0) { + pending_payload = + createIncomingPayload(payload_transfer_frame, from_endpoint_id); + if (pending_payload.isNull()) { + // TODO(tracyzhou): Add logging. + // Send the error to the remote endpoint. + sendControlMessage(std::vector(1, from_endpoint_id), + payload_header, payload_chunk.offset(), + PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR); + return; + } + + // Also, let the client know of this new incoming payload. + payload_status_update_executor_->execute( + MakePtr(new payload_manager::ProcessDataPacketRunnable( + to_client_proxy, from_endpoint_id, + pending_payload->getInternalPayload()->releasePayload()))); + // TODO(tracyzhou): Add logging. + } else { + pending_payload = pending_payloads_->getPayload(payload_header.id()); + if (pending_payload.isNull()) { + // TODO(tracyzhou): Add logging. + 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 + handleFinishedIncomingPayload( + to_client_proxy, 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()); + + Exception::Value attach_next_chunk_exception = + pending_payload->getInternalPayload()->attachNextChunk( + MakeConstPtr(new ByteArray(payload_chunk.body().data(), + payload_chunk.body().size()))); + if (Exception::NONE != attach_next_chunk_exception) { + if (Exception::IO == attach_next_chunk_exception) { + // TODO(tracyzhou): Add logging. + handleFinishedIncomingPayload( + to_client_proxy, from_endpoint_id, payload_header, + payload_chunk.offset(), + proto::connections::PayloadStatus::LOCAL_ERROR); + return; + } + } + + handleSuccessfulIncomingChunk( + to_client_proxy, from_endpoint_id, payload_header, payload_chunk.flags(), + payload_chunk.offset(), payload_chunk.body().size()); +} + +template +void PayloadManager::processControlPacket( + Ptr > to_client_proxy, const string& from_endpoint_id, + const PayloadTransferFrame& payload_transfer_frame) { + const PayloadTransferFrame::PayloadHeader& payload_header = + payload_transfer_frame.payload_header(); + const PayloadTransferFrame::ControlMessage& control_message = + payload_transfer_frame.control_message(); + Ptr::PendingPayload> pending_payload = + pending_payloads_->getPayload(payload_header.id()); + if (pending_payload.isNull()) { + // TODO(tracyzhou): Add logging. + return; + } + + switch (control_message.event()) { + case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: + if (pending_payload->isIncoming()) { + // 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_proxy, from_endpoint_id, payload_header, + control_message.offset(), + controlMessageEventToPayloadStatus(control_message.event())); + } else { + // 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_proxy, 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; + } +} + +template +void PayloadManager::notifyClientOfIncomingPayloadTransferUpdate( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferUpdate& payload_transfer_update, + bool done_with_payload) { + client_proxy->onPayloadTransferUpdate(endpoint_id, payload_transfer_update); + if (done_with_payload) { + // We're done with this payload (either received the last chunk, or had a + // failure), so remove it from the incoming payloads that we're tracking. + Ptr::PendingPayload> pending_payload = + pending_payloads_->stopTrackingPayload( + payload_transfer_update.payload_id); + pending_payload->close(); + pending_payload.destroy(); + } +} + +template +void PayloadManager::enqueueOutgoingPayload( + Ptr executor, + Ptr runnable) { + executor->execute(runnable); +} + +///////////////////////////////// EndpointInfo ///////////////////////////////// + +template +PayloadManager::EndpointInfo::EndpointInfo(string id) + : id_(id), status_(Status::AVAILABLE), offset_(0) {} + +template +typename PayloadManager::EndpointInfo::Status::Value +PayloadManager::EndpointInfo::controlMessageEventToEndpointInfoStatus( + PayloadTransferFrame::ControlMessage::EventType event) { + switch (event) { + case PayloadTransferFrame::ControlMessage::PAYLOAD_ERROR: + return Status::ERROR; + case PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED: + return Status::CANCELED; + default: + // TODO(tracyzhou): Add logging. + return Status::UNKNOWN; + } +} + +template +string PayloadManager::EndpointInfo::getId() const { + return id_; +} + +template +typename PayloadManager::EndpointInfo::Status::Value +PayloadManager::EndpointInfo::getStatus() const { + return status_; +} + +template +std::int64_t PayloadManager::EndpointInfo::getOffset() const { + return offset_; +} + +template +void PayloadManager::EndpointInfo::setStatus( + const PayloadTransferFrame::ControlMessage& control_message) { + status_ = controlMessageEventToEndpointInfoStatus(control_message.event()); +} + +template +void PayloadManager::EndpointInfo::setOffset(std::int64_t offset) { + offset_ = offset; +} + +//////////////////////////////// PendingPayload //////////////////////////////// + +template +Ptr::PendingPayload> +PayloadManager::PendingPayload::createIncoming( + Ptr internal_payload, const string& endpoint_id) { + return MakeRefCountedPtr(new PendingPayload( + internal_payload, std::vector(1, endpoint_id), true)); +} + +template +Ptr::PendingPayload> +PayloadManager::PendingPayload::createOutgoing( + Ptr internal_payload, + const std::vector& endpoint_ids) { + return MakeRefCountedPtr( + new PendingPayload(internal_payload, endpoint_ids, false)); +} + +template +PayloadManager::PendingPayload::PendingPayload( + Ptr internal_payload, + const std::vector& endpoint_ids, bool is_incoming) + : lock_(Platform::createLock()), + internal_payload_(internal_payload), + is_incoming_(is_incoming), + is_locally_cancelled_(Platform::createAtomicBoolean(false)), + endpoints_() { + for (std::vector::const_iterator it = endpoint_ids.begin(); + it != endpoint_ids.end(); it++) { + endpoints_.insert(std::make_pair(*it, MakePtr(new EndpointInfo(*it)))); + } +} + +template +PayloadManager::PendingPayload::~PendingPayload() { + for (typename EndpointsMap::iterator it = endpoints_.begin(); + it != endpoints_.end(); it++) { + it->second.destroy(); + } + endpoints_.clear(); +} + +template +std::int64_t PayloadManager::PendingPayload::getId() { + return internal_payload_->getId(); +} + +template +Ptr +PayloadManager::PendingPayload::getInternalPayload() { + return internal_payload_.get(); +} + +template +bool PayloadManager::PendingPayload::isLocallyCanceled() { + return is_locally_cancelled_->get(); +} + +template +void PayloadManager::PendingPayload::markLocallyCanceled() { + is_locally_cancelled_->set(true); +} + +template +bool PayloadManager::PendingPayload::isIncoming() { + return is_incoming_; +} + +template +std::vector::EndpointInfo> > +PayloadManager::PendingPayload::getEndpoints() const { + Synchronized s(lock_.get()); + + std::vector::EndpointInfo> > result; + for (typename EndpointsMap::const_iterator it = endpoints_.begin(); + it != endpoints_.end(); it++) { + result.push_back(it->second); + } + return result; +} + +template +Ptr::EndpointInfo> +PayloadManager::PendingPayload::getEndpoint( + const string& endpoint_id) { + Synchronized s(lock_.get()); + + typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); + if (it == endpoints_.end()) { + return Ptr::EndpointInfo>(); + } + + return it->second; +} + +template +void PayloadManager::PendingPayload::removeEndpoints( + const std::vector& endpoint_ids_to_remove) { + Synchronized s(lock_.get()); + + for (std::vector::const_iterator it = endpoint_ids_to_remove.begin(); + it != endpoint_ids_to_remove.end(); it++) { + payload_manager::eraseOwnedPtrFromMap(endpoints_, *it); + } +} + +template +void PayloadManager::PendingPayload:: + setEndpointStatusFromControlMessage( + const string& endpoint_id, + const PayloadTransferFrame::ControlMessage& control_message) { + Synchronized s(lock_.get()); + + typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); + if (it != endpoints_.end()) { + it->second->setStatus(control_message); + } +} + +template +void PayloadManager::PendingPayload::setOffsetForEndpoint( + const string& endpoint_id, std::int64_t offset) { + Synchronized s(lock_.get()); + + typename EndpointsMap::iterator it = endpoints_.find(endpoint_id); + if (it != endpoints_.end()) { + it->second->setOffset(offset); + } +} + +template +void PayloadManager::PendingPayload::close() { + internal_payload_->close(); +} + +/////////////////////////////// PendingPayloads /////////////////////////////// + +template +PayloadManager::PendingPayloads::PendingPayloads() + : lock_(Platform::createLock()), pending_payloads_() {} + +template +PayloadManager::PendingPayloads::~PendingPayloads() { + for (typename PendingPayloadsMap::iterator it = pending_payloads_.begin(); + it != pending_payloads_.end(); it++) { + it->second.destroy(); + } + pending_payloads_.clear(); +} + +template +void PayloadManager::PendingPayloads::startTrackingPayload( + std::int64_t payload_id, + Ptr::PendingPayload> pending_payload) { + Synchronized s(lock_.get()); + + pending_payloads_.insert(std::make_pair(payload_id, pending_payload)); +} + +template +Ptr::PendingPayload> +PayloadManager::PendingPayloads::stopTrackingPayload( + std::int64_t payload_id) { + Synchronized s(lock_.get()); + + typename PendingPayloadsMap::iterator it = pending_payloads_.find(payload_id); + if (it == pending_payloads_.end()) { + return Ptr::PendingPayload>(); + } + + Ptr::PendingPayload> pending_payload = + it->second; + pending_payloads_.erase(it); + + return pending_payload; +} + +template +Ptr::PendingPayload> +PayloadManager::PendingPayloads::getPayload(std::int64_t payload_id) { + Synchronized s(lock_.get()); + + typename PendingPayloadsMap::iterator it = pending_payloads_.find(payload_id); + if (it == pending_payloads_.end()) { + return Ptr::PendingPayload>(); + } + return it->second; +} + +template +std::vector::PendingPayload> > +PayloadManager::PendingPayloads::getAllPayloads() { + Synchronized s(lock_.get()); + + std::vector::PendingPayload> > result; + for (typename PendingPayloadsMap::iterator it = pending_payloads_.begin(); + it != pending_payloads_.end(); it++) { + result.push_back(it->second); + } + return result; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/payload_manager.h b/cpp/core/internal/payload_manager.h new file mode 100644 index 00000000..27949155 --- /dev/null +++ b/cpp/core/internal/payload_manager.h @@ -0,0 +1,288 @@ +#ifndef CORE_INTERNAL_PAYLOAD_MANAGER_H_ +#define CORE_INTERNAL_PAYLOAD_MANAGER_H_ + +#include +#include +#include + +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/internal_payload.h" +#include "core/internal/internal_payload_factory.h" +#include "core/internal/loop_runner.h" +#include "core/listeners.h" +#include "core/payload.h" +#include "core/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/lock.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace payload_manager { + +template +class SendPayloadRunnable; +template +class ProcessEndpointDisconnectionRunnable; +template +class SendClientCallbacksForFinishedOutgoingPayloadRunnable; +template +class SendClientCallbacksForFinishedIncomingPayloadRunnable; +template +class HandleSuccessfulOutgoingChunkRunnable; +template +class HandleSuccessfulIncomingChunkRunnable; + +} // namespace payload_manager + +template +class PayloadManager + : public EndpointManager::IncomingOfflineFrameProcessor { + public: + explicit PayloadManager(Ptr > endpoint_manager); + ~PayloadManager() override; + + void sendPayload(Ptr > client_proxy, + const std::vector& endpoint_ids, + ConstPtr payload); + Status::Value cancelPayload(Ptr > client_proxy, + std::int64_t payload_id); + + // @EndpointManagerReaderThread + void processIncomingOfflineFrame( + ConstPtr offline_frame, const string& from_endpoint_id, + Ptr > to_client_proxy, + proto::connections::Medium current_medium) override; + + // @EndpointManagerThread + void processEndpointDisconnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr process_disconnection_barrier) override; + + private: + // Information about an endpoint for a particular payload. + class EndpointInfo { + public: + // Status set for the endpoint out-of-band via a ControlMessage. + struct Status { + enum Value { UNKNOWN, AVAILABLE, CANCELED, ERROR }; + }; + + explicit EndpointInfo(string id); + + string getId() const; + typename EndpointInfo::Status::Value getStatus() const; + std::int64_t getOffset() const; + + void setStatus(const PayloadTransferFrame::ControlMessage& control_message); + void setOffset(std::int64_t offset); + + private: + static typename Status::Value controlMessageEventToEndpointInfoStatus( + PayloadTransferFrame::ControlMessage::EventType event); + + const string id_; + typename Status::Value status_; + std::int64_t offset_; + }; + + // Tracks state for an InternalPayload and the endpoints associated with it. + class PendingPayload { + public: + static Ptr createIncoming( + Ptr internal_payload, const string& endpoint_id); + static Ptr createOutgoing( + Ptr internal_payload, + const std::vector& endpoint_ids); + + ~PendingPayload(); + + std::int64_t getId(); + + Ptr getInternalPayload(); + + bool isLocallyCanceled(); + void markLocallyCanceled(); + bool isIncoming(); + + // Gets the EndpointInfo objects for the endpoints (still) associated with + // this payload. + std::vector > getEndpoints() const; + // Returns the EndpointInfo for a given endpoint ID. Returns null if the + // endpoint is not associated with this payload. + Ptr getEndpoint(const string& endpoint_id); + + // Removes the given endpoints, e.g. on error. + void removeEndpoints(const std::vector& endpoint_ids_to_remove); + + // Sets the status for a particular endpoint. + void setEndpointStatusFromControlMessage( + const string& endpoint_id, + const PayloadTransferFrame::ControlMessage& control_message); + + // Sets the offset for a particular endpoint. + void setOffsetForEndpoint(const string& endpoint_id, std::int64_t offset); + + void close(); + + private: + PendingPayload(Ptr internal_payload, + const std::vector& endpoint_ids, bool is_incoming); + + ScopedPtr > lock_; + + ScopedPtr > internal_payload_; + const bool is_incoming_; + ScopedPtr > is_locally_cancelled_; + typedef std::map > EndpointsMap; + EndpointsMap endpoints_; + }; + + // Tracks and manages PendingPayload objects in a synchronized manner. + class PendingPayloads { + public: + PendingPayloads(); + ~PendingPayloads(); + + void startTrackingPayload(std::int64_t payload_id, + Ptr pending_payload); + Ptr stopTrackingPayload(std::int64_t payload_id); + Ptr getPayload(std::int64_t payload_id); + std::vector > getAllPayloads(); + + private: + ScopedPtr > lock_; + typedef std::map > PendingPayloadsMap; + PendingPayloadsMap pending_payloads_; + }; + + template + friend class payload_manager::SendPayloadRunnable; + template + friend class payload_manager::ProcessEndpointDisconnectionRunnable; + template + friend class payload_manager:: + SendClientCallbacksForFinishedOutgoingPayloadRunnable; + template + friend class payload_manager:: + SendClientCallbacksForFinishedIncomingPayloadRunnable; + template + friend class payload_manager::HandleSuccessfulOutgoingChunkRunnable; + template + friend class payload_manager::HandleSuccessfulIncomingChunkRunnable; + + // Converts the status of an endpoint that's been set out-of-band via a remote + // ControlMessage to the PayloadStatus for handling of that endpoint-payload + // pair. + static proto::connections::PayloadStatus endpointInfoStatusToPayloadStatus( + typename EndpointInfo::Status::Value status); + // Converts a ControlMessage::EventType for a particular payload to a + // PayloadStatus. Called when we've received a ControlMessage with this event + // from a remote endpoint; thus the PayloadStatuses are REMOTE_*. + static proto::connections::PayloadStatus controlMessageEventToPayloadStatus( + PayloadTransferFrame::ControlMessage::EventType event); + static PayloadTransferUpdate::Status::Value + payloadStatusToTransferUpdateStatus(proto::connections::PayloadStatus status); + + ConstPtr createPayloadHeader( + ConstPtr internal_payload); + ConstPtr createPayloadChunk( + std::int64_t payload_chunk_offset, + ConstPtr payload_chunk_body); + + Ptr createIncomingPayload( + const PayloadTransferFrame& payload_transfer_frame, + const string& endpoint_id); + + void sendClientCallbacksForFinishedOutgoingPayload( + Ptr > client_proxy, + const std::vector& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status); + void sendClientCallbacksForFinishedIncomingPayload( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status); + + void sendControlMessage( + const std::vector& endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + PayloadTransferFrame::ControlMessage::EventType event_type); + + // Handles a finished outgoing payload for the given endpointIds. All statuses + // except for SUCCESS are handled here. + void handleFinishedOutgoingPayload( + Ptr > client_proxy, + const std::vector& finished_endpoint_ids, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t num_bytes_successfully_transferred, + proto::connections::PayloadStatus status); + void handleFinishedIncomingPayload( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int64_t offset_bytes, proto::connections::PayloadStatus status); + + void handleSuccessfulOutgoingChunk( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size); + void handleSuccessfulIncomingChunk( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferFrame::PayloadHeader& payload_header, + std::int32_t payload_chunk_flags, std::int64_t payload_chunk_offset, + std::int64_t payload_chunk_body_size); + + void processDataPacket(Ptr > to_client_proxy, + const string& from_endpoint_id, + const PayloadTransferFrame& payload_transfer_frame); + void processControlPacket(Ptr > to_client_proxy, + const string& from_endpoint_id, + const PayloadTransferFrame& payload_transfer_frame); + + // @PayloadStatusUpdateThread + void notifyClientOfIncomingPayloadTransferUpdate( + Ptr > client_proxy, const string& endpoint_id, + const PayloadTransferUpdate& payload_transfer_update, + bool done_with_payload); + + Ptr getOutgoingPayloadExecutor( + Payload::Type::Value payload_type); + + void enqueueOutgoingPayload( + Ptr executor, + Ptr runnable); + + ScopedPtr > > internal_payload_factory_; + ScopedPtr > send_payload_loop_runner_; + ScopedPtr > pending_payloads_; + + ScopedPtr > + bytes_payload_executor_; + ScopedPtr > + file_payload_executor_; + ScopedPtr > + stream_payload_executor_; + ScopedPtr > + payload_status_update_executor_; + + Ptr > endpoint_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/payload_manager.cc" + +#endif // CORE_INTERNAL_PAYLOAD_MANAGER_H_ diff --git a/cpp/core/internal/pcp.h b/cpp/core/internal/pcp.h new file mode 100644 index 00000000..427d974f --- /dev/null +++ b/cpp/core/internal/pcp.h @@ -0,0 +1,21 @@ +#ifndef CORE_INTERNAL_PCP_H_ +#define CORE_INTERNAL_PCP_H_ + +namespace location { +namespace nearby { +namespace connections { + +struct PCP { + enum Value { + UNKNOWN = 0, + P2P_STAR = 1, + P2P_CLUSTER = 2, + P2P_POINT_TO_POINT = 3, + }; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_PCP_H_ diff --git a/cpp/core/internal/pcp_handler.h b/cpp/core/internal/pcp_handler.h new file mode 100644 index 00000000..4babed34 --- /dev/null +++ b/cpp/core/internal/pcp_handler.h @@ -0,0 +1,63 @@ +#ifndef CORE_INTERNAL_PCP_HANDLER_H_ +#define CORE_INTERNAL_PCP_HANDLER_H_ + +#include + +#include "core/internal/client_proxy.h" +#include "core/internal/pcp.h" +#include "core/listeners.h" +#include "core/options.h" +#include "core/status.h" +#include "core/strategy.h" +#include "platform/port/string.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the set of methods that need to be implemented to handle the +// per-PCP-specific operations in the OfflineServiceController. +// +//

These methods are all meant to be synchronous, and should return only +// after knowing they've done what they were supposed to do (or unequivocally +// failed to do so). +template +class PCPHandler { + public: + virtual ~PCPHandler() {} + + virtual Strategy getStrategy() = 0; + virtual PCP::Value getPCP() = 0; + + virtual Status::Value startAdvertising( + Ptr > client_proxy, const string& service_id, + const string& local_endpoint_name, + const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) = 0; + virtual void stopAdvertising(Ptr > client_proxy) = 0; + + virtual Status::Value startDiscovery( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) = 0; + virtual void stopDiscovery(Ptr > client_proxy) = 0; + + virtual Status::Value requestConnection( + Ptr > client_proxy, + const string& local_endpoint_name, const string& endpoint_id, + Ptr connection_lifecycle_listener) = 0; + virtual Status::Value acceptConnection( + Ptr > clientProxy, const string& endpoint_id, + Ptr payload_listener) = 0; + virtual Status::Value rejectConnection( + Ptr > client_proxy, const string& endpoint_id) = 0; + + virtual proto::connections::Medium getBandwidthUpgradeMedium() = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_PCP_HANDLER_H_ diff --git a/cpp/core/internal/pcp_manager.cc b/cpp/core/internal/pcp_manager.cc new file mode 100644 index 00000000..50500e2e --- /dev/null +++ b/cpp/core/internal/pcp_manager.cc @@ -0,0 +1,160 @@ +#include "core/internal/pcp_manager.h" + +#include "core/internal/p2p_cluster_pcp_handler.h" +#include "core/internal/p2p_point_to_point_pcp_handler.h" +#include "core/internal/p2p_star_pcp_handler.h" + +namespace location { +namespace nearby { +namespace connections { + +template +PCPManager::PCPManager( + Ptr > medium_manager, + Ptr > endpoint_channel_manager, + Ptr > endpoint_manager, + Ptr > bandwidth_upgrade_manager) + : pcp_handlers_(), current_pcp_handler_() { + pcp_handlers_[PCP::P2P_CLUSTER] = MakePtr(new P2PClusterPCPHandler( + medium_manager, endpoint_manager, endpoint_channel_manager, + bandwidth_upgrade_manager)); + pcp_handlers_[PCP::P2P_STAR] = MakePtr(new P2PStarPCPHandler( + medium_manager, endpoint_manager, endpoint_channel_manager, + bandwidth_upgrade_manager)); + pcp_handlers_[PCP::P2P_POINT_TO_POINT] = + MakePtr(new P2PPointToPointPCPHandler( + medium_manager, endpoint_manager, endpoint_channel_manager, + bandwidth_upgrade_manager)); +} + +template +PCPManager::~PCPManager() { + // TODO(tracyzhou): Add logging. + + // clear() instead of destroy() because this is just a reference -- the real + // object will be destroyed in the loop below. + current_pcp_handler_.clear(); + + for (typename PCPHandlersMap::iterator it = pcp_handlers_.begin(); + it != pcp_handlers_.end(); it++) { + it->second.destroy(); + } + pcp_handlers_.clear(); +} + +template +Status::Value PCPManager::startAdvertising( + Ptr > client_proxy, const string& endpoint_name, + const string& service_id, const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) { + if (!setCurrentPCPHandler(advertising_options.strategy)) { + return Status::ERROR; + } + + return current_pcp_handler_->startAdvertising( + client_proxy, service_id, endpoint_name, advertising_options, + connection_lifecycle_listener); +} + +template +void PCPManager::stopAdvertising( + Ptr > client_proxy) { + if (!current_pcp_handler_.isNull()) { + current_pcp_handler_->stopAdvertising(client_proxy); + } +} + +template +Status::Value PCPManager::startDiscovery( + Ptr > client_proxy, const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) { + if (!setCurrentPCPHandler(discovery_options.strategy)) { + return Status::ERROR; + } + + return current_pcp_handler_->startDiscovery( + client_proxy, service_id, discovery_options, discovery_listener); +} + +template +void PCPManager::stopDiscovery( + Ptr > client_proxy) { + if (!current_pcp_handler_.isNull()) { + current_pcp_handler_->stopDiscovery(client_proxy); + } +} + +template +Status::Value PCPManager::requestConnection( + Ptr > client_proxy, const string& endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener) { + if (current_pcp_handler_.isNull()) { + return Status::OUT_OF_ORDER_API_CALL; + } + + return current_pcp_handler_->requestConnection( + client_proxy, endpoint_name, endpoint_id, connection_lifecycle_listener); +} + +template +Status::Value PCPManager::acceptConnection( + Ptr > client_proxy, const string& endpoint_id, + Ptr payload_listener) { + if (current_pcp_handler_.isNull()) { + return Status::OUT_OF_ORDER_API_CALL; + } + + return current_pcp_handler_->acceptConnection(client_proxy, endpoint_id, + payload_listener); +} + +template +Status::Value PCPManager::rejectConnection( + Ptr > client_proxy, const string& endpoint_id) { + if (current_pcp_handler_.isNull()) { + return Status::OUT_OF_ORDER_API_CALL; + } + + return current_pcp_handler_->rejectConnection(client_proxy, endpoint_id); +} + +template +proto::connections::Medium PCPManager::getBandwidthUpgradeMedium() { + if (current_pcp_handler_.isNull()) { + return proto::connections::Medium::UNKNOWN_MEDIUM; + } + + return current_pcp_handler_->getBandwidthUpgradeMedium(); +} + +template +bool PCPManager::setCurrentPCPHandler(const Strategy& strategy) { + current_pcp_handler_ = getPCPHandler(deducePCP(strategy)); + + return !current_pcp_handler_.isNull(); +} + +template +PCP::Value PCPManager::deducePCP(const Strategy& strategy) { + if (Strategy::kP2PCluster == strategy) { + return PCP::P2P_CLUSTER; + } else if (Strategy::kP2PStar == strategy) { + return PCP::P2P_STAR; + } else if (Strategy::kP2PPointToPoint == strategy) { + return PCP::P2P_POINT_TO_POINT; + } else { + // TODO(tracyzhou): Add logging. + return PCP::UNKNOWN; + } +} + +template +Ptr > PCPManager::getPCPHandler(PCP::Value pcp) { + return pcp_handlers_[pcp]; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/pcp_manager.h b/cpp/core/internal/pcp_manager.h new file mode 100644 index 00000000..8bb77a32 --- /dev/null +++ b/cpp/core/internal/pcp_manager.h @@ -0,0 +1,77 @@ +#ifndef CORE_INTERNAL_PCP_MANAGER_H_ +#define CORE_INTERNAL_PCP_MANAGER_H_ + +#include + +#include "core/internal/bandwidth_upgrade_manager.h" +#include "core/internal/client_proxy.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/endpoint_manager.h" +#include "core/internal/medium_manager.h" +#include "core/internal/pcp_handler.h" +#include "core/listeners.h" +#include "core/options.h" +#include "core/status.h" +#include "core/strategy.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all known PCPHandler implementations, delegating operations to the +// appropriate one as per the parameters passed in. +// +//

This will only ever be used by the OfflineServiceController, which has all +// of its entrypoints invoked serially, so there's no synchronization needed. +template +class PCPManager { + public: + PCPManager(Ptr > medium_manager, + Ptr > endpoint_channel_manager, + Ptr > endpoint_manager, + Ptr > bandwidth_upgrade_manager); + ~PCPManager(); + + Status::Value startAdvertising( + Ptr > client_proxy, const string& endpoint_name, + const string& service_id, const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener); + void stopAdvertising(Ptr > client_proxy); + + Status::Value startDiscovery(Ptr > client_proxy, + const string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener); + void stopDiscovery(Ptr > client_proxy); + + Status::Value requestConnection( + Ptr > client_proxy, const string& endpoint_name, + const string& endpoint_id, + Ptr connection_lifecycle_listener); + Status::Value acceptConnection(Ptr > client_proxy, + const string& endpoint_id, + Ptr payload_listener); + Status::Value rejectConnection(Ptr > client_proxy, + const string& endpoint_id); + + proto::connections::Medium getBandwidthUpgradeMedium(); + + private: + bool setCurrentPCPHandler(const Strategy& strategy); + PCP::Value deducePCP(const Strategy& strategy); + Ptr > getPCPHandler(PCP::Value pcp); + + typedef std::map > > PCPHandlersMap; + PCPHandlersMap pcp_handlers_; + Ptr > current_pcp_handler_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/pcp_manager.cc" + +#endif // CORE_INTERNAL_PCP_MANAGER_H_ diff --git a/cpp/core/internal/service_controller.h b/cpp/core/internal/service_controller.h new file mode 100644 index 00000000..05f37071 --- /dev/null +++ b/cpp/core/internal/service_controller.h @@ -0,0 +1,67 @@ +#ifndef CORE_INTERNAL_SERVICE_CONTROLLER_H_ +#define CORE_INTERNAL_SERVICE_CONTROLLER_H_ + +#include +#include + +#include "core/internal/client_proxy.h" +#include "core/listeners.h" +#include "core/options.h" +#include "core/payload.h" +#include "core/status.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +template +class ServiceController { + public: + virtual ~ServiceController() {} + + virtual Status::Value startAdvertising( + Ptr > client_proxy, + const std::string& endpoint_name, const std::string& service_id, + const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) = 0; + virtual void stopAdvertising(Ptr > client_proxy) = 0; + + virtual Status::Value startDiscovery( + Ptr > client_proxy, const std::string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) = 0; + virtual void stopDiscovery(Ptr > client_proxy) = 0; + + virtual Status::Value requestConnection( + Ptr > client_proxy, + const std::string& endpoint_name, const std::string& endpoint_id, + Ptr connection_lifecycle_listener) = 0; + virtual Status::Value acceptConnection( + Ptr > client_proxy, const std::string& endpoint_id, + Ptr payload_listener) = 0; + virtual Status::Value rejectConnection( + Ptr > client_proxy, + const std::string& endpoint_id) = 0; + + virtual void initiateBandwidthUpgrade( + Ptr > client_proxy, + const std::string& endpoint_id) = 0; + + virtual void sendPayload(Ptr > client_proxy, + const std::vector& endpoint_ids, + ConstPtr payload) = 0; + + virtual Status::Value cancelPayload(Ptr > client_proxy, + std::int64_t payload_id) = 0; + + virtual void disconnectFromEndpoint(Ptr > client_proxy, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_SERVICE_CONTROLLER_H_ diff --git a/cpp/core/internal/service_controller_router.cc b/cpp/core/internal/service_controller_router.cc new file mode 100644 index 00000000..41428368 --- /dev/null +++ b/cpp/core/internal/service_controller_router.cc @@ -0,0 +1,750 @@ +#include "core/internal/service_controller_router.h" + +#include "core/internal/offline_service_controller.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace service_controller_router { + +// Base class for the following Runnable classes. They all need a +// ServiceControllerRouter object and a ClientProxy object. +// ServiceControllerRouter is kept as a reference because the passed in +// Ptr > should outlive it. +template +class ServiceControllerRouterRunnable : public Runnable { + protected: + ServiceControllerRouterRunnable( + Ptr > service_controller_router, + Ptr > client_proxy) + : service_controller_router_(service_controller_router), + client_proxy_(client_proxy) {} + + Ptr > service_controller_router_; + Ptr > client_proxy_; +}; + +template +class StartAdvertisingRunnable + : public ServiceControllerRouterRunnable { + public: + StartAdvertisingRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr start_advertising_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(start_advertising_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + Status::Value status = + this->service_controller_router_->acquireServiceControllerForClient( + this->client_proxy_, params_->advertising_options.strategy); + if (Status::SUCCESS != status) { + result_listener->onResult(status); + return; + } + + if (this->client_proxy_->isAdvertising()) { + result_listener->onResult(Status::ALREADY_ADVERTISING); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->startAdvertising(this->client_proxy_, params_->name, + params_->service_id, + params_->advertising_options, + params_->connection_lifecycle_listener)); + } + + private: + ScopedPtr > params_; +}; + +template +class StopAdvertisingRunnable + : public ServiceControllerRouterRunnable { + public: + StopAdvertisingRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr stop_advertising_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(stop_advertising_params) {} + + void run() override { + if (this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_) && + this->client_proxy_->isAdvertising()) { + this->service_controller_router_->current_service_controller_ + ->stopAdvertising(this->client_proxy_); + } + } + + private: + ScopedPtr > params_; +}; + +template +class StartDiscoveryRunnable + : public ServiceControllerRouterRunnable { + public: + StartDiscoveryRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr start_discovery_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(start_discovery_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + Status::Value status = + this->service_controller_router_->acquireServiceControllerForClient( + this->client_proxy_, params_->discovery_options.strategy); + if (Status::SUCCESS != status) { + result_listener->onResult(status); + return; + } + + if (this->client_proxy_->isDiscovering()) { + result_listener->onResult(Status::ALREADY_DISCOVERING); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->startDiscovery(this->client_proxy_, params_->service_id, + params_->discovery_options, + params_->discovery_listener)); + } + + private: + ScopedPtr > params_; +}; + +template +class StopDiscoveryRunnable : public ServiceControllerRouterRunnable { + public: + StopDiscoveryRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr stop_discovery_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(stop_discovery_params) {} + + void run() override { + if (this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_) && + this->client_proxy_->isDiscovering()) { + this->service_controller_router_->current_service_controller_ + ->stopDiscovery(this->client_proxy_); + } + } + + private: + ScopedPtr > params_; +}; + +template +class SendConnectionRequestRunnable + : public ServiceControllerRouterRunnable { + public: + SendConnectionRequestRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr request_connection_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(request_connection_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + const string& remote_endpoint_id = params_->remote_endpoint_id; + + if (this->client_proxy_->hasPendingConnectionToEndpoint( + remote_endpoint_id) || + this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { + result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->requestConnection(this->client_proxy_, params_->name, + remote_endpoint_id, + params_->connection_lifecycle_listener)); + } + + private: + ScopedPtr > params_; +}; + +template +class AcceptConnectionRequestRunnable + : public ServiceControllerRouterRunnable { + public: + AcceptConnectionRequestRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr accept_connection_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(accept_connection_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + const string& remote_endpoint_id = params_->remote_endpoint_id; + + if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { + result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); + return; + } + + if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) { + // TODO(tracyzhou): logging + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->acceptConnection(this->client_proxy_, remote_endpoint_id, + params_->payload_listener)); + } + + private: + ScopedPtr > params_; +}; + +template +class RejectConnectionRequestRunnable + : public ServiceControllerRouterRunnable { + public: + RejectConnectionRequestRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr reject_connection_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(reject_connection_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + const string& remote_endpoint_id = params_->remote_endpoint_id; + + if (this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id)) { + result_listener->onResult(Status::ALREADY_CONNECTED_TO_ENDPOINT); + return; + } + + if (this->client_proxy_->hasLocalEndpointResponded(remote_endpoint_id)) { + // TODO(tracyzhou): logging + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->rejectConnection(this->client_proxy_, remote_endpoint_id)); + } + + private: + ScopedPtr > params_; +}; + +template +class InitiateBandwidthUpgradeRunnable + : public ServiceControllerRouterRunnable { + public: + InitiateBandwidthUpgradeRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr + initiate_bandwidth_upgrade_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(initiate_bandwidth_upgrade_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_) || + !this->client_proxy_->isConnectedToEndpoint( + params_->remote_endpoint_id)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + this->service_controller_router_->current_service_controller_ + ->initiateBandwidthUpgrade(this->client_proxy_, + params_->remote_endpoint_id); + + // The caller can listen to + // ConnectionLifecycleListener.onBandwidthChanged() to determine success. + result_listener->onResult(Status::SUCCESS); + } + + private: + ScopedPtr > params_; +}; + +template +class SendPayloadRunnable : public ServiceControllerRouterRunnable { + public: + SendPayloadRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr send_payload_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(send_payload_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + if (!ServiceControllerRouter:: + clientHasConnectionToAtLeastOneEndpoint( + this->client_proxy_, params_->remote_endpoint_ids)) { + result_listener->onResult(Status::ENDPOINT_UNKNOWN); + return; + } + + this->service_controller_router_->current_service_controller_->sendPayload( + this->client_proxy_, params_->remote_endpoint_ids, params_->payload); + + // At this point, we've queued up the send Payload request with the + // ServiceController; any further failures (e.g. one of the endpoints is + // unknown, goes away, or otherwise fails) will be returned to the client + // as a PayloadTransferUpdate. + result_listener->onResult(Status::SUCCESS); + } + + private: + ScopedPtr > params_; +}; + +template +class CancelPayloadRunnable : public ServiceControllerRouterRunnable { + public: + CancelPayloadRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr cancel_payload_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(cancel_payload_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + result_listener->onResult(Status::OUT_OF_ORDER_API_CALL); + return; + } + + result_listener->onResult( + this->service_controller_router_->current_service_controller_ + ->cancelPayload(this->client_proxy_, params_->payload_id)); + } + + private: + ScopedPtr > params_; +}; + +template +class DisconnectFromEndpointRunnable + : public ServiceControllerRouterRunnable { + public: + DisconnectFromEndpointRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr disconnect_from_endpoint_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(disconnect_from_endpoint_params) {} + + void run() override { + if (this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + const string& remote_endpoint_id = params_->remote_endpoint_id; + + if (!this->client_proxy_->isConnectedToEndpoint(remote_endpoint_id) && + !this->client_proxy_->hasPendingConnectionToEndpoint( + remote_endpoint_id)) { + return; + } + this->service_controller_router_->current_service_controller_ + ->disconnectFromEndpoint(this->client_proxy_, remote_endpoint_id); + } + } + + private: + ScopedPtr > params_; +}; + +template +class StopAllEndpointsRunnable + : public ServiceControllerRouterRunnable { + public: + StopAllEndpointsRunnable( + Ptr > service_controller_router, + Ptr > client_proxy, + ConstPtr stop_all_endpoints_params) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy), + params_(stop_all_endpoints_params) {} + + void run() override { + ScopedPtr > result_listener(params_->result_listener); + + if (this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + this->service_controller_router_->doneWithStrategySessionForClient( + this->client_proxy_); + } + result_listener->onResult(Status::SUCCESS); + } + + private: + ScopedPtr > params_; +}; + +template +class ClientDisconnectingRunnable + : public ServiceControllerRouterRunnable { + public: + ClientDisconnectingRunnable( + Ptr> service_controller_router, + Ptr> client_proxy) + : ServiceControllerRouterRunnable(service_controller_router, + client_proxy) {} + + void run() override { + if (!this->service_controller_router_->clientHasAquiredServiceController( + this->client_proxy_)) { + return; + } + + this->service_controller_router_->doneWithStrategySessionForClient( + this->client_proxy_); + + // Log the completion of this client's connection. + // TODO(tracyzhou): Add logging. + } +}; + +} // namespace service_controller_router + +template +ServiceControllerRouter::ServiceControllerRouter() + : current_service_controller_clients_(), + current_service_controller_(new OfflineServiceController()), + current_strategy_(), + serializer_(Platform::createSingleThreadExecutor()) {} + +template +ServiceControllerRouter::~ServiceControllerRouter() { + // TODO(tracyzhou): Add logging. + + // And make sure that cleanup is the last thing we do. + serializer_->shutdown(); + + current_service_controller_.destroy(); + current_strategy_.destroy(); + current_service_controller_clients_.clear(); +} + +template +void ServiceControllerRouter::startAdvertising( + Ptr > client_proxy, + ConstPtr start_advertising_params) { + routeToServiceController( + MakePtr(new service_controller_router::StartAdvertisingRunnable( + MakePtr(this), client_proxy, start_advertising_params))); +} + +template +void ServiceControllerRouter::stopAdvertising( + Ptr > client_proxy, + ConstPtr stop_advertising_params) { + routeToServiceController( + MakePtr(new service_controller_router::StopAdvertisingRunnable( + MakePtr(this), client_proxy, stop_advertising_params))); +} + +template +void ServiceControllerRouter::startDiscovery( + Ptr > client_proxy, + ConstPtr start_discovery_params) { + routeToServiceController( + MakePtr(new service_controller_router::StartDiscoveryRunnable( + MakePtr(this), client_proxy, start_discovery_params))); +} + +template +void ServiceControllerRouter::stopDiscovery( + Ptr > client_proxy, + ConstPtr stop_discovery_params) { + routeToServiceController( + MakePtr(new service_controller_router::StopDiscoveryRunnable( + MakePtr(this), client_proxy, stop_discovery_params))); +} + +template +void ServiceControllerRouter::requestConnection( + Ptr > client_proxy, + ConstPtr request_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::SendConnectionRequestRunnable( + MakePtr(this), client_proxy, request_connection_params))); +} + +template +void ServiceControllerRouter::acceptConnection( + Ptr > client_proxy, + ConstPtr accept_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::AcceptConnectionRequestRunnable( + MakePtr(this), client_proxy, accept_connection_params))); +} + +template +void ServiceControllerRouter::rejectConnection( + Ptr > client_proxy, + ConstPtr reject_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::RejectConnectionRequestRunnable( + MakePtr(this), client_proxy, reject_connection_params))); +} + +template +void ServiceControllerRouter::initiateBandwidthUpgrade( + Ptr > client_proxy, + ConstPtr + initiate_bandwidth_upgrade_params) { + routeToServiceController(MakePtr( + new service_controller_router::InitiateBandwidthUpgradeRunnable( + MakePtr(this), client_proxy, initiate_bandwidth_upgrade_params))); +} + +template +void ServiceControllerRouter::sendPayload( + Ptr > client_proxy, + ConstPtr send_payload_params) { + routeToServiceController( + MakePtr(new service_controller_router::SendPayloadRunnable( + MakePtr(this), client_proxy, send_payload_params))); +} + +template +void ServiceControllerRouter::cancelPayload( + Ptr > client_proxy, + ConstPtr cancel_payload_params) { + routeToServiceController( + MakePtr(new service_controller_router::CancelPayloadRunnable( + MakePtr(this), client_proxy, cancel_payload_params))); +} + +template +void ServiceControllerRouter::disconnectFromEndpoint( + Ptr > client_proxy, + ConstPtr disconnect_from_endpoint_params) { + routeToServiceController(MakePtr( + new service_controller_router::DisconnectFromEndpointRunnable( + MakePtr(this), client_proxy, disconnect_from_endpoint_params))); +} + +template +void ServiceControllerRouter::stopAllEndpoints( + Ptr > client_proxy, + ConstPtr stop_all_endpoint_params) { + routeToServiceController( + MakePtr(new service_controller_router::StopAllEndpointsRunnable( + MakePtr(this), client_proxy, stop_all_endpoint_params))); +} + +template +void ServiceControllerRouter::clientDisconnecting( + Ptr> client_proxy) { + routeToServiceController(MakePtr( + new service_controller_router::ClientDisconnectingRunnable( + MakePtr(this), client_proxy))); +} + +template +Status::Value +ServiceControllerRouter::acquireServiceControllerForClient( + Ptr > client_proxy, const Strategy& strategy) { + if (current_strategy_.isNull()) { + // Case 1: There is no existing Strategy at all. + + // Set everything up for the first time. + Status::Value status = updateCurrentServiceControllerAndStrategy(strategy); + if (status != Status::SUCCESS) { + return status; + } + current_service_controller_clients_.insert(client_proxy); + return Status::SUCCESS; + } else if (strategy == *current_strategy_) { + // Case 2: The existing Strategy matches. + + // The new client just needs to be added to the set of clients using the + // current ServiceController. + current_service_controller_clients_.insert(client_proxy); + return Status::SUCCESS; + } else { + // Case 3: The existing Strategy doesn't match. + + // It's only safe for a client to cause a switch if it's the only client + // using the current ServiceController. + bool is_the_only_client_of_service_controller = + current_service_controller_clients_.size() == 1 && + current_service_controller_clients_.find(client_proxy) != + current_service_controller_clients_.end(); + if (!is_the_only_client_of_service_controller) { + // TODO(tracyzhou): logging + return Status::ALREADY_HAVE_ACTIVE_STRATEGY; + } + + // If the client still has connected endpoints, they must disconnect before + // they can switch. + if (!client_proxy->getConnectedEndpoints().empty()) { + // TODO(tracyzhou): logging + return Status::OUT_OF_ORDER_API_CALL; + } + + // By this point, it's safe to switch the Strategy and ServiceController + // (and since it's the only client, there's no need to add it to the set of + // clients using the current ServiceController). + return updateCurrentServiceControllerAndStrategy(strategy); + } +} + +template +bool ServiceControllerRouter::clientHasAquiredServiceController( + Ptr > client_proxy) { + return (current_service_controller_clients_.find(client_proxy) != + current_service_controller_clients_.end()); +} + +template +void ServiceControllerRouter::releaseServiceControllerForClient( + Ptr > client_proxy) { + current_service_controller_clients_.erase(client_proxy); + + if (current_service_controller_clients_.empty()) { + current_service_controller_.destroy(); + current_strategy_.destroy(); + } +} + +/** Clean up all state for this client. The client is now free to switch + * strategies. */ +template +void ServiceControllerRouter::doneWithStrategySessionForClient( + Ptr > client_proxy) { + // Disconnect from all the connected endpoints tied to this clientProxy. + std::vector pending_connected_endpoints = + client_proxy->getPendingConnectedEndpoints(); + + for (std::vector::iterator it = pending_connected_endpoints.begin(); + it != pending_connected_endpoints.end(); it++) { + current_service_controller_->disconnectFromEndpoint(client_proxy, *it); + } + + std::vector connected_endpoints = + client_proxy->getConnectedEndpoints(); + + for (std::vector::iterator it = connected_endpoints.begin(); + it != connected_endpoints.end(); it++) { + current_service_controller_->disconnectFromEndpoint(client_proxy, *it); + } + + // Stop any advertising and discovery that may be underway due to this + // clientProxy. + current_service_controller_->stopAdvertising(client_proxy); + current_service_controller_->stopDiscovery(client_proxy); + + // Finally, clear all state maintained by this clientProxy. + client_proxy->reset(); + + releaseServiceControllerForClient(client_proxy); +} + +template +void ServiceControllerRouter::routeToServiceController( + Ptr runnable) { + serializer_->execute(runnable); +} + +template +bool ServiceControllerRouter::clientHasConnectionToAtLeastOneEndpoint( + Ptr > client_proxy, + const std::vector& remote_endpoint_ids) { + for (std::vector::const_iterator it = remote_endpoint_ids.begin(); + it != remote_endpoint_ids.end(); it++) { + if (client_proxy->isConnectedToEndpoint(*it)) { + return true; + } + } + return false; +} + +template +Status::Value +ServiceControllerRouter::updateCurrentServiceControllerAndStrategy( + const Strategy& strategy) { + if (!strategy.isValid()) { + // TODO(tracyzhou): logging + return Status::ERROR; + } + + current_service_controller_.destroy(); + current_service_controller_ = + MakePtr(new OfflineServiceController()); + current_strategy_.destroy(); + current_strategy_ = MakePtr(new Strategy(strategy)); + + return Status::SUCCESS; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/service_controller_router.h b/cpp/core/internal/service_controller_router.h new file mode 100644 index 00000000..cf8e7d88 --- /dev/null +++ b/cpp/core/internal/service_controller_router.h @@ -0,0 +1,151 @@ +#ifndef CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ +#define CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ + +#include +#include + +#include "core/internal/client_proxy.h" +#include "core/internal/service_controller.h" +#include "core/params.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace service_controller_router { + +template +class StartAdvertisingRunnable; +template +class StopAdvertisingRunnable; +template +class StartDiscoveryRunnable; +template +class StopDiscoveryRunnable; +template +class SendConnectionRequestRunnable; +template +class AcceptConnectionRequestRunnable; +template +class RejectConnectionRequestRunnable; +template +class InitiateBandwidthUpgradeRunnable; +template +class SendPayloadRunnable; +template +class CancelPayloadRunnable; +template +class DisconnectFromEndpointRunnable; +template +class StopAllEndpointsRunnable; +template +class ClientDisconnectingRunnable; + +} // namespace service_controller_router + +template +class ServiceControllerRouter { + public: + ServiceControllerRouter(); + ~ServiceControllerRouter(); + + void startAdvertising( + Ptr > client_proxy, + ConstPtr start_advertising_params); + void stopAdvertising(Ptr > client_proxy, + ConstPtr stop_advertising_params); + + void startDiscovery(Ptr > client_proxy, + ConstPtr start_discovery_params); + void stopDiscovery(Ptr > client_proxy, + ConstPtr stop_discovery_params); + + void requestConnection( + Ptr > client_proxy, + ConstPtr request_connection_params); + void acceptConnection( + Ptr > client_proxy, + ConstPtr accept_connection_params); + void rejectConnection( + Ptr > client_proxy, + ConstPtr reject_connection_params); + + void initiateBandwidthUpgrade(Ptr > client_proxy, + ConstPtr + initiate_bandwidth_upgrade_params); + + void sendPayload(Ptr > client_proxy, + ConstPtr send_payload_params); + void cancelPayload(Ptr > client_proxy, + ConstPtr cancel_payload_params); + + void disconnectFromEndpoint( + Ptr > client_proxy, + ConstPtr disconnect_from_endpoint_params); + void stopAllEndpoints( + Ptr > client_proxy, + ConstPtr stop_all_endpoint_params); + + void clientDisconnecting(Ptr > client_proxy); + + private: + template + friend class service_controller_router::StartAdvertisingRunnable; + template + friend class service_controller_router::StopAdvertisingRunnable; + template + friend class service_controller_router::StartDiscoveryRunnable; + template + friend class service_controller_router::StopDiscoveryRunnable; + template + friend class service_controller_router::SendConnectionRequestRunnable; + template + friend class service_controller_router::AcceptConnectionRequestRunnable; + template + friend class service_controller_router::RejectConnectionRequestRunnable; + template + friend class service_controller_router::InitiateBandwidthUpgradeRunnable; + template + friend class service_controller_router::SendPayloadRunnable; + template + friend class service_controller_router::CancelPayloadRunnable; + template + friend class service_controller_router::DisconnectFromEndpointRunnable; + template + friend class service_controller_router::StopAllEndpointsRunnable; + template + friend class service_controller_router::ClientDisconnectingRunnable; + + static bool clientHasConnectionToAtLeastOneEndpoint( + Ptr > client_proxy, + const std::vector& remote_endpoint_ids); + + void routeToServiceController(Ptr runnable); + + Status::Value acquireServiceControllerForClient( + Ptr > client_proxy, const Strategy& strategy); + bool clientHasAquiredServiceController( + Ptr > client_proxy); + void releaseServiceControllerForClient( + Ptr > client_proxy); + void doneWithStrategySessionForClient( + Ptr > client_proxy); + Status::Value updateCurrentServiceControllerAndStrategy( + const Strategy& strategy); + + std::set > > current_service_controller_clients_; + Ptr > current_service_controller_; + Ptr current_strategy_; + ScopedPtr > serializer_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/service_controller_router.cc" + +#endif // CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc new file mode 100644 index 00000000..7df38ed7 --- /dev/null +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -0,0 +1,63 @@ +#include "core/internal/wifi_lan_upgrade_handler.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace wifi_lan_upgrade_handler { + +template +class OnIncomingWifiConnectionRunnable : public Runnable { + public: + void run() {} +}; + +} // namespace wifi_lan_upgrade_handler + +template +WifiLanUpgradeHandler::WifiLanUpgradeHandler( + Ptr > medium_manager, + Ptr > endpoint_channel_manager) + : BaseBandwidthUpgradeHandler(endpoint_channel_manager), + medium_manager_(medium_manager) {} + +template +WifiLanUpgradeHandler::~WifiLanUpgradeHandler() {} + +template +proto::connections::Medium WifiLanUpgradeHandler::getUpgradeMedium() { + return proto::connections::Medium::WIFI_LAN; +} + +template +void WifiLanUpgradeHandler::revertImpl() {} + +template +void WifiLanUpgradeHandler::onIncomingWifiConnection( + Ptr socket) {} + +// TODO(ahlee): This will differ from the Java code (previously threw an +// UpgradeException). Leaving the return type simple for the skeleton - I'll +// switch to a pair if the result enum is needed. +template +ConstPtr +WifiLanUpgradeHandler::initializeUpgradedMediumForEndpoint( + const string& endpoint_id) { + return ConstPtr(); +} + +// TODO(ahlee): This will differ from the Java code (previously threw an +// exception). +template +Ptr +WifiLanUpgradeHandler::createUpgradedEndpointChannel( + const string& endpoint_id, + ConstPtr + upgrade_path_info) { + return Ptr(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.h b/cpp/core/internal/wifi_lan_upgrade_handler.h new file mode 100644 index 00000000..26781c47 --- /dev/null +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -0,0 +1,93 @@ +#ifndef CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_ +#define CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_ + +#include "core/internal/base_bandwidth_upgrade_handler.h" +#include "core/internal/endpoint_channel_manager.h" +#include "core/internal/medium_manager.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/socket.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace wifi_lan_upgrade_handler { + +template +class OnIncomingWifiConnectionRunnable; + +} // namespace wifi_lan_upgrade_handler + +// Manages the WIFI_LAN-specific methods needed to upgrade an EndpointChannel +template +class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { + // TODO(ahlee): Uncomment when WIFI_LAN plumbing is done. + // public MediumManager::IncomingWifiConnectionProcessor { + public: + WifiLanUpgradeHandler( + Ptr > medium_manager_, + Ptr > endpoint_channel_manager); + ~WifiLanUpgradeHandler(); + + void onIncomingWifiConnection(Ptr socket); + + protected: + // @BandwidthUpgradeHandlerThread + ConstPtr initializeUpgradedMediumForEndpoint( + const string& endpoint_id); + // @BandwidthUpgradeHandlerThread + Ptr createUpgradedEndpointChannel( + const string& endpoint_id, + ConstPtr + upgrade_path_info); + // TODO(ahlee): Change the java counterparts of these methods to private. + proto::connections::Medium getUpgradeMedium(); + // @BandwidthUpgradeHandlerThread + void revertImpl(); + + private: + class IncomingWifiLanSocketConnection + : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { + public: + IncomingWifiLanSocketConnection(Ptr socket) + : new_endpoint_channel_(Ptr()), + // TODO(ahlee): Uncomment when plumbing for WIFI_LAN is done. + // new_endpoint_channel_(getEndpointChannelManager() + // .createOutgoingWifiLanEndpointChannel(socket)), + wifi_socket_(socket) {} + // TODO(ahlee): This is only used for logging which is not currently + // implemented. If we want to match the Java code in the future, we'll need + // to add toString() to socket.h. + string socketToString() { return string(); } + void closeSocket() { + // Ignore the potential Exception returned by close(), as a counterpart + // to Java's closeQuietly(). + wifi_socket_->close(); + } + // TODO(ahlee): Double check that the ownership of this is correct when + // this is fully implemented. + Ptr getEndpointChannel() { + return new_endpoint_channel_.release(); + } + + private: + ScopedPtr > new_endpoint_channel_; + Ptr wifi_socket_; + }; + + template + friend class wifi_lan_upgrade_handler::OnIncomingWifiConnectionRunnable; + + Ptr > medium_manager_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/wifi_lan_upgrade_handler.cc" + +#endif // CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_ diff --git a/cpp/core/listeners.h b/cpp/core/listeners.h new file mode 100644 index 00000000..28ab7a30 --- /dev/null +++ b/cpp/core/listeners.h @@ -0,0 +1,167 @@ +#ifndef CORE_LISTENERS_H_ +#define CORE_LISTENERS_H_ + +#include + +#include "core/payload.h" +#include "core/status.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +struct OnConnectionInitiatedParams { + const std::string remote_endpoint_id; + const std::string remote_endpoint_name; + const std::string authentication_token; + ConstPtr raw_authentication_token; + const bool is_incoming_connection; + + OnConnectionInitiatedParams(const std::string& remote_endpoint_id, + const std::string& remote_endpoint_name, + const std::string& authentication_token, + ConstPtr raw_authentication_token, + bool is_incoming_connection) + : remote_endpoint_id(remote_endpoint_id), + remote_endpoint_name(remote_endpoint_name), + authentication_token(authentication_token), + raw_authentication_token(raw_authentication_token), + is_incoming_connection(is_incoming_connection) {} +}; + +struct OnConnectionResultParams { + const std::string remote_endpoint_id; + const Status::Value status; + + OnConnectionResultParams(const std::string& remote_endpoint_id, + Status::Value status) + : remote_endpoint_id(remote_endpoint_id), status(status) {} +}; + +struct OnDisconnectedParams { + const std::string remote_endpoint_id; + + explicit OnDisconnectedParams(const std::string& remote_endpoint_id) + : remote_endpoint_id(remote_endpoint_id) {} +}; + +struct OnBandwidthChangedParams { + const std::string remote_endpoint_id; + const std::int32_t quality; + + OnBandwidthChangedParams(const std::string& remote_endpoint_id, + std::int32_t quality) + : remote_endpoint_id(remote_endpoint_id), quality(quality) {} +}; + +struct OnPayloadReceivedParams { + const std::string remote_endpoint_id; + const ConstPtr payload; + + OnPayloadReceivedParams(const std::string& remote_endpoint_id, + ConstPtr payload) + : remote_endpoint_id(remote_endpoint_id), payload(payload) {} +}; + +struct PayloadTransferUpdate { + const std::int64_t payload_id; + struct Status { + enum Value { + SUCCESS, + FAILURE, + IN_PROGRESS, + CANCELED, + }; + }; + const Status::Value status; + const std::int64_t total_bytes; + const std::int64_t bytes_transferred; + + PayloadTransferUpdate(std::int64_t payload_id, Status::Value status, + std::int64_t total_bytes, + std::int64_t bytes_transferred) + : payload_id(payload_id), + status(status), + total_bytes(total_bytes), + bytes_transferred(bytes_transferred) {} +}; + +struct OnPayloadTransferUpdateParams { + const std::string remote_endpoint_id; + const PayloadTransferUpdate update; + + OnPayloadTransferUpdateParams(const std::string& remote_endpoint_id, + const PayloadTransferUpdate& update) + : remote_endpoint_id(remote_endpoint_id), update(update) {} +}; + +struct OnEndpointFoundParams { + const std::string endpoint_id; + const std::string service_id; + const std::string endpoint_name; + + OnEndpointFoundParams(const std::string& endpoint_id, + const std::string& service_id, + const std::string& endpoint_name) + : endpoint_id(endpoint_id), + service_id(service_id), + endpoint_name(endpoint_name) {} +}; + +struct OnEndpointLostParams { + const std::string endpoint_id; + + explicit OnEndpointLostParams(const std::string& endpoint_id) + : endpoint_id(endpoint_id) {} +}; + +class ResultListener { + public: + virtual ~ResultListener() {} + + virtual void onResult(Status::Value status) = 0; +}; + +class ConnectionLifecycleListener { + public: + virtual ~ConnectionLifecycleListener() {} + + virtual void onConnectionInitiated( + ConstPtr on_connection_initiated_params) = 0; + virtual void onConnectionResult( + ConstPtr on_connection_result_params) = 0; + virtual void onDisconnected( + ConstPtr on_disconnected_params) = 0; + virtual void onBandwidthChanged( + ConstPtr on_bandwidth_changed_params) = 0; +}; + +class DiscoveryListener { + public: + virtual ~DiscoveryListener() {} + + virtual void onEndpointFound( + ConstPtr on_endpoint_found_params) = 0; + virtual void onEndpointLost( + ConstPtr on_endpoint_lost_params) = 0; +}; + +class PayloadListener { + public: + virtual ~PayloadListener() {} + + virtual void onPayloadReceived( + ConstPtr on_payload_received_params) = 0; + virtual void onPayloadTransferUpdate( + ConstPtr + on_payload_transfer_update_params) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_LISTENERS_H_ diff --git a/cpp/core/options.h b/cpp/core/options.h new file mode 100644 index 00000000..222f0060 --- /dev/null +++ b/cpp/core/options.h @@ -0,0 +1,32 @@ +#ifndef CORE_OPTIONS_H_ +#define CORE_OPTIONS_H_ + +#include "core/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +struct AdvertisingOptions { + const Strategy strategy; + const bool auto_upgrade_bandwidth; + const bool enforce_topology_constraints; + + AdvertisingOptions(Strategy strategy, bool auto_upgrade_bandwidth, + bool enforce_topology_constraints) + : strategy(strategy), + auto_upgrade_bandwidth(auto_upgrade_bandwidth), + enforce_topology_constraints(enforce_topology_constraints) {} +}; + +struct DiscoveryOptions { + const Strategy strategy; + + explicit DiscoveryOptions(Strategy strategy) : strategy(strategy) {} +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_OPTIONS_H_ diff --git a/cpp/core/params.h b/cpp/core/params.h new file mode 100644 index 00000000..754d0ec6 --- /dev/null +++ b/cpp/core/params.h @@ -0,0 +1,148 @@ +#ifndef CORE_PARAMS_H_ +#define CORE_PARAMS_H_ + +#include +#include + +#include "core/listeners.h" +#include "core/options.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +struct StartAdvertisingParams { + Ptr result_listener; + const std::string name; + const std::string service_id; + const AdvertisingOptions advertising_options; + Ptr connection_lifecycle_listener; + + StartAdvertisingParams( + Ptr result_listener, const std::string& name, + const std::string& service_id, + const AdvertisingOptions& advertising_options, + Ptr connection_lifecycle_listener) + : result_listener(result_listener), + name(name), + service_id(service_id), + advertising_options(advertising_options), + connection_lifecycle_listener(connection_lifecycle_listener) {} +}; + +struct StopAdvertisingParams { + // Intentionally left empty. +}; + +struct StartDiscoveryParams { + Ptr result_listener; + const std::string service_id; + const DiscoveryOptions discovery_options; + Ptr discovery_listener; + + StartDiscoveryParams(Ptr result_listener, + const std::string& service_id, + const DiscoveryOptions& discovery_options, + Ptr discovery_listener) + : result_listener(result_listener), + service_id(service_id), + discovery_options(discovery_options), + discovery_listener(discovery_listener) {} +}; + +struct StopDiscoveryParams { + // Intentionally left empty. +}; + +struct RequestConnectionParams { + Ptr result_listener; + const std::string name; + const std::string remote_endpoint_id; + Ptr connection_lifecycle_listener; + + RequestConnectionParams( + Ptr result_listener, const std::string& name, + const std::string& remote_endpoint_id, + Ptr connection_lifecycle_listener) + : result_listener(result_listener), + name(name), + remote_endpoint_id(remote_endpoint_id), + connection_lifecycle_listener(connection_lifecycle_listener) {} +}; + +struct AcceptConnectionParams { + Ptr result_listener; + const std::string remote_endpoint_id; + Ptr payload_listener; + + AcceptConnectionParams(Ptr result_listener, + const std::string& remote_endpoint_id, + Ptr payload_listener) + : result_listener(result_listener), + remote_endpoint_id(remote_endpoint_id), + payload_listener(payload_listener) {} +}; + +struct RejectConnectionParams { + Ptr result_listener; + const std::string remote_endpoint_id; + + RejectConnectionParams(Ptr result_listener, + const std::string& remote_endpoint_id) + : result_listener(result_listener), + remote_endpoint_id(remote_endpoint_id) {} +}; + +struct SendPayloadParams { + Ptr result_listener; + const std::vector remote_endpoint_ids; + ConstPtr payload; + + SendPayloadParams(Ptr result_listener, + const std::vector& remote_endpoint_ids, + ConstPtr payload) + : result_listener(result_listener), + remote_endpoint_ids(remote_endpoint_ids), + payload(payload) {} +}; + +struct CancelPayloadParams { + Ptr result_listener; + const std::int64_t payload_id; + + CancelPayloadParams(Ptr result_listener, + std::int64_t payload_id) + : result_listener(result_listener), payload_id(payload_id) {} +}; + +struct InitiateBandwidthUpgradeParams { + Ptr result_listener; + const std::string remote_endpoint_id; + + InitiateBandwidthUpgradeParams(Ptr result_listener, + const std::string& remote_endpoint_id) + : result_listener(result_listener), + remote_endpoint_id(remote_endpoint_id) {} +}; + +struct DisconnectFromEndpointParams { + const std::string remote_endpoint_id; + + explicit DisconnectFromEndpointParams(const std::string& remote_endpoint_id) + : remote_endpoint_id(remote_endpoint_id) {} +}; + +struct StopAllEndpointsParams { + Ptr result_listener; + + explicit StopAllEndpointsParams(Ptr result_listener) + : result_listener(result_listener) {} +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_PARAMS_H_ diff --git a/cpp/core/payload.cc b/cpp/core/payload.cc new file mode 100644 index 00000000..cafabcee --- /dev/null +++ b/cpp/core/payload.cc @@ -0,0 +1,80 @@ +#include "core/payload.h" + +#include +#include + +#include "platform/prng.h" + +namespace location { +namespace nearby { +namespace connections { + +////////////////////////////////// Payload ////////////////////////////////// + +Ptr Payload::fromBytes(ConstPtr bytes) { + return MakePtr(new Payload(generateId(), bytes)); +} + +Ptr Payload::fromStream(Ptr input_stream) { + return MakePtr( + new Payload(generateId(), MakeConstPtr(new Stream(input_stream)))); +} + +Ptr Payload::fromFile(const Ptr& input_file) { + return MakePtr(new Payload(generateId(), MakeConstPtr(new File(input_file)))); +} + +ConstPtr Payload::asBytes() const { return bytes_.get(); } + +ConstPtr Payload::asStream() const { return stream_.get(); } + +ConstPtr Payload::asFile() const { return file_.get(); } + +ConstPtr Payload::releaseBytes() const { return bytes_.release(); } + +std::int64_t Payload::getId() const { return id_; } + +Payload::Type::Value Payload::getType() const { return type_; } + +std::int64_t Payload::generateId() { return Prng().nextInt64(); } + +Payload::Payload(std::int64_t id, ConstPtr bytes) + : id_(id), + type_(Type::BYTES), + bytes_(std::move(bytes)), + file_(ConstPtr()), + stream_(ConstPtr()) {} + +Payload::Payload(std::int64_t id, ConstPtr file) + : id_(id), + type_(Type::FILE), + bytes_(ConstPtr()), + file_(std::move(file)), + stream_(ConstPtr()) {} + +Payload::Payload(std::int64_t id, ConstPtr stream) + : id_(id), + type_(Type::STREAM), + bytes_(ConstPtr()), + file_(ConstPtr()), + stream_(stream) {} + +//////////////////////////// Payload::File //////////////////////////////// + +Ptr Payload::File::asInputFile() const { return input_file_.get(); } + +Payload::File::File(const Ptr& input_file) + : input_file_(input_file) {} + +//////////////////////////// Payload::Stream //////////////////////////////// + +Ptr Payload::Stream::asInputStream() const { + return input_stream_.get(); +} + +Payload::Stream::Stream(Ptr input_stream) + : input_stream_(input_stream) {} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/payload.h b/cpp/core/payload.h new file mode 100644 index 00000000..5dd3436f --- /dev/null +++ b/cpp/core/payload.h @@ -0,0 +1,86 @@ +#ifndef CORE_PAYLOAD_H_ +#define CORE_PAYLOAD_H_ + +#include + +#include "platform/api/input_file.h" +#include "platform/api/input_stream.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { + +class Payload { + public: + struct Type { + enum Value { UNKNOWN = 0, BYTES = 1, FILE = 2, STREAM = 3 }; + }; + + class Stream { + public: + Ptr asInputStream() const; + + private: + template + friend class InternalPayloadFactory; + friend class Payload; + + explicit Stream(Ptr input_stream); + ScopedPtr > input_stream_; + }; + + class File { + public: + Ptr asInputFile() const; + + private: + template + friend class InternalPayloadFactory; + friend class Payload; + + explicit File(const Ptr& input_file); + ScopedPtr > input_file_; + }; + + static Ptr fromBytes(ConstPtr bytes); + static Ptr fromStream(Ptr input_stream); + static Ptr fromFile(const Ptr& input_file); + + ConstPtr asBytes() const; + ConstPtr asStream() const; + ConstPtr asFile() const; + + // For when clients of this class want to assume ownership of the + // ConstPtr that represents a BYTES Payload. + ConstPtr releaseBytes() const; + + std::int64_t getId() const; + Type::Value getType() const; + + private: + template + friend class InternalPayloadFactory; + + static std::int64_t generateId(); + + Payload(std::int64_t id, ConstPtr bytes); + Payload(std::int64_t id, ConstPtr stream); + Payload(std::int64_t id, ConstPtr file); + + std::int64_t id_; + Type::Value type_; + // This field is mutable because of releaseBytes(), which is just a physically + // non-const operation that doesn't alter the conceptual const-ness of the + // Payload object. + mutable ScopedPtr > bytes_; + ScopedPtr > file_; + ScopedPtr > stream_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_PAYLOAD_H_ diff --git a/cpp/core/status.h b/cpp/core/status.h new file mode 100644 index 00000000..ea0de7b6 --- /dev/null +++ b/cpp/core/status.h @@ -0,0 +1,30 @@ +#ifndef CORE_STATUS_H_ +#define CORE_STATUS_H_ + +namespace location { +namespace nearby { +namespace connections { + +struct Status { + enum Value { + SUCCESS, + ERROR, + OUT_OF_ORDER_API_CALL, + ALREADY_HAVE_ACTIVE_STRATEGY, + ALREADY_ADVERTISING, + ALREADY_DISCOVERING, + ENDPOINT_IO_ERROR, + ENDPOINT_UNKNOWN, + CONNECTION_REJECTED, + ALREADY_CONNECTED_TO_ENDPOINT, + NOT_CONNECTED_TO_ENDPOINT, + BLUETOOTH_ERROR, + PAYLOAD_UNKNOWN, + }; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_STATUS_H_ diff --git a/cpp/core/strategy.cc b/cpp/core/strategy.cc new file mode 100644 index 00000000..dfa1637c --- /dev/null +++ b/cpp/core/strategy.cc @@ -0,0 +1,51 @@ +#include "core/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +const Strategy Strategy::kP2PCluster(Strategy::ConnectionType::P2P, + Strategy::TopologyType::M_TO_N); + +const Strategy Strategy::kP2PStar(Strategy::ConnectionType::P2P, + Strategy::TopologyType::ONE_TO_N); + +const Strategy Strategy::kP2PPointToPoint(Strategy::ConnectionType::P2P, + Strategy::TopologyType::ONE_TO_ONE); + +Strategy::Strategy(ConnectionType::Value connection_type, + TopologyType::Value topology_type) + : connection_type(connection_type), topology_type(topology_type) {} + +Strategy::Strategy(const Strategy& that) + : connection_type(that.connection_type), + topology_type(that.topology_type) {} + +bool Strategy::isValid() const { + return kP2PStar == *this || kP2PCluster == *this || kP2PPointToPoint == *this; +} + +string Strategy::getName() const { + if (Strategy::kP2PCluster == *this) { + return "P2P_CLUSTER"; + } else if (Strategy::kP2PStar == *this) { + return "P2P_STAR"; + } else if (Strategy::kP2PPointToPoint == *this) { + return "P2P_POINT_TO_POINT"; + } else { + return "UNKNOWN"; + } +} + +bool operator==(const Strategy& lhs, const Strategy& rhs) { + return lhs.connection_type == rhs.connection_type && + lhs.topology_type == rhs.topology_type; +} + +bool operator!=(const Strategy& lhs, const Strategy& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/strategy.h b/cpp/core/strategy.h new file mode 100644 index 00000000..2cd1dacd --- /dev/null +++ b/cpp/core/strategy.h @@ -0,0 +1,43 @@ +#ifndef CORE_STRATEGY_H_ +#define CORE_STRATEGY_H_ + +#include "platform/port/string.h" + +namespace location { +namespace nearby { +namespace connections { + +struct Strategy { + public: + static const Strategy kP2PCluster; + static const Strategy kP2PStar; + static const Strategy kP2PPointToPoint; + + Strategy(const Strategy& that); + + bool isValid() const; + std::string getName() const; + + friend bool operator==(const Strategy& lhs, const Strategy& rhs); + friend bool operator!=(const Strategy& lhs, const Strategy& rhs); + + private: + struct ConnectionType { + enum Value { P2P = 1 }; + }; + struct TopologyType { + enum Value { ONE_TO_ONE = 1, ONE_TO_N = 2, M_TO_N = 3 }; + }; + + const ConnectionType::Value connection_type; + const TopologyType::Value topology_type; + + Strategy(ConnectionType::Value connection_type, + TopologyType::Value topology_type); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_STRATEGY_H_ diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD new file mode 100644 index 00000000..e7482ef0 --- /dev/null +++ b/cpp/platform/BUILD @@ -0,0 +1,144 @@ +cc_library( + name = "utils", + srcs = [ + "base64_utils.cc", + "file_impl.cc", + "prng.cc", + "reliability_utils.cc", + ], + hdrs = [ + "base64_utils.h", + "cancelable_alarm.cc", + "cancelable_alarm.h", + "file_impl.h", + "pipe.cc", + "pipe.h", + "prng.h", + "reliability_utils.h", + "synchronized.h", + ], + visibility = [ + "//core:__subpackages__", + "//platform/impl:__subpackages__", + "//location/nearby/setup/core/internal:__subpackages__", + ], + deps = [ + ":types", + "//platform/api", + "//platform/port:string", + "//strings", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "types", + srcs = [ + "ptr.cc", + ], + hdrs = [ + "byte_array.h", + "callable.h", + "cancelable.h", + "container_of.h", + "exception.cc", + "exception.h", + "ptr.h", + "runnable.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + ":logging", + "//platform/impl/default:lock", + "//platform/port:down_cast", + "//platform/port:string", + ], +) + +cc_library( + name = "logging", + hdrs = [ + "logging.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + ], + deps = [ + "//absl/base", + "//absl/base:raw_logging_internal", + ], +) + +cc_test( + name = "container_of_test", + srcs = ["container_of_test.cc"], + deps = [ + ":types", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "ptr_test", + srcs = ["ptr_test.cc"], + deps = [ + ":types", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "prng_test", + srcs = ["prng_test.cc"], + deps = [ + ":utils", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "file_test", + srcs = ["file_impl_test.cc"], + deps = [ + ":utils", + "//file/util:temp_path", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "pipe_test", + timeout = "short", + srcs = ["pipe_test.cc"], + deps = [ + ":utils", + "//platform:types", + "//platform/impl/default:condition_variable", + "//platform/impl/default:lock", + "//platform/port:string", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) + +cc_test( + name = "byte_array_test", + timeout = "short", + srcs = ["byte_array_test.cc"], + deps = [ + ":utils", + "//platform:types", + "//platform/impl/default:condition_variable", + "//platform/impl/default:lock", + "//platform/port:string", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD new file mode 100644 index 00000000..1b474e13 --- /dev/null +++ b/cpp/platform/api/BUILD @@ -0,0 +1,58 @@ +package(default_visibility = [ + "//core:__subpackages__", + "//platform:__subpackages__", + "//location/nearby/setup/core:__subpackages__", +]) + +cc_library( + name = "api", + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "ble.h", + "ble_v2.h", + "bluetooth_adapter.h", + "bluetooth_classic.h", + "condition_variable.h", + "count_down_latch.h", + "executor.h", + "future.h", + "hash_utils.h", + "input_file.h", + "input_stream.h", + "lock.h", + "multi_thread_executor.h", + "output_file.h", + "output_stream.h", + "scheduled_executor.h", + "settable_future.h", + "single_thread_executor.h", + "socket.h", + "submittable_executor.h", + "system_clock.h", + "thread_utils.h", + "wifi.h", + ], + deps = [ + "//platform:types", + "//platform/port:down_cast", + "//platform/port:string", + ], +) + +cc_library( + name = "lock", + hdrs = ["lock.h"], + visibility = [ + "//platform:__subpackages__", + ], +) + +cc_library( + name = "condition_variable", + hdrs = ["condition_variable.h"], + visibility = [ + "//platform:__subpackages__", + ], + deps = ["//platform:types"], +) diff --git a/cpp/platform/api/atomic_boolean.h b/cpp/platform/api/atomic_boolean.h new file mode 100644 index 00000000..41f94165 --- /dev/null +++ b/cpp/platform/api/atomic_boolean.h @@ -0,0 +1,21 @@ +#ifndef PLATFORM_API_ATOMIC_BOOLEAN_H_ +#define PLATFORM_API_ATOMIC_BOOLEAN_H_ + +namespace location { +namespace nearby { + +// A boolean value that may be updated atomically. +// +// https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html +class AtomicBoolean { + public: + virtual ~AtomicBoolean() {} + + virtual bool get() = 0; + virtual void set(bool value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api/atomic_reference.h b/cpp/platform/api/atomic_reference.h new file mode 100644 index 00000000..52a8b14e --- /dev/null +++ b/cpp/platform/api/atomic_reference.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API_ATOMIC_REFERENCE_H_ +#define PLATFORM_API_ATOMIC_REFERENCE_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 { + public: + virtual ~AtomicReference() {} + + virtual T get() = 0; + virtual void set(T value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api/ble.h b/cpp/platform/api/ble.h new file mode 100644 index 00000000..e9d44250 --- /dev/null +++ b/cpp/platform/api/ble.h @@ -0,0 +1,124 @@ +#ifndef PLATFORM_API_BLE_H_ +#define PLATFORM_API_BLE_H_ + +#include "platform/api/bluetooth_classic.h" +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Opaque wrapper over a BLE peripheral. Must contain enough data about a +// particular BLE device to connect to its GATT server. +class BLEPeripheral { + public: + virtual ~BLEPeripheral() {} + + // The returned Ptr is not owned by the caller, and can be invalidated once + // the corresponding BLEPeripheral object is destroyed. + virtual Ptr getBluetoothDevice() = 0; +}; + +class BLESocket { + public: + virtual ~BLESocket() {} + + // Returns the InputStream of the BLESocket, or a null Ptr + // on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BLESocket object is destroyed. + virtual Ptr getInputStream() = 0; + + // Returns the OutputStream of the BLESocket, or a null + // Ptr on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BLESocket object is destroyed. + virtual Ptr getOutputStream() = 0; + + // Conforms to the same contract as + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close(). + // + // Returns Exception::IO on error, Exception::NONE otherwise. + virtual Exception::Value close() = 0; + + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BLESocket object is destroyed. + virtual Ptr getRemotePeripheral() = 0; +}; + +// Container of operations that can be performed over the BLE medium. +class BLEMedium { + public: + virtual ~BLEMedium() {} + + // Takes ownership of (and is responsible for destroying) the passed-in + // 'advertisement'. + virtual bool startAdvertising(const std::string& service_id, + ConstPtr advertisement) = 0; + virtual void stopAdvertising(const std::string& service_id) = 0; + + class DiscoveredPeripheralCallback { + public: + virtual ~DiscoveredPeripheralCallback() {} + + // The Ptrs provided in these callback methods will be owned (and + // destroyed) by the recipient of the callback methods (i.e. the creator of + // the concrete DiscoveredPeripheralCallback object). + virtual void onPeripheralDiscovered(Ptr ble_peripheral, + const std::string& service_id, + ConstPtr advertisement) = 0; + virtual void onPeripheralLost(Ptr ble_peripheral, + const std::string& service_id) = 0; + }; + + // Returns true once the BLE scan has been initiated. + // + // Does not take ownership of the passed-in discovered_peripheral_callback -- + // destroying that is up to the caller. + virtual bool startScanning( + const std::string& service_id, + Ptr discovered_peripheral_callback) = 0; + // Returns true once BLE scanning for service_id is well and truly stopped; + // after this returns, there must be no more invocations of the + // DiscoveredPeripheralCallback passed in to startScanning() for service_id. + // + // Does not need to bother with destroying the DiscoveredPeripheralCallback + // passed in to startScanning() -- that's the job of the caller. + virtual void stopScanning(const std::string& service_id) = 0; + + // Callback that is invoked when a new connection is accepted. + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() {} + + // The Ptr provided in this callback method will be owned (and + // destroyed) by the recipient of the callback methods (i.e. the creator of + // the concrete AcceptedConnectionCallback object). + virtual void onConnectionAccepted(Ptr socket, + const std::string& service_id) = 0; + }; + + // Returns true once BLE socket connection requests to service_id can be + // accepted. + // + // Does not take ownership of the passed-in accepted_connection_callback -- + // destroying that is up to the caller. + virtual bool startAcceptingConnections( + const std::string& service_id, + Ptr accepted_connection_callback) = 0; + virtual void stopAcceptingConnections(const std::string& service_id) = 0; + + // The returned Ptr will be owned (and destroyed) by the caller. Returns + // a null Ptr on error. + virtual Ptr connect(Ptr ble_peripheral, + const std::string& service_id) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_BLE_H_ diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h new file mode 100644 index 00000000..93607126 --- /dev/null +++ b/cpp/platform/api/ble_v2.h @@ -0,0 +1,401 @@ +#ifndef PLATFORM_API_BLE_V2_H_ +#define PLATFORM_API_BLE_V2_H_ + +#include +#include +#include +#include + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/le/AdvertiseData +// +// Bundle of data found in a BLE advertisement. +// +// All service UUIDs will conform to the 16-bit Bluetooth base UUID, +// 0000xxxx-0000-1000-8000-00805F9B34FB. This makes it possible to store two +// byte service UUIDs in the advertisement. +struct BLEAdvertisementData { + typedef std::int8_t TXPowerLevel; + + static const TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = + std::numeric_limits::min(); + + bool is_connectable; + // When set to UNSPECIFIED_TX_POWER_LEVEL, TX power should not be included in + // the advertisement data. + TXPowerLevel tx_power_level; + // When set to an empty string, local name should not be included in the + // advertisement data. + std::string local_name; + // When set to an empty vector, the set of 16-bit service class UUIDs should + // not be included in the advertisement data. + std::set service_uuids; + // Maps service UUIDs to their service data. + // Ownership of the map values is tied to ownership of BLEAdvertisementData. + std::map > service_data; +}; + +// Opaque wrapper over a BLE peripheral. Must be able to uniquely identify a +// peripheral so that we can connect to its GATT server. +// +// BLEPeripheralV2 should always be created as a RefCountedPtr because ownership +// is shared between the per-platform implementation and the internals of Nearby +// Connections. +class BLEPeripheralV2 { + public: + virtual ~BLEPeripheralV2() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getAddress() + // + // This should be the MAC address when possible. If the implementation is + // unable to retrieve that, any unique identifier should suffice. + virtual std::string getId() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic +// +// Representation of a GATT characteristic. +// +// GATTCharacteristics are RefCounted so that ownership can be shared between +// the per-platform implementation and C++ internals. All GATTCharacteristics +// should be created with MakeRefCountedPtr(). +class GATTCharacteristic { + public: + virtual ~GATTCharacteristic() {} + + // Possible permissions of a GATT characteristic. + struct Permission { + enum Value { + UNKNOWN = 0, + READ = 1, + WRITE = 2, + }; + }; + + // Possible properties of a GATT characteristic. + struct Property { + enum Value { + UNKNOWN = 0, + READ = 1, + WRITE = 2, + INDICATE = 3, + }; + }; + + // Returns the UUID of this characteristic. + virtual std::string getUUID() = 0; + + // Returns the UUID of the containing GATT service. + virtual std::string getServiceUUID() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGatt +// +// Representation of a client GATT connection to a remote GATT server. +class ClientGATTConnection { + public: + virtual ~ClientGATTConnection() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getDevice() + // + // Retrieves the BLE peripheral that this connection is tied to. + virtual Ptr getPeripheral() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices() + // + // Discovers all available services and characteristics on this connection. + // Returns whether or not discovery finished successfully. + // + // This function should block until discovery has finished. + virtual bool discoverServices() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID) + // + // Retrieves a GATT characteristic. A null Ptr is returned upon error. + // + // discoverServices() should be called before this method to fetch all + // available services and characteristics first. + // + // It is okay for duplicate services to exist, as long as the specified + // characteristic UUID is unique among all services of the same UUID. + virtual Ptr getCharacteristic( + const std::string& service_uuid, + const std::string& characteristic_uuid) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue() + // + // Reads a GATT characteristic. A null ConstPtr is returned upon error. + virtual ConstPtr readCharacteristic( + Ptr characteristic) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // + // Sends a remote characteristic write request to the server and returns + // whether or not it was successful. + virtual bool writeCharacteristic(Ptr characteristic, + ConstPtr value) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect() + // + // Disconnects a GATT connection. + virtual void disconnect() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer +// +// Representation of a server GATT connection to a remote GATT client. +class ServerGATTConnection { + public: + virtual ~ServerGATTConnection() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattServer.html#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice,%20android.bluetooth.BluetoothGattCharacteristic,%20boolean) + // + // Sends a notification (via indication) to the client that a characteristic + // has changed with the given value. Returns whether or not it was successful. + // + // The value sent does not have to reflect the locally stored characteristic + // value. To update the local value, call GATTServer::updateCharacteristic. + virtual bool sendCharacteristic(Ptr characteristic, + ConstPtr value) = 0; +}; + +// Callback for asynchronous events on the client side of a GATT connection. +class ClientGATTConnectionLifecycleCallback { + public: + virtual ~ClientGATTConnectionLifecycleCallback() {} + + // Called when the client is disconnected from the GATT server. + virtual void onDisconnected(Ptr connection) = 0; +}; + +// Callback for asynchronous events on the server side of a GATT connection. +class ServerGATTConnectionLifecycleCallback { + public: + virtual ~ServerGATTConnectionLifecycleCallback() {} + + // Called when a remote peripheral connected to us and subscribed to one of + // our characteristics. + virtual void onCharacteristicSubscription( + Ptr connection, + Ptr characteristic) = 0; + + // Called when a remote peripheral unsubscribed from one of our + // characteristics. + virtual void onCharacteristicUnsubscription( + Ptr connection, + Ptr characteristic) = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer +// +// Representation of a BLE GATT server. +class GATTServer { + public: + virtual ~GATTServer() {} + + // Creates a characteristic and adds it to the GATT server under the given + // characteristic and service UUIDs. Returns a null Ptr upon error. + // + // Characteristics of the same service UUID should be put under one + // service rather than many services with the same UUID. + // + // If the INDICATE property is included, the characteristic should include the + // official Bluetooth Client Characteristic Configuration descriptor with UUID + // 0x2902 and a WRITE permission. This allows remote clients to write to this + // descriptor and subscribe for characteristic changes. For more information + // about this descriptor, please go to: + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + virtual Ptr createCharacteristic( + const std::string& service_uuid, const std::string& characteristic_uuid, + const std::set& permissions, + const std::set& properties) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // + // Locally updates the value of a characteristic and returns whether or not it + // was successful. + // Takes ownership of (and is responsible for destroying) the passed-in + // 'value'. + virtual bool updateCharacteristic(Ptr characteristic, + ConstPtr value) = 0; + + // Stops a GATT server. + virtual void stop() = 0; +}; + +// A BLE socket representation. +class BLESocketV0 { + public: + virtual ~BLESocketV0() {} + + // Returns the remote BLE peripheral tied to this socket. + virtual Ptr getRemotePeripheral() = 0; + + // Writes a message on the socket and blocks until finished. Returns + // Exception::IO upon error, and Exception::NONE otherwise. + virtual Exception::Value write(ConstPtr message) = 0; + + // Closes the socket and blocks until finished. Returns Exception::IO upon + // error, and Exception::NONE otherwise. + virtual Exception::Value close() = 0; +}; + +// Callback for asynchronous events on a BLESocketV0 object. +class BLESocketLifecycleCallback { + public: + virtual ~BLESocketLifecycleCallback() {} + + // Called when a message arrives on a socket. + virtual void onMessageReceived(Ptr socket, + ConstPtr message) = 0; + + // Called when a socket gets disconnected. + virtual void onDisconnected(Ptr socket) = 0; +}; + +// Callback for asynchronous events on the server side of a BLESocketV0 object. +class ServerBLESocketLifecycleCallback : public BLESocketLifecycleCallback { + public: + ~ServerBLESocketLifecycleCallback() override {} + + // Called when a new incoming socket has been established. + virtual void onSocketEstablished(Ptr socket) = 0; +}; + +// The main BLE medium used inside of Nearby. This serves as the entry point for +// all BLE and GATT related operations. +class BLEMediumV2 { + public: + virtual ~BLEMediumV2() {} + + typedef std::uint32_t MTU; + + // Coarse representation of power settings throughout all BLE operations. + struct PowerMode { + enum Value { + UNKNOWN = 0, + LOW = 1, + HIGH = 2, + }; + }; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#startAdvertising(android.bluetooth.le.AdvertiseSettings,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseCallback) + // + // Starts BLE advertising and returns whether or not it was successful. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Advertising interval = ~1000ms + // - TX power = low + // HIGH: + // - Advertising interval = ~100ms + // - TX power = high + virtual bool startAdvertising( + ConstPtr advertisement_data, + ConstPtr scan_response, + PowerMode::Value power_mode) = 0; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#stopAdvertising(android.bluetooth.le.AdvertiseCallback) + // + // Stops advertising. + virtual void stopAdvertising() = 0; + + // https://developer.android.com/reference/android/bluetooth/le/ScanCallback + // + // Callback for BLE scan results. + class ScanCallback { + public: + virtual ~ScanCallback() {} + + // https://developer.android.com/reference/android/bluetooth/le/ScanCallback.html#onScanResult(int,%20android.bluetooth.le.ScanResult) + // + // Called when a BLE advertisement is discovered. + // + // The passed in advertisement_data is the merged combination of both + // advertisement data and scan response. + // + // Every discovery of an advertisement should be reported, even if the + // advertisement was discovered before. + // + // Ownership of the BLEAdvertisementData transfers to the caller at this + // point. + virtual void onAdvertisementFound( + Ptr peripheral, + ConstPtr advertisement_data) = 0; + }; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(java.util.List%3Candroid.bluetooth.le.ScanFilter%3E,%20android.bluetooth.le.ScanSettings,%20android.bluetooth.le.ScanCallback) + // + // Starts scanning and returns whether or not it was successful. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Scan window = ~512ms + // - Scan interval = ~5120ms + // HIGH: + // - Scan window = ~4096ms + // - Scan interval = ~4096ms + virtual bool startScanning(const std::set& service_uuids, + PowerMode::Value power_mode, + Ptr scan_callback) = 0; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#stopScan(android.bluetooth.le.ScanCallback) + // + // Stops scanning. + virtual void stopScanning() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothManager#openGattServer(android.content.Context,%20android.bluetooth.BluetoothGattServerCallback) + // + // Starts a GATT server. Returns a null Ptr upon error. + virtual Ptr startGATTServer( + Ptr + connection_lifecycle_callback) = 0; + + // Starts listening for incoming BLE sockets and returns false upon error. + virtual bool startListeningForIncomingBLESockets( + Ptr socket_lifecycle_callback) = 0; + + // Stops listening for incoming BLE sockets. + virtual void stopListeningForIncomingBLESockets() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context,%20boolean,%20android.bluetooth.BluetoothGattCallback) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestConnectionPriority(int) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestMtu(int) + // + // Connects to a GATT server and negotiates the specified connection + // parameters. Returns a null Ptr upon error. + // + // Both connection interval and MTU can be negotiated on a best-effort basis. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Connection interval = ~11.25ms - 15ms + // HIGH: + // - Connection interval = ~100ms - 125ms + virtual Ptr connectToGATTServer( + Ptr peripheral, MTU mtu, PowerMode::Value power_mode, + Ptr + connection_lifecycle_callback) = 0; + + // Establishes a BLE socket to the specified remote peripheral. Returns a null + // Ptr on error. + virtual Ptr establishBLESocket( + Ptr ble_peripheral, + Ptr socket_lifecycle_callback) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_BLE_V2_H_ diff --git a/cpp/platform/api/bluetooth_adapter.h b/cpp/platform/api/bluetooth_adapter.h new file mode 100644 index 00000000..04223492 --- /dev/null +++ b/cpp/platform/api/bluetooth_adapter.h @@ -0,0 +1,58 @@ +#ifndef PLATFORM_API_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_API_BLUETOOTH_ADAPTER_H_ + +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter { + public: + virtual ~BluetoothAdapter() {} + + // Eligible statuses of the BluetoothAdapter. + struct Status { + enum Value { + DISABLED, + ENABLED, + }; + }; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + virtual bool setStatus(Status::Value status) = 0; + // Returns true if the BluetoothAdapter's current status is + // Status::Value::ENABLED. + virtual bool isEnabled() = 0; + + // Scan modes of a BluetoothAdapter, as described at + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). + struct ScanMode { + enum Value { + UNKNOWN, + CONNECTABLE_DISCOVERABLE, + }; + }; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::UNKNOWN on error. + virtual ScanMode::Value getScanMode() = 0; + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + virtual bool setScanMode(ScanMode::Value scan_mode) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // + // Returns a null Ptr on error. + virtual Ptr getName() = 0; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + virtual bool setName(const std::string& name) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api/bluetooth_classic.h b/cpp/platform/api/bluetooth_classic.h new file mode 100644 index 00000000..0ba04b11 --- /dev/null +++ b/cpp/platform/api/bluetooth_classic.h @@ -0,0 +1,139 @@ +#ifndef PLATFORM_API_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_API_BLUETOOTH_CLASSIC_H_ + +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice { + public: + virtual ~BluetoothDevice() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + virtual std::string getName() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket { + public: + virtual ~BluetoothSocket() {} + + // Returns the InputStream of the BluetoothSocket, or a null Ptr + // on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BluetoothSocket object is destroyed. + virtual Ptr getInputStream() = 0; + + // Returns the OutputStream of the BluetoothSocket, or a null + // Ptr on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BluetoothSocket object is destroyed. + virtual Ptr getOutputStream() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close() + // + // Returns Exception::IO on error, Exception::NONE otherwise. + virtual Exception::Value close() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the BluetoothSocket object is destroyed. + virtual Ptr getRemoteDevice() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket { + public: + virtual ~BluetoothServerSocket() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // The returned Ptr will be owned (and destroyed) by the caller. Returns + // Exception::IO on error. + virtual ExceptionOr > accept() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::IO on error, Exception::NONE otherwise. + virtual Exception::Value close() = 0; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium { + public: + virtual ~BluetoothClassicMedium() {} + + class DiscoveryCallback { + public: + virtual ~DiscoveryCallback() {} + + // The Ptrs provided in these callback methods will be owned (and + // destroyed) by the recipient of the callback methods (i.e. the creator of + // the concrete DiscoveryCallback object). + virtual void onDeviceDiscovered(Ptr device) = 0; + virtual void onDeviceNameChanged(Ptr device) = 0; + virtual void onDeviceLost(Ptr device) = 0; + }; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + // + // Does not take ownership of the passed-in discovery_callback -- destroying + // that is up to the caller. + virtual bool startDiscovery(Ptr discovery_callback) = 0; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // startDiscovery(). + // + // Does not need to bother with destroying the DiscoveryCallback passed in to + // startDiscovery() -- that's the job of the caller. + virtual bool stopDiscovery() = 0; + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // The returned Ptr will be owned (and destroyed) by the caller. Returns + // Exception::IO on error. + virtual ExceptionOr > connectToService( + Ptr remote_device, const std::string& service_uuid) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // The returned Ptr will be owned (and destroyed) by the caller. Returns + // Exception::IO on error. + virtual ExceptionOr > listenForService( + const std::string& service_name, const std::string& service_uuid) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform/api/condition_variable.h b/cpp/platform/api/condition_variable.h new file mode 100644 index 00000000..b40aa7f5 --- /dev/null +++ b/cpp/platform/api/condition_variable.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_API_CONDITION_VARIABLE_H_ +#define PLATFORM_API_CONDITION_VARIABLE_H_ + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// The ConditionVariable class is a synchronization primitive that can be used +// to block a thread, or multiple threads at the same time, until another thread +// both modifies a shared variable (the condition), and notifies the +// ConditionVariable. +class ConditionVariable { + public: + virtual ~ConditionVariable() {} + + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + virtual void notify() = 0; + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- + virtual Exception::Value wait() = 0; // throws Exception::INTERRUPTED +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api/count_down_latch.h b/cpp/platform/api/count_down_latch.h new file mode 100644 index 00000000..d5b99f95 --- /dev/null +++ b/cpp/platform/api/count_down_latch.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_API_COUNT_DOWN_LATCH_H_ +#define PLATFORM_API_COUNT_DOWN_LATCH_H_ + +#include + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch { + public: + virtual ~CountDownLatch() {} + + virtual Exception::Value await() = 0; // throws Exception::INTERRUPTED + virtual ExceptionOr await( + std::int32_t timeout_millis) = 0; // throws Exception::INTERRUPTED + virtual void countDown() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api/executor.h b/cpp/platform/api/executor.h new file mode 100644 index 00000000..2c425d15 --- /dev/null +++ b/cpp/platform/api/executor.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_API_EXECUTOR_H_ +#define PLATFORM_API_EXECUTOR_H_ + +namespace location { +namespace nearby { + +// This abstract class is the superclass of all classes representing an +// Executor. +class Executor { + public: + virtual ~Executor() {} + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- + virtual void shutdown() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_EXECUTOR_H_ diff --git a/cpp/platform/api/future.h b/cpp/platform/api/future.h new file mode 100644 index 00000000..34f0bbed --- /dev/null +++ b/cpp/platform/api/future.h @@ -0,0 +1,24 @@ +#ifndef PLATFORM_API_FUTURE_H_ +#define PLATFORM_API_FUTURE_H_ + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// A Future represents the result of an asynchronous computation. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html +template +class Future { + public: + virtual ~Future() {} + + virtual ExceptionOr + get() = 0; // throws Exception::INTERRUPTED, Exception::EXECUTION +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_FUTURE_H_ diff --git a/cpp/platform/api/hash_utils.h b/cpp/platform/api/hash_utils.h new file mode 100644 index 00000000..12083380 --- /dev/null +++ b/cpp/platform/api/hash_utils.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API_HASH_UTILS_H_ +#define PLATFORM_API_HASH_UTILS_H_ + +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// A provider of standard hashing algorithms. +class HashUtils { + public: + virtual ~HashUtils() {} + + virtual ConstPtr md5(const std::string& input) = 0; + virtual ConstPtr sha256(const std::string& input) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_HASH_UTILS_H_ diff --git a/cpp/platform/api/input_file.h b/cpp/platform/api/input_file.h new file mode 100644 index 00000000..28615919 --- /dev/null +++ b/cpp/platform/api/input_file.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_API_INPUT_FILE_H_ +#define PLATFORM_API_INPUT_FILE_H_ + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// An InputFile represents a readable file on the system. +class InputFile { + public: + virtual ~InputFile() {} + + // The returned ConstPtr will be owned (and destroyed) by the caller. + // When we have exhausted reading the file and no bytes remain, read will + // always return an empty ConstPtr for which isNull() is true. + virtual ExceptionOr > read( + std::int64_t size) = 0; // throws Exception::IO when the file cannot be + // opened or read. + virtual std::string getFilePath() const = 0; + virtual std::int64_t getTotalSize() const = 0; + virtual void close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_INPUT_FILE_H_ diff --git a/cpp/platform/api/input_stream.h b/cpp/platform/api/input_stream.h new file mode 100644 index 00000000..08bc4a50 --- /dev/null +++ b/cpp/platform/api/input_stream.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_API_INPUT_STREAM_H_ +#define PLATFORM_API_INPUT_STREAM_H_ + +#include + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// An InputStream represents an input stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html +class InputStream { + public: + virtual ~InputStream() {} + + // The returned ConstPtr will be owned (and destroyed) by the caller. + virtual ExceptionOr > read() = 0; // throws Exception::IO + // The returned ConstPtr will be owned (and destroyed) by the caller. + virtual ExceptionOr > read( + std::int64_t size) = 0; // throws Exception::IO + virtual Exception::Value close() = 0; // throws Exception::IO +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_INPUT_STREAM_H_ diff --git a/cpp/platform/api/lock.h b/cpp/platform/api/lock.h new file mode 100644 index 00000000..1c93aa8c --- /dev/null +++ b/cpp/platform/api/lock.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API_LOCK_H_ +#define PLATFORM_API_LOCK_H_ + +namespace location { +namespace nearby { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class Lock { + public: + virtual ~Lock() {} + + virtual void lock() = 0; + virtual void unlock() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_LOCK_H_ diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h new file mode 100644 index 00000000..52a261b5 --- /dev/null +++ b/cpp/platform/api/multi_thread_executor.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_API_MULTI_THREAD_EXECUTOR_H_ + +#include "platform/api/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- +template +class MultiThreadExecutor : + public SubmittableExecutor { + public: + ~MultiThreadExecutor() override {} +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api/output_file.h b/cpp/platform/api/output_file.h new file mode 100644 index 00000000..b600d539 --- /dev/null +++ b/cpp/platform/api/output_file.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_API_OUTPUT_FILE_H_ +#define PLATFORM_API_OUTPUT_FILE_H_ + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// An OutputFile represents a writable file on the system. +class OutputFile { + public: + virtual ~OutputFile() {} + + // Takes ownership of the passed-in ConstPtr, and ensures that it is destroyed + // even upon error (i.e. when the return value is not Exception::NONE). + virtual Exception::Value write( + ConstPtr data) = 0; // throws Exception::IO + virtual void close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_OUTPUT_FILE_H_ diff --git a/cpp/platform/api/output_stream.h b/cpp/platform/api/output_stream.h new file mode 100644 index 00000000..fd4d8ea3 --- /dev/null +++ b/cpp/platform/api/output_stream.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_API_OUTPUT_STREAM_H_ +#define PLATFORM_API_OUTPUT_STREAM_H_ + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// An OutputStream represents an output stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html +class OutputStream { + public: + virtual ~OutputStream() {} + + // Takes ownership of the passed-in ConstPtr, and ensures that it is destroyed + // even upon error (i.e. when the return value is not Exception::NONE). + virtual Exception::Value write( + ConstPtr data) = 0; // throws Exception::IO + virtual Exception::Value flush() = 0; // throws Exception::IO + virtual Exception::Value close() = 0; // throws Exception::IO +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api/scheduled_executor.h b/cpp/platform/api/scheduled_executor.h new file mode 100644 index 00000000..2100058b --- /dev/null +++ b/cpp/platform/api/scheduled_executor.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_API_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_API_SCHEDULED_EXECUTOR_H_ + +#include + +#include "platform/api/executor.h" +#include "platform/cancelable.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public Executor { + public: + virtual ~ScheduledExecutor() {} + + virtual Ptr schedule(Ptr runnable, + std::int64_t delay_millis) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h new file mode 100644 index 00000000..253a5005 --- /dev/null +++ b/cpp/platform/api/settable_future.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API_SETTABLE_FUTURE_H_ +#define PLATFORM_API_SETTABLE_FUTURE_H_ + +#include "platform/api/future.h" + +namespace location { +namespace nearby { + +// A SettableFuture is a type of Future whose result can be set. +// +// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +template +class SettableFuture : public Future { + public: + ~SettableFuture() override {} + + virtual bool set(T value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SETTABLE_FUTURE_H_ diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h new file mode 100644 index 00000000..7e2f9c6d --- /dev/null +++ b/cpp/platform/api/single_thread_executor.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_API_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform/api/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- +template +class SingleThreadExecutor : + public SubmittableExecutor { + public: + ~SingleThreadExecutor() override {} +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api/socket.h b/cpp/platform/api/socket.h new file mode 100644 index 00000000..e6e69775 --- /dev/null +++ b/cpp/platform/api/socket.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_API_SOCKET_H_ +#define PLATFORM_API_SOCKET_H_ + +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// A socket is an endpoint for communication between two machines. +// +// https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html +class Socket { + public: + virtual ~Socket() {} + + virtual Ptr getInputStream() = 0; + virtual Ptr getOutputStream() = 0; + virtual void close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SOCKET_H_ diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h new file mode 100644 index 00000000..4775d8ee --- /dev/null +++ b/cpp/platform/api/submittable_executor.h @@ -0,0 +1,43 @@ +#ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ + +#include "platform/api/executor.h" +#include "platform/api/future.h" +#include "platform/callable.h" +#include "platform/port/down_cast.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +// Each per-platform concrete implementation is expected to extend from +// SubmittableExecutor and provide an override of its submit() method. +// +// e.g. +// class IOSSubmittableExecutor +// : public SubmittableExecutor { +// public: +// template +// Ptr > submit(Ptr > callable) { +// ... +// } +// } +template +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override {} + + template + Ptr > submit(Ptr > callable) { + return DOWN_CAST(this)->submit(callable); + } + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- + virtual void execute(Ptr runnable) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api/system_clock.h b/cpp/platform/api/system_clock.h new file mode 100644 index 00000000..d85ae1ca --- /dev/null +++ b/cpp/platform/api/system_clock.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API_SYSTEM_CLOCK_H_ +#define PLATFORM_API_SYSTEM_CLOCK_H_ + +#include + +namespace location { +namespace nearby { + +class SystemClock { + public: + virtual ~SystemClock() {} + + // Returns the time (in milliseconds) since the system was booted, and + // includes deep sleep. This clock should be guaranteed to be monotonic, and + // should continue to tick even when the CPU is in power saving modes. + virtual std::int64_t elapsedRealtime() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api/thread_utils.h b/cpp/platform/api/thread_utils.h new file mode 100644 index 00000000..e477662b --- /dev/null +++ b/cpp/platform/api/thread_utils.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API_THREAD_UTILS_H_ +#define PLATFORM_API_THREAD_UTILS_H_ + +#include + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +class ThreadUtils { + public: + virtual ~ThreadUtils() {} + + // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long) + virtual Exception::Value sleep( + std::int64_t millis) = 0; // throws Exception::INTERRUPTED +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_THREAD_UTILS_H_ diff --git a/cpp/platform/api/wifi.h b/cpp/platform/api/wifi.h new file mode 100644 index 00000000..0cb2566a --- /dev/null +++ b/cpp/platform/api/wifi.h @@ -0,0 +1,90 @@ +#ifndef PLATFORM_API_WIFI_H_ +#define PLATFORM_API_WIFI_H_ + +#include +#include + +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Possible authentication types for a WiFi network. +struct WifiAuthType { + enum Value { + UNKNOWN = 0, + OPEN = 1, + WPA_PSK = 2, + WEP = 3, + }; +}; + +// Possible statuses of a device's connection to a WiFi network. +struct WifiConnectionStatus { + enum Value { + UNKNOWN = 0, + CONNECTED = 1, + CONNECTION_FAILURE = 2, + AUTH_FAILURE = 3, + }; +}; + +// Represents a WiFi network found during a call to WifiMedium#scan(). +class WifiScanResult { + public: + virtual ~WifiScanResult() {} + + // Gets the SSID of this WiFi network. + virtual std::string getSSID() const = 0; + // Gets the signal strength of this WiFi network in dBm. + virtual std::int32_t getSignalStrengthDbm() const = 0; + // Gets the frequency band of this WiFi network in MHz. + virtual std::int32_t getFrequencyMhz() const = 0; + // Gets the authentication type of this WiFi network. + virtual WifiAuthType::Value getAuthType() const = 0; +}; + +// Container of operations that can be performed over the WiFi medium. +class WifiMedium { + public: + virtual ~WifiMedium() {} + + class ScanResultCallback { + public: + virtual ~ScanResultCallback() {} + + // The ConstPtr objects contained in scan_results will be + // owned (and destroyed) by the recipient of the callback methods (i.e. the + // creator of the concrete ScanResultCallback object). + virtual void onScanResults( + const std::vector >& scan_results) = 0; + }; + + // Does not take ownership of the passed-in scan_result_callback -- destroying + // that is up to the caller. + virtual bool scan(Ptr scan_result_callback) = 0; + + // If 'password' is an empty string, none has been provided. Returns + // WifiConnectionStatus::CONNECTED on success, or the appropriate failure code + // otherwise. + virtual WifiConnectionStatus::Value connectToNetwork( + const std::string& ssid, const std::string& password, + WifiAuthType::Value auth_type) = 0; + + // Blocks until it's certain of there being a connection to the internet, or + // returns false if it fails to do so. + // + // How this method wants to verify said connection is totally up to it (so it + // can feel free to ping whatever server, download whatever resource, etc. + // that it needs to gain confidence that the internet is reachable hereon in). + virtual bool verifyInternetConnectivity() = 0; + + // Returns the local device's IP address in the IPv4 dotted-quad format. + virtual std::string getIPAddress() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_WIFI_H_ diff --git a/cpp/platform/base64_utils.cc b/cpp/platform/base64_utils.cc new file mode 100644 index 00000000..51cb5635 --- /dev/null +++ b/cpp/platform/base64_utils.cc @@ -0,0 +1,56 @@ +#include "platform/base64_utils.h" + +#include "strings/escaping.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { + +std::string Base64Utils::encode(ConstPtr bytes) { + std::string base64_string; + + if (!bytes.isNull()) { + absl::WebSafeBase64Escape(std::string(bytes->getData(), bytes->size()), + &base64_string); + } + + return base64_string; +} + +std::string Base64Utils::encode(const ByteArray& bytes) { + std::string base64_string; + absl::WebSafeBase64Escape(std::string(bytes.getData(), bytes.size()), + &base64_string); + + return base64_string; +} + +std::string Base64Utils::encode(const std::string& input) { + std::string base64_string; + absl::WebSafeBase64Escape(input, &base64_string); + + return base64_string; +} + +template<> +Ptr Base64Utils::decode(const std::string& base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return Ptr(); + } + + return MakePtr(new ByteArray(decoded_string.data(), decoded_string.size())); +} + +template<> +ByteArray Base64Utils::decode(const std::string& base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return ByteArray(); + } + + return ByteArray(decoded_string.data(), decoded_string.size()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h new file mode 100644 index 00000000..76b8cb7d --- /dev/null +++ b/cpp/platform/base64_utils.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_BASE64_UTILS_H_ +#define PLATFORM_BASE64_UTILS_H_ + +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +class Base64Utils { + public: + static std::string encode(const std::string& input); + static std::string encode(const ByteArray& bytes); + static std::string encode(ConstPtr bytes); + + template + static T decode(const std::string& base64_string); + template <> + Ptr decode(const std::string& base64_string); + template <> + ByteArray decode(const std::string& base64_string); + static Ptr decode(const std::string& base64_string) { + return decode>(base64_string); + } +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_BASE64_UTILS_H_ diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h new file mode 100644 index 00000000..a3ea830e --- /dev/null +++ b/cpp/platform/byte_array.h @@ -0,0 +1,66 @@ +#ifndef PLATFORM_BYTE_ARRAY_H_ +#define PLATFORM_BYTE_ARRAY_H_ + +#include "platform/port/string.h" + +namespace location { +namespace nearby { + +class ByteArray { + public: + // Create an empty ByteArray + ByteArray() {} + + // Create ByteArray from string. + explicit ByteArray(const std::string& source) { + data_ = source; + } + + // Create default-initialized ByteArray of a given size. + explicit ByteArray(size_t size) { + setData(size); + } + + // Create value-initialized ByteArray of a given size. + ByteArray(const char* data, size_t size) { + setData(data, size); + } + + // Assign a new value to this ByteArray, as a copy of data, with a given size. + void setData(const char* data, size_t size) { + data_.assign(data, size); + } + + // Assign a new value of a given size to this ByteArray + // (as a repeated char value). + void setData(size_t size, char value = 0) { + data_.assign(size, value); + } + + char* getData() { return data_.data(); } + const char* getData() const { return data_.data(); } + size_t size() const { return data_.size(); } + + // Operator overloads when comparing ConstPtr. + bool operator==(const ByteArray& rhs) const { + return this->size() == rhs.size() && + memcmp(this->getData(), rhs.getData(), this->size()) == 0; + } + bool operator!=(const ByteArray& rhs) const { return !(*this == rhs); } + bool operator<(const ByteArray& rhs) const { + if (this->size() != rhs.size()) { + return this->size() < rhs.size(); + } + return memcmp(this->getData(), rhs.getData(), this->size()) < 0; + } + // TODO(b/149869249) : rename according to go/c-style + std::string asString() const { return data_; } + + private: + std::string data_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_BYTE_ARRAY_H_ diff --git a/cpp/platform/byte_array_test.cc b/cpp/platform/byte_array_test.cc new file mode 100644 index 00000000..8bd5ee97 --- /dev/null +++ b/cpp/platform/byte_array_test.cc @@ -0,0 +1,39 @@ +#include "platform/byte_array.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using location::nearby::ByteArray; + +TEST(ByteArrayTest, DefaultSizeIsZero) { + ByteArray bytes; + ASSERT_EQ(0, bytes.size()); +} + +TEST(ByteArrayTest, SetFromString) { + std::string setup("setup_test"); + ByteArray bytes{setup}; // array initialized with a copy of string. + ASSERT_EQ(setup.size(), bytes.size()); + ASSERT_EQ(bytes.asString(), setup); +} + +TEST(ByteArrayTest, SetExplicitSize) { + constexpr size_t kArraySize = 10; + char reference[kArraySize]{}; + ByteArray bytes{kArraySize}; // array of size 10, zero-initialized. + ASSERT_EQ(kArraySize, bytes.size()); + ASSERT_EQ(0, memcmp(bytes.getData(), reference, kArraySize)); +} + +TEST(ByteArrayTest, SetExplicitData) { + constexpr static const char message[] {"test_message"}; + constexpr size_t kMessageSize = sizeof(message); + ByteArray bytes{message, kMessageSize}; + ASSERT_EQ(kMessageSize, bytes.size()); + ASSERT_NE(message, bytes.getData()); + ASSERT_EQ(0, memcmp(message, bytes.getData(), kMessageSize)); +} + +} // namespace diff --git a/cpp/platform/callable.h b/cpp/platform/callable.h new file mode 100644 index 00000000..792a207d --- /dev/null +++ b/cpp/platform/callable.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_CALLABLE_H_ +#define PLATFORM_CALLABLE_H_ + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// The Callable interface should be implemented by any class whose instances are +// intended to be executed by a thread, and need to return a result. The class +// must define a method named call() with no arguments and a specific return +// type. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html +template +class Callable { + public: + virtual ~Callable() {} + + virtual ExceptionOr call() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_CALLABLE_H_ diff --git a/cpp/platform/cancelable.h b/cpp/platform/cancelable.h new file mode 100644 index 00000000..74a2d634 --- /dev/null +++ b/cpp/platform/cancelable.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_CANCELABLE_H_ +#define PLATFORM_CANCELABLE_H_ + +namespace location { +namespace nearby { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable { + public: + virtual ~Cancelable() {} + + virtual bool cancel() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_CANCELABLE_H_ diff --git a/cpp/platform/cancelable_alarm.cc b/cpp/platform/cancelable_alarm.cc new file mode 100644 index 00000000..326a893e --- /dev/null +++ b/cpp/platform/cancelable_alarm.cc @@ -0,0 +1,37 @@ +#include "platform/cancelable_alarm.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { + +template +CancelableAlarm::CancelableAlarm( + const string &name, Ptr runnable, std::int64_t delay_millis, + Ptr scheduled_executor) + : name_(name), + lock_(Platform::createLock()), + cancelable_(scheduled_executor->schedule(runnable, delay_millis)) {} + +template +CancelableAlarm::~CancelableAlarm() { + cancelable_.destroy(); +} + +template +bool CancelableAlarm::cancel() { + Synchronized s(lock_.get()); + + if (cancelable_.isNull()) { + // TODO(tracyzhou): Add logging + return false; + } + + bool canceled = cancelable_->cancel(); + // TODO(tracyzhou): Add logging + cancelable_.destroy(); + return canceled; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/cancelable_alarm.h b/cpp/platform/cancelable_alarm.h new file mode 100644 index 00000000..d5549cf6 --- /dev/null +++ b/cpp/platform/cancelable_alarm.h @@ -0,0 +1,41 @@ +#ifndef PLATFORM_CANCELABLE_ALARM_H_ +#define PLATFORM_CANCELABLE_ALARM_H_ + +#include + +#include "platform/api/lock.h" +#include "platform/cancelable.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +/** + * A cancelable alarm with a name. This is a simple wrapper around the logic + * for posting a Runnable on a ScheduledExecutor and (possibly) later + * canceling it. + */ +template +class CancelableAlarm { + public: + CancelableAlarm( + const string& name, Ptr runnable, std::int64_t delay_millis, + Ptr scheduled_executor); + ~CancelableAlarm(); + + bool cancel(); + + private: + string name_; + ScopedPtr > lock_; + Ptr cancelable_; +}; + +} // namespace nearby +} // namespace location + +#include "platform/cancelable_alarm.cc" + +#endif // PLATFORM_CANCELABLE_ALARM_H_ diff --git a/cpp/platform/container_of.h b/cpp/platform/container_of.h new file mode 100644 index 00000000..ccdccd7a --- /dev/null +++ b/cpp/platform/container_of.h @@ -0,0 +1,71 @@ +#ifndef PLATFORM_CONTAINER_OF_H_ +#define PLATFORM_CONTAINER_OF_H_ + +#include +#include + +namespace location::nearby { + +// Similar to offsetof() macro, but implemented in a type-safe way, +// OffsetOf() returns the byte offset of a given data +// member in the ClassType. +// Behavior is undefined if member is not a direct, non-static data member of +// type ClassType. +// usage example: +// struct S { int x; double y; }; +// size_t y_offset = OffsetOf(&S::y); +// CHECK(y_offset >= sizeof(int)); +// +// the following is not guaranteed to work: +// struct S1 { int x; }; +// struct S2 { double y; }; +// struct S : public S1, S2 { char t; }; +// size_t y_offset_bad = OffsetOf(&S::y); +// because S::y is not a direct member of S; it is a member by inheritance. +// To make sure OffsetOf works with inherited members, it must be called +// with explicitly defined template parameters, as follows: +// size_t y_offset_ok = OffsetOf(&S::y); +// +// However, the following is guaranteed to work: +// struct S1 { int x; }; +// struct S2 { double y; }; +// struct S3 { double z; }; +// struct S : public S1, S2 { S3 s3; char t; }; +// size_t s3_offset = OffsetOf(&S::s3); + +template +constexpr size_t OffsetOf(const ValueType ClassType::*member) { + std::aligned_storage_t obj_memory; + ClassType* obj = reinterpret_cast(&obj_memory); + return reinterpret_cast(&(obj->*member)) - + reinterpret_cast(obj); +} + +// Similar to Linux containerof() macro, this function returns pointer to +// the type instance that contains the specified member; +// ContainerOf(, ); +// usage example: +// struct S { int x; double y; } a; +// S *b = ContainerOf(&a.y, &S::y); +// CHECK(b == &a); +template +ClassType* ContainerOf(ValueType* ptr, ValueType ClassType::*member) { + using BaseValueType = std::remove_volatile_t; + return reinterpret_cast( + reinterpret_cast(const_cast(ptr)) - + OffsetOf(member)); +} + +template +const ClassType* ContainerOf(const ValueType* ptr, + ValueType ClassType::*member) { + using BaseValueType = std::remove_volatile_t; + return reinterpret_cast( + reinterpret_cast(const_cast(ptr)) - + OffsetOf(member)); +} + +} // namespace location::nearby + +#endif // PLATFORM_CONTAINER_OF_H_ diff --git a/cpp/platform/container_of_test.cc b/cpp/platform/container_of_test.cc new file mode 100644 index 00000000..72c5d8c6 --- /dev/null +++ b/cpp/platform/container_of_test.cc @@ -0,0 +1,47 @@ +#include "platform/container_of.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby { + +TEST(OffsetOf, OffsetOfTest) { + struct [[gnu::packed]] S { + char x; + double y; + }; + EXPECT_EQ(OffsetOf(&S::x), 0U); + EXPECT_EQ(OffsetOf(&S::y), sizeof(S::x)); +} + +TEST(OffsetOf, ExplicitOffsetOfTest) { + struct [[gnu::packed]] S1 { int x; }; + struct [[gnu::packed]] S2 { double y; }; + struct [[gnu::packed]] S : public S1, S2 { char t; }; + EXPECT_EQ((OffsetOf().x), S>(&S::x)), 0U); + EXPECT_EQ((OffsetOf().y), S>(&S::y)), sizeof(S::x)); +} + +TEST(ContainerOf, ContainerOfTest) { + struct [[gnu::packed]] S { + char x; + double y; + } s; + char* p = &s.x; + double* q = &s.y; + EXPECT_EQ(ContainerOf(p, &S::x), &s); + EXPECT_EQ(ContainerOf(q, &S::y), &s); +} + +TEST(ContainerOf, ContainerOfTestConst) { + struct [[gnu::packed]] S { + char x; + double y; + } s; + const char* p = &s.x; + const double* q = &s.y; + EXPECT_EQ(ContainerOf(p, &S::x), &s); + EXPECT_EQ(ContainerOf(q, &S::y), &s); +} + +} // namespace location::nearby diff --git a/cpp/platform/exception.cc b/cpp/platform/exception.cc new file mode 100644 index 00000000..c5dd53a4 --- /dev/null +++ b/cpp/platform/exception.cc @@ -0,0 +1,30 @@ +#include "platform/exception.h" + +namespace location { +namespace nearby { + +template +ExceptionOr::ExceptionOr(T result) + : result_(result), exception_(Exception::NONE) {} + +template +ExceptionOr::ExceptionOr(Exception::Value exception) + : result_(), exception_(exception) {} + +template +bool ExceptionOr::ok() const { + return Exception::NONE == exception_; +} + +template +T ExceptionOr::result() const { + return result_; +} + +template +Exception::Value ExceptionOr::exception() const { + return exception_; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h new file mode 100644 index 00000000..8b07e565 --- /dev/null +++ b/cpp/platform/exception.h @@ -0,0 +1,57 @@ +#ifndef PLATFORM_EXCEPTION_H_ +#define PLATFORM_EXCEPTION_H_ + +namespace location { +namespace nearby { + +struct Exception { + enum Value { + NONE, + IO, + INTERRUPTED, + INVALID_PROTOCOL_BUFFER, + EXECUTION, + }; +}; + +// ExceptionOr models the concept of the return value of a function that might +// throw an exception. +// +// If ok() returns true, result() is a usable return value. Otherwise, +// exception() explains why such a value is not present. +// +// A typical pattern of usage is as follows: +// +// if (!e.ok()) { +// if (Exception::EXCEPTION_TYPE_1 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_1. +// } else if (Exception::EXCEPTION_TYPE_2 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_2. +// } +// +// return; +// } +// +// // Use e.result(). +template +class ExceptionOr { + public: + explicit ExceptionOr(T result); + explicit ExceptionOr(Exception::Value exception); + + bool ok() const; + + T result() const; + Exception::Value exception() const; + + private: + T result_; + Exception::Value exception_; +}; + +} // namespace nearby +} // namespace location + +#include "platform/exception.cc" + +#endif // PLATFORM_EXCEPTION_H_ diff --git a/cpp/platform/file_impl.cc b/cpp/platform/file_impl.cc new file mode 100644 index 00000000..67bab338 --- /dev/null +++ b/cpp/platform/file_impl.cc @@ -0,0 +1,75 @@ +#include "platform/file_impl.h" + +#include +#include + +namespace location { +namespace nearby { + +// InputFile + +InputFileImpl::InputFileImpl(const std::string& path, std::int64_t size) + : file_(path), path_(path), total_size_(size) {} + +ExceptionOr> InputFileImpl::read(int64_t size) { + if (!file_.is_open()) { + return ExceptionOr>(Exception::IO); + } + + if (file_.peek() == EOF) { + return ExceptionOr>(ConstPtr()); + } + + if (!file_.good()) { + return ExceptionOr>(Exception::IO); + } + + std::unique_ptr read_bytes {new char [size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr>(Exception::IO); + } + + return ExceptionOr>( + MakeConstPtr(new ByteArray(read_bytes.get(), num_bytes_read))); +} + +std::string InputFileImpl::getFilePath() const { return path_; } + +std::int64_t InputFileImpl::getTotalSize() const { return total_size_; } + +void InputFileImpl::close() { + if (file_.is_open()) { + file_.close(); + } +} + +// OutputFile + +OutputFileImpl::OutputFileImpl(const std::string& path) : file_(path) {} + +Exception::Value OutputFileImpl::write(ConstPtr data) { + ScopedPtr> scoped_data(data); + + if (!file_.is_open()) { + return Exception::IO; + } + + if (!file_.good()) { + return Exception::IO; + } + + file_.write(data->getData(), data->size()); + file_.flush(); + return file_.good() ? Exception::NONE : Exception::IO; +} + +void OutputFileImpl::close() { + if (file_.is_open()) { + file_.close(); + } +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/file_impl.h b/cpp/platform/file_impl.h new file mode 100644 index 00000000..db522c23 --- /dev/null +++ b/cpp/platform/file_impl.h @@ -0,0 +1,46 @@ +#ifndef PLATFORM_FILE_IMPL_H_ +#define PLATFORM_FILE_IMPL_H_ + +#include +#include + +#include "platform/api/input_file.h" +#include "platform/api/output_file.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +class InputFileImpl final : public InputFile { + public: + explicit InputFileImpl(const std::string& path, std::int64_t size); + ~InputFileImpl() override {} + + ExceptionOr> read(std::int64_t size) override; + std::string getFilePath() const override; + std::int64_t getTotalSize() const override; + void close() override; + + private: + std::ifstream file_; + const std::string path_; + const std::int64_t total_size_; +}; + +class OutputFileImpl final : public OutputFile { + public: + explicit OutputFileImpl(const std::string& path); + ~OutputFileImpl() override {} + + Exception::Value write(ConstPtr data) override; + void close() override; + + private: + std::ofstream file_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_FILE_IMPL_H_ diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc new file mode 100644 index 00000000..f1397d5c --- /dev/null +++ b/cpp/platform/file_impl_test.cc @@ -0,0 +1,133 @@ +#include "platform/file_impl.h" + +#include +#include +#include +#include + +#include "file/util/temp_path.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +class FileImplTest : public ::testing::Test { + protected: + void SetUp() override { + temp_path_ = std::make_unique(TempPath::Local); + path_ = temp_path_->path() + "/file.txt"; + std::ofstream output_file(path_); + file_ = std::fstream(path_, std::fstream::in | std::fstream::out); + } + + void WriteToFile(const std::string& text) { + file_ << text; + file_.flush(); + size_ += text.size(); + } + + size_t GetSize() const { return size_; } + + void AssertEquals(const ExceptionOr>& bytes, + const std::string& expected) { + ASSERT_TRUE(bytes.ok()); + ScopedPtr> byte_array(bytes.result()); + ASSERT_STREQ(byte_array->getData(), expected.c_str()); + ASSERT_EQ(byte_array->size(), expected.length()); + } + + void AssertNull(const ExceptionOr>& bytes) { + ASSERT_TRUE(bytes.ok()); + ASSERT_TRUE(bytes.result().isNull()); + } + + static const int64_t kMaxSize = 3; + + std::unique_ptr temp_path_; + std::string path_; + std::fstream file_; + size_t size_ = 0; +}; + +TEST_F(FileImplTest, InputFile_NonExistentPath) { + InputFileImpl input_file("/not/a/valid/path.txt", GetSize()); + ExceptionOr> read_result = input_file.read(kMaxSize); + ASSERT_FALSE(read_result.ok()); + ASSERT_EQ(read_result.exception(), Exception::IO); +} + +TEST_F(FileImplTest, InputFile_GetFilePath) { + InputFileImpl input_file(path_, GetSize()); + ASSERT_EQ(input_file.getFilePath(), path_); +} + +TEST_F(FileImplTest, InputFile_EmptyFileEOF) { + InputFileImpl input_file(path_, GetSize()); + AssertNull(input_file.read(kMaxSize)); +} + +TEST_F(FileImplTest, InputFile_ReadWorks) { + WriteToFile("abc"); + InputFileImpl input_file(path_, GetSize()); + auto read_data = input_file.read(kMaxSize); + read_data.result().destroy(); + SUCCEED(); +} + +TEST_F(FileImplTest, InputFile_ReadUntilEOF) { + WriteToFile("abc"); + InputFileImpl input_file(path_, GetSize()); + AssertEquals(input_file.read(kMaxSize), "abc"); + AssertNull(input_file.read(kMaxSize)); +} + +TEST_F(FileImplTest, InputFile_ReadWithSize) { + WriteToFile("abc"); + InputFileImpl input_file(path_, GetSize()); + AssertEquals(input_file.read(2), "ab"); + AssertEquals(input_file.read(1), "c"); + AssertNull(input_file.read(kMaxSize)); +} + +TEST_F(FileImplTest, InputFile_GetTotalSize) { + WriteToFile("abc"); + InputFileImpl input_file(path_, GetSize()); + EXPECT_EQ(input_file.getTotalSize(), 3); + AssertEquals(input_file.read(1), "a"); + EXPECT_EQ(input_file.getTotalSize(), 3); +} + +TEST_F(FileImplTest, InputFile_Close) { + WriteToFile("abc"); + InputFileImpl input_file(path_, GetSize()); + input_file.close(); + ExceptionOr> read_result = input_file.read(kMaxSize); + ASSERT_FALSE(read_result.ok()); + ASSERT_EQ(read_result.exception(), Exception::IO); +} + +TEST_F(FileImplTest, OutputFile_NonExistentPath) { + OutputFileImpl output_file("/not/a/valid/path.txt"); + ConstPtr bytes = MakeConstPtr(new ByteArray("a", 1)); + Exception::Value write_result = output_file.write(bytes); + ASSERT_EQ(write_result, Exception::IO); +} + +TEST_F(FileImplTest, OutputFile_Write) { + OutputFileImpl output_file(path_); + ConstPtr bytes1 = MakeConstPtr(new ByteArray("a", 1)); + ConstPtr bytes2 = MakeConstPtr(new ByteArray("bc", 2)); + ASSERT_EQ(output_file.write(bytes1), Exception::NONE); + ASSERT_EQ(output_file.write(bytes2), Exception::NONE); + InputFileImpl input_file(path_, GetSize()); + AssertEquals(input_file.read(kMaxSize), "abc"); +} + +TEST_F(FileImplTest, OutputFile_Close) { + OutputFileImpl output_file(path_); + output_file.close(); + ConstPtr bytes = MakeConstPtr(new ByteArray("a", 1)); + ASSERT_EQ(output_file.write(bytes), Exception::IO); +} +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/default/BUILD new file mode 100644 index 00000000..24d48ca7 --- /dev/null +++ b/cpp/platform/impl/default/BUILD @@ -0,0 +1,45 @@ +cc_library( + name = "default", + srcs = [ + "default_condition_variable.cc", + "default_lock.cc", + "default_platform.cc", + ], + hdrs = [ + "default_condition_variable.h", + "default_lock.h", + "default_platform.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform/api", + ], +) + +cc_library( + name = "lock", + srcs = ["default_lock.cc"], + hdrs = ["default_lock.h"], + visibility = [ + "//platform:__subpackages__", + ], + deps = ["//platform/api:lock"], +) + +cc_library( + name = "condition_variable", + srcs = ["default_condition_variable.cc"], + hdrs = ["default_condition_variable.h"], + visibility = [ + "//platform:__subpackages__", + ], + deps = [ + ":default", + "//platform:types", + "//platform/api:condition_variable", + ], +) diff --git a/cpp/platform/impl/default/default_condition_variable.cc b/cpp/platform/impl/default/default_condition_variable.cc new file mode 100644 index 00000000..d7e3811f --- /dev/null +++ b/cpp/platform/impl/default/default_condition_variable.cc @@ -0,0 +1,28 @@ +#include "platform/impl/default/default_condition_variable.h" + +namespace location { +namespace nearby { + +DefaultConditionVariable::DefaultConditionVariable(Ptr lock) + : lock_(lock), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +DefaultConditionVariable::~DefaultConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void DefaultConditionVariable::notify() { pthread_cond_broadcast(&cond_); } + +Exception::Value DefaultConditionVariable::wait() { + pthread_cond_wait(&cond_, &(lock_->mutex_)); + + return Exception::NONE; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/default/default_condition_variable.h b/cpp/platform/impl/default/default_condition_variable.h new file mode 100644 index 00000000..4aa1343f --- /dev/null +++ b/cpp/platform/impl/default/default_condition_variable.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ + +#include + +#include "platform/api/condition_variable.h" +#include "platform/impl/default/default_lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +class DefaultConditionVariable : public ConditionVariable { + public: + explicit DefaultConditionVariable(Ptr lock); + ~DefaultConditionVariable() override; + + void notify() override; + Exception::Value wait() override; + + private: + Ptr lock_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_lock.cc b/cpp/platform/impl/default/default_lock.cc new file mode 100644 index 00000000..bfd3cf0b --- /dev/null +++ b/cpp/platform/impl/default/default_lock.cc @@ -0,0 +1,24 @@ +#include "platform/impl/default/default_lock.h" + +namespace location { +namespace nearby { + +DefaultLock::DefaultLock() : attr_(), mutex_() { + pthread_mutexattr_init(&attr_); + pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&mutex_, &attr_); +} + +DefaultLock::~DefaultLock() { + pthread_mutex_destroy(&mutex_); + + pthread_mutexattr_destroy(&attr_); +} + +void DefaultLock::lock() { pthread_mutex_lock(&mutex_); } + +void DefaultLock::unlock() { pthread_mutex_unlock(&mutex_); } + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/default/default_lock.h b/cpp/platform/impl/default/default_lock.h new file mode 100644 index 00000000..18d50e44 --- /dev/null +++ b/cpp/platform/impl/default/default_lock.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#define PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ + +#include + +#include "platform/api/lock.h" + +namespace location { +namespace nearby { + +class DefaultLock : public Lock { + public: + DefaultLock(); + ~DefaultLock() override; + + void lock() override; + void unlock() override; + + private: + friend class DefaultConditionVariable; + + pthread_mutexattr_t attr_; + pthread_mutex_t mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ diff --git a/cpp/platform/impl/default/default_platform.cc b/cpp/platform/impl/default/default_platform.cc new file mode 100644 index 00000000..3d41d42b --- /dev/null +++ b/cpp/platform/impl/default/default_platform.cc @@ -0,0 +1,17 @@ +#include "platform/impl/default/default_platform.h" + +#include "platform/impl/default/default_condition_variable.h" +#include "platform/impl/default/default_lock.h" + +namespace location { +namespace nearby { + +Ptr DefaultPlatform::createLock() { return MakePtr(new DefaultLock()); } + +Ptr DefaultPlatform::createConditionVariable( + Ptr lock) { + return MakePtr(new DefaultConditionVariable(DowncastPtr(lock))); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/default/default_platform.h b/cpp/platform/impl/default/default_platform.h new file mode 100644 index 00000000..0d001825 --- /dev/null +++ b/cpp/platform/impl/default/default_platform.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ +#define PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ + +#include "platform/api/condition_variable.h" +#include "platform/api/lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Provides obvious portable implementations of a subset of the hooks specified +// within //platform/api/. +// +// It's highly recommended that custom Platform implementations delegate to +// these methods unless there's a very good reason not to. +class DefaultPlatform { + public: + static Ptr createLock(); + + static Ptr createConditionVariable(Ptr lock); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ diff --git a/cpp/platform/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD new file mode 100644 index 00000000..e69de29b diff --git a/cpp/platform/impl/ios/BUILD b/cpp/platform/impl/ios/BUILD new file mode 100644 index 00000000..a75790f9 --- /dev/null +++ b/cpp/platform/impl/ios/BUILD @@ -0,0 +1,9 @@ +objc_library( + name = "ios", + visibility = [ + "//googlemac/iPhone/Nearby/HelloSetup:__subpackages__", + ], + deps = [ + "//googlemac/iPhone/Shared/Nearby/Connections:Platform", + ], +) diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD new file mode 100644 index 00000000..892ccd51 --- /dev/null +++ b/cpp/platform/impl/sample/BUILD @@ -0,0 +1,20 @@ +cc_library( + name = "sample", + srcs = [ + "sample_wifi_medium.cc", + "sample_wifi_medium.h", + ], + hdrs = ["sample_platform.h"], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/time", + ], +) diff --git a/cpp/platform/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h new file mode 100644 index 00000000..113f4636 --- /dev/null +++ b/cpp/platform/impl/sample/sample_platform.h @@ -0,0 +1,141 @@ +#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ +#define PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/multi_thread_executor.h" +#include "platform/api/settable_future.h" +#include "platform/api/single_thread_executor.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/wifi.h" +#include "platform/cancelable.h" +#include "platform/impl/sample/sample_wifi_medium.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { +namespace sample { + +// The SamplePlatform class below shows an example of the factory functions +// and typedefs. +class SamplePlatform { + public: + class SampleSubmittableExecutor + : public SubmittableExecutor { + public: + template + Ptr > submit(Ptr > callable) { + return Ptr >(); + } + }; + + class SampleSingleThreadExecutor + : public SingleThreadExecutor { + public: + void execute(Ptr runnable) override {} + void shutdown() override {} + }; + + class SampleMultiThreadExecutor + : public MultiThreadExecutor { + public: + void execute(Ptr runnable) override {} + void shutdown() override {} + }; + + class SampleScheduledExecutor { + public: + Ptr schedule(Ptr runnable, + std::int64_t delay_millis) { + return Ptr(); + } + void shutdown() {} + }; + + typedef SampleSingleThreadExecutor SingleThreadExecutorType; + static Ptr createSingleThreadExecutor() { + return MakePtr(new SingleThreadExecutorType()); + } + + typedef SampleMultiThreadExecutor MultiThreadExecutorType; + static Ptr createMultiThreadExecutor( + std::int32_t max_concurrency) { + return MakePtr(new MultiThreadExecutorType()); + } + + typedef SampleScheduledExecutor ScheduledExecutorType; + static Ptr createScheduledExecutor() { + return MakePtr(new ScheduledExecutorType()); + } + + static Ptr createBluetoothAdapter() { + return Ptr(); + } + + static Ptr createWifiMedium() { + return MakePtr(new SampleWifiMedium()); + } + + static Ptr createCountDownLatch(std::int32_t count) { + return Ptr(); + } + + template + static Ptr > createSettableFuture() { + return Ptr >(); + } + + static Ptr createThreadUtils() { return Ptr(); } + + static Ptr createSystemClock() { return Ptr(); } + + static Ptr createAtomicBoolean(bool initial_value) { + return Ptr(); + } + + template + static Ptr > createAtomicReference(T initial_value) { + return Ptr >(); + } + + static Ptr createBluetoothClassicMedium() { + return Ptr(); + } + + static Ptr createBLEMedium() { return Ptr(); } + + static Ptr createBLEMediumV2() { return Ptr(); } + + static Ptr createLock() { return Ptr(); } + + static Ptr createConditionVariable(Ptr lock) { + return Ptr(); + } + + static Ptr createHashUtils() { return Ptr(); } + + static std::string getDeviceId() { return ""; } + + static std::string getPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); + } +}; + +} // namespace sample +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ diff --git a/cpp/platform/impl/sample/sample_wifi_medium.cc b/cpp/platform/impl/sample/sample_wifi_medium.cc new file mode 100644 index 00000000..89b68391 --- /dev/null +++ b/cpp/platform/impl/sample/sample_wifi_medium.cc @@ -0,0 +1,110 @@ +#include "platform/impl/sample/sample_wifi_medium.h" + +#include + +#include "platform/prng.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace sample { + +namespace { + +const char* kOpenSSID = "__OPEN__"; +const char* kWpaPskSSID = "__WPA_PSK__"; +const char* kWepSSID = "__WEP__"; +const char* kNoInternetConnectivitySSID = "__NO_INTERNET_CONNECTIVITY__"; +const char* kConnectionFailureSSID = "__CONNECTION_FAILURE__"; +const char* kAuthFailureSSID = "__AUTH_FAILURE__"; + +std::uint32_t boundedUInt32(std::uint32_t upper_limit) { + return Prng().nextUInt32() % (upper_limit + 1); +} + +void randomSleep(std::uint32_t upper_limit_millis) { + absl::SleepFor(absl::Milliseconds(boundedUInt32(upper_limit_millis))); +} + +} // namespace + +std::vector SampleWifiMedium::canned_scan_results_; + +SampleWifiMedium::SampleWifiMedium() : current_ssid_() { + // One-time initialization of our static canned_scan_results_. + if (canned_scan_results_.empty()) { + canned_scan_results_.push_back( + SampleWifiScanResult(kOpenSSID, 1, 2401, WifiAuthType::OPEN)); + canned_scan_results_.push_back( + SampleWifiScanResult(kWpaPskSSID, 2, 5002, WifiAuthType::WPA_PSK)); + canned_scan_results_.push_back( + SampleWifiScanResult(kWepSSID, 3, 2403, WifiAuthType::WEP)); + canned_scan_results_.push_back(SampleWifiScanResult( + kNoInternetConnectivitySSID, 4, 5004, WifiAuthType::OPEN)); + canned_scan_results_.push_back(SampleWifiScanResult( + kConnectionFailureSSID, 5, 2405, WifiAuthType::OPEN)); + canned_scan_results_.push_back( + SampleWifiScanResult(kAuthFailureSSID, 6, 5006, WifiAuthType::OPEN)); + } +} + +SampleWifiMedium::~SampleWifiMedium() {} + +bool SampleWifiMedium::scan( + Ptr scan_result_callback) { + // Sleep for up to 10 seconds, to simulate performing an actual Wifi scan. + randomSleep(10 * 1000); + + // Construct the response. + std::vector > scan_results; + for (std::vector::const_iterator it = + canned_scan_results_.begin(); + it != canned_scan_results_.end(); it++) { + scan_results.push_back(ConstPtr( + new SampleWifiScanResult(it->getSSID(), it->getSignalStrengthDbm(), + it->getFrequencyMhz(), it->getAuthType()))); + } + + // And report it back. + scan_result_callback->onScanResults(scan_results); + + return false; +} + +WifiConnectionStatus::Value SampleWifiMedium::connectToNetwork( + const std::string& ssid, const std::string& password, + WifiAuthType::Value auth_type) { + // Sleep for up to 10 seconds, to simulate actually connecting to the SSID. + randomSleep(10 * 1000); + + if (kConnectionFailureSSID == ssid) { + return WifiConnectionStatus::CONNECTION_FAILURE; + } + + if (kAuthFailureSSID == ssid) { + return WifiConnectionStatus::AUTH_FAILURE; + } + + return WifiConnectionStatus::CONNECTED; +} + +bool SampleWifiMedium::verifyInternetConnectivity() { + if (current_ssid_.empty()) { + return false; + } + + // Sleep for up to 5 seconds, to simulate actually verifying internet + // connectivity. + randomSleep(5 * 1000); + + return current_ssid_ != kNoInternetConnectivitySSID; +} + +std::string SampleWifiMedium::getIPAddress() { + return current_ssid_.empty() ? "" : "1.2.3.4"; +} + +} // namespace sample +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/sample/sample_wifi_medium.h b/cpp/platform/impl/sample/sample_wifi_medium.h new file mode 100644 index 00000000..e64f1b8e --- /dev/null +++ b/cpp/platform/impl/sample/sample_wifi_medium.h @@ -0,0 +1,59 @@ +#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#define PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ + +#include "platform/api/wifi.h" + +namespace location { +namespace nearby { +namespace sample { + +class SampleWifiScanResult : public WifiScanResult { + public: + SampleWifiScanResult(const std::string& ssid, + std::int32_t signal_strength_dbm, + std::int32_t frequency_mhz, + WifiAuthType::Value auth_type) + : ssid_(ssid), + signal_strength_dbm_(signal_strength_dbm), + frequency_mhz_(frequency_mhz), + auth_type_(auth_type) {} + ~SampleWifiScanResult() override {} + + std::string getSSID() const override { return ssid_; } + std::int32_t getSignalStrengthDbm() const override { + return signal_strength_dbm_; + } + std::int32_t getFrequencyMhz() const override { return frequency_mhz_; } + WifiAuthType::Value getAuthType() const override { return auth_type_; } + + private: + const std::string ssid_; + const std::int32_t signal_strength_dbm_; + const std::int32_t frequency_mhz_; + const WifiAuthType::Value auth_type_; +}; + +class SampleWifiMedium : public WifiMedium { + public: + SampleWifiMedium(); + ~SampleWifiMedium() override; + + bool scan(Ptr scan_result_callback) override; + WifiConnectionStatus::Value connectToNetwork( + const std::string& ssid, const std::string& password, + WifiAuthType::Value auth_type) override; + bool verifyInternetConnectivity() override; + std::string getIPAddress() override; + + private: + static std::vector canned_scan_results_; + + // The SSID this Wifi stack is currently connected to; empty string if none. + std::string current_ssid_; +}; + +} // namespace sample +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ diff --git a/cpp/platform/logging.h b/cpp/platform/logging.h new file mode 100644 index 00000000..836ce62f --- /dev/null +++ b/cpp/platform/logging.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_LOGGING_H_ +#define PLATFORM_LOGGING_H_ + +#include "absl/base/internal/raw_logging.h" + +namespace location { +namespace nearby { + +// This uses an explicit printf-format and arguments list, and supports the +// following severities: +// +// - INFO +// - WARNING +// - ERROR +// - FATAL +// +// To make it easy to filer while debugging, it prepends "[NEARBY] " to all its +// logged messages. +// +// Sample usage: +// +// NEARBY_LOG(INFO, "%d is an int and %s is a std::string", i, s.c_str()); +#define NEARBY_LOG(severity, ...) \ + ABSL_RAW_LOG(severity, "[NEARBY] " __VA_ARGS__) + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_LOGGING_H_ diff --git a/cpp/platform/pipe.cc b/cpp/platform/pipe.cc new file mode 100644 index 00000000..e5572127 --- /dev/null +++ b/cpp/platform/pipe.cc @@ -0,0 +1,199 @@ +#include "platform/pipe.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { + +namespace pipe { + +template +class PipeInputStream : public InputStream { + public: + explicit PipeInputStream(Ptr> pipe) : pipe_(pipe) {} + ~PipeInputStream() override { + close(); + } + ExceptionOr> read() override { return read(kChunkSize); } + + ExceptionOr> read(std::int64_t size) override { + return pipe_->read(size); + } + + Exception::Value close() override { + pipe_->markInputStreamClosed(); + + return Exception::NONE; + } + + private: + static const std::int64_t kChunkSize = 64 * 1024; + + Ptr> pipe_; +}; + +template +class PipeOutputStream : public OutputStream { + public: + explicit PipeOutputStream(Ptr> pipe) : pipe_(pipe) {} + ~PipeOutputStream() override { + close(); + } + + Exception::Value write(ConstPtr data) override { + // Avoid leaks. + ScopedPtr> scoped_data(data); + + return pipe_->write(scoped_data.release()); + } + + Exception::Value flush() override { + // No-op. + return Exception::NONE; + } + + Exception::Value close() override { + pipe_->markOutputStreamClosed(); + + return Exception::NONE; + } + + private: + Ptr> pipe_; +}; + +} // namespace pipe + +template +Pipe::Pipe() + : lock_(Platform::createLock()), + cond_(Platform::createConditionVariable(lock_.get())), + buffer_(), + input_stream_closed_(false), + output_stream_closed_(false), + read_all_chunks_(false) {} + +template +Pipe::~Pipe() { + // Deallocate all the chunks still left in buffer_. + for (BufferType::iterator chunk_iter = buffer_.begin(); + chunk_iter != buffer_.end(); ++chunk_iter) { + (*chunk_iter).destroy(); + } +} + +template +Ptr Pipe::createInputStream(Ptr self) { + assert(self.isRefCounted()); + return MakeRefCountedPtr(new pipe::PipeInputStream(self)); +} + +template +Ptr Pipe::createOutputStream(Ptr self) { + assert(self.isRefCounted()); + return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); +} + +template +ExceptionOr> Pipe::read(std::int64_t size) { + Synchronized s(lock_.get()); + + // We're done reading all the chunks that were written before the OutputStream + // was closed, so there's nothing to do here other than return an empty chunk + // to serve as an EOF indication to callers. + if (read_all_chunks_) { + ExceptionOr>(ConstPtr()); + } + + while (buffer_.empty() && !input_stream_closed_) { + Exception::Value wait_exception = cond_->wait(); + + if (Exception::NONE != wait_exception) { + if (Exception::INTERRUPTED == wait_exception) { + return ExceptionOr>(Exception::IO); + } + } + } + + if (input_stream_closed_) { + return ExceptionOr>(Exception::IO); + } + + ScopedPtr> first_chunk(buffer_.front()); + buffer_.pop_front(); + + // If we received our sentinel chunk, mark the fact that there cannot + // possibly be any more chunks to read here on in, and return an empty chunk + // to serve as an EOF indication to callers. + if (first_chunk.isNull()) { + read_all_chunks_ = true; + return ExceptionOr>(ConstPtr()); + } + + // If first_chunk is small enough to not overshoot the requested 'size', just + // return that. + if (first_chunk->size() <= size) { + return ExceptionOr>(first_chunk.release()); + } else { + // Break first_chunk into 2 parts -- the first one of which (next_chunk) + // will be 'size' bytes long, and will be returned, and the second one of + // which (overflow_chunk) will be re-inserted into buffer_, at the head of + // the queue, to be served up in the next call to read(). + ScopedPtr> next_chunk( + MakeConstPtr(new ByteArray(first_chunk->getData(), size))); + ScopedPtr> overflow_chunk(MakeConstPtr(new ByteArray( + first_chunk->getData() + size, first_chunk->size() - size))); + buffer_.push_front(overflow_chunk.release()); + return ExceptionOr>(next_chunk.release()); + } +} + +template +Exception::Value Pipe::write(ConstPtr data) { + Synchronized s(lock_.get()); + + return writeLocked(data); +} + +template +void Pipe::markInputStreamClosed() { + Synchronized s(lock_.get()); + + input_stream_closed_ = true; + // Trigger cond_ to unblock a potentially-blocked call to read(), and to let + // it know to return Exception::IO. + cond_->notify(); +} + +template +void Pipe::markOutputStreamClosed() { + Synchronized s(lock_.get()); + + // Write a sentinel null chunk before marking output_stream_closed as true. + writeLocked(ConstPtr()); + output_stream_closed_ = true; +} + +template +Exception::Value Pipe::writeLocked(ConstPtr data) { + // Avoid leaks. + ScopedPtr> scoped_data(data); + + if (eitherStreamClosed()) { + return Exception::IO; + } + + buffer_.push_back(scoped_data.release()); + // Trigger cond_ to unblock a potentially-blocked call to read(), now that + // there's more data for it to consume. + cond_->notify(); + return Exception::NONE; +} + +template +bool Pipe::eitherStreamClosed() const { + return input_stream_closed_ || output_stream_closed_; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/pipe.h b/cpp/platform/pipe.h new file mode 100644 index 00000000..29e06d11 --- /dev/null +++ b/cpp/platform/pipe.h @@ -0,0 +1,75 @@ +#ifndef PLATFORM_PIPE_H_ +#define PLATFORM_PIPE_H_ + +#include +#include + +#include "platform/api/condition_variable.h" +#include "platform/api/input_stream.h" +#include "platform/api/lock.h" +#include "platform/api/output_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +namespace pipe { + +template +class PipeInputStream; +template +class PipeOutputStream; + +} // namespace pipe + +template +class Pipe { + public: + Pipe(); + ~Pipe(); + + // The returned InputStream is auto-destroyed when no longer referenced. + static Ptr createInputStream(Ptr); + // The returned OutputStream is auto-destroyed when no longer referenced. + static Ptr createOutputStream(Ptr); + + private: + ////////////////////////////////////////////////////////////////////////////// + // Everything in this first private: section is only used by PipeInputStream + // and PipeOutputStream, thus forming the interface presented to those 2 + // classes. + ////////////////////////////////////////////////////////////////////////////// + + template + friend class pipe::PipeInputStream; + template + friend class pipe::PipeOutputStream; + + ExceptionOr > read(std::int64_t size); + Exception::Value write(ConstPtr data); + + void markInputStreamClosed(); + void markOutputStreamClosed(); + + private: + Exception::Value writeLocked(ConstPtr data); + + bool eitherStreamClosed() const; + + ScopedPtr > lock_; + ScopedPtr > cond_; + typedef std::deque > BufferType; + BufferType buffer_; + bool input_stream_closed_; + bool output_stream_closed_; + bool read_all_chunks_; +}; + +} // namespace nearby +} // namespace location + +#include "platform/pipe.cc" + +#endif // PLATFORM_PIPE_H_ diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc new file mode 100644 index 00000000..35b97a2d --- /dev/null +++ b/cpp/platform/pipe_test.cc @@ -0,0 +1,407 @@ +#include "platform/pipe.h" + +#include + +#include + +#include "platform/impl/default/default_condition_variable.h" +#include "platform/impl/default/default_lock.h" +#include "platform/port/string.h" +#include "platform/prng.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +class SamplePlatform { + public: + static Ptr createLock() { return MakePtr(new DefaultLock()); } + static Ptr createConditionVariable(Ptr lock) { + return MakePtr( + new DefaultConditionVariable(DowncastPtr(lock))); + } +}; + +using SamplePipe = Pipe; + +TEST(PipeTest, SimpleWriteRead) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + std::string data("ABCD"); + ASSERT_EQ(Exception::NONE, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); + + ExceptionOr> read_data = input_stream->read(); + ASSERT_TRUE(read_data.ok()); + ScopedPtr> scoped_read_data(read_data.result()); + ASSERT_EQ(data.size(), scoped_read_data->size()); + ASSERT_EQ(0, memcmp(data.data(), scoped_read_data->getData(), + scoped_read_data->size())); +} + +TEST(PipeTest, WriteEndClosedBeforeRead) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + std::string data("ABCD"); + ASSERT_EQ(Exception::NONE, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); + + // Close the write end before the read end has even begun reading. + ASSERT_EQ(Exception::NONE, output_stream->close()); + + // We should still be able to read what was written. + ExceptionOr> read_data = input_stream->read(); + ASSERT_TRUE(read_data.ok()); + ScopedPtr> scoped_read_data(read_data.result()); + ASSERT_EQ(data.size(), scoped_read_data->size()); + ASSERT_EQ(0, memcmp(data.data(), scoped_read_data->getData(), + scoped_read_data->size())); + + // And after that, we should get our indication that all the data that could + // ever be read, has already been read. + read_data = input_stream->read(); + ASSERT_TRUE(read_data.ok()); + ASSERT_TRUE(read_data.result().isNull()); +} + +TEST(PipeTest, ReadEndClosedBeforeWrite) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + // Close the read end before the write end has even begun writing. + ASSERT_EQ(Exception::NONE, input_stream->close()); + + std::string data("ABCD"); + ASSERT_EQ(Exception::IO, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); +} + +TEST(PipeTest, SizedReadMoreThanFirstChunkSize) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + std::string data("ABCD"); + ASSERT_EQ(Exception::NONE, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); + + // Even though we ask for double of what's there in the first chunk, we should + // get back only what's there in that first chunk, and that's alright. + ExceptionOr> read_data = + input_stream->read(data.size() * 2); + ASSERT_TRUE(read_data.ok()); + ScopedPtr> scoped_read_data(read_data.result()); + ASSERT_EQ(data.size(), scoped_read_data->size()); + ASSERT_EQ(0, memcmp(data.data(), scoped_read_data->getData(), + scoped_read_data->size())); +} + +TEST(PipeTest, SizedReadLessThanFirstChunkSize) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + // Compose 'data' of 2 parts, to make it easier to validate our expectations. + std::string data_first_part("ABCD"); + std::string data_second_part("EFGHIJ"); + std::string data = data_first_part + data_second_part; + ASSERT_EQ(Exception::NONE, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); + + // When we ask for less than what's there in the first chunk, we should get + // back exactly what we asked for, with the remainder still being available + // for the next read. + std::int64_t desired_size = data_first_part.size(); + ExceptionOr> first_read_data = + input_stream->read(desired_size); + ASSERT_TRUE(first_read_data.ok()); + ScopedPtr> scoped_first_read_data( + first_read_data.result()); + ASSERT_EQ(desired_size, scoped_first_read_data->size()); + ASSERT_EQ(0, memcmp(data_first_part.data(), scoped_first_read_data->getData(), + scoped_first_read_data->size())); + + // Now read the remainder, and get everything that ought to have been left. + std::int64_t remaining_size = data_second_part.size(); + ExceptionOr> second_read_data = input_stream->read(); + ASSERT_TRUE(second_read_data.ok()); + ScopedPtr> scoped_second_read_data( + second_read_data.result()); + ASSERT_EQ(remaining_size, scoped_second_read_data->size()); + ASSERT_EQ(0, + memcmp(data_second_part.data(), scoped_second_read_data->getData(), + scoped_second_read_data->size())); +} + +TEST(PipeTest, ReadAfterInputStreamClosed) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + input_stream->close(); + + ExceptionOr> read_data = input_stream->read(); + ASSERT_TRUE(!read_data.ok()); + ASSERT_EQ(Exception::IO, read_data.exception()); +} + +TEST(PipeTest, WriteAfterOutputStreamClosed) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + output_stream->close(); + + std::string data("ABCD"); + ASSERT_EQ(Exception::IO, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); +} + +TEST(PipeTest, RepeatedClose) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + ASSERT_EQ(Exception::NONE, output_stream->close()); + ASSERT_EQ(Exception::NONE, output_stream->close()); + ASSERT_EQ(Exception::NONE, output_stream->close()); + + ASSERT_EQ(Exception::NONE, input_stream->close()); + ASSERT_EQ(Exception::NONE, input_stream->close()); + ASSERT_EQ(Exception::NONE, input_stream->close()); +} + +class Thread { + public: + Thread() : thread_(), attr_(), runnable_() { + pthread_attr_init(&attr_); + pthread_attr_setdetachstate(&attr_, PTHREAD_CREATE_JOINABLE); + } + ~Thread() { pthread_attr_destroy(&attr_); } + + void start(Ptr runnable) { + runnable_ = runnable; + + pthread_create(&thread_, &attr_, Thread::body, this); + } + + void join() { + pthread_join(thread_, nullptr); + + runnable_.destroy(); + } + + private: + static void* body(void* args) { + reinterpret_cast(args)->runnable_->run(); + return nullptr; + } + + pthread_t thread_; + pthread_attr_t attr_; + Ptr runnable_; +}; + +TEST(PipeTest, ReadBlockedUntilWrite) { + typedef volatile bool CrossThreadBool; + + class ReaderRunnable : public Runnable { + public: + ReaderRunnable(Ptr input_stream, + const std::string& expected_read_data, + CrossThreadBool* ok_for_read_to_unblock) + : input_stream_(input_stream), + expected_read_data_(expected_read_data), + ok_for_read_to_unblock_(ok_for_read_to_unblock) {} + ~ReaderRunnable() override {} + + void run() override { + ExceptionOr> read_data = input_stream_->read(); + + // Make sure read() doesn't return before it's appropriate. + if (!*ok_for_read_to_unblock_) { + FAIL() << "read() unblocked before it was supposed to."; + } + + // And then run our normal set of checks to make sure the read() was + // successful. + ASSERT_TRUE(read_data.ok()); + ScopedPtr> scoped_read_data(read_data.result()); + ASSERT_EQ(expected_read_data_.size(), scoped_read_data->size()); + ASSERT_EQ(0, + memcmp(expected_read_data_.data(), scoped_read_data->getData(), + scoped_read_data->size())); + } + + private: + ScopedPtr> input_stream_; + const std::string& expected_read_data_; + CrossThreadBool* ok_for_read_to_unblock_; + }; + + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + // State shared between this thread (the writer) and reader_thread. + CrossThreadBool ok_for_read_to_unblock = false; + std::string data("ABCD"); + + // Kick off reader_thread. + Thread reader_thread; + reader_thread.start(MakePtr(new ReaderRunnable( + SamplePipe::createInputStream(pipe), data, &ok_for_read_to_unblock))); + + // Introduce a delay before we actually write anything. + absl::SleepFor(absl::Seconds(5)); + // Mark that we're done with the delay, and that the write is about to occur + // (this is slightly earlier than it ought to be, but there's no way to + // atomically set this from within the implementation of write(), and doing it + // after is too late for the purposes of this test). + ok_for_read_to_unblock = true; + + // Perform the actual write. + ASSERT_EQ(Exception::NONE, output_stream->write(MakeConstPtr( + new ByteArray(data.data(), data.size())))); + + // And wait for reader_thread to finish. + reader_thread.join(); +} + +TEST(PipeTest, ConcurrentWriteAndRead) { + class BaseRunnable : public Runnable { + protected: + explicit BaseRunnable(const std::vector& chunks) + : chunks_(chunks), prng_() {} + ~BaseRunnable() override {} + + void randomSleep() { + // Generate a random sleep between 100 and 1000 milliseconds. + absl::SleepFor(absl::Milliseconds(boundedUInt32(100, 1000))); + } + + const std::vector& chunks_; + + private: + // Both ends of the bounds are inclusive. + std::uint32_t boundedUInt32(std::uint32_t lower_bound, + std::uint32_t upper_bound) { + return (prng_.nextUInt32() % (upper_bound - lower_bound + 1)) + + lower_bound; + } + + Prng prng_; + }; + + class WriterRunnable : public BaseRunnable { + public: + WriterRunnable(Ptr output_stream, + const std::vector& chunks) + : BaseRunnable(chunks), output_stream_(output_stream) {} + ~WriterRunnable() override {} + + void run() override { + for (std::vector::const_iterator it = chunks_.begin(); + it != chunks_.end(); ++it) { + const std::string& chunk = *it; + + randomSleep(); // Random pauses before each write. + ASSERT_EQ(Exception::NONE, + output_stream_->write( + MakeConstPtr(new ByteArray(chunk.data(), chunk.size())))); + } + + randomSleep(); // A random pause before closing the writer end. + ASSERT_EQ(Exception::NONE, output_stream_->close()); + } + + private: + ScopedPtr> output_stream_; + }; + + class ReaderRunnable : public BaseRunnable { + public: + ReaderRunnable(Ptr input_stream, + const std::vector& chunks) + : BaseRunnable(chunks), input_stream_(input_stream) {} + ~ReaderRunnable() override {} + + void run() override { + // First, calculate what we expect to receive, in total. + std::string expected_data; + for (std::vector::const_iterator it = chunks_.begin(); + it != chunks_.end(); ++it) { + expected_data += *it; + } + + // Then, start actually receiving. + std::string actual_data; + while (true) { + randomSleep(); // Random pauses before each read. + ExceptionOr> read_data = input_stream_->read(); + if (read_data.ok()) { + ScopedPtr> scoped_read_data(read_data.result()); + if (scoped_read_data.isNull()) { + break; // Normal exit from the read loop. + } + actual_data += std::string(scoped_read_data->getData(), + scoped_read_data->size()); + } else { + break; // Erroneous exit from the read loop. + } + } + + // And once we're done, check that we got everything we expected. + ASSERT_EQ(expected_data, actual_data); + } + + private: + ScopedPtr> input_stream_; + }; + + auto pipe = MakeRefCountedPtr(new SamplePipe()); + + std::vector chunks; + chunks.push_back("ABCD"); + chunks.push_back("EFGH"); + chunks.push_back("IJKL"); + + Thread writer_thread; + Thread reader_thread; + writer_thread.start(MakePtr( + new WriterRunnable(SamplePipe::createOutputStream(pipe), chunks))); + reader_thread.start( + MakePtr(new ReaderRunnable(SamplePipe::createInputStream(pipe), chunks))); + writer_thread.join(); + reader_thread.join(); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform/port/BUILD b/cpp/platform/port/BUILD new file mode 100644 index 00000000..80447569 --- /dev/null +++ b/cpp/platform/port/BUILD @@ -0,0 +1,38 @@ +cc_library( + name = "config", + hdrs = [ + "config.h", + ], + visibility = [ + "//visibility:private", + ], +) + +cc_library( + name = "string", + hdrs = [ + "string.h", + ], + visibility = [ + "//core:__subpackages__", + "//platform:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + ":config", + ], +) + +cc_library( + name = "down_cast", + hdrs = [ + "down_cast.h", + ], + visibility = [ + "//core:__subpackages__", + "//platform:__subpackages__", + ], + deps = [ + ":config", + ], +) diff --git a/cpp/platform/port/config.h b/cpp/platform/port/config.h new file mode 100644 index 00000000..841168b0 --- /dev/null +++ b/cpp/platform/port/config.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_PORT_CONFIG_H_ +#define PLATFORM_PORT_CONFIG_H_ + +// Clients can modify this file to customize the Nearby C++ codebase as per +// their particular constraints and environments. + +// Note: Every entry in this file should conform to the following format, to +// give precedence to command-line options (-D) that set these symbols: +// +// #ifndef XXX +// #define XXX 0/1 +// #endif + +#ifndef NEARBY_USE_STD_STRING +#define NEARBY_USE_STD_STRING 0 +#endif + +#ifndef NEARBY_USE_RTTI +#define NEARBY_USE_RTTI 1 +#endif + +#endif // PLATFORM_PORT_CONFIG_H_ diff --git a/cpp/platform/port/down_cast.h b/cpp/platform/port/down_cast.h new file mode 100644 index 00000000..161884c8 --- /dev/null +++ b/cpp/platform/port/down_cast.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_PORT_DOWN_CAST_H_ +#define PLATFORM_PORT_DOWN_CAST_H_ + +#include "platform/port/config.h" + +#if NEARBY_USE_RTTI +#define DOWN_CAST dynamic_cast +#else +#define DOWN_CAST static_cast +#endif + +#endif // PLATFORM_PORT_DOWN_CAST_H_ diff --git a/cpp/platform/port/string.h b/cpp/platform/port/string.h new file mode 100644 index 00000000..d9a0cdff --- /dev/null +++ b/cpp/platform/port/string.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_PORT_STRING_H_ +#define PLATFORM_PORT_STRING_H_ + +#include + +#include "platform/port/config.h" + +#if NEARBY_USE_STD_STRING +using std::string; +#endif + +#endif // PLATFORM_PORT_STRING_H_ diff --git a/cpp/platform/prng.cc b/cpp/platform/prng.cc new file mode 100644 index 00000000..7f98870c --- /dev/null +++ b/cpp/platform/prng.cc @@ -0,0 +1,45 @@ +#include "platform/prng.h" + +#include + +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +#define UNSIGNED_INT_BITMASK (std::numeric_limits::max()) + +Prng::Prng() { + // absl::GetCurrentTimeNanos() returns 64 bits, but srand() wants an unsigned + // int, so we may have to lose some of those 64 bits. + // + // The lower bits of the current-time-in-nanos are likely to have more entropy + // than the upper bits, so choose the former. + srand(static_cast(absl::GetCurrentTimeNanos() & + UNSIGNED_INT_BITMASK)); +} + +Prng::~Prng() { + // Nothing to do. +} + +#define RANDOM_BYTE (rand() & 0x0FF) // NOLINT + +std::int32_t Prng::nextInt32() { + return (static_cast(RANDOM_BYTE) << 24) | + (static_cast(RANDOM_BYTE) << 16) | + (static_cast(RANDOM_BYTE) << 8) | + (static_cast(RANDOM_BYTE)); +} + +std::uint32_t Prng::nextUInt32() { + return static_cast(nextInt32()); +} + +std::int64_t Prng::nextInt64() { + return (static_cast(nextInt32()) << 32) | + (static_cast(nextInt32())); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/prng.h b/cpp/platform/prng.h new file mode 100644 index 00000000..9a7ff34a --- /dev/null +++ b/cpp/platform/prng.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_PRNG_H_ +#define PLATFORM_PRNG_H_ + +#include + +namespace location { +namespace nearby { + +// A (non-cryptographic) pseudo-random number generator. +class Prng { + public: + Prng(); + ~Prng(); + + std::int32_t nextInt32(); + std::uint32_t nextUInt32(); + std::int64_t nextInt64(); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_PRNG_H_ diff --git a/cpp/platform/prng_test.cc b/cpp/platform/prng_test.cc new file mode 100644 index 00000000..e4115944 --- /dev/null +++ b/cpp/platform/prng_test.cc @@ -0,0 +1,27 @@ +#include "platform/prng.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PrngTest, NextInt32) { + std::int32_t i = Prng().nextInt32(); + ASSERT_LE(i, std::numeric_limits::max()); + ASSERT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextUInt32) { + std::uint32_t i = Prng().nextUInt32(); + ASSERT_LE(i, std::numeric_limits::max()); + ASSERT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextInt64) { + std::int64_t i = Prng().nextInt64(); + ASSERT_LE(i, std::numeric_limits::max()); + ASSERT_GE(i, std::numeric_limits::min()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/ptr.cc b/cpp/platform/ptr.cc new file mode 100644 index 00000000..64cbe3c0 --- /dev/null +++ b/cpp/platform/ptr.cc @@ -0,0 +1,13 @@ +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +namespace ptr_impl { + +const std::int32_t RefCount::kInitialCount = 0; + +} // namespace ptr_impl + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h new file mode 100644 index 00000000..caa9093a --- /dev/null +++ b/cpp/platform/ptr.h @@ -0,0 +1,400 @@ +#ifndef PLATFORM_PTR_H_ +#define PLATFORM_PTR_H_ + +#include +#include +#include + +#include "platform/impl/default/default_lock.h" +#include "platform/logging.h" +#include "platform/port/down_cast.h" + +namespace location { +namespace nearby { + +namespace ptr_impl { + +class RefCount { + public: + RefCount() : lock_(), count_(kInitialCount) {} + + // Returns false if this operation doesn't make conceptual sense any more + // (for example, if it leads to bringing count_ back from the dead). + bool increment() { + bool result; + + lock_.lock(); + { + // Avoid coming back from the dead. + if (count_ < kInitialCount) { + result = false; + } else { + count_++; + result = true; + } + } + lock_.unlock(); + + return result; + } + + // Returns true if after this operation, count_ is 0. + bool decrement() { + bool result; + + lock_.lock(); + { + // It's alright for count_ to go negative because it will only be exactly + // 0 once (since increment() makes sure that once you go negative, you + // can't come back from the dead). + count_--; + result = (count_ == 0); + } + lock_.unlock(); + + return result; + } + + private: + static const std::int32_t kInitialCount; + + DefaultLock lock_; + std::int32_t count_; +}; + +} // namespace ptr_impl + +template +class ObjectDestroyer { + public: + static void destroy(T* t) { delete t; } +}; + +template +class ArrayDestroyer { + public: + static void destroy(T* t) { delete[] t; } +}; + +// Forward declarations to make it possible for Ptr (a class template) to +// declare ConstifyPtr, DowncastPtr, and DowncastConstPtr (function templates) +// as friends. +// +// Note that the default template parameters to Ptr need to be defined here (at +// the first point of declaration), as opposed to at the actual definition of +// Ptr (which is what one might reasonably expect). +// +// See https://isocpp.org/wiki/faq/templates#template-friends for more. +template class Destroyer = ObjectDestroyer> +class Ptr; +template +class ConstPtr; +template +ConstPtr ConstifyPtr(Ptr ptr); +template +Ptr DowncastPtr(Ptr base_ptr); +template +ConstPtr DowncastConstPtr(ConstPtr base_ptr); + +// A layer of indirection over a raw pointer, to buy flexibility in the +// future to use, for instance: +// +// a) the in-built shared_ptr in modern implementations of C++, +// b) a custom reference-counting mechanism, etc. +// +// , all without having to touch every line of our codebase that uses +// pointers. +// +// Destroyer defines how the owned pointee should be destroyed, and is +// expected to be a class template that provides at least a destroy() +// method, like so: +// +// template +// class MyDestroyer { +// public: +// static void destroy(T* t); +// }; +// +// It defaults to ObjectDestroyer. +template class Destroyer> +class Ptr { + public: + // Provide an alias for use as a dependent name. + typedef T PointeeType; + + Ptr() : pointee_(nullptr), ref_count_(nullptr) {} + explicit Ptr(T* pointee, bool is_ref_counted = false, + ptr_impl::RefCount* ref_count = nullptr) + : pointee_(pointee), + ref_count_( + is_ref_counted + ? (ref_count != nullptr ? ref_count : new ptr_impl::RefCount()) + : nullptr) { + init(); + } + Ptr(const Ptr& that) : pointee_(that.pointee_), ref_count_(that.ref_count_) { + init(); + } + + Ptr& operator=(const Ptr& other) { + if (pointee_ != other.pointee_) { + // If we're not currently ref-counted, then an assignment shouldn't lead + // to any destruction of our past state -- that's the responsibility of + // whichever instance of Ptr believes it owns pointee_. + destroy(false); + + pointee_ = other.pointee_; + ref_count_ = other.ref_count_; + + init(); + } + return *this; + } + + // Conversion to Ptr, where T is trivially convertible to T2. E.g. + // conversion from derived to base class. + template + operator Ptr() { + return Ptr(pointee_, isRefCounted(), ref_count_); + } + + ~Ptr() { + if (isRefCounted()) { + destroy(); + } else { + // Left empty on purpose. + } + } + + bool operator==(const Ptr& other) const { + assert(!(this->isNull())); + assert(!(other.isNull())); + + return ((*(this->pointee_) == *(other.pointee_)) && + (this->isRefCounted() == other.isRefCounted())); + } + + bool operator!=(const Ptr& other) const { return !(*this == other); } + + bool operator<(const Ptr& other) const { + assert(!(this->isNull())); + assert(!(other.isNull())); + + return *(this->pointee_) < *(other.pointee_); + } + + // Calls Destroyer::destroy() to perform deallocation of pointee_. + void destroy(bool should_destroy_if_not_ref_counted = true) { + bool need_to_destroy = isRefCounted() ? ref_count_->decrement() + : should_destroy_if_not_ref_counted; + if (need_to_destroy) { + delete ref_count_; + Destroyer::destroy(pointee_); + } + + ref_count_ = NULL; // NOLINT + pointee_ = NULL; // NOLINT + } + + // Use this function only when the ownership is held by someone else, and this + // Ptr object has no responsibility to destroy it. + void clear() { + if (isRefCounted()) { + NEARBY_LOG(FATAL, "Attempting to invoke clear() on a RefCounted Ptr."); + } + + pointee_ = NULL; // NOLINT + } + + T& operator*() const { + assert(pointee_ != NULL); // NOLINT + return *pointee_; + } + + T* operator->() const { + assert(pointee_ != NULL); // NOLINT + return pointee_; + } + + bool isNull() const { return pointee_ == nullptr; } + bool isRefCounted() const { return ref_count_ != nullptr; } + + private: + template + friend ConstPtr ConstifyPtr(Ptr ptr); + template + friend Ptr DowncastPtr(Ptr base_ptr); + template + friend ConstPtr DowncastConstPtr(ConstPtr base_ptr); + + void init() { + if (isRefCounted()) { + if (!ref_count_->increment()) { + NEARBY_LOG(FATAL, "Failed to increment RefCount."); + } + } + } + + T* pointee_; + ptr_impl::RefCount* ref_count_; +}; + +// Convenience wrapper for a read-only version of Ptr (in which the pointee +// cannot be modified). +// +// The C++11 equivalent would be: +// +// using ConstPtr = Ptr; +// +// Thus, +// +// Ptr x1(new X(...)); +// +// allows the underlying X instance to be modified, whereas +// +// ConstPtr x2(new X(...)); +// +// disallows that. +template +class ConstPtr : public Ptr { + public: + ConstPtr() {} + explicit ConstPtr(T* pointee, bool is_ref_counted = false, + ptr_impl::RefCount* ref_count = nullptr) + : Ptr(pointee, is_ref_counted, ref_count) {} +}; + +// RAII wrapper over Ptr and ConstPtr (hereon referred to by the PtrType +// placeholder), to allow for guarantees that the wrapped PtrType will be +// automatically destroyed when this wrapper object goes out of scope. +// +// Any class that has a PtrType member that it owns (and thus needs to invoke +// destroy() on) should wrap that PtrType in a ScopedPtr object. +// +// Similarly, any method that manipulates a (likely local) PtrType variable +// that needs to be destroy()ed at the end of that method should wrap that +// PtrType variable in a ScopedPtr object. +// +// Sample usage: +// +// Ptr x1(new X(...)); +// ScopedPtr > sx1(x1); +// +// ConstPtr x2(new X(...)); +// ScopedPtr > sx2(x2); +// +// ScopedPtr > sx3(new X(...)); +// +// ScopedPtr > sx4(new X(...)); +template +class ScopedPtr { + public: + explicit ScopedPtr(typename PtrType::PointeeType* pointee) : ptr_(pointee) {} + explicit ScopedPtr(PtrType ptr) : ptr_(ptr) {} + ~ScopedPtr() { ptr_.destroy(); } + + // Shadow methods for the underlying Ptr. + typename PtrType::PointeeType& operator*() const { return ptr_.operator*(); } + typename PtrType::PointeeType* operator->() const { + return ptr_.operator->(); + } + bool isNull() const { return ptr_.isNull(); } + + // Accessor for the underlying Ptr. + PtrType get() const { return ptr_; } + + // Releases the underlying Ptr from the clutches of this ScopedPtr, + // effectively resetting this ScopedPtr (and making its destructor be a no-op) + // -- useful for transfer of ownership from one ScopedPtr to another across + // scopes. + PtrType release() { + PtrType released = ptr_; + ptr_ = PtrType(); + return released; + } + + private: + // Disallow copy and assignment. + ScopedPtr(const ScopedPtr&); + ScopedPtr& operator=(const ScopedPtr&); + + PtrType ptr_; +}; + +// Utility function to create Ptr objects with less template-y noise by +// leveraging template argument deduction, in the same vein as std::make_pair(). +// +// Helps convert +// +// Ptr >(new MyRichType()); +// +// to +// +// MakePtr(new MyRichType()); +template +Ptr MakePtr(T* raw_ptr) { + return Ptr(raw_ptr); +} + +// Like MakePtr(), utility function to create ConstPtr objects with less +// template-y noise. +template +ConstPtr MakeConstPtr(T* raw_ptr) { + return ConstPtr(raw_ptr); +} + +// Used to create Ptr instances that are reference-counted (for when the +// lifetime and/or ownership of the pointee is not deterministic, like when a +// cache gives out handles to its cached objects to multiple threads to manage +// independently). +// +// Needless to say, the reference-counted-ness of these Ptr instances propagates +// across all copies and assignments, and as one might expect, the underlying +// pointee is deallocated when the reference count goes to 0. +// +// That implies that it's not strictly necessary to wrap these in ScopedPtrs +// (but it's perfectly fine to do so, and is even recommended, so readers of +// your code get a better understanding of the ownership story for each +// reference). +template +Ptr MakeRefCountedPtr(T* raw_ptr) { + return Ptr(raw_ptr, true); +} + +// ConstPtr counterpart to MakeRefCountedPtr(). +template +ConstPtr MakeRefCountedConstPtr(T* raw_ptr) { + return ConstPtr(raw_ptr, true); +} + +// Use this function to convert a Ptr object to a ConstPtr object. +template +ConstPtr ConstifyPtr(Ptr ptr) { + return ConstPtr(ptr.pointee_, ptr.isRefCounted(), ptr.ref_count_); +} + +// Use this function to downcast from a Ptr to a Ptr. +// +// Because BaseT can be automatically deduced based on the base_ptr that's +// passed in, invocations of this method only need to explicitly specify ChildT, +// like so: +// +// Ptr my_child_ptr = DowncastPtr(my_base_ptr); +template +Ptr DowncastPtr(Ptr base_ptr) { + return Ptr(DOWN_CAST(base_ptr.pointee_), + base_ptr.isRefCounted(), base_ptr.ref_count_); +} + +// ConstPtr counterpart to DowncastPtr(). +template +ConstPtr DowncastConstPtr(ConstPtr base_ptr) { + return ConstPtr( + const_cast(DOWN_CAST(base_ptr.pointee_)), + base_ptr.isRefCounted(), base_ptr.ref_count_); +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_PTR_H_ diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc new file mode 100644 index 00000000..a68a58b9 --- /dev/null +++ b/cpp/platform/ptr_test.cc @@ -0,0 +1,272 @@ +#include "platform/ptr.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PtrTest, RefCountedPtr_SingleReference) { + Ptr ref_counted = MakeRefCountedPtr(new int(1234)); + + // We just want to make sure that this test doesn't lead to a leak. + SUCCEED(); +} + +TEST(PtrTest, RefCountedPtr_MultipleReferences) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + Ptr ref_counted_2 = ref_counted_1; + Ptr ref_counted_3(ref_counted_2); + + // We just want to make sure that this test doesn't lead to a leak, nor to + // double-deletion. + SUCCEED(); +} + +TEST(PtrTest, RefCountedPtr_IsRefCounted_Works) { + Ptr ref_counted = MakeRefCountedPtr(new int(1234)); + Ptr manually_counted = MakePtr(new int(1234)); + ScopedPtr > scoped_manually_counted(manually_counted); + + ASSERT_TRUE(ref_counted.isRefCounted()); + ASSERT_FALSE(manually_counted.isRefCounted()); +} + +TEST(PtrTest, RefCountedPtr_MultipleReferencesWithScoped) { + Ptr ref_counted = MakeRefCountedPtr(new int(1234)); + ScopedPtr > scoped_ref_counted_1(ref_counted); + ScopedPtr > scoped_ref_counted_2(ref_counted); + + // We just want to make sure that this test doesn't lead to a leak, nor to + // double-deletion. + SUCCEED(); +} + +TEST(PtrTest, AssignmentOperator_RefCountedToRefCounted) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + Ptr ref_counted_2 = MakeRefCountedPtr(new int(5678)); + + ref_counted_2 = ref_counted_1; + + ASSERT_EQ(1234, *ref_counted_1); + ASSERT_EQ(1234, *ref_counted_2); +} + +TEST(PtrTest, AssignmentOperator_ManuallyCountedToManuallyCounted) { + Ptr manually_counted_1 = MakePtr(new int(1234)); + Ptr manually_counted_2 = MakePtr(new int(5678)); + // Avoid leaks. + ScopedPtr > scoped_manually_counted_1(manually_counted_1); + ScopedPtr > scoped_manually_counted_2(manually_counted_2); + + manually_counted_2 = manually_counted_1; + + ASSERT_EQ(1234, *manually_counted_1); + ASSERT_EQ(1234, *manually_counted_2); + ASSERT_EQ(1234, *scoped_manually_counted_1); + ASSERT_EQ(5678, *scoped_manually_counted_2); +} + +TEST(PtrTest, AssignmentOperator_RefCountedToManuallyCounted) { + Ptr ref_counted = MakeRefCountedPtr(new int(1234)); + Ptr manually_counted = MakePtr(new int(5678)); + // Avoid leaks. + ScopedPtr > scoped_manually_counted(manually_counted); + + manually_counted = ref_counted; + + ASSERT_EQ(1234, *ref_counted); + ASSERT_EQ(1234, *manually_counted); + ASSERT_EQ(5678, *scoped_manually_counted); +} + +TEST(PtrTest, AssignmentOperator_ManuallyCountedToRefCounted) { + Ptr manually_counted = MakePtr(new int(1234)); + Ptr ref_counted = MakeRefCountedPtr(new int(5678)); + // Avoid leaks. + ScopedPtr > scoped_manually_counted(manually_counted); + + ref_counted = manually_counted; + + ASSERT_EQ(1234, *ref_counted); + ASSERT_EQ(1234, *manually_counted); + ASSERT_EQ(1234, *scoped_manually_counted); +} + +TEST(PtrTest, AssignmentOperator_SelfAssignment_ManuallyCounted) { + Ptr manually_counted_1 = MakePtr(new int(1234)); + Ptr manually_counted_2(manually_counted_1); + // Avoid leaks. + ScopedPtr > scoped_manually_counted_1(manually_counted_1); + + manually_counted_1 = manually_counted_2; + + ASSERT_EQ(1234, *manually_counted_1); + ASSERT_EQ(1234, *manually_counted_2); + ASSERT_EQ(1234, *scoped_manually_counted_1); +} + +TEST(PtrTest, AssignmentOperator_SelfAssignment_RefCounted) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + Ptr ref_counted_2(ref_counted_1); + + ref_counted_1 = ref_counted_2; + + ASSERT_EQ(1234, *ref_counted_1); + ASSERT_EQ(1234, *ref_counted_2); +} + +TEST(PtrTest, EqualityOperator_ManuallyCounted) { + Ptr manually_counted_1 = MakePtr(new int(1234)); + Ptr manually_counted_2(manually_counted_1); + // Avoid leaks. + ScopedPtr > scoped_manually_counted_1(manually_counted_1); + + ASSERT_TRUE(manually_counted_1 == manually_counted_2); + + manually_counted_1 = manually_counted_2; + + ASSERT_TRUE(manually_counted_1 == manually_counted_2); +} + +TEST(PtrTest, EqualityOperator_RefCounted) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + Ptr ref_counted_2(ref_counted_1); + + ASSERT_TRUE(ref_counted_1 == ref_counted_2); + + ref_counted_1 = ref_counted_2; + + ASSERT_TRUE(ref_counted_1 == ref_counted_2); +} + +TEST(PtrTest, EqualityOperator_ManuallyAndRefCounted) { + int* raw = new int(1234); + Ptr manually_counted = MakePtr(raw); + Ptr ref_counted = MakeRefCountedPtr(raw); + // No need for a ScopedPtr for manually_counted here because we know that + // ref_counted will take care of deallocating 'raw'. + + ASSERT_FALSE(manually_counted == ref_counted); +} + +namespace { + +class Base { + public: + virtual ~Base() {} + + virtual int getInt() const = 0; +}; + +class Derived : public Base { + public: + explicit Derived(int i) : i_(i) {} + ~Derived() override {} + + int getInt() const override { return i_; } + + private: + const int i_; +}; + +} // namespace + +TEST(PtrTest, DerivedToBaseConversion_ManuallyCounted) { + Ptr derived = MakePtr(new Derived(1234)); + Ptr base = derived; + // Avoid leaks. + ScopedPtr > scoped_derived(derived); + + ASSERT_EQ(1234, base->getInt()); + ASSERT_EQ(1234, derived->getInt()); + ASSERT_EQ(1234, scoped_derived->getInt()); +} + +TEST(PtrTest, DerivedToBaseConversion_RefCounted) { + Ptr derived = MakeRefCountedPtr(new Derived(1234)); + Ptr base = derived; + + ASSERT_EQ(1234, derived->getInt()); + derived.destroy(); + // Additionally, make sure that 'base' is valid even after 'derived' has been + // destroyed. + ASSERT_EQ(1234, base->getInt()); +} + +TEST(PtrTest, ScopedPtr_Release_ManuallyCounted) { + Ptr manually_counted_1 = MakePtr(new int(1234)); + ScopedPtr > scoped_manually_counted_1(manually_counted_1); + + Ptr manually_counted_2 = scoped_manually_counted_1.release(); + // Avoid leaks. + ScopedPtr > scoped_manually_counted_2(manually_counted_2); + + ASSERT_TRUE(scoped_manually_counted_1.isNull()); + ASSERT_EQ(1234, *manually_counted_2); + ASSERT_EQ(1234, *scoped_manually_counted_2); +} + +TEST(PtrTest, ScopedPtr_Release_RefCounted) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + ScopedPtr > scoped_ref_counted_1(ref_counted_1); + + Ptr ref_counted_2 = scoped_ref_counted_1.release(); + + ASSERT_TRUE(scoped_ref_counted_1.isNull()); + ASSERT_EQ(1234, *ref_counted_2); +} + +TEST(PtrTest, ConstifyPtr_ManuallyCounted) { + Ptr manually_counted = MakePtr(new int(1234)); + // Avoid leaks. + ScopedPtr > scoped_manually_counted(manually_counted); + + ConstPtr const_manually_counted = ConstifyPtr(manually_counted); + + ASSERT_EQ(1234, *const_manually_counted); + ASSERT_EQ(1234, *manually_counted); + ASSERT_EQ(1234, *scoped_manually_counted); +} + +TEST(PtrTest, ConstifyPtr_RefCounted) { + Ptr ref_counted = MakeRefCountedPtr(new int(1234)); + + ConstPtr const_ref_counted = ConstifyPtr(ref_counted); + + ASSERT_EQ(1234, *ref_counted); + ref_counted.destroy(); + // Additionally, make sure that const_ref_counted is valid even after + // ref_counted has been destroyed. + ASSERT_EQ(1234, *const_ref_counted); +} + +TEST(PtrTest, DowncastPtr_ManuallyCounted) { + Ptr derived = MakePtr(new Derived(1234)); + Ptr base = derived; + // Avoid leaks. + ScopedPtr > scoped_derived(derived); + + Ptr derived_from_downcast = DowncastPtr(base); + + ASSERT_EQ(1234, base->getInt()); + ASSERT_EQ(1234, derived->getInt()); + ASSERT_EQ(1234, derived_from_downcast->getInt()); +} + +TEST(PtrTest, DowncastPtr_RefCounted) { + Ptr derived = MakeRefCountedPtr(new Derived(1234)); + Ptr base = derived; + + Ptr derived_from_downcast = DowncastPtr(base); + + ASSERT_EQ(1234, base->getInt()); + base.destroy(); + ASSERT_EQ(1234, derived->getInt()); + derived.destroy(); + // Additionally, make sure that derived_from_downcast is valid even after + // derived has been destroyed. + ASSERT_EQ(1234, derived_from_downcast->getInt()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/reliability_utils.cc b/cpp/platform/reliability_utils.cc new file mode 100644 index 00000000..1f296eb8 --- /dev/null +++ b/cpp/platform/reliability_utils.cc @@ -0,0 +1,42 @@ +#include "platform/reliability_utils.h" + +namespace location { +namespace nearby { + +bool ReliabilityUtils::attemptRepeatedly(Ptr runnable, + const std::string &runnable_name, + Ptr recovery_runnable) { + return false; +} + +bool ReliabilityUtils::attemptRepeatedly(Ptr runnable, + const std::string &runnable_name, + Ptr recovery_runnable, + const AtomicBoolean &isCancelled) { + return false; +} + +bool ReliabilityUtils::attemptRepeatedly(Ptr runnable, + const std::string &runnable_name, + std::int64_t recovery_pause_millis) { + return false; +} + +bool ReliabilityUtils::attemptRepeatedly(Ptr runnable, + const std::string &runnable_name, + std::int64_t recovery_pause_millis, + const AtomicBoolean &isCancelled) { + return false; +} + +bool ReliabilityUtils::attemptRepeatedly(Ptr runnable, + const std::string &runnable_name, + int num_attempts, + std::int64_t recovery_pause_millis, + Ptr recovery_runnable, + const AtomicBoolean &isCancelled) { + return false; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/reliability_utils.h b/cpp/platform/reliability_utils.h new file mode 100644 index 00000000..a4262e89 --- /dev/null +++ b/cpp/platform/reliability_utils.h @@ -0,0 +1,43 @@ +#ifndef PLATFORM_RELIABILITY_UTILS_H_ +#define PLATFORM_RELIABILITY_UTILS_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +class ReliabilityUtils { + public: + static bool attemptRepeatedly(Ptr runnable, + const std::string& runnable_name, + Ptr recovery_runnable); + static bool attemptRepeatedly(Ptr runnable, + const std::string& runnable_name, + Ptr recovery_runnable, + const AtomicBoolean& isCancelled); + static bool attemptRepeatedly(Ptr runnable, + const std::string& runnable_name, + std::int64_t recovery_pause_millis); + static bool attemptRepeatedly(Ptr runnable, + const std::string& runnable_name, + std::int64_t recovery_pause_millis, + const AtomicBoolean& isCancelled); + + private: + static bool attemptRepeatedly(Ptr runnable, + const std::string& runnable_name, + int num_attempts, + std::int64_t recovery_pause_millis, + Ptr recovery_runnable, + const AtomicBoolean& isCancelled); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_RELIABILITY_UTILS_H_ diff --git a/cpp/platform/runnable.h b/cpp/platform/runnable.h new file mode 100644 index 00000000..e70bd512 --- /dev/null +++ b/cpp/platform/runnable.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_RUNNABLE_H_ +#define PLATFORM_RUNNABLE_H_ + +namespace location { +namespace nearby { + +// The Runnable interface should be implemented by any class whose instances are +// intended to be executed by a thread. The class must define a method named +// run() with no arguments. +// +// https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html +class Runnable { + public: + virtual ~Runnable() {} + + virtual void run() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_RUNNABLE_H_ diff --git a/cpp/platform/synchronized.h b/cpp/platform/synchronized.h new file mode 100644 index 00000000..81b37789 --- /dev/null +++ b/cpp/platform/synchronized.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_SYNCHRONIZED_H_ +#define PLATFORM_SYNCHRONIZED_H_ + +#include "platform/api/lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +// +// https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html +// https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html +class Synchronized { + public: + explicit Synchronized(Ptr lock) : lock_(lock) { lock_->lock(); } + ~Synchronized() { lock_->unlock(); } + + private: + Ptr lock_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_SYNCHRONIZED_H_ diff --git a/proto/BUILD b/proto/BUILD new file mode 100644 index 00000000..62d9d917 --- /dev/null +++ b/proto/BUILD @@ -0,0 +1,219 @@ +# Proto for Nearby products + +load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") +load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "bootstrap_enums_proto", + srcs = ["bootstrap_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = ["//logs/proto/logs_annotations"], +) + +cc_proto_library( + name = "bootstrap_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":bootstrap_enums_proto"], +) + +java_lite_proto_library( + name = "bootstrap_enums_java_proto_lite", + visibility = [ + "//java/com/google/android/gmscore/integ/modules/nearby:nearby_packages", + ], + deps = [":bootstrap_enums_proto"], +) + +proto_library( + name = "discovery_enums_proto", + srcs = ["discovery_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = ["//logs/proto/logs_annotations"], +) + +cc_proto_library( + name = "discovery_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":discovery_enums_proto"], +) + +java_lite_proto_library( + name = "discovery_enums_java_proto_lite", + deps = [":discovery_enums_proto"], +) + +java_proto_library( + name = "discovery_enums_java_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":discovery_enums_proto"], +) + +proto_library( + name = "connections_enums_proto", + srcs = ["connections_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "connections_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":connections_enums_proto"], +) + +java_lite_proto_library( + name = "connections_enums_java_proto_lite", + deps = [":connections_enums_proto"], +) + +go_proto_library( + name = "connections_enums_go_proto", + deps = [":connections_enums_proto"], +) + +portable_proto_library( + name = "connections_enums_portable_proto", + config = ":connections_enums_proto_config", + copts = [ + "-DGOOGLE_PROTOBUF_NO_RTTI=1", + ], + header_outs = [ + "connections_enums.pb.h", + ], + proto_deps = [ + ":connections_enums_proto", + ], + visibility = ["//location/nearby/connections:__subpackages__"], +) + +filegroup( + name = "connections_enums_proto_config", + srcs = ["connections_enums_proto_config.asciipb"], +) + +proto_library( + name = "setup_enums_proto", + srcs = ["setup_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "setup_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":setup_enums_proto"], +) + +java_lite_proto_library( + name = "setup_enums_java_proto_lite", + deps = [":setup_enums_proto"], +) + +proto_library( + name = "nearby_client_enums_proto", + srcs = ["nearby_client_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "nearby_client_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":nearby_client_enums_proto"], +) + +java_lite_proto_library( + name = "nearby_client_enums_java_proto_lite", + deps = [":nearby_client_enums_proto"], +) + +go_proto_library( + name = "nearby_client_enums_go_proto", + deps = [":nearby_client_enums_proto"], +) + +proto_library( + name = "magic_pair_enums_proto", + srcs = ["magic_pair_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "magic_pair_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":magic_pair_enums_proto"], +) + +java_lite_proto_library( + name = "magic_pair_enums_java_proto_lite", + deps = [":magic_pair_enums_proto"], +) + +go_proto_library( + name = "magic_pair_enums_go_proto", + deps = [":magic_pair_enums_proto"], +) + +proto_library( + name = "sharing_enums_proto", + srcs = ["sharing_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "sharing_enums_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":sharing_enums_proto"], +) + +java_lite_proto_library( + name = "sharing_enums_java_proto_lite", + deps = [":sharing_enums_proto"], +) + +proto_library( + name = "nearby_event_codes_proto", + srcs = ["nearby_event_codes.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +cc_proto_library( + name = "nearby_event_codes_cc_proto", + compatible_with = ["//buildenv/target:appengine"], + deps = [":nearby_event_codes_proto"], +) + +java_lite_proto_library( + name = "nearby_event_codes_java_proto_lite", + deps = [":nearby_event_codes_proto"], +) + +go_proto_library( + name = "nearby_event_codes_go_proto", + deps = [":nearby_event_codes_proto"], +) diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto new file mode 100644 index 00000000..9c378983 --- /dev/null +++ b/proto/bootstrap_enums.proto @@ -0,0 +1,86 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "BootstrapEnums"; + +// Medium used for offline socket. +enum SocketMedium { + SOCKET_MEDIUM_UNKNOWN = 0; + + // Bluetooth rfcomm socket. + SOCKET_BLUETOOTH_RFCOMM = 1; + + // BLE gatt socket. + SOCKET_BLE_GATT = 2; +} + +enum NearbyBootstrapEvent { + EVENT_UNKNOWN = 0; + + // Enable target mode. + EVENT_ENABLE_TARGET = 1; + + // Disable target mode. + EVENT_DISABLE_TARGET = 2; + + // Start scan devices. + EVENT_START_SCAN = 3; + + // Stop scan devices. + EVENT_STOP_SCAN = 4; + + // Find one or more scan results. + EVENT_HAS_SCAN_RESULT = 5; + + // Start connect in SPAKE. + EVENT_START_CONNECT_SPAKE = 6; + + // Start connect in ECDH. + EVENT_START_CONNECT_ECDH = 7; + + // Input token for SPAKE connection if previous one is incorrect. + EVENT_INPUT_TOKEN = 8; + + // Confirm connection for ECDH connection. + EVENT_CONFIRM = 9; + + // Disconnect. + EVENT_DISCONNECT = 10; + + // Connection is established. + EVENT_CONNECTED = 11; + + // Connection is disconnected. + EVENT_DISCONNECTED = 12; + + // Connect is timeout. + EVENT_CONNECT_TIMEOUT = 13; +} + +enum NearbyBootstrapDeviceType { + DEVICE_TYPE_UNKNOWN = 0; + + DEVICE_TYPE_ANDROID_PHONE = 1; + + DEVICE_TYPE_ANDROID_TABLET = 2; + + DEVICE_TYPE_ANDROID_TV = 3; + + DEVICE_TYPE_ANDROID_WEAR = 4; +} + +enum NearbyBootstrapDeviceRole { + DEVICE_ROLE_UNKNOWN = 0; + + // Device to be bootstrapped. + DEVICE_ROLE_TARGET = 1; + + // Device that initiate the bootstrap. + DEVICE_ROLE_SOURCE = 2; +} diff --git a/proto/connections/BUILD b/proto/connections/BUILD new file mode 100644 index 00000000..90411711 --- /dev/null +++ b/proto/connections/BUILD @@ -0,0 +1,50 @@ +load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") +load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") + +proto_library( + name = "offline_wire_formats_proto", + srcs = [ + "offline_wire_formats.proto", + ], + cc_api_version = 2, + visibility = ["//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__"], +) + +cc_proto_library( + name = "offline_wire_formats_cc_proto", + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/connection:__subpackages__", + "//javatests/com/google/android/gmscore/integ/modules/nearby/robolectric/connections/src/com/google/android/gms/nearby/connection:__subpackages__", + ], + deps = [":offline_wire_formats_proto"], +) + +java_lite_proto_library( + name = "offline_wire_formats_java_proto_lite", + visibility = [ + "//java/com/google/android/gmscore/integ/modules/nearby:__subpackages__", + "//javatests/com/google/android/gmscore/integ/modules/nearby:__subpackages__", + ], + deps = [":offline_wire_formats_proto"], +) + +portable_proto_library( + name = "offline_wire_formats_portable_proto", + config = ":offline_wire_formats_proto_config", + copts = [ + "-DGOOGLE_PROTOBUF_NO_RTTI=1", + ], + header_outs = [ + "offline_wire_formats.pb.h", + ], + proto_deps = [ + ":offline_wire_formats_proto", + ], + visibility = ["//location/nearby/connections:__subpackages__"], +) + +filegroup( + name = "offline_wire_formats_proto_config", + srcs = ["offline_wire_formats_proto_config.asciipb"], +) diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto new file mode 100644 index 00000000..b5d2901e --- /dev/null +++ b/proto/connections/offline_wire_formats.proto @@ -0,0 +1,239 @@ +syntax = "proto2"; + +package location.nearby.connections; + +option java_outer_classname = "OfflineWireFormatsProto"; +option java_package = "com.google.location.nearby.connections.proto"; +option objc_class_prefix = "GNCP"; + +message OfflineFrame { + enum Version { + UNKNOWN_VERSION = 0; + V1 = 1; + } + optional Version version = 1; + + // Right now there's only 1 version, but if there are more, exactly one of + // the following fields will be set. + optional V1Frame v1 = 2; +} + +message V1Frame { + enum FrameType { + UNKNOWN_FRAME_TYPE = 0; + CONNECTION_REQUEST = 1; + CONNECTION_RESPONSE = 2; + PAYLOAD_TRANSFER = 3; + BANDWIDTH_UPGRADE_NEGOTIATION = 4; + KEEP_ALIVE = 5; + DISCONNECTION = 6; + PAIRED_KEY_ENCRYPTION = 7; + } + optional FrameType type = 1; + + // Exactly one of the following fields will be set. + optional ConnectionRequestFrame connection_request = 2; + optional ConnectionResponseFrame connection_response = 3; + optional PayloadTransferFrame payload_transfer = 4; + optional BandwidthUpgradeNegotiationFrame bandwidth_upgrade_negotiation = 5; + optional KeepAliveFrame keep_alive = 6; + optional DisconnectionFrame disconnection = 7; + optional PairedKeyEncryptionFrame paired_key_encryption = 8; +} + +message ConnectionRequestFrame { + // Should always match cs/symbol:location.nearby.proto.connections.Medium + enum Medium { + UNKNOWN_MEDIUM = 0; + MDNS = 1; + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; + } + + optional string endpoint_id = 1; + optional string endpoint_name = 2; + optional bytes handshake_data = 3; + // A random number generated for each outgoing connection that is presently + // used to act as a tiebreaker when 2 devices connect to each other + // simultaneously; this can also be used for other initialization-scoped + // things in the future. + optional int32 nonce = 4; + // The mediums this device supports upgrading to. This list should be filtered + // by both the strategy and this device's individual limitations. + repeated Medium mediums = 5; + optional bytes endpoint_info = 6; + optional MediumMetadata medium_metadata = 7; +} + +message ConnectionResponseFrame { + // This doesn't need to send back endpoint_id and endpoint_name (like + // the ConnectionRequestFrame does) because those have already been + // transmitted out-of-band, at the time this endpoint was discovered. + + // One of: + // + // - ConnectionsStatusCodes.STATUS_OK + // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. + optional int32 status = 1; + optional bytes handshake_data = 2; +} + +message PayloadTransferFrame { + enum PacketType { + UNKNOWN_PACKET_TYPE = 0; + DATA = 1; + CONTROL = 2; + } + + message PayloadHeader { + enum PayloadType { + UNKNOWN_PAYLOAD_TYPE = 0; + BYTES = 1; + FILE = 2; + STREAM = 3; + } + optional int64 id = 1; + optional PayloadType type = 2; + optional int64 total_size = 3; + } + + // Accompanies DATA packets. + message PayloadChunk { + enum Flags { LAST_CHUNK = 0x1; } + optional int32 flags = 1; + optional int64 offset = 2; + optional bytes body = 3; + } + + // Accompanies CONTROL packets. + message ControlMessage { + enum EventType { + UNKNOWN_EVENT_TYPE = 0; + PAYLOAD_ERROR = 1; + PAYLOAD_CANCELED = 2; + } + + optional EventType event = 1; + optional int64 offset = 2; + } + + optional PacketType packet_type = 1; + optional PayloadHeader payload_header = 2; + + // Exactly one of the following fields will be set, depending on the type. + optional PayloadChunk payload_chunk = 3; + optional ControlMessage control_message = 4; +} + +message BandwidthUpgradeNegotiationFrame { + enum EventType { + UNKNOWN_EVENT_TYPE = 0; + UPGRADE_PATH_AVAILABLE = 1; + LAST_WRITE_TO_PRIOR_CHANNEL = 2; + SAFE_TO_CLOSE_PRIOR_CHANNEL = 3; + CLIENT_INTRODUCTION = 4; + UPGRADE_FAILURE = 5; + } + + // Accompanies UPGRADE_PATH_AVAILABLE and UPGRADE_FAILURE events. + message UpgradePathInfo { + // Should always match cs/symbol:location.nearby.proto.connections.Medium + enum Medium { + UNKNOWN_MEDIUM = 0; + MDNS = 1; + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; + } + + // Accompanies Medium.WIFI_HOTSPOT. + message WifiHotspotCredentials { + optional string ssid = 1; + optional string password = 2; + optional int32 port = 3; + optional string gateway = 4 [default = "0.0.0.0"]; + } + + // Accompanies Medium.WIFI_LAN. + message WifiLanSocket { + optional bytes ip_address = 1; + optional int32 wifi_port = 2; + } + + // Accompanies Medium.BLUETOOTH. + message BluetoothCredentials { + optional string service_name = 1; + optional string mac_address = 2; + } + + // Accompanies Medium.WIFI_AWARE. + message WifiAwareCredentials { + optional string service_id = 1; + optional bytes service_info = 2; + optional string password = 3; + } + + // Accompanies Medium.WIFI_DIRECT. + message WifiDirectCredentials { + optional string ssid = 1; + optional string password = 2; + optional int32 port = 3; + optional int32 frequency = 4; + } + + optional Medium medium = 1; + + // Exactly one of the following fields will be set. + optional WifiHotspotCredentials wifi_hotspot_credentials = 2; + optional WifiLanSocket wifi_lan_socket = 3; + optional BluetoothCredentials bluetooth_credentials = 4; + optional WifiAwareCredentials wifi_aware_credentials = 5; + optional WifiDirectCredentials wifi_direct_credentials = 6; + } + + // Accompanies CLIENT_INTRODUCTION events. + message ClientIntroduction { + optional string endpoint_id = 1; + } + + optional EventType event_type = 1; + + // Exactly one of the following fields will be set. + optional UpgradePathInfo upgrade_path_info = 2; + optional ClientIntroduction client_introduction = 3; +} + +message KeepAliveFrame { + // Empty on purpose. +} + +// Informs the remote side to immediately severe the socket connection. +// Used in bandwidth upgrades to get around a race condition, but may be used +// in other situations to trigger a faster disconnection event than waiting for +// socket closed on the remote side. +message DisconnectionFrame { + // Empty on purpose. +} + +// A paired key encryption packet sent between devices, contains signed data. +message PairedKeyEncryptionFrame { + // The encrypted data (raw authentication token for the established + // connection) in byte array format. + optional bytes signed_data = 1; +} + +message MediumMetadata { + // True if local device supports 5GHz. + optional bool supports_5_ghz = 1; +} diff --git a/proto/connections/offline_wire_formats_proto_config.asciipb b/proto/connections/offline_wire_formats_proto_config.asciipb new file mode 100644 index 00000000..06e3dd55 --- /dev/null +++ b/proto/connections/offline_wire_formats_proto_config.asciipb @@ -0,0 +1,25 @@ +optimize_mode: LITE_RUNTIME + +allowed_message: "location.nearby.connections.OfflineFrame" +allowed_enum: "location.nearby.connections.OfflineFrame.Version" +allowed_message: "location.nearby.connections.V1Frame" +allowed_enum: "location.nearby.connections.V1Frame.FrameType" +allowed_message: "location.nearby.connections.ConnectionRequestFrame" +allowed_enum: "location.nearby.connections.ConnectionRequestFrame.Medium" +allowed_message: "location.nearby.connections.ConnectionResponseFrame" +allowed_message: "location.nearby.connections.PayloadTransferFrame" +allowed_enum: "location.nearby.connections.PayloadTransferFrame.PacketType" +allowed_message: "location.nearby.connections.PayloadTransferFrame.PayloadHeader" +allowed_enum: "location.nearby.connections.PayloadTransferFrame.PayloadHeader.PayloadType" +allowed_message: "location.nearby.connections.PayloadTransferFrame.PayloadChunk" +allowed_enum: "location.nearby.connections.PayloadTransferFrame.PayloadChunk.Flags" +allowed_message: "location.nearby.connections.PayloadTransferFrame.ControlMessage" +allowed_enum: "location.nearby.connections.PayloadTransferFrame.ControlMessage.EventType" +allowed_message: "location.nearby.connections.BandwidthUpgradeNegotiationFrame" +allowed_enum: "location.nearby.connections.BandwidthUpgradeNegotiationFrame.EventType" +allowed_message: "location.nearby.connections.BandwidthUpgradeNegotiationFrame.UpgradePathInfo" +allowed_enum: "location.nearby.connections.BandwidthUpgradeNegotiationFrame.UpgradePathInfo.Medium" +allowed_message: "location.nearby.connections.BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WifiHotspotCredentials" +allowed_message: "location.nearby.connections.BandwidthUpgradeNegotiationFrame.ClientIntroduction" +allowed_message: "location.nearby.connections.KeepAliveFrame" + diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto new file mode 100644 index 00000000..f4eaaa2a --- /dev/null +++ b/proto/connections_enums.proto @@ -0,0 +1,252 @@ +syntax = "proto2"; + +package location.nearby.proto.connections; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "ConnectionsEnums"; +option objc_class_prefix = "GNCP"; + +// The type of event being logged. +// Lightweight START_* and STOP_* events track instances of potential crashes +// that would result in a ClientSession not being logged. +enum EventType { + UNKNOWN_EVENT_TYPE = 0; + + // A completed ClientSession, logged after a client disconnects. + CLIENT_SESSION = 1; + + // Corresponds to googleApiClient.connect() and the beginning of a + // ClientSession. + START_CLIENT_SESSION = 2; + + // Corresponds to googleApiClient.disconnect() and the end of a ClientSession. + STOP_CLIENT_SESSION = 3; + + // Corresponds to the beginning of a StrategySession. + START_STRATEGY_SESSION = 4; + + // Corresponds to the end of a StrategySession. + STOP_STRATEGY_SESSION = 5; +} + +// The strategy used for a session of Nearby.Connections. +// Values correspond to +// http://cs/?q=symbol:com.google.android.gms.nearby.connection.Strategy +enum ConnectionsStrategy { + UNKNOWN_STRATEGY = 0; + MDNS_LOCAL_WIFI = 1 [deprecated = true]; + RADIO_P2P = 2 [deprecated = true]; + P2P_CLUSTER = 3; + P2P_STAR = 4; + P2P_POINT_TO_POINT = 5; +} + +// The role a device is playing in one StrategySession. +enum SessionRole { + UNKNOWN_SESSION_ROLE = 0; + ADVERTISER = 1; + DISCOVERER = 2; +} + +enum Medium { + UNKNOWN_MEDIUM = 0; + MDNS = 1; + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; +} + +// The result of a ConnectionRequest. +enum ConnectionRequestResponse { + UNKNOWN_CONNECTION_REQUEST_RESPONSE = 0; + ACCEPTED = 1; + REJECTED = 2; + + // The advertiser neither accepted nor rejected the request. + IGNORED = 3; + + // The corresponding ConnectionAttempt failed, and so the request never + // reached the advertiser. + NOT_SENT = 4; +} + +// Result of a connection attempt. +enum ConnectionAttemptResult { + UNKNOWN_CONNECTION_ATTEMPT_RESULT = 0; + RESULT_SUCCESS = 1; + RESULT_ERROR = 2; + RESULT_CANCELLED = 3; +} + +// Whether this device is attempting an incoming or outgoing connection. +enum ConnectionAttemptDirection { + UNKNOWN_CONNECTION_ATTEMPT_DIRECTION = 0; + INCOMING = 1; + OUTGOING = 2; +} + +// Whether this is an initial or upgrade connection attempt. +enum ConnectionAttemptType { + UNKNOWN_CONNECTION_ATTEMPT_TYPE = 0; + INITIAL = 1; + UPGRADE = 2; +} + +// The reason that an EstablishedConnection was disconnected. +enum DisconnectionReason { + UNKNOWN_DISCONNECTION_REASON = 0; + LOCAL_DISCONNECTION = 1; + REMOTE_DISCONNECTION = 2; + IO_ERROR = 3; + UPGRADED = 4; + SHUTDOWN = 5; + UNFINISHED = 6; +} + +// The type of a Payload. +// Values correspond to +// http://cs/?q=symbol:com.google.android.gms.nearby.connection.Payload.Type +enum PayloadType { + UNKNOWN_PAYLOAD_TYPE = 0; + BYTES = 1; + FILE = 2; + STREAM = 3; +} + +// The status of a Payload. +enum PayloadStatus { + UNKNOWN_PAYLOAD_STATUS = 0; + SUCCESS = 1; + + // A local error like failing to attach/detach a chunk. + LOCAL_ERROR = 2; + + // The remote endpoint notified us of a local error on their end. + REMOTE_ERROR = 3; + + // An IO error while reading from or writing to the remote endpoint. + ENDPOINT_IO_ERROR = 4; + + // No errors so far; we expect this payload to be completed on a new medium. + MOVED_TO_NEW_MEDIUM = 5; + + // The connection was closed before this payload could complete. + CONNECTION_CLOSED = 6; + + // The payload was canceled by the local client. + LOCAL_CANCELLATION = 7; + + // The payload was canceled by the remote endpoint. + REMOTE_CANCELLATION = 8; +} + +// Result of an upgrade attempt. +enum BandwidthUpgradeResult { + UNKNOWN_BANDWIDTH_UPGRADE_RESULT = 0; + UPGRADE_RESULT_SUCCESS = 1; + + // Generic error not covered by a more specific error. + UPGRADE_RESULT_ERROR = 2; + + // Error during setup of the new medium, e.g. failure to start or connect to + // the hotspot. + MEDIUM_ERROR = 3; + + // Error during the protocol handshake (e.g. received an unexpected frame). + PROTOCOL_ERROR = 4; + + // Failure to read or write, on either the new or old medium. + RESULT_IO_ERROR = 5; + + // E.g. no endpoint channel found. + CHANNEL_ERROR = 6; + + // E.g. upgrading from Bluetooth to Bluetooth. + ALREADY_ON_MEDIUM_ERROR = 7; + + // For some reason, the attempt was never finished before it was time to + // record analytics (e.g. the client disconnected). + UNFINISHED_ERROR = 10; + + // TODO(mariaines): add a REMOTE_ERROR when we implement a cancellation + // message, for the case when the remote endpoint had an error on their end. +} + +// The stage at which an error occurred. +enum BandwidthUpgradeErrorStage { + UNKNOWN_BANDWIDTH_UPGRADE_ERROR_STAGE = 0; + + // Common protocol or setup stages. + + CLIENT_INTRODUCTION = 1; + NETWORK_AVAILABLE = 2; + LAST_WRITE_TO_PRIOR_CHANNEL = 3; + SAFE_TO_CLOSE_PRIOR_CHANNEL = 4; + // Creating the new EndpointChannel. + SOCKET_CREATION = 5; + // Getting the previous EndpointChannel + PRIOR_ENDPOINT_CHANNEL = 6; + // The upgrade attempt was not finished. + UPGRADE_UNFINISHED = 7; + // Upgrade successfully + UPGRADE_SUCCESS = 8; + + // Medium-specific stages. + // TODO(xlythe) Make sure each stage maps to one, and only one, possible + // failure. Re-using these stages makes it hard to understand what happened. + + // WIFI_HOTSPOT + // On the incoming side, starting up the hotspot. + WIFI_START_HOTSPOT = 10; + // On the incoming side, listening for incoming wifi connections. + WIFI_LISTEN_INCOMING = 11; + // On the outgoing side, connecting to the hotspot. + WIFI_CONNECT_TO_HOTSPOT = 12; + + // WIFI_LAN + // On the incoming side, listening for incoming wifi connections. + WIFI_LAN_LISTEN_INCOMING = 13; + // On the incoming side, invalid (null or loopback) Inet Address. + WIFI_LAN_IP_ADDRESS = 14; + // On the outgoing side, connecting to the local wifi socket. + WIFI_LAN_SOCKET_CONNECTION = 15; + + // BLUETOOTH + // On the incoming side, listening for incoming Bluetooth connections. + BLUETOOTH_LISTEN_INCOMING = 16; + // On the incoming side, obtaining the local Bluetooth MAC address. + BLUETOOTH_OBTAIN_MAC_ADDRESS = 17; + // On the outgoing side, connecting to a Bluetooth socket. + BLUETOOTH_CONNECT_OUTGOING = 18; + // On the outgoing side, parsing the remote Bluetooth MAC address. + BLUETOOTH_PARSE_MAC_ADDRESS = 19; + + // WIFI_AWARE + // On the incoming side, listening for incoming Wifi Aware connections. + WIFI_AWARE_LISTEN_INCOMING = 20; + // On the incoming side, publishing a Wifi Aware advertisement. + WIFI_AWARE_PUBLISH = 21; + // On the outgoing side, subscribing for Wifi Aware advertisements. + WIFI_AWARE_SUBSCRIBE = 22; + // On the outgoing side, connecting to the Wifi Aware network. + WIFI_AWARE_CONNECT_TO_NETWORK = 23; + + // WIFI_DIRECT + // On the incoming side, listening for incoming Wifi Direct connections. + WIFI_DIRECT_LISTEN_INCOMING = 24; + // On the incoming side, starting a Wifi Direct group. + WIFI_DIRECT_CREATE_GROUP = 25; + // On the outgoing side, connecting to a Wifi Direct socket. + WIFI_DIRECT_CONNECT_OUTGOING = 26; + // On the outgoing side, parsing the remote device address. + WIFI_DIRECT_PARSE_DEVICE_ADDRESS = 27; +} diff --git a/proto/connections_enums_proto_config.asciipb b/proto/connections_enums_proto_config.asciipb new file mode 100644 index 00000000..b5ea0aa5 --- /dev/null +++ b/proto/connections_enums_proto_config.asciipb @@ -0,0 +1,5 @@ +optimize_mode: LITE_RUNTIME + +allowed_enum: "location.nearby.proto.connections.Medium" +allowed_enum: "location.nearby.proto.connections.DisconnectionReason" +allowed_enum: "location.nearby.proto.connections.PayloadStatus" diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto new file mode 100644 index 00000000..71492b95 --- /dev/null +++ b/proto/discovery_enums.proto @@ -0,0 +1,471 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "DiscoveryEnums"; + +// NEXT ID: 130 +enum DiscoveryEvent { + UNKNOWN_DISCOVERY_EVENT = 0; + + // Discoverer created the beacon opt-in notification + BEACON_OPT_IN_NOTIFICATION_TRIGGERED = 1; + // User clicked on the beacon opt-in notification + BEACON_OPT_IN_NOTIFICATION_CLICKED = 2; + + // Discoverer created an item notification. + NOTIFICATION_TRIGGERED = 3; + // Discoverer' notification timed out without user action. + NOTIFICATION_TIMED_OUT = 4; + // User clicked on the "Manage Settings" button in notification. + // Deprecated. "Manage Settings" button replaced with "mute" for individual + // items and not present for grouped notifications. + NOTIFICATION_MANAGE_SETTINGS_CLICKED = 5 [deprecated = true]; + // Notification dismiss back off policy maxed out. + NOTIFICATION_DISMISS_BACKOFF_MAXED = 6; + + // User clicked on group notification. + NOTIFICATION_GROUP_CLICKED = 7; + // User specifically dismissed group notification. + NOTIFICATION_GROUP_DISMISSED = 8; + // User clicked on a one item notification. + NOTIFICATION_ITEM_CLICKED = 9; + // User specifically dismissed a one item notification. + NOTIFICATION_ITEM_DISMISSED = 10; + + // User clicked on a list item. + LIST_ITEM_CLICKED = 11; + // User disabled individual list items. + LIST_ITEMS_DISABLED = 12; + // User enabled individual list items. + LIST_ITEMS_ENABLED = 13; + + // The list view was launched (from Notificaion, Google Settings, etc.) + LIST_VIEW_LAUNCHED = 14; + // User clicked the positive button in notification settings opt-in + // dialog in ListView. + // Deprecated. Removed in v8. Use NOTIFICATION_MASTER_SWITCH_ENABLED + LIST_VIEW_OPT_IN_DIALOG_POSITIVE = 15 [deprecated = true]; + // User clicked the "got it" button to confirm turning off Nearby + // notification through master switch in education module. + LIST_VIEW_OPT_IN_DIALOG_NEGATIVE = 16; + // User clicked the refresh button in ListView + LIST_VIEW_REFRESHED = 17; + // User clicked the Help link in action bar menu. + LIST_VIEW_HELP_LINK_CLICKED = 18; + + // The notification settings Activity was launched. + NOTIFICATION_SETTINGS_LAUNCHED = 19; + // User enabled notification(device or link) in settings page. + NOTIFICATION_SETTINGS_ENABLED = 20; + // User disabled notification(device or link) in settings page. + NOTIFICATION_SETTINGS_DISABLED = 21; + + // App was installed after user being redirected to Play Store. + APP_INSTALLED = 22; + + // User clicked the notification settings entry in list view. + LIST_VIEW_NOTIFICATION_SETTINGS_CLICKED = 23; + + // The permission dialog for BT & Location was shown. + PERMISSION_DIALOG_TRIGGERED = 24; + // User clicked "yes" to enable BT & Location permission. + PERMISSION_DIALOG_POSITIVE = 25; + // User clicked "cancel" on permission dialog. + PERMISSION_DIALOG_NEGATIVE = 26; + + // The user was redirected to play store. + REDIRECTED_TO_PLAYSTORE = 27; + + // Data was cleared in debug mode. + // Deprecated. Removed in v11. + DATA_CLEARED = 28 [deprecated = true]; + + // Discoverer was launched by user clicking nearby in Google settings. + GOOGLE_SETTING_CLICKED = 29; + + // The "network is disabled" message is shown + // Deprecated. Use NetworkState instead. + NETWORK_UNAVAILABLE = 30 [deprecated = true]; + + // The QuickSettings Nearby tile was added + TILE_ADDED = 31; + // The QuickSettings Nearby tile was removed + TILE_REMOVED = 32; + // The QuickSettings Nearby tile was clicked + TILE_CLICKED = 33; + + // User disabled item from notification. + NOTIFICATION_DISABLED = 34; + + // User leaves the list view. + LIST_VIEW_EXIT = 35; + + // TODO(haoxiangl): distinguish whether it is launched from home screen or + // launched after Chrome Custom Tab is dismissed + // Discoverer was launched by user clicking home screen shortcut icon. + LIST_VIEW_LAUNCHED_FROM_HOME_SCREEN = 36; + + // Discoverer was launched by user clicking group/opt-in notification. + LIST_VIEW_LAUNCHED_FROM_NOTIFICATION = 37; + + // Discoverer was launched by user clicking QS tile + LIST_VIEW_LAUNCHED_FROM_QS_TILE = 38; + + // The list view refresh was triggered automatically by empty list or + // device permission turned on. + LIST_VIEW_AUTO_REFRESHED = 39; + + // When the home screen icon was successfully added. + HOME_SCREEN_ICON_ADDED = 40; + + // When user choose to add the home screen icon in overflow menu. + HOME_SCREEN_ICON_OVERFLOW_ADDED = 41; + + // When user choose to add the home screen icon in warm welcome flow. + HOME_SCREEN_ICON_WW_ACCEPTED = 42; + + // When user choose not to add the home screen icon in warm welcome flow. + HOME_SCREEN_ICON_WW_REJECTED = 43; + + // Beacon opt-in notification (education) was timed out. + BEACON_OPT_IN_NOTIFICATION_TIMED_OUT = 44; + + // Beacon opt-in notification (education) was dismissed by user. + BEACON_OPT_IN_NOTIFICATION_DISMISSED = 45; + + // User enabled all notifications using list view master switch. + NOTIFICATION_MASTER_SWITCH_ENABLED = 46; + // User disabled all notifications using list view master switch. + NOTIFICATION_MASTER_SWITCH_DISABLED = 47; + + // A new ChromeCustomTab session is started + CHROME_CUSTOM_TAB_START = 48; + // A ChromeCustomTab session is finished + CHROME_CUSTOM_TAB_FINISH = 49; + + // The user swiped away the "Pairing..." notification. + MAGIC_PAIR_PAIRING_NOTIFICATION_DISMISSED = 50; + + // The user has started pairing with a device associated with a FastPair + // item. The user may have started pairing via Bluetooth Settings rather than + // via the notification. (We may not have even shown the notification, i.e. + // if it's not within the distance threshold.) + BLUETOOTH_BONDING = 57; + + // An item was launched automatically (e.g. because it passed a very high + // relevance threshold), without the user clicking it. + ITEM_AUTO_LAUNCHED = 51; + + // A Listview item is viewed by the users. + LIST_ITEM_VIEWED = 52; + + // Web url was launched in a browser other than in Chrome Custom Tab. + WEB_URL_LAUNCHED_IN_BROWSER = 53; + + // "Do not show again" was clicked in the notification + NOTIFICATION_DO_NOT_SHOW_AGAIN_CLICKED = 54; + + // "Report" was clicked in the Chrome Custom Tab + CHROME_CUSTOM_TAB_REPORT_CLICKED = 55; + + // The user clicked the notification displayed after pairing has finished. + MAGIC_PAIR_POST_COMPLETION_INTENT_LAUNCHED = 56; + + // Abuse Report was submitted + REPORT_ABUSE_SUBMITTED = 58; + + // "Report" was clicked in the Discovery Report Snackbar + SNACKBAR_REPORT_CLICKED = 59; + + // Devices activity was launched by user clicking settings button. + DEVICES_LIST_VIEW_LAUNCHED_FROM_SETTINGS = 60; + + // Devices activity was launched by user clicking a notification. + DEVICES_LIST_VIEW_LAUNCHED_FROM_NOTIFICATION = 61; + + // User left the devices activity. + DEVICES_LIST_VIEW_EXIT = 62; + + // A list item was viewed by the user in the devices activity. + DEVICES_LIST_ITEM_VIEWED = 63; + + // A list item in the devices activity is clicked by the user. + DEVICES_LIST_ITEM_CLICKED = 64; + + // User clicked the Help link in the action bar menu. + DEVICES_LIST_VIEW_ACTION_BAR_HELP_LINK_CLICKED = 66; + + // User toggled the 'Notifications' item in the devices list view. + DEVICES_LIST_VIEW_NOTIFICATIONS_TOGGLED = 73; + + // User clicked the help link in Fast Pair account settings. + FAST_PAIR_ACCOUNT_SETTINGS_ACTION_BAR_HELP_LINK_CLICKED = 74; + + // User enables device notifications + DEVICE_NOTIFICATION_SETTINGS_ENABLED = 75; + + // User disables device notifications + DEVICE_NOTIFICATION_SETTINGS_DISABLED = 76; + + // User connected to a bluetooth device with a battery level and we showed a + // toast to let them know the current level. + BLUETOOTH_BATTERY_LEVEL_TOAST_SHOWN = 77; + + // User connected to a Fast Pair 2 device and the connected/disconnected + // status was uploaded to Find My Accessories. + FIND_MY_ACCESSORY_UPLOADED = 78; + + // User went to the Fast Pair account page. + FAST_PAIR_ACCOUNT_SETTINGS_LAUNCHED = 79; + + // User switched the account being shown on the Fast Pair account page. + FAST_PAIR_ACCOUNT_SETTINGS_SWITCHED = 80; + + // User disabled saving Fast Pair devices to their account. + FAST_PAIR_ACCOUNT_SETTINGS_SAVE_DISABLED = 81; + + // User enabled saving Fast Pair devices to their account. + FAST_PAIR_ACCOUNT_SETTINGS_SAVE_ENABLED = 82; + + // User clicked a device item on the account settings page. + FAST_PAIR_ACCOUNT_SETTINGS_DEVICE_ITEM_LAUNCHED = 83; + + // User renamed a Fast Pair device from Fast Pair's device settings page. + FAST_PAIR_DEVICE_RENAMED = 84; + + // User forgot a Fast Pair device from Fast Pair's device settings page. + FAST_PAIR_DEVICE_FORGOTTEN = 85; + + // User clicked the Find My Device item from device settings. + FAST_PAIR_DEVICE_FIND_DEVICE_CLICKED = 86; + + // User clicked the install companion app item from device settings. + FAST_PAIR_DEVICE_INSTALL_COMPANION_APP_CLICKED = 87; + + // User clicked the open companion app item from device settings. + FAST_PAIR_DEVICE_OPEN_COMPANION_APP_CLICKED = 88; + + // User clicked a slice item from the companion app on the device settings + // page. + FAST_PAIR_DEVICE_SLICE_ITEM_CLICKED = 89; + + // User launched the find device activity for ringing their device. + FAST_PAIR_FIND_DEVICE_LAUNCHED = 90; + + // User clicked the find device ring button. + FAST_PAIR_FIND_DEVICE_RING_CLICKED = 91; + + // User clicked the find device mute button. + FAST_PAIR_FIND_DEVICE_MUTE_CLICKED = 92; + + // User clicked the find device ring left button. + FAST_PAIR_FIND_DEVICE_RING_LEFT_CLICKED = 93; + + // User clicked the find device ring right button. + FAST_PAIR_FIND_DEVICE_RING_RIGHT_CLICKED = 94; + + // User clicked the find device mute left button. + FAST_PAIR_FIND_DEVICE_MUTE_LEFT_CLICKED = 95; + + // User clicked the find device mute right button. + FAST_PAIR_FIND_DEVICE_MUTE_RIGHT_CLICKED = 96; + + // User clicked the show device location history button. + FAST_PAIR_FIND_DEVICE_HISTORY_CLICKED = 97; + + // User queried the connected device settings slices. + FAST_PAIR_CONNECTED_DEVICE_SLICE_QUERIED = 98; + + // User requested to download the optional module. + FAST_PAIR_OPTIONAL_MODULE_REQUEST_SUCCEEDED = 99; + + // User requested to download the optional module, but it failed. + FAST_PAIR_OPTIONAL_MODULE_REQUEST_FAILED = 100; + + // User finished installing the optional module and it is enabled. + FAST_PAIR_OPTIONAL_MODULE_ENABLED = 101; + + // Android first discovers a device broadcasting a Fast Pair advertisement + // containing a model id + FAST_PAIR_DEVICE_DETECTED_WITH_MODEL_ID = 102; + + // Android first discovers a device broadcasting a Fast Pair advertisement + // containing a bloom filter + FAST_PAIR_DEVICE_DETECTED_WITH_BLOOM_FILTER = 103; + + // Detected model id was found in the local Fast Pair device database. + FAST_PAIR_LOCAL_DB_CACHE_HIT = 104; + + // Detected model id was not found in the local Fast Pair device database, + // and a request was sent to the GetObservedDevices for device info. + FAST_PAIR_DEVICE_INFO_SERVER_REQUEST_SENT = 105; + + // Failed to receive a valid response from the Device info request. + FAST_PAIR_DEVICE_INFO_SERVER_ERROR_RESPONSE = 106; + + // Received response for previous Fast Pair Device info request. + FAST_PAIR_DEVICE_INFO_SERVER_RESPONSE_RECEIVED = 107; + + // User was shown notification for the first time using the internal Fast Pair + // scanning stack. + FAST_PAIR_NOTIFICATION_SHOWN = 108; + + // User dismissed notification generated from the internal Fast Pair scan + // stack. + FAST_PAIR_NOTIFICATION_DISMISSED = 109; + + // User triggered notification's "Do not show" action. + FAST_PAIR_NOTIFICATION_DO_NOT_SHOW_CLICKED = 110; + + // Notification generated from the internal Fast Pair scan stack has + // timed-out. + FAST_PAIR_NOTIFICATION_TIMEOUT = 111; + + // Internal Fast Pair scanner detected a device that has triggered an + // auto launch interaction type for the first time. + FAST_PAIR_AUTO_LAUNCH_TRIGGERED = 112; + + // User tapped the pairing notification generated from the internal Fast Pair + // scan stack. + FAST_PAIR_NOTIFICATION_CLICKED = 113; + + // User has seen a battery notification. + FAST_PAIR_BATTERY_NOTIFICATION_SHOWN = 114; + + // User has dismissed the battery notification. + FAST_PAIR_BATTERY_NOTIFICATION_DISMISSED = 115; + + // User has clicked the battery notification. + FAST_PAIR_BATTERY_NOTIFICATION_CLICKED = 116; + + // A "smart" battery remaining number was displayed to the user. + FAST_PAIR_BATTERY_NOTIFICATION_DISPLAYED_SMART_BATTERY = 117; + + // User has clicked the assistant settings slice. + FAST_PAIR_DEVICE_ASSISTANT_SETTINGS_CLICKED = 118; + + // A post action notification of installing or launching companion apps was + // shown. + FAST_PAIR_POST_ACTION_NOTIFICATION_SHOWN = 119; + + // A user clicked event of installing a companion app. + FAST_PAIR_POST_ACTION_INSTALL_COMPANION_APP = 120; + + // A user clicked event of launching a companion app. + FAST_PAIR_POST_ACTION_LAUNCH_COMPANION_APP = 121; + + // User has clicked the companion oobe slice from device settings. + FAST_PAIR_DEVICE_COMPANION_OOBE_CLICKED = 122; + + // User has clicked the companion settings slice from device settings. + FAST_PAIR_DEVICE_COMPANION_SETTINGS_CLICKED = 123; + + // User was shown notification for the first time secondary device available. + FAST_PAIR_SECONDARY_DEVICE_NOTIFICATION_SHOWN = 124; + + // User dismissed notification for the secondary device available. + FAST_PAIR_SECONDARY_DEVICE_NOTIFICATION_DISMISSED = 125; + + // User triggered notification's "Do not show again" action for the secondary + // device available. + FAST_PAIR_SECONDARY_DEVICE_NOTIFICATION_DO_NOT_SHOW_CLICKED = 126; + + // The notification has timed-out for the secondary device available. + FAST_PAIR_SECONDARY_DEVICE_NOTIFICATION_TIMEOUT = 127; + + // User tapped the pairing notification for the secondary device available. + FAST_PAIR_SECONDARY_DEVICE_NOTIFICATION_CLICKED = 128; + + // A user dismissed event of launching a companion app. + FAST_PAIR_POST_ACTION_DISMISS_COMPANION_APP = 129; + + // Deprecated. + reserved 65, 67 to 72; +} + +// Deprecated: use NearbyType or different events for different types instead. +enum DiscoveryType { + option deprecated = true; + + UNKNOWN_TYPE = 0; + // The action is related to a device setup item. e.g. Chromecast + DEVICE = 1; + // The action is related to a beacon item. e.g. PWS or PBS. + BEACON = 2; + // The action is related to a popular here item. e.g. popular here url/apps. + POPULAR_HERE = 3; +} + +enum ActionIntentType { + UNKNOWN_ACTION_INTENT_TYPE = 0; + // Open a web url directly. + INTENT_WEB_URL = 1; + // Launch an installed app. + INTENT_APP = 2; + // Open the fallback web url due to app not installed. + INTENT_FALLBACK_URL = 3; + // Redirect user to play store due to app not installed. + INTENT_PLAY_STORE = 4; +} + +enum BlockType { + UNKNOWN_BLOCK_TYPE = 0; + // The item is enabled by user + ITEM_ENABLED = 1; + // The item is disabled by user + ITEM_DISABLED = 2; +} + +enum SettingState { + UNKNOWN_STATE_TYPE = 0; + // The setting entry is not set. (before opt-in) + NOT_SET = 1; + // The setting is enabled by user + ENABLED = 2; + // The setting is disabled by user + DISABLED = 3; +} + +enum TileState { + UNKNOWN_TILE_STATE_TYPE = 0; + // The quick settings tile is not available for this device + NOT_AVAILABLE = 1; + // The quick settings tile is enabled + TILE_ENABLED = 2; + // The quick settings tile is disabled + TILE_DISABLED = 3; +} + +enum NetworkState { + UNKNOWN_NETWORK_STATE_TYPE = 0; + // Device has no network connection + DISCONNECTED = 1; + // Device is connected on Wifi + ON_WIFI = 2; + // Device is connected on cellular + ON_CELLULAR = 3; +} + +enum EducationState { + UNKNOWN_EDUCATION_STATE = 0; + // User has finished the education workflow + EDUCATION_COMPLETE = 1; + // User has not finished the education workflow + EDUCATION_NOT_COMPLETE = 2; +} + +// LINT.IfChange +enum ScreenState { + UNKNOWN_SCREEN_STATE = 0; + // Device's screen is interactive + SCREEN_INTERACTIVE = 1; + // Device's screen is non-interactive + SCREEN_NOT_INTERACTIVE = 2; +} +// LINT.ThenChange(//depot/google3/location/nearby/discovery_signal_store/proto/discovery_signal_store.proto) diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto new file mode 100644 index 00000000..51eef973 --- /dev/null +++ b/proto/magic_pair_enums.proto @@ -0,0 +1,66 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "MagicPairEnums"; +option objc_class_prefix = "GNCP"; + +// Enums related to logged events. For event codes, see NearbyEventCodes. +message MagicPairEvent { + // These numbers match BluetoothDevice on Android: + // http://cs/android/frameworks/base/core/java/android/bluetooth/BluetoothDevice.java?l=283&rcl=0d05da79fb6c0fb04f6ebd3cc16265c5ff9e6764 + enum BondState { + UNKNOWN_BOND_STATE = 0; + NONE = 10; + BONDING = 11; + BONDED = 12; + } + + // Generally applicable error codes. + enum ErrorCode { + UNKNOWN_ERROR_CODE = 0; + + // Check the other fields for a more specific error code. + OTHER_ERROR = 1; + + // The operation timed out. + TIMEOUT = 2; + + // The thread was interrupted. + INTERRUPTED = 3; + + // Some reflective call failed (should never happen). + REFLECTIVE_OPERATION_EXCEPTION = 4; + + // A Future threw an exception (should never happen). + EXECUTION_EXCEPTION = 5; + + // Parsing something (e.g. BR/EDR Handover data) failed. + PARSE_EXCEPTION = 6; + } + + enum BrEdrHandoverErrorCode { + UNKNOWN_BR_EDR_HANDOVER_ERROR_CODE = 0; + CONTROL_POINT_RESULT_CODE_NOT_SUCCESS = 1; + BLUETOOTH_MAC_INVALID = 2; + TRANSPORT_BLOCK_INVALID = 3; + } + + enum CreateBondErrorCode { + UNKNOWN_BOND_ERROR_CODE = 0; + BOND_BROKEN = 1; + POSSIBLE_MITM = 2; + } + + enum ConnectErrorCode { + UNKNOWN_CONNECT_ERROR_CODE = 0; + UNSUPPORTED_PROFILE = 1; + GET_PROFILE_PROXY_FAILED = 2; + DISCONNECTED = 3; + } +} diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto new file mode 100644 index 00000000..59dc9116 --- /dev/null +++ b/proto/nearby_client_enums.proto @@ -0,0 +1,30 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "NearbyClientEnums"; +option objc_class_prefix = "GNCP"; + +// The user type that is logging. +enum UserType { + UNKNOWN_USER_TYPE = 0; + PRODUCTION = 1; + MODULEFOOD = 2; + TEST = 3; + PRESTO_DOGFOOD = 4; + AUTO_TEST = 5; +} + +// The client that is logging. +enum ClientType { + UNKNOWN_CLIENT_TYPE = 0; + CONNECTIONS = 1; + MAGIC_PAIR = 2; + SETUP = 3; + SHARING = 4; +} diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto new file mode 100644 index 00000000..91610ae5 --- /dev/null +++ b/proto/nearby_event_codes.proto @@ -0,0 +1,58 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "NearbyEventCodes"; + +// Event codes for the NEARBY log source. See: +// http://google3/wireless/android/play/playlog/proto/event_code_enums.proto +message NearbyEvent { + enum EventCode { + UNKNOWN_EVENT_TYPE = 0; + + // Codes for Magic Pair. + // Starting at 1000 to not conflict with other existing codes (e.g. + // DiscoveryEvent) that may be migrated to become official Event Codes. + MAGIC_PAIR_START = 1010; + WAIT_FOR_SCREEN_UNLOCK = 1020; + GATT_CONNECT = 1030; + BR_EDR_HANDOVER_WRITE_CONTROL_POINT_REQUEST = 1040; + BR_EDR_HANDOVER_READ_BLUETOOTH_MAC = 1050; + BR_EDR_HANDOVER_READ_TRANSPORT_BLOCK = 1060; + GET_PROFILES_VIA_SDP = 1070; + DISCOVER_DEVICE = 1080; + CANCEL_DISCOVERY = 1090; + REMOVE_BOND = 1100; + CANCEL_BOND = 1110; + CREATE_BOND = 1120; + CONNECT_PROFILE = 1130; + DISABLE_BLUETOOTH = 1140; + ENABLE_BLUETOOTH = 1150; + MAGIC_PAIR_END = 1160; + SECRET_HANDSHAKE = 1170; + WRITE_ACCOUNT_KEY = 1180; + WRITE_TO_FOOTPRINTS = 1190; + PASSKEY_EXCHANGE = 1200; + DEVICE_RECOGNIZED = 1210; + GET_LOCAL_PUBLIC_ADDRESS = 1220; + DIRECTLY_CONNECTED_TO_PROFILE = 1230; + DEVICE_ALIAS_CHANGED = 1240; + WRITE_DEVICE_NAME = 1250; + UPDATE_PROVIDER_NAME_START = 1260; + UPDATE_PROVIDER_NAME_END = 1270; + READ_FIRMWARE_VERSION = 1280; + RETROACTIVE_PAIR_START = 1290; + RETROACTIVE_PAIR_END = 1300; + SUBSEQUENT_PAIR_START = 1310; + SUBSEQUENT_PAIR_END = 1320; + BISTO_PAIR_START = 1330; + BISTO_PAIR_END = 1340; + REMOTE_PAIR_START = 1350; + REMOTE_PAIR_END = 1360; + } +} diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto new file mode 100644 index 00000000..2bb334ca --- /dev/null +++ b/proto/setup_enums.proto @@ -0,0 +1,28 @@ +syntax = "proto2"; + +package location.nearby.proto.setup; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "SetupEnums"; +option objc_class_prefix = "GNSP"; + +// The type of event being logged. +// Lightweight START_* and STOP_* events track instances of potential crashes +// that would result in a ClientSession not being logged. +enum EventType { + UNKNOWN_EVENT_TYPE = 0; + + // A completed ClientSession, logged after a client disconnects. + CLIENT_SESSION = 1; + + // Corresponds to googleApiClient.connect() and the beginning of a + // ClientSession. + START_CLIENT_SESSION = 2; + + // Corresponds to googleApiClient.disconnect() and the end of a ClientSession. + STOP_CLIENT_SESSION = 3; +} diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto new file mode 100644 index 00000000..a1df8808 --- /dev/null +++ b/proto/sharing_enums.proto @@ -0,0 +1,249 @@ +syntax = "proto2"; + +package location.nearby.proto.sharing; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "SharingEnums"; +option objc_class_prefix = "GNSHP"; + +/* +We use event based logging (an event object can be constructed and logged +immediately when they occur). To obtain session based information (e.g. +durations, counting incoming introductions), we use flowId (sender/receiver) in +NearbyClearcutLogger for all events (may exclude settings), and session_id for a +pair of events (start and end of a session). + */ +enum EventType { + UNKNOWN_EVENT_TYPE = 0; + + // When new users accept agreements (like grant permission to contacts for + // CONTACT_ONLY visibility) and are enrolled into Nearby Sharing. This event + // is used to count number of new users. + ACCEPT_AGREEMENTS = 1; + + // User enables/disables nearby sharing from setting or tile service. + ENABLE_NEARBY_SHARING = 2; + + // User sets visibility preference from setting. + SET_VISIBILITY = 3; + + // Describe attachments immediately when Nearby Sharing is opened by another + // app which is used to generate/attach attachments to be shared with other + // devices. + DESCRIBE_ATTACHMENTS = 4; + + // Start of a scanning phase at sender. + SCAN_FOR_SHARE_TARGETS_START = 5; + + // End of the scanning phase at sender. + SCAN_FOR_SHARE_TARGETS_END = 6; + + // Receiver advertises itself for presence (a pseudo session). + ADVERTISE_DEVICE_PRESENCE_START = 7; + + // End of the advertising phase at receiver. + ADVERTISE_DEVICE_PRESENCE_END = 8; + + // Sender sends a fast initialization to receiver. + SEND_FAST_INITIALIZATION = 9; + + // Receiver receives the fast initialization. + RECEIVE_FAST_INITIALIZATION = 10; + + // Sender discovers a share target. + DISCOVER_SHARE_TARGET = 11; + + // Sender sends introduction (before attachments being sent). + SEND_INTRODUCTION = 12; + + // Receiver receives introduction. + RECEIVE_INTRODUCTION = 13; + + // Receiver responds to introduction (before attachments being sent). + // Actions: Accept, Reject, or (for some reason) Fail. + RESPOND_TO_INTRODUCTION = 14; + + // Start of the sending attachments phase at sender. + SEND_ATTACHMENTS_START = 15; + + // End of sending attachments phase at sender. + SEND_ATTACHMENTS_END = 16; + + // Start of the receiving attachments phase at receiver. + RECEIVE_ATTACHMENTS_START = 17; + + // End of receiving attachments phase at receiver. + RECEIVE_ATTACHMENTS_END = 18; + + // Sender cancels sending attachments. + CANCEL_SENDING_ATTACHMENTS = 19; + + // Receiver cancels receiving attachments. + CANCEL_RECEIVING_ATTACHMENTS = 20; + + // Receiver opens received attachments. + OPEN_RECEIVED_ATTACHMENTS = 21; + + // User opens the setup activity. + LAUNCH_SETUP_ACTIVITY = 22; + + // User adds a contact. + ADD_CONTACT = 23; + + // User removes a contact. + REMOVE_CONTACT = 24; + + // Local devices all Fast Share server. + FAST_SHARE_SERVER_RESPONSE = 25; + + // The start of a sending session. + SEND_START = 26; + + // Receiver accepts a fast initialization. + ACCEPT_FAST_INITIALIZATION = 27; + + // Set internet preference. + SET_INTERNET_PREFERENCE = 28; +} + +// Status of nearby sharing. +enum NearbySharingStatus { + UNKNOWN_NEARBY_SHARING_STATUS = 0; + + ON = 1; + OFF = 2; +} + +enum Visibility { + UNKNOWN_VISIBILITY = 0; + + CONTACTS_ONLY = 1; + EVERYONE = 2; + SELECTED_CONTACTS_ONLY = 3; + HIDDEN = 4; +} + +enum InternetPreference { + UNKNOWN_INTERNET_PREFERENCE = 0; + + ONLINE = 1; + WIFI_ONLY = 2; + OFFLINE = 3; +} + +// The status of sending and receiving attachments. Used by SEND_ATTACHMENTS. +enum AttachmentTransmissionStatus { + UNKNOWN_ATTACHMENT_TRANSMISSION_STATUS = 0; + + COMPLETE_ATTACHMENT_TRANSMISSION_STATUS = 1; + CANCELED_ATTACHMENT_TRANSMISSION_STATUS = 2; + FAILED_ATTACHMENT_TRANSMISSION_STATUS = 3; +} + +// The status of advertising and discovering sessions. Used by +// SCAN_FOR_SHARE_TARGETS and ADVERTISE_DEVICE_PRESENCE. +enum SessionStatus { + UNKNOWN_SESSION_STATUS = 0; + + SUCCEEDED_SESSION_STATUS = 1; + FAILED_SESSION_STATUS = 2; +} + +// User's response to introductions. +enum ResponseToIntroduction { + UNKNOWN_RESPONSE_TO_INTRODUCTION = 0; + + ACCEPT_INTRODUCTION = 1; + REJECT_INTRODUCTION = 2; + FAIL_INTRODUCTION = 3; +} + +// TODO(fdi): may eventually include desktop, etc. +// The type of a remote device. +enum DeviceType { + UNKNOWN_DEVICE_TYPE = 0; + + PHONE = 1; + TABLET = 2; + LAPTOP = 3; +} + +// TODO(fdi): may eventually include windows, iOS, etc. +// The OS type of a remote device. +enum OSType { + UNKNOWN_OS_TYPE = 0; + + ANDROID = 1; + CHROME_OS = 2; +} + +// Relationship of remote device to sender device. +enum DeviceRelationship { + UNKNOWN_DEVICE_RELATIONSHIP = 0; + + // The remote device belongs to the same owner as sender device. + IS_SELF = 1; + // The remote device is a contact of sender. + IS_CONTACT = 2; + // The remote device is a stranger. + IS_STRANGER = 3; +} + +// The device sources of the clearcut log. +enum LogSource { + UNSPECIFIED_SOURCE = 0; + + // Represents the devices in Nearby labs. + LAB_DEVICES = 1; + // Represents the devices tested by Nearby engs, in the long term can include + // any devices with newest feature flags. + INTERNAL_DEVICES = 2; + // Represents the devices testing our in-development features before they're + // released to the greater public. + BETA_TESTER_DEVICES = 3; + // Represents the OEM partners (like Samsung) that we're working with to + // verify functionality on their devices. + OEM_DEVICES = 4; +} + +// The Fast Share server action name. +enum ServerActionName { + UNKNOWN_SERVER_ACTION = 0; + + UPLOAD_CERTIFICATES = 1; + DOWNLOAD_CERTIFICATES = 2; + CHECK_REACHABILITY = 3; + UPLOAD_CONTACTS = 4; + UPDATE_DEVICE_NAME = 5; +} + +// The Fast Share server response state. +enum ServerResponseState { + UNKNOWN_SERVER_RESPONSE_STATE = 0; + + SERVER_RESPONSE_SUCCESS = 1; + SERVER_RESPONSE_UNKNOWN_FAILURE = 2; + + // For StatusException. + SERVER_RESPONSE_STATUS_OTHER_FAILURE = 3; + SERVER_RESPONSE_STATUS_DEADLINE_EXCEEDED = 4; + SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5; + SERVER_RESPONSE_STATUS_UNAVAILABLE = 6; + SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7; + + // For GoogleAuthException. + SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8; +} + +// The type of Nearby Sharing scanning. +enum ScanType { + UNKNOWN_SCAN_TYPE = 0; + + FOREGROUND_SCAN = 1; + FOREGROUND_RETRY_SCAN = 2; + DIRECT_SHARE_SCAN = 3; +} From d455926d89dc491d1cb4e8b9c9a56437eef0102e Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Sat, 4 Apr 2020 11:56:39 -0700 Subject: [PATCH 03/52] nearby: snapshot of cl/304428753 Signed-off-by: Alexey Polyudov Change-Id: I0023ac6e40456fc4a8169c3173bcbff3b5ccc476 --- cpp/core/internal/BUILD | 40 +- cpp/core/internal/base_endpoint_channel.cc | 3 +- .../internal/base_endpoint_channel_test.cc | 60 +++ cpp/core/internal/base_pcp_handler.cc | 34 +- cpp/core/internal/base_pcp_handler.h | 1 + cpp/core/internal/ble_advertisement_test.cc | 18 +- cpp/core/internal/endpoint_channel_manager.cc | 5 +- cpp/core/internal/endpoint_manager.cc | 12 +- cpp/core/internal/endpoint_manager.h | 2 + .../mediums/advertisement_read_result_test.cc | 20 +- .../mediums/ble_advertisement_test.cc | 7 +- cpp/core/internal/mediums/ble_packet.h | 32 ++ cpp/core/internal/mediums/ble_peripheral.h | 15 + cpp/core/internal/mediums/ble_v2.cc | 13 +- cpp/core/internal/mediums/ble_v2.h | 1 + .../internal/mediums/bloom_filter_test.cc | 3 +- cpp/core/internal/offline_frames.cc | 132 +++--- cpp/core/internal/offline_frames_test.cc | 88 ++++ cpp/core/internal/p2p_cluster_pcp_handler.cc | 16 +- cpp/core/internal/p2p_cluster_pcp_handler.h | 4 + cpp/core/internal/payload_manager.cc | 20 +- cpp/core/internal/payload_manager.h | 1 + .../internal/service_controller_router.cc | 26 +- cpp/core/internal/service_controller_router.h | 1 + cpp/core/internal/wifi_lan_service_info.cc | 202 +++++++++ cpp/core/internal/wifi_lan_service_info.h | 96 +++++ .../internal/wifi_lan_service_info_test.cc | 151 +++++++ cpp/platform/BUILD | 14 +- cpp/platform/api/BUILD | 5 + cpp/platform/api/ble_v2.h | 9 +- cpp/platform/api/bluetooth_classic.h | 12 +- cpp/platform/api/executor.h | 6 + cpp/platform/api/future.h | 7 + cpp/platform/api/input_file.h | 2 +- cpp/platform/api/input_stream.h | 4 +- cpp/platform/api/listenable_future.h | 28 ++ cpp/platform/api/multi_thread_executor.h | 4 +- cpp/platform/api/server_sync.h | 64 +++ cpp/platform/api/settable_future.h | 6 +- cpp/platform/api/single_thread_executor.h | 4 +- cpp/platform/api/submittable_executor.h | 6 +- cpp/platform/api/webrtc.h | 46 +++ cpp/platform/api/wifi.h | 5 +- cpp/platform/api/wifi_lan.h | 94 +++++ cpp/platform/api2/BUILD | 65 +++ cpp/platform/api2/atomic_boolean.h | 21 + cpp/platform/api2/atomic_reference.h | 22 + cpp/platform/api2/ble.h | 111 +++++ cpp/platform/api2/ble_v2.h | 390 ++++++++++++++++++ cpp/platform/api2/bluetooth_adapter.h | 55 +++ cpp/platform/api2/bluetooth_classic.h | 124 ++++++ cpp/platform/api2/condition_variable.h | 26 ++ cpp/platform/api2/count_down_latch.h | 29 ++ cpp/platform/api2/executor.h | 26 ++ cpp/platform/api2/future.h | 30 ++ cpp/platform/api2/hash_utils.h | 20 + cpp/platform/api2/input_file.h | 24 ++ cpp/platform/api2/input_stream.h | 27 ++ cpp/platform/api2/listenable_future.h | 29 ++ cpp/platform/api2/multi_thread_executor.h | 23 ++ cpp/platform/api2/mutex.h | 22 + cpp/platform/api2/output_file.h | 20 + cpp/platform/api2/output_stream.h | 25 ++ cpp/platform/api2/scheduled_executor.h | 29 ++ cpp/platform/api2/server_sync.h | 60 +++ cpp/platform/api2/settable_future.h | 24 ++ cpp/platform/api2/single_thread_executor.h | 23 ++ cpp/platform/api2/socket.h | 25 ++ cpp/platform/api2/submittable_executor.h | 42 ++ cpp/platform/api2/system_clock.h | 22 + cpp/platform/api2/thread_utils.h | 22 + cpp/platform/api2/webrtc.h | 46 +++ cpp/platform/api2/wifi.h | 88 ++++ cpp/platform/base64_utils.cc | 7 +- cpp/platform/base64_utils.h | 11 +- cpp/platform/exception.cc | 30 -- cpp/platform/exception.h | 44 +- cpp/platform/exception_test.cc | 76 ++++ cpp/platform/impl/default/BUILD | 6 +- cpp/platform/ptr.cc | 13 - cpp/platform/ptr.h | 258 +++--------- cpp/platform/ptr_test.cc | 147 +------ proto/BUILD | 42 -- proto/connections/BUILD | 11 - proto/connections_enums.proto | 44 +- proto/discovery_enums.proto | 14 +- proto/sharing_enums.proto | 8 +- 87 files changed, 2869 insertions(+), 631 deletions(-) create mode 100644 cpp/core/internal/base_endpoint_channel_test.cc create mode 100644 cpp/core/internal/offline_frames_test.cc create mode 100644 cpp/core/internal/wifi_lan_service_info.cc create mode 100644 cpp/core/internal/wifi_lan_service_info.h create mode 100644 cpp/core/internal/wifi_lan_service_info_test.cc create mode 100644 cpp/platform/api/listenable_future.h create mode 100644 cpp/platform/api/server_sync.h create mode 100644 cpp/platform/api/webrtc.h create mode 100644 cpp/platform/api/wifi_lan.h create mode 100644 cpp/platform/api2/BUILD create mode 100644 cpp/platform/api2/atomic_boolean.h create mode 100644 cpp/platform/api2/atomic_reference.h create mode 100644 cpp/platform/api2/ble.h create mode 100644 cpp/platform/api2/ble_v2.h create mode 100644 cpp/platform/api2/bluetooth_adapter.h create mode 100644 cpp/platform/api2/bluetooth_classic.h create mode 100644 cpp/platform/api2/condition_variable.h create mode 100644 cpp/platform/api2/count_down_latch.h create mode 100644 cpp/platform/api2/executor.h create mode 100644 cpp/platform/api2/future.h create mode 100644 cpp/platform/api2/hash_utils.h create mode 100644 cpp/platform/api2/input_file.h create mode 100644 cpp/platform/api2/input_stream.h create mode 100644 cpp/platform/api2/listenable_future.h create mode 100644 cpp/platform/api2/multi_thread_executor.h create mode 100644 cpp/platform/api2/mutex.h create mode 100644 cpp/platform/api2/output_file.h create mode 100644 cpp/platform/api2/output_stream.h create mode 100644 cpp/platform/api2/scheduled_executor.h create mode 100644 cpp/platform/api2/server_sync.h create mode 100644 cpp/platform/api2/settable_future.h create mode 100644 cpp/platform/api2/single_thread_executor.h create mode 100644 cpp/platform/api2/socket.h create mode 100644 cpp/platform/api2/submittable_executor.h create mode 100644 cpp/platform/api2/system_clock.h create mode 100644 cpp/platform/api2/thread_utils.h create mode 100644 cpp/platform/api2/webrtc.h create mode 100644 cpp/platform/api2/wifi.h delete mode 100644 cpp/platform/exception.cc create mode 100644 cpp/platform/exception_test.cc delete mode 100644 cpp/platform/ptr.cc diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index e9d2dd67..f54df70d 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -8,7 +8,7 @@ cc_library( "loop_runner.cc", "loop_runner.h", "offline_frames.cc", - "offline_frames.h", + "wifi_lan_service_info.cc", ], hdrs = [ "bandwidth_upgrade_handler.h", @@ -40,6 +40,7 @@ cc_library( "internal_payload_factory.h", "medium_manager.cc", "medium_manager.h", + "offline_frames.h", "offline_service_controller.cc", "offline_service_controller.h", "p2p_cluster_pcp_handler.cc", @@ -57,6 +58,7 @@ cc_library( "service_controller.h", "service_controller_router.cc", "service_controller_router.h", + "wifi_lan_service_info.h", "wifi_lan_upgrade_handler.cc", "wifi_lan_upgrade_handler.h", ], @@ -80,6 +82,18 @@ cc_library( ], ) +cc_test( + name = "base_endpoint_channel_test", + srcs = ["base_endpoint_channel_test.cc"], + deps = [ + ":internal", + "//platform:utils", + "//platform/impl/default", + "//proto:connections_enums_portable_proto", + "//testing/base/public:gunit_main", + ], +) + cc_test( name = "bluetooth_device_name_test", srcs = ["bluetooth_device_name_test.cc"], @@ -100,3 +114,27 @@ cc_test( "//testing/base/public:gunit_main", ], ) + +cc_test( + name = "wifi_lan_service_info_test", + srcs = ["wifi_lan_service_info_test.cc"], + deps = [ + ":internal", + "//platform:utils", + "//platform/port:string", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "offline_frames_test", + srcs = [ + "offline_frames_test.cc", + ], + deps = [ + ":internal", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform:types", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc index e6d288c7..6665a2b5 100644 --- a/cpp/core/internal/base_endpoint_channel.cc +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -49,7 +49,7 @@ ExceptionOr > readExactly(Ptr reader, ScopedPtr > scoped_read_bytes(read_bytes.result()); // In Java, EOFException is a sub-variant of IOException. - if (scoped_read_bytes->size() == 0) { + if (scoped_read_bytes.isNull() || scoped_read_bytes->size() == 0) { return ExceptionOr >(Exception::IO); } @@ -81,6 +81,7 @@ Exception::Value writeInt(Ptr writer, std::int32_t value) { } // namespace +// TODO(b/150763574): Move implementatiopn to header or .inc file. template BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, Ptr reader, diff --git a/cpp/core/internal/base_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc new file mode 100644 index 00000000..abdb5dbe --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -0,0 +1,60 @@ +#include "core/internal/base_endpoint_channel.h" + +#include "platform/impl/default/default_platform.h" +#include "platform/pipe.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +class TestPlatform : public DefaultPlatform { + public: + static SystemClock* createSystemClock() { return nullptr; } + + static Ptr createAtomicBoolean(bool initial_value) { + return Ptr(); + } + + template + static Ptr> createAtomicReference(const T& initial_value) { + return Ptr>(); + } +}; + +class TestEndpointChannel : public BaseEndpointChannel { + public: + explicit TestEndpointChannel(Ptr input_stream) + : BaseEndpointChannel("channel", input_stream, Ptr()) {} + + MOCK_METHOD(proto::connections::Medium, getMedium, (), (override)); + MOCK_METHOD(void, closeImpl, (), (override)); +}; + +using SamplePipe = Pipe; + +TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { + auto pipe = MakeRefCountedPtr(new SamplePipe()); + ScopedPtr> input_stream(SamplePipe::createInputStream(pipe)); + ScopedPtr> output_stream( + SamplePipe::createOutputStream(pipe)); + + TestEndpointChannel test_channel(input_stream.get()); + + // Close the output stream before trying to read from the input. + output_stream->close(); + + // Trying to read should fail gracefully with an IO error. + ExceptionOr> result = test_channel.read(); + + ASSERT_FALSE(result.ok()); + ASSERT_EQ(Exception::IO, result.exception()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index 84e736f6..e885e42c 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -701,7 +701,10 @@ BasePCPHandler::~BasePCPHandler() { // Unregister ourselves from the IncomingOfflineFrameProcessors. endpoint_manager_->unregisterIncomingOfflineFrameProcessor( - V1Frame::CONNECTION_RESPONSE, MakePtr(this)); + V1Frame::CONNECTION_RESPONSE, + std::static_pointer_cast< + typename EndpointManager::IncomingOfflineFrameProcessor>( + self_)); encryption_runner_.destroy(); @@ -747,7 +750,7 @@ Status::Value BasePCPHandler::startAdvertising( ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StartAdvertisingCallable( - MakePtr(this), client_proxy, service_id, local_endpoint_name, + self_, client_proxy, service_id, local_endpoint_name, advertising_options, connection_lifecycle_listener)))); return waitForResult("startAdvertising(" + local_endpoint_name + ")", client_proxy->getClientId(), result.get()); @@ -759,7 +762,7 @@ void BasePCPHandler::stopAdvertising( ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StopAdvertisingRunnable( - MakePtr(this), client_proxy, latch.get()))); + self_, client_proxy, latch.get()))); waitForLatch("stopAdvertising", latch.get()); } @@ -771,7 +774,7 @@ Status::Value BasePCPHandler::startDiscovery( ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StartDiscoveryCallable( - MakePtr(this), client_proxy, service_id, discovery_options, + self_, client_proxy, service_id, discovery_options, discovery_listener)))); return waitForResult("startDiscovery(" + service_id + ")", client_proxy->getClientId(), result.get()); @@ -783,7 +786,7 @@ void BasePCPHandler::stopDiscovery( ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::StopDiscoveryRunnable( - MakePtr(this), client_proxy, latch.get()))); + self_, client_proxy, latch.get()))); waitForLatch("stopDiscovery", latch.get()); } @@ -796,7 +799,7 @@ Status::Value BasePCPHandler::requestConnection( Platform::template createSettableFuture()); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::RequestConnectionRunnable( - MakePtr(this), client_proxy, local_endpoint_name, endpoint_id, + self_, client_proxy, local_endpoint_name, endpoint_id, connection_lifecycle_listener, result.get()))); return waitForResult("requestConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); @@ -809,7 +812,7 @@ Status::Value BasePCPHandler::acceptConnection( ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::AcceptConnectionCallable( - MakePtr(this), client_proxy, endpoint_id, payload_listener)))); + self_, client_proxy, endpoint_id, payload_listener)))); return waitForResult("acceptConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); } @@ -820,7 +823,7 @@ Status::Value BasePCPHandler::rejectConnection( ScopedPtr>> result( runOnPCPHandlerThread( MakePtr(new base_pcp_handler::RejectConnectionCallable( - MakePtr(this), client_proxy, endpoint_id)))); + self_, client_proxy, endpoint_id)))); return waitForResult("rejectConnection(" + endpoint_id + ")", client_proxy->getClientId(), result.get()); } @@ -845,8 +848,7 @@ void BasePCPHandler::processEndpointDisconnection( Ptr process_disconnection_barrier) { runOnPCPHandlerThread(MakePtr( new base_pcp_handler::ProcessEndpointDisconnectionRunnable( - MakePtr(this), client_proxy, endpoint_id, - process_disconnection_barrier))); + self_, client_proxy, endpoint_id, process_disconnection_barrier))); } template @@ -856,7 +858,7 @@ void BasePCPHandler::onEncryptionSuccessImpl( ConstPtr raw_authentication_token) { runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnEncryptionSuccessRunnable( - MakePtr(this), endpoint_id, ukey2_handshake, authentication_token, + self_, endpoint_id, ukey2_handshake, authentication_token, raw_authentication_token))); } @@ -865,7 +867,7 @@ void BasePCPHandler::onEncryptionFailureImpl( const string& endpoint_id, Ptr channel) { runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnEncryptionFailureRunnable( - MakePtr(this), endpoint_id, channel))); + self_, endpoint_id, channel))); } template @@ -1025,8 +1027,8 @@ void BasePCPHandler::onConnectionResponse( ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnPCPHandlerThread( MakePtr(new base_pcp_handler::OnConnectionResponseRunnable( - MakePtr(this), client_proxy, endpoint_id, - connection_response_offline_frame, latch.get()))); + self_, client_proxy, endpoint_id, connection_response_offline_frame, + latch.get()))); waitForLatch("onConnectionResponse()", latch.get()); } @@ -1154,8 +1156,8 @@ Exception::Value BasePCPHandler::onIncomingConnection( // Next, we'll set up encryption. encryption_runner_->startServer( client_proxy, connection_request.endpoint_id(), endpoint_channel, - MakePtr(new typename BasePCPHandler::ResultListenerFacade( - MakePtr(this)))); + MakePtr(new + typename BasePCPHandler::ResultListenerFacade(self_))); return Exception::NONE; } diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h index 0d243165..9019a9b9 100644 --- a/cpp/core/internal/base_pcp_handler.h +++ b/cpp/core/internal/base_pcp_handler.h @@ -496,6 +496,7 @@ class BasePCPHandler // This should have been a ScopedPtr, but we are making this a Ptr to manually // control the order of destruction. Ptr > encryption_runner_; + std::shared_ptr self_{this, [](void*){}}; }; } // namespace connections diff --git a/cpp/core/internal/ble_advertisement_test.cc b/cpp/core/internal/ble_advertisement_test.cc index 953e5262..683aa6b3 100644 --- a/cpp/core/internal/ble_advertisement_test.cc +++ b/cpp/core/internal/ble_advertisement_test.cc @@ -290,9 +290,14 @@ TEST(BLEAdvertisementTest, DeserializationPassesWithLongLength) { endpoint_name, bluetooth_mac_address)); // Add bytes to the end of the valid BLE advertisement. + auto new_array = + new ByteArray(BLEAdvertisement::kMinAdvertisementLength + 1000); + ASSERT_LE(scoped_ble_advertisement_bytes->size(), new_array->size()); + memcpy(new_array->getData(), + scoped_ble_advertisement_bytes->getData(), + scoped_ble_advertisement_bytes->size()); ScopedPtr > long_ble_advertisement_bytes(MakeConstPtr( - new ByteArray(scoped_ble_advertisement_bytes.get()->getData(), - BLEAdvertisement::kMinAdvertisementLength + 1000))); + new_array)); // Deserialize the long BLE advertisement. ScopedPtr > scoped_long_ble_advertisement( @@ -327,9 +332,14 @@ TEST(BLEAdvertisementTest, DeserializationWorksWithLongEndpointName) { corrupt_ble_advertisement_bytes.size()))); // Increase the size of the advertisement so that there's enough data for the // now-longer endpoint name. + auto new_array = + new ByteArray(BLEAdvertisement::kMinAdvertisementLength + 1000); + ASSERT_LE(scoped_ble_advertisement_bytes->size(), new_array->size()); + memcpy(new_array->getData(), + scoped_ble_advertisement_bytes->getData(), + scoped_ble_advertisement_bytes->size()); ScopedPtr > long_ble_advertisement_bytes(MakeConstPtr( - new ByteArray(scoped_corrupt_ble_advertisement_bytes.get()->getData(), - BLEAdvertisement::kMinAdvertisementLength + 1000))); + new_array)); // And deserialize the changed BLE Advertisement. ScopedPtr > scoped_ble_advertisement( diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc index 745c41b1..80222b6a 100644 --- a/cpp/core/internal/endpoint_channel_manager.cc +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -201,10 +201,7 @@ EndpointChannelManager::ChannelState::updateChannelForEndpoint( ScopedPtr > scoped_previous_endpoint_channel( previous_endpoint_channel); - // Upgrade endpoint_channel to be reference-counted before starting to track - // it (and make it clear that endpoint_channel no longer owns the raw - // pointer). - endpoint_metadata->endpoint_channel = MakeRefCountedPtr(&(*endpoint_channel)); + endpoint_metadata->endpoint_channel = endpoint_channel; endpoint_channel.clear(); endpoint_id_to_metadata_[endpoint_id] = endpoint_metadata; diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc index ce63e36a..d4d6c3de 100644 --- a/cpp/core/internal/endpoint_manager.cc +++ b/cpp/core/internal/endpoint_manager.cc @@ -497,7 +497,7 @@ void EndpointManager::registerIncomingOfflineFrameProcessor( processor) { runOnEndpointManagerThread(MakePtr( new endpoint_manager::RegisterIncomingOfflineFrameProcessorRunnable< - Platform>(MakePtr(this), frame_type, processor))); + Platform>(self_, frame_type, processor))); } template @@ -507,7 +507,7 @@ void EndpointManager::unregisterIncomingOfflineFrameProcessor( processor) { runOnEndpointManagerThread(MakePtr( new endpoint_manager::UnregisterIncomingOfflineFrameProcessorRunnable< - Platform>(MakePtr(this), frame_type, processor))); + Platform>(self_, frame_type, processor))); } template @@ -521,7 +521,7 @@ EndpointManager::getOfflineFrameProcessor( ScopedPtr future_result( runOnEndpointManagerThread(MakePtr( new endpoint_manager::GetOfflineFrameProcessorCallable( - MakePtr(this), frame_type)))); + self_, frame_type)))); return waitForResult("getOfflineFrameProcessor", future_result.get()); } @@ -536,7 +536,7 @@ void EndpointManager::registerEndpoint( ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnEndpointManagerThread( MakePtr(new endpoint_manager::RegisterEndpointRunnable( - MakePtr(this), client_proxy, endpoint_id, endpoint_name, + self_, client_proxy, endpoint_id, endpoint_name, authentication_token, raw_authentication_token, is_incoming, endpoint_channel, connection_lifecycle_listener, latch.get()))); waitForLatch("registerEndpoint", latch.get()); @@ -548,7 +548,7 @@ void EndpointManager::unregisterEndpoint( ScopedPtr> latch(Platform::createCountDownLatch(1)); runOnEndpointManagerThread( MakePtr(new endpoint_manager::UnregisterEndpointRunnable( - MakePtr(this), client_proxy, endpoint_id, latch.get()))); + self_, client_proxy, endpoint_id, latch.get()))); waitForLatch("unregisterEndpoint", latch.get()); } @@ -557,7 +557,7 @@ void EndpointManager::discardEndpoint( Ptr> client_proxy, const string& endpoint_id) { runOnEndpointManagerThread( MakePtr(new endpoint_manager::DiscardEndpointRunnable( - MakePtr(this), client_proxy, endpoint_id))); + self_, client_proxy, endpoint_id))); } template diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h index ae176b1d..05263f2c 100644 --- a/cpp/core/internal/endpoint_manager.h +++ b/cpp/core/internal/endpoint_manager.h @@ -2,6 +2,7 @@ #define CORE_INTERNAL_ENDPOINT_MANAGER_H_ #include +#include #include "core/internal/client_proxy.h" #include "core/internal/endpoint_channel.h" @@ -221,6 +222,7 @@ class EndpointManager { ScopedPtr > endpoint_readers_thread_pool_; ScopedPtr > serial_executor_; + std::shared_ptr> self_{this, [](void*){}}; }; } // namespace connections diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index dd3e7c8b..158e01fb 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -10,8 +10,6 @@ namespace nearby { namespace connections { namespace mediums { -namespace { - class SampleSystemClock : public SystemClock { public: SampleSystemClock() {} @@ -30,13 +28,24 @@ class SamplePlatform { } }; -// We keep a copy of these constants because this is an old-school test (so we -// can't delare it as a friend class of AdvertisementReadResult). +constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; + +// Default values may be too big and impractical to wait for in the test. +// For the test platform, we redefine them to some reasonable values. const absl::Duration kAdvertisementBaseBackoffDuration = absl::Milliseconds(1000); // 1 second const absl::Duration kAdvertisementMaxBackoffDuration = absl::Milliseconds(6000); // 6 seconds -const char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; + +template <> +const std::int64_t AdvertisementReadResult< + SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = + ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); +template <> +const std::int64_t + AdvertisementReadResult< + SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = + ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); TEST(AdvertisementReadResultTest, AdvertisementExists) { AdvertisementReadResult advertisement_read_result; @@ -141,7 +150,6 @@ TEST(AdvertisementReadResultTest, GetDurationSinceRead) { ASSERT_GE(advertisement_read_result.getDurationSinceReadMillis(), sleepTime); } -} // namespace } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core/internal/mediums/ble_advertisement_test.cc b/cpp/core/internal/mediums/ble_advertisement_test.cc index 22200fa3..965cd543 100644 --- a/cpp/core/internal/mediums/ble_advertisement_test.cc +++ b/cpp/core/internal/mediums/ble_advertisement_test.cc @@ -1,5 +1,7 @@ #include "core/internal/mediums/ble_advertisement.h" +#include + #include "gtest/gtest.h" namespace location { @@ -224,9 +226,10 @@ TEST(BLEAdvertisementTest, DeserializationWorksWithExtraBytes) { // Copy the bytes into a new array with extra bytes. We must explicitly // define how long our array is because we can't use variable length arrays. - char raw_ble_advertisement_bytes[kLongAdvertisementLength]; + char raw_ble_advertisement_bytes[kLongAdvertisementLength] {}; memcpy(raw_ble_advertisement_bytes, scoped_ble_advertisement_bytes->getData(), - kLongAdvertisementLength); + std::min(sizeof(raw_ble_advertisement_bytes), + scoped_ble_advertisement_bytes->size())); // Re-parse the BLE advertisement using our extra long advertisement bytes. ScopedPtr > scoped_long_ble_advertisement_bytes( diff --git a/cpp/core/internal/mediums/ble_packet.h b/cpp/core/internal/mediums/ble_packet.h index 14b218be..ec7cf0c7 100644 --- a/cpp/core/internal/mediums/ble_packet.h +++ b/cpp/core/internal/mediums/ble_packet.h @@ -41,6 +41,38 @@ class BLEPacket { ScopedPtr > data_; }; +// Represents the format of data sent over BLE sockets. +// +// [SERVICE_ID_HASH][DATA] +// +// See go/nearby-ble-design for more information. +class BlePacket { + public: + static BlePacket FromBytes(const ByteArray& bytes); + + static ByteArray ToBytes(const ByteArray& service_id_hash, + const ByteArray& data); + + static const uint32_t kServiceIdHashLength; + + ~BlePacket(); + + ByteArray GetServiceIdHash() const; + ByteArray GetData() const; + + private: + static size_t ComputeDataSize(const ByteArray& ble_packet_bytes); + static size_t ComputePacketLength(const ByteArray& data); + + static const uint32_t kMinPacketLength; + static const uint32_t kMaxDataSize; + + BlePacket(const ByteArray& service_id_hash, const ByteArray& data); + + ByteArray service_id_hash_; + ByteArray data_; +}; + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core/internal/mediums/ble_peripheral.h b/cpp/core/internal/mediums/ble_peripheral.h index c305171f..0c5acd01 100644 --- a/cpp/core/internal/mediums/ble_peripheral.h +++ b/cpp/core/internal/mediums/ble_peripheral.h @@ -22,6 +22,21 @@ class BLEPeripheral { ScopedPtr> id_; }; + +// Represents BLE peripheral for testing. +class BlePeripheral { + public: + explicit BlePeripheral(const ByteArray& id) : id_(id) {} + ~BlePeripheral() = default; + + const ByteArray& GetId() const { return id_; } + + private: + // A unique identifier for this peripheral. It can be the BLE advertisement it + // was found on, or even simply the BLE MAC address. + const ByteArray id_; +}; + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc index 145a9904..32ba762c 100644 --- a/cpp/core/internal/mediums/ble_v2.cc +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -405,7 +405,7 @@ bool BLEV2::startScanning( fast_advertisement_service_uuid); // Avoid leaks. ScopedPtr> scan_callback_facade( - new ScanCallbackFacade(MakePtr(this))); + new ScanCallbackFacade(self_)); std::set service_uuids; service_uuids.insert(kCopresenceServiceUuid); if (!ble_medium_->startScanning(service_uuids, power_mode, @@ -427,7 +427,7 @@ void BLEV2::onAdvertisementFoundImpl( ConstPtr advertisement_data) { offloadFromPlatformThread( MakePtr(new ble_v2::OnAdvertisementFoundRunnable( - MakePtr(this), ble_peripheral, advertisement_data))); + self_, ble_peripheral, advertisement_data))); } // This method is synchronized because it affects class state, but is called @@ -461,11 +461,6 @@ void BLEV2::stopScanning() { // TODO(b/112199086) Change to RecurringCancelableAlarm template Ptr> BLEV2::createOnLostAlarm() { - // return MakePtr(new CancelableAlarm( - // "BluetoothLowEnergy.startScanning() onLost", - // MakePtr(new - // ble_v2::ProcessOnLostRunnable(MakePtr(this))), - // kOnLostTimeoutMillis, on_lost_executor_.get())); return Ptr>(); } @@ -606,7 +601,7 @@ bool BLEV2::internalStartAdvertisementGattServer( ScopedPtr> connection_lifecycle_callback( - new ServerGATTConnectionLifecycleCallbackFacade(MakePtr(this))); + new ServerGATTConnectionLifecycleCallbackFacade(self_)); ScopedPtr> gatt_server( ble_medium_->startGATTServer(connection_lifecycle_callback.get())); if (gatt_server.isNull()) { @@ -737,7 +732,7 @@ BLEV2::internalReadFromAdvertisementGattServer( ScopedPtr> connection_lifecycle_callback( - new ClientGATTConnectionLifecycleCallbackFacade(MakePtr(this))); + new ClientGATTConnectionLifecycleCallbackFacade(self_)); ScopedPtr> gatt_connection( ble_medium_->connectToGATTServer(peripheral, kDefaultMtu, BLEMediumV2::PowerMode::HIGH, diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h index fc568d10..d8f07bd2 100644 --- a/cpp/core/internal/mediums/ble_v2.h +++ b/cpp/core/internal/mediums/ble_v2.h @@ -300,6 +300,7 @@ class BLEV2 { Ptr advertising_info_; Ptr gatt_server_info_; Ptr accepting_connections_info_; + std::shared_ptr self_{this, [](void*){}}; }; } // namespace mediums diff --git a/cpp/core/internal/mediums/bloom_filter_test.cc b/cpp/core/internal/mediums/bloom_filter_test.cc index fe3fbfe1..00ad384a 100644 --- a/cpp/core/internal/mediums/bloom_filter_test.cc +++ b/cpp/core/internal/mediums/bloom_filter_test.cc @@ -71,8 +71,7 @@ TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) { ScopedPtr> scoped_bloom_filter_bytes( scoped_bloom_filter->asBytes()); std::string empty_string(kByteArrayLength, '\0'); - ASSERT_NE(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(), - empty_string.size())); + ASSERT_NE(scoped_bloom_filter_bytes->asString(), empty_string); } /** diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index 8004286a..232a6c89 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -1,85 +1,71 @@ #include "core/internal/offline_frames.h" -#include "platform/port/down_cast.h" +#include +#include + +#include "platform/byte_array.h" namespace location { namespace nearby { namespace connections { +using ExceptionOrOfflineFrame = ExceptionOr>; + namespace { - -template -T *downcastToRaw(Ptr message) { - return DOWN_CAST(message.operator->()); -} - -// This method takes ownership of the passed-in 'message'. -// -// This can be implemented more efficiently by taking in a reference to an -// OfflineFrame object created on the caller's stack, but we instead create it -// on the heap and return a Ptr to it for the sake of consistency. -ConstPtr newOfflineFrame(V1Frame::FrameType frame_type, - Ptr message) { +std::unique_ptr NewOfflineFrame( + V1Frame::FrameType frame_type, + std::unique_ptr message) { V1Frame *v1_frame = new V1Frame(); v1_frame->set_type(frame_type); switch (frame_type) { case V1Frame::CONNECTION_REQUEST: v1_frame->set_allocated_connection_request( - downcastToRaw(message)); + static_cast(message.release())); break; case V1Frame::CONNECTION_RESPONSE: v1_frame->set_allocated_connection_response( - downcastToRaw(message)); + static_cast(message.release())); break; case V1Frame::PAYLOAD_TRANSFER: v1_frame->set_allocated_payload_transfer( - downcastToRaw(message)); + static_cast(message.release())); break; case V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION: v1_frame->set_allocated_bandwidth_upgrade_negotiation( - downcastToRaw(message)); + static_cast(message.release())); break; case V1Frame::KEEP_ALIVE: v1_frame->set_allocated_keep_alive( - downcastToRaw(message)); + static_cast(message.release())); break; default: break; } - Ptr offline_frame(new OfflineFrame()); + auto offline_frame = std::make_unique(); offline_frame->set_version(OfflineFrame::V1); offline_frame->set_allocated_v1(v1_frame); - return ConstifyPtr(offline_frame); + return offline_frame; } -// This method takes ownership of the passed-in 'offline_frame' and destroys it -// before returning. -ConstPtr toBytes(ConstPtr offline_frame) { - ScopedPtr > scoped_offline_frame(offline_frame); - - size_t serialized_size = offline_frame->ByteSizeLong(); - Ptr bytes{new ByteArray{serialized_size}}; - - offline_frame->SerializeToArray(bytes->getData(), serialized_size); - return ConstifyPtr(bytes); +ConstPtr toBytes(std::unique_ptr offline_frame) { + auto *bytes = new ByteArray{offline_frame->ByteSizeLong()}; + offline_frame->SerializeToArray(bytes->getData(), bytes->size()); + return MakeConstPtr(bytes); } } // namespace -ExceptionOr > OfflineFrames::fromBytes( +ExceptionOrOfflineFrame OfflineFrames::fromBytes( ConstPtr offline_frame_bytes) { - ScopedPtr > offline_frame(new OfflineFrame()); + auto offline_frame = std::make_unique(); - if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(), - offline_frame_bytes->size())) { - return ExceptionOr >( - Exception::INVALID_PROTOCOL_BUFFER); + if (!offline_frame->ParseFromString(offline_frame_bytes->asString())) { + return ExceptionOrOfflineFrame(Exception::INVALID_PROTOCOL_BUFFER); } - return ExceptionOr >( - ConstifyPtr(offline_frame.release())); + return ExceptionOrOfflineFrame(MakeConstPtr(offline_frame.release())); } V1Frame::FrameType OfflineFrames::getFrameType( @@ -96,7 +82,7 @@ ConstPtr OfflineFrames::forConnectionRequest( const std::string &endpoint_id, const std::string &endpoint_name, std::int32_t nonce, const std::vector &mediums) { - Ptr connection_request(new ConnectionRequestFrame()); + auto connection_request = std::make_unique(); connection_request->set_endpoint_id(endpoint_id); connection_request->set_endpoint_name(endpoint_name); connection_request->set_nonce(nonce); @@ -107,113 +93,113 @@ ConstPtr OfflineFrames::forConnectionRequest( connection_request->add_mediums(mediumToConnectionRequestMedium(*it)); } - return toBytes( - newOfflineFrame(V1Frame::CONNECTION_REQUEST, connection_request)); + return toBytes(NewOfflineFrame(V1Frame::CONNECTION_REQUEST, + std::move(connection_request))); } ConstPtr OfflineFrames::forConnectionResponse(std::int32_t status) { - Ptr connection_response( - new ConnectionResponseFrame()); + auto connection_response = std::make_unique(); connection_response->set_status(status); - return toBytes( - newOfflineFrame(V1Frame::CONNECTION_RESPONSE, connection_response)); + return toBytes(NewOfflineFrame(V1Frame::CONNECTION_RESPONSE, + std::move(connection_response))); } ConstPtr OfflineFrames::forDataPayloadTransferFrame( const PayloadTransferFrame::PayloadHeader &header, const PayloadTransferFrame::PayloadChunk &chunk) { - Ptr payload_transfer(new PayloadTransferFrame()); + auto payload_transfer = std::make_unique(); payload_transfer->set_packet_type(PayloadTransferFrame::DATA); *payload_transfer->mutable_payload_header() = header; *payload_transfer->mutable_payload_chunk() = chunk; - return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer)); + return toBytes( + NewOfflineFrame(V1Frame::PAYLOAD_TRANSFER, std::move(payload_transfer))); } ConstPtr OfflineFrames::forControlPayloadTransferFrame( const PayloadTransferFrame::PayloadHeader &header, const PayloadTransferFrame::ControlMessage &control) { - Ptr payload_transfer(new PayloadTransferFrame()); + auto payload_transfer = std::make_unique(); payload_transfer->set_packet_type(PayloadTransferFrame::CONTROL); *payload_transfer->mutable_payload_header() = header; *payload_transfer->mutable_control_message() = control; - return toBytes(newOfflineFrame(V1Frame::PAYLOAD_TRANSFER, payload_transfer)); + return toBytes( + NewOfflineFrame(V1Frame::PAYLOAD_TRANSFER, std::move(payload_transfer))); } ConstPtr OfflineFrames:: forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent( const std::string &ssid, const std::string &password, std::int32_t port) { - BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WifiHotspotCredentials - *wifi_hotspot_credentials = new BandwidthUpgradeNegotiationFrame:: - UpgradePathInfo::WifiHotspotCredentials(); + auto *wifi_hotspot_credentials = new BandwidthUpgradeNegotiationFrame:: + UpgradePathInfo::WifiHotspotCredentials(); wifi_hotspot_credentials->set_ssid(ssid); wifi_hotspot_credentials->set_password(password); wifi_hotspot_credentials->set_port(port); - BandwidthUpgradeNegotiationFrame::UpgradePathInfo *upgrade_path_info = + auto *upgrade_path_info = new BandwidthUpgradeNegotiationFrame::UpgradePathInfo(); upgrade_path_info->set_medium( BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); upgrade_path_info->set_allocated_wifi_hotspot_credentials( wifi_hotspot_credentials); - Ptr bandwidth_upgrade_negotiation( - new BandwidthUpgradeNegotiationFrame()); + auto bandwidth_upgrade_negotiation = + std::make_unique(); bandwidth_upgrade_negotiation->set_event_type( BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); bandwidth_upgrade_negotiation->set_allocated_upgrade_path_info( upgrade_path_info); - return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, - bandwidth_upgrade_negotiation)); + return toBytes(NewOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + std::move(bandwidth_upgrade_negotiation))); } ConstPtr OfflineFrames::forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent() { - Ptr bandwidth_upgrade_negotiation( - new BandwidthUpgradeNegotiationFrame()); + auto bandwidth_upgrade_negotiation = + std::make_unique(); bandwidth_upgrade_negotiation->set_event_type( BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL); - return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, - bandwidth_upgrade_negotiation)); + return toBytes(NewOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + std::move(bandwidth_upgrade_negotiation))); } ConstPtr OfflineFrames::forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent() { - Ptr bandwidth_upgrade_negotiation( - new BandwidthUpgradeNegotiationFrame()); + auto bandwidth_upgrade_negotiation = + std::make_unique(); bandwidth_upgrade_negotiation->set_event_type( BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL); - return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, - bandwidth_upgrade_negotiation)); + return toBytes(NewOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + std::move(bandwidth_upgrade_negotiation))); } ConstPtr OfflineFrames::forClientIntroductionBandwidthUpgradeNegotiationEvent( const std::string &endpoint_id) { - BandwidthUpgradeNegotiationFrame::ClientIntroduction *client_introduction = + auto *client_introduction = new BandwidthUpgradeNegotiationFrame::ClientIntroduction(); client_introduction->set_endpoint_id(endpoint_id); - Ptr bandwidth_upgrade_negotiation( - new BandwidthUpgradeNegotiationFrame()); + auto bandwidth_upgrade_negotiation = + std::make_unique(); bandwidth_upgrade_negotiation->set_event_type( BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION); bandwidth_upgrade_negotiation->set_allocated_client_introduction( client_introduction); - return toBytes(newOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, - bandwidth_upgrade_negotiation)); + return toBytes(NewOfflineFrame(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, + std::move(bandwidth_upgrade_negotiation))); } ConstPtr OfflineFrames::forKeepAlive() { - Ptr keep_alive_frame(new KeepAliveFrame()); - return toBytes(newOfflineFrame(V1Frame::KEEP_ALIVE, keep_alive_frame)); + return toBytes( + NewOfflineFrame(V1Frame::KEEP_ALIVE, std::make_unique())); } ConnectionRequestFrame::Medium OfflineFrames::mediumToConnectionRequestMedium( diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc new file mode 100644 index 00000000..874b3a74 --- /dev/null +++ b/cpp/core/internal/offline_frames_test.cc @@ -0,0 +1,88 @@ +#include "core/internal/offline_frames.h" + +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby::connections { + +namespace { +using Medium = proto::connections::Medium; + +std::unique_ptr MakeFrame(V1Frame* sub_frame) { + auto frame = std::make_unique(); + frame->set_version(OfflineFrame::V1); + frame->set_allocated_v1(sub_frame); + return frame; +} + +void SetSubframe(V1Frame* frame, ConnectionRequestFrame* sub_frame) { + frame->set_type(V1Frame::CONNECTION_REQUEST); + frame->set_allocated_connection_request(sub_frame); +} + +constexpr ConnectionRequestFrame::Medium ToConnectionRequestMedium( + proto::connections::Medium medium) { + switch (medium) { + case proto::connections::MDNS: + return ConnectionRequestFrame::MDNS; + case proto::connections::BLUETOOTH: + return ConnectionRequestFrame::BLUETOOTH; + case proto::connections::WIFI_HOTSPOT: + return ConnectionRequestFrame::WIFI_HOTSPOT; + case proto::connections::BLE: + return ConnectionRequestFrame::BLE; + case proto::connections::WIFI_LAN: + return ConnectionRequestFrame::WIFI_LAN; + default: + return ConnectionRequestFrame::UNKNOWN_MEDIUM; + } +} + +} // namespace + +TEST(OfflineFramesTest, CanParseMessageFromBytes) { + const string endpoint_id{"ABC"}; + const string endpoint_name{"XYZ"}; + const int32 nonce{1234}; + const std::vector mediums{Medium::BLE, + Medium::BLUETOOTH}; + + auto* v1_frame = new V1Frame{}; + auto* sub_frame = new ConnectionRequestFrame{}; + sub_frame->set_endpoint_id(endpoint_id); + sub_frame->set_endpoint_name(endpoint_name); + sub_frame->set_nonce(nonce); + + for (auto& medium : mediums) { + sub_frame->add_mediums(ToConnectionRequestMedium(medium)); + } + + SetSubframe(v1_frame, sub_frame); + auto frame = MakeFrame(v1_frame); + + auto bytes = MakeConstPtr(new ByteArray(frame->SerializeAsString())); + + auto ret_value = OfflineFrames::fromBytes(bytes); + ASSERT_TRUE(ret_value.ok()); + const auto& rx_message = ret_value.result(); + ASSERT_TRUE(rx_message->has_version()); + ASSERT_EQ(rx_message->version(), OfflineFrame::V1); + ASSERT_TRUE(rx_message->has_v1()); + const auto& rx_frame = rx_message->v1(); + ASSERT_EQ(rx_frame.type(), V1Frame::CONNECTION_REQUEST); + ASSERT_TRUE(rx_frame.has_connection_request()); + const auto& req = rx_frame.connection_request(); + ASSERT_TRUE(req.has_endpoint_id()); + ASSERT_TRUE(req.has_endpoint_name()); + ASSERT_TRUE(req.has_nonce()); + ASSERT_EQ(req.endpoint_id(), endpoint_id); + ASSERT_EQ(req.endpoint_name(), endpoint_name); + ASSERT_EQ(req.nonce(), nonce); + ASSERT_EQ(req.mediums_size(), mediums.size()); +} + +} // namespace location::nearby::connections diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index 36443bd0..84881eef 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -134,14 +134,14 @@ P2PClusterPCPHandler::startDiscoveryImpl( proto::connections::Medium bluetooth_medium = startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor( - MakePtr(this), client_proxy, service_id)), + self_, client_proxy, service_id)), client_proxy, service_id); if (proto::connections::UNKNOWN_MEDIUM != bluetooth_medium) { mediums_started_successfully.push_back(bluetooth_medium); } proto::connections::Medium ble_medium = startBleDiscovery( - MakePtr(new FoundBleAdvertisementProcessor(MakePtr(this), client_proxy)), + MakePtr(new FoundBleAdvertisementProcessor(self_, client_proxy)), client_proxy, service_id); if (proto::connections::UNKNOWN_MEDIUM != ble_medium) { mediums_started_successfully.push_back(ble_medium); @@ -312,7 +312,7 @@ void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: onFoundBluetoothDevice(Ptr bluetooth_device) { pcp_handler_->runOnPCPHandlerThread( MakePtr(new OnFoundBluetoothDeviceRunnable(pcp_handler_, client_proxy_, - MakePtr(this), service_id_, + self_, service_id_, bluetooth_device))); } @@ -320,7 +320,7 @@ template void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: onLostBluetoothDevice(Ptr bluetooth_device) { pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBluetoothDeviceRunnable( - pcp_handler_, client_proxy_, MakePtr(this), service_id_, + pcp_handler_, client_proxy_, self_, service_id_, bluetooth_device))); } @@ -450,7 +450,7 @@ void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: const string& service_id, ConstPtr advertisement_bytes) { pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundBlePeripheralRunnable( - pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral, + pcp_handler_, client_proxy_, self_, service_id, ble_peripheral, advertisement_bytes))); } @@ -532,7 +532,7 @@ void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: onLostBlePeripheral(Ptr ble_peripheral, const string& service_id) { pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBlePeripheralRunnable( - pcp_handler_, client_proxy_, MakePtr(this), service_id, ble_peripheral))); + pcp_handler_, client_proxy_, self_, service_id, ble_peripheral))); } template @@ -597,7 +597,7 @@ P2PClusterPCPHandler::startBluetoothAdvertising( if (!medium_manager_->startListeningForIncomingBluetoothConnections( service_id, MakePtr(new IncomingBluetoothConnectionProcessor( - MakePtr(this), client_proxy, local_endpoint_name)))) { + self_, client_proxy, local_endpoint_name)))) { // TODO(tracyzhou): Add logging. return proto::connections::UNKNOWN_MEDIUM; } @@ -654,7 +654,7 @@ proto::connections::Medium P2PClusterPCPHandler::startBleAdvertising( if (!medium_manager_->startListeningForIncomingBleConnections( service_id, MakePtr(new IncomingBleConnectionProcessor( - MakePtr(this), client_proxy, local_endpoint_name)))) { + self_, client_proxy, local_endpoint_name)))) { // TODO(ahlee): logger.atWarning().log("In startBleAdvertising(%s), client // %d failed to start listening for incoming BLE connections to ServiceId // %s", local_endpoint_name, clientProxy.getClientId(), service_id); diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h index 06e09a7e..78d5c757 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -208,6 +208,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { Ptr > client_proxy_; const string service_id_; ScopedPtr > expected_service_id_hash_; + std::shared_ptr self_{this, + [](void*) {}}; }; class FoundBleAdvertisementProcessor @@ -281,6 +283,7 @@ class P2PClusterPCPHandler : public BasePCPHandler { // Maps a BLEPeripheral to its corresponding BLEEndpointState. typedef std::map FoundBLEEndpointsMap; FoundBLEEndpointsMap found_ble_endpoints_; + std::shared_ptr self_{this, [](void*) {}}; }; class BluetoothEndpoint @@ -367,6 +370,7 @@ class P2PClusterPCPHandler : public BasePCPHandler { Ptr > client_proxy, Ptr ble_endpoint); Ptr > medium_manager_; + std::shared_ptr self_{this, [](void*) {}}; }; } // namespace connections diff --git a/cpp/core/internal/payload_manager.cc b/cpp/core/internal/payload_manager.cc index ada9ed74..fd749499 100644 --- a/cpp/core/internal/payload_manager.cc +++ b/cpp/core/internal/payload_manager.cc @@ -581,7 +581,9 @@ PayloadManager::PayloadManager( payload_status_update_executor_(Platform::createSingleThreadExecutor()), endpoint_manager_(endpoint_manager) { endpoint_manager_->registerIncomingOfflineFrameProcessor( - V1Frame::PAYLOAD_TRANSFER, MakePtr(this)); + V1Frame::PAYLOAD_TRANSFER, std::static_pointer_cast< + typename EndpointManager::IncomingOfflineFrameProcessor>( + self_)); } template @@ -591,7 +593,9 @@ PayloadManager::~PayloadManager() { // Unregister ourselves from the IncomingOfflineFrameProcessors. endpoint_manager_->unregisterIncomingOfflineFrameProcessor( - V1Frame::CONNECTION_RESPONSE, MakePtr(this)); + V1Frame::CONNECTION_RESPONSE, std::static_pointer_cast< + typename EndpointManager::IncomingOfflineFrameProcessor>( + self_)); // Stop all the ongoing Runnables (as gracefully as possible). payload_status_update_executor_->shutdown(); @@ -640,7 +644,7 @@ void PayloadManager::sendPayload( enqueueOutgoingPayload( send_payload_executor, MakePtr(new payload_manager::SendPayloadRunnable( - MakePtr(this), client_proxy, endpoint_ids, + self_, client_proxy, endpoint_ids, scoped_payload.release()))); // TODO(tracyzhou): Add logging. } @@ -694,7 +698,7 @@ void PayloadManager::processEndpointDisconnection( Ptr process_disconnection_barrier) { payload_status_update_executor_->execute(MakePtr( new payload_manager::ProcessEndpointDisconnectionRunnable( - MakePtr(this), client_proxy, endpoint_id, + self_, client_proxy, endpoint_id, process_disconnection_barrier))); } @@ -830,7 +834,7 @@ void PayloadManager::sendClientCallbacksForFinishedOutgoingPayload( payload_status_update_executor_->execute(MakePtr( new payload_manager:: SendClientCallbacksForFinishedOutgoingPayloadRunnable( - MakePtr(this), client_proxy, finished_endpoint_ids, + self_, client_proxy, finished_endpoint_ids, payload_header, num_bytes_successfully_transferred, status))); } @@ -842,7 +846,7 @@ void PayloadManager::sendClientCallbacksForFinishedIncomingPayload( payload_status_update_executor_->execute(MakePtr( new payload_manager:: SendClientCallbacksForFinishedIncomingPayloadRunnable( - MakePtr(this), client_proxy, endpoint_id, payload_header, + self_, client_proxy, endpoint_id, payload_header, offset_bytes, status))); } @@ -935,7 +939,7 @@ void PayloadManager::handleSuccessfulOutgoingChunk( std::int64_t payload_chunk_body_size) { payload_status_update_executor_->execute(MakePtr( new payload_manager::HandleSuccessfulOutgoingChunkRunnable( - MakePtr(this), client_proxy, endpoint_id, payload_header, + self_, client_proxy, endpoint_id, payload_header, payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); } @@ -947,7 +951,7 @@ void PayloadManager::handleSuccessfulIncomingChunk( std::int64_t payload_chunk_body_size) { payload_status_update_executor_->execute(MakePtr( new payload_manager::HandleSuccessfulIncomingChunkRunnable( - MakePtr(this), client_proxy, endpoint_id, payload_header, + self_, client_proxy, endpoint_id, payload_header, payload_chunk_flags, payload_chunk_offset, payload_chunk_body_size))); } diff --git a/cpp/core/internal/payload_manager.h b/cpp/core/internal/payload_manager.h index 27949155..4058ec6f 100644 --- a/cpp/core/internal/payload_manager.h +++ b/cpp/core/internal/payload_manager.h @@ -277,6 +277,7 @@ class PayloadManager payload_status_update_executor_; Ptr > endpoint_manager_; + std::shared_ptr self_{this, [](void*){}}; }; } // namespace connections diff --git a/cpp/core/internal/service_controller_router.cc b/cpp/core/internal/service_controller_router.cc index 41428368..aa76330e 100644 --- a/cpp/core/internal/service_controller_router.cc +++ b/cpp/core/internal/service_controller_router.cc @@ -497,7 +497,7 @@ void ServiceControllerRouter::startAdvertising( ConstPtr start_advertising_params) { routeToServiceController( MakePtr(new service_controller_router::StartAdvertisingRunnable( - MakePtr(this), client_proxy, start_advertising_params))); + self_, client_proxy, start_advertising_params))); } template @@ -506,7 +506,7 @@ void ServiceControllerRouter::stopAdvertising( ConstPtr stop_advertising_params) { routeToServiceController( MakePtr(new service_controller_router::StopAdvertisingRunnable( - MakePtr(this), client_proxy, stop_advertising_params))); + self_, client_proxy, stop_advertising_params))); } template @@ -515,7 +515,7 @@ void ServiceControllerRouter::startDiscovery( ConstPtr start_discovery_params) { routeToServiceController( MakePtr(new service_controller_router::StartDiscoveryRunnable( - MakePtr(this), client_proxy, start_discovery_params))); + self_, client_proxy, start_discovery_params))); } template @@ -524,7 +524,7 @@ void ServiceControllerRouter::stopDiscovery( ConstPtr stop_discovery_params) { routeToServiceController( MakePtr(new service_controller_router::StopDiscoveryRunnable( - MakePtr(this), client_proxy, stop_discovery_params))); + self_, client_proxy, stop_discovery_params))); } template @@ -533,7 +533,7 @@ void ServiceControllerRouter::requestConnection( ConstPtr request_connection_params) { routeToServiceController(MakePtr( new service_controller_router::SendConnectionRequestRunnable( - MakePtr(this), client_proxy, request_connection_params))); + self_, client_proxy, request_connection_params))); } template @@ -542,7 +542,7 @@ void ServiceControllerRouter::acceptConnection( ConstPtr accept_connection_params) { routeToServiceController(MakePtr( new service_controller_router::AcceptConnectionRequestRunnable( - MakePtr(this), client_proxy, accept_connection_params))); + self_, client_proxy, accept_connection_params))); } template @@ -551,7 +551,7 @@ void ServiceControllerRouter::rejectConnection( ConstPtr reject_connection_params) { routeToServiceController(MakePtr( new service_controller_router::RejectConnectionRequestRunnable( - MakePtr(this), client_proxy, reject_connection_params))); + self_, client_proxy, reject_connection_params))); } template @@ -561,7 +561,7 @@ void ServiceControllerRouter::initiateBandwidthUpgrade( initiate_bandwidth_upgrade_params) { routeToServiceController(MakePtr( new service_controller_router::InitiateBandwidthUpgradeRunnable( - MakePtr(this), client_proxy, initiate_bandwidth_upgrade_params))); + self_, client_proxy, initiate_bandwidth_upgrade_params))); } template @@ -570,7 +570,7 @@ void ServiceControllerRouter::sendPayload( ConstPtr send_payload_params) { routeToServiceController( MakePtr(new service_controller_router::SendPayloadRunnable( - MakePtr(this), client_proxy, send_payload_params))); + self_, client_proxy, send_payload_params))); } template @@ -579,7 +579,7 @@ void ServiceControllerRouter::cancelPayload( ConstPtr cancel_payload_params) { routeToServiceController( MakePtr(new service_controller_router::CancelPayloadRunnable( - MakePtr(this), client_proxy, cancel_payload_params))); + self_, client_proxy, cancel_payload_params))); } template @@ -588,7 +588,7 @@ void ServiceControllerRouter::disconnectFromEndpoint( ConstPtr disconnect_from_endpoint_params) { routeToServiceController(MakePtr( new service_controller_router::DisconnectFromEndpointRunnable( - MakePtr(this), client_proxy, disconnect_from_endpoint_params))); + self_, client_proxy, disconnect_from_endpoint_params))); } template @@ -597,7 +597,7 @@ void ServiceControllerRouter::stopAllEndpoints( ConstPtr stop_all_endpoint_params) { routeToServiceController( MakePtr(new service_controller_router::StopAllEndpointsRunnable( - MakePtr(this), client_proxy, stop_all_endpoint_params))); + self_, client_proxy, stop_all_endpoint_params))); } template @@ -605,7 +605,7 @@ void ServiceControllerRouter::clientDisconnecting( Ptr> client_proxy) { routeToServiceController(MakePtr( new service_controller_router::ClientDisconnectingRunnable( - MakePtr(this), client_proxy))); + self_, client_proxy))); } template diff --git a/cpp/core/internal/service_controller_router.h b/cpp/core/internal/service_controller_router.h index cf8e7d88..73e2784c 100644 --- a/cpp/core/internal/service_controller_router.h +++ b/cpp/core/internal/service_controller_router.h @@ -140,6 +140,7 @@ class ServiceControllerRouter { Ptr > current_service_controller_; Ptr current_strategy_; ScopedPtr > serializer_; + std::shared_ptr> self_{this, [](void*){}}; }; } // namespace connections diff --git a/cpp/core/internal/wifi_lan_service_info.cc b/cpp/core/internal/wifi_lan_service_info.cc new file mode 100644 index 00000000..7cfb9b3e --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info.cc @@ -0,0 +1,202 @@ +#include "core/internal/wifi_lan_service_info.h" + +#include + +#include "platform/base64_utils.h" + +namespace location { +namespace nearby { +namespace connections { + +Ptr WifiLanServiceInfo::FromString( + absl::string_view wifi_lan_service_info_string) { + ScopedPtr > scoped_wifi_lan_service_info_name_bytes( + Base64Utils::decode(wifi_lan_service_info_string)); + if (scoped_wifi_lan_service_info_name_bytes.isNull()) { + // TODO(b/149806065): logger.atDebug().log("Cannot deserialize + // WifiLanServiceInfo: failed Base64 decoding of %s", + // WifiLanServiceInfoString); + return Ptr(); + } + + if (scoped_wifi_lan_service_info_name_bytes->size() > + kMaxLanServiceNameLength) { + // TODO(b/149806065): logger.atDebug().log("Cannot deserialize + // WifiLanServiceInfo: expecting max %d raw bytes, got %d", + // MAX_WIFILAN_SERVICE_INFO_LENGTH, wifiLanServiceInfoNameBytes.length); + return Ptr(); + } + + if (scoped_wifi_lan_service_info_name_bytes->size() < + kMinLanServiceNameLength) { + // TODO(b/149806065): logger.atDebug().log("Cannot deserialize + // WifiLanServiceInfo: expecting min %d raw bytes, got %d", + // MIN_WIFILAN_SERVICE_INFO_LENGTH, wifiLanServiceInfoNameBytes.length); + return Ptr(); + } + + // The upper 3 bits are supposed to be the version. + Version version = static_cast( + (scoped_wifi_lan_service_info_name_bytes->getData()[0] & + kVersionBitmask) >> + kVersionShift); + + switch (version) { + case Version::kV1: + return CreateV1WifiLanServiceInfo( + ConstifyPtr(scoped_wifi_lan_service_info_name_bytes.get())); + + default: + // TODO(b/149806065): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer ones. + + // TODO(b/149806065): logger.atDebug().log("Cannot deserialize + // WifiLanServiceInfo: unsupported Version %d", version); + return Ptr(); + } +} + +std::string WifiLanServiceInfo::AsString(Version version, PCP::Value pcp, + absl::string_view endpoint_id, + ConstPtr service_id_hash) { + Ptr wifi_lan_service_info_name_bytes; + switch (version) { + case Version::kV1: + wifi_lan_service_info_name_bytes = + CreateV1Bytes(pcp, endpoint_id, service_id_hash); + if (wifi_lan_service_info_name_bytes.isNull()) { + return ""; + } + break; + + default: + // TODO(b/149806065): logger.atDebug().log("Cannot serialize + // WifiLanServiceInfo: unsupported Version %d", version); + return ""; + } + ScopedPtr > scoped_wifi_lan_service_info_name_bytes( + wifi_lan_service_info_name_bytes); + + // WifiLanServiceInfo needs to be binary safe, so apply a Base64 encoding + // over the raw bytes. + return Base64Utils::encode( + ConstifyPtr(scoped_wifi_lan_service_info_name_bytes.get())); +} + +Ptr WifiLanServiceInfo::CreateV1WifiLanServiceInfo( + ConstPtr wifi_lan_service_info_name_bytes) { + const char* wifi_lan_service_info_name_bytes_read_ptr = + wifi_lan_service_info_name_bytes->getData(); + + // The lower 5 bits of the V1 payload are supposed to be the PCP. + PCP::Value pcp = static_cast( + *wifi_lan_service_info_name_bytes_read_ptr & kPcpBitmask); + wifi_lan_service_info_name_bytes_read_ptr++; + + switch (pcp) { + case PCP::P2P_CLUSTER: // Fall through + case PCP::P2P_STAR: // Fall through + case PCP::P2P_POINT_TO_POINT: { + // The next 32 bits are supposed to be the endpoint_id. + std::string endpoint_id(wifi_lan_service_info_name_bytes_read_ptr, + kEndpointIdLength); + wifi_lan_service_info_name_bytes_read_ptr += kEndpointIdLength; + + // The next 24 bits are supposed to be the scoped_service_id_hash. + ScopedPtr > scoped_service_id_hash( + MakeConstPtr(new ByteArray(wifi_lan_service_info_name_bytes_read_ptr, + kServiceIdHashLength))); + wifi_lan_service_info_name_bytes_read_ptr += kServiceIdHashLength; + + // The next bits are supposed to be endpoint_name. + // TODO(b/149806065): Implements it. Temp to set "found_device". + std::string endpoint_name("found_device"); + + return MakePtr(new WifiLanServiceInfo(Version::kV1, pcp, endpoint_id, + scoped_service_id_hash.release(), + endpoint_name)); + } + default: + // TODO(b/149806065): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer ones. + + // TODO(b/149806065): logger.atDebug().log("Cannot deserialize + // WifiLanServiceInfo: unsupported V1 PCP %d", pcp); + return Ptr(); + } +} + +std::uint32_t WifiLanServiceInfo::ComputeEndpointNameLength( + ConstPtr wifi_lan_service_info_name_bytes) { + return kMaxEndpointNameLength - + (kMaxLanServiceNameLength - wifi_lan_service_info_name_bytes->size()); +} + +Ptr WifiLanServiceInfo::CreateV1Bytes( + PCP::Value pcp, absl::string_view endpoint_id, + ConstPtr service_id_hash) { + Ptr wifi_lan_service_info_name_bytes{ + new ByteArray{kMinLanServiceNameLength}}; + + char* wifi_lan_service_info_name_bytes_write_ptr = + wifi_lan_service_info_name_bytes->getData(); + + // The upper 3 bits are the Version. + char 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(pcp & kPcpBitmask); + *wifi_lan_service_info_name_bytes_write_ptr = version_and_pcp_byte; + wifi_lan_service_info_name_bytes_write_ptr++; + + switch (pcp) { + case PCP::P2P_CLUSTER: // Fall through + case PCP::P2P_STAR: // Fall through + case PCP::P2P_POINT_TO_POINT: + // The next 32 bits are the endpoint_id. + if (endpoint_id.size() != kEndpointIdLength) { + // TODO(b/149806065): logger.atDebug().log("Cannot serialize + // WifiLanServiceInfo: V1 Endpoint ID %s (%d bytes) should be exactly + // %d bytes", endpointId, endpointId.length(), ENDPOINT_ID_LENGTH); + return Ptr(); + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, endpoint_id.data(), + kEndpointIdLength); + wifi_lan_service_info_name_bytes_write_ptr += kEndpointIdLength; + + // The next 24 bits are the service_id_hash. + if (service_id_hash->size() != kServiceIdHashLength) { + // TODO(b/149806065): logger.atDebug().log("Cannot serialize + // WifiLanServiceInfo: V1 ServiceID hash (%d bytes) should be exactly + // %d bytes", serviceIdHash.length, SERVICE_ID_HASH_LENGTH); + return Ptr(); + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, + service_id_hash->getData(), kServiceIdHashLength); + wifi_lan_service_info_name_bytes_write_ptr += kServiceIdHashLength; + + // The next bits are the endpoint_name. + // TODO(b/149806065): Implements to parse endpoint_name. + break; + default: + // TODO(b/149806065): logger.atDebug().log("Cannot serialize + // WifiLanServiceInfo: unsupported V1 PCP %d", pcp); + return Ptr(); + } + + return wifi_lan_service_info_name_bytes; +} + +WifiLanServiceInfo::WifiLanServiceInfo(Version version, PCP::Value pcp, + absl::string_view endpoint_id, + ConstPtr service_id_hash, + absl::string_view endpoint_name) + : version_(version), + pcp_(pcp), + endpoint_id_(endpoint_id), + service_id_hash_(service_id_hash), + endpoint_name_(endpoint_name) {} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/wifi_lan_service_info.h b/cpp/core/internal/wifi_lan_service_info.h new file mode 100644 index 00000000..f1114e5f --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info.h @@ -0,0 +1,96 @@ +#ifndef CORE_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ +#define CORE_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ + +#include + +#include "core/internal/pcp.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the WifiLan service info used in Advertising + +// Discovery. +// +// See go/nearby-offline-data-interchange-formats for the specification. +class WifiLanServiceInfo { + public: + // Versions of the WifiLanServiceInfo. + enum class Version { + kV1 = 1, + }; + + // Static method to deserialize from the encrypted string to + // WifiLanServiceInfo object. + // TODO(b/149762166): Ptr is deprectaed. Uses shrared_ptr or unique_ptr. + static Ptr FromString( + absl::string_view wifi_lan_service_info_string); + + // Static method to serialize to encrypted string from WifiLanServiceInfo + // object. + static std::string AsString(Version version, PCP::Value pcp, + absl::string_view endpoint_id, + ConstPtr service_id_hash); + + static constexpr std::uint32_t kServiceIdHashLength = 3; + + ~WifiLanServiceInfo() = default; + + inline Version GetVersion() const { return version_; } + inline PCP::Value GetPcp() const { return pcp_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline ConstPtr GetServiceIdHash() const { + return service_id_hash_.get(); + } + inline std::string GetEndpointName() const { return endpoint_name_; } + + private: + static Ptr CreateV1WifiLanServiceInfo( + ConstPtr wifi_lan_service_info_name_bytes); + static std::uint32_t ComputeEndpointNameLength( + ConstPtr wifi_lan_service_info_name_bytes); + static Ptr CreateV1Bytes(PCP::Value pcp, + absl::string_view endpoint_id, + ConstPtr service_id_hash); + + // The maximum length of encrypted WifiLanServiceInfo string. + static constexpr int kMaxLanServiceNameLength = 47; + // The minimum length of encrypted WifiLanServiceInfo string. + static constexpr int kMinLanServiceNameLength = 9; + // The length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kEndpointIdLength = 4; + // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kMaxEndpointNameLength = 131; + + static constexpr uint16 kVersionBitmask = 0x0E0; + static constexpr uint16 kPcpBitmask = 0x01F; + static constexpr uint16 kVersionShift = 5; + + WifiLanServiceInfo(Version version, PCP::Value pcp, + absl::string_view endpoint_id, + ConstPtr service_id_hash, + absl::string_view endpoint_name); + + // WifiLanServiceInfo version. + const Version version_; + // Pre-Connection Protocols version. + const PCP::Value pcp_; + // Connected endpoint id. + const std::string endpoint_id_; + // Connected hash service id. + ScopedPtr > service_id_hash_; + // TODO(b/149806065): Replaces endpointName as endPointInfo eventually; + // it is not in this version yet for endpointName. + // Connected endpoint name. + const std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ diff --git a/cpp/core/internal/wifi_lan_service_info_test.cc b/cpp/core/internal/wifi_lan_service_info_test.cc new file mode 100644 index 00000000..7b7c5ced --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info_test.cc @@ -0,0 +1,151 @@ +#include "core/internal/wifi_lan_service_info.h" + +#include + +#include "platform/base64_utils.h" +#include "platform/port/string.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; +const PCP::Value kPcp = PCP::P2P_CLUSTER; +const char kEndPointID[] = "AB12"; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +// TODO(b/149806065): Implements test endpoint_name. + +TEST(WifiLanServiceInfoTest, SerializationDeserializationWorks) { + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = WifiLanServiceInfo::AsString( + kVersion, kPcp, kEndPointID, ConstifyPtr(scoped_service_id_hash.get())); + ScopedPtr > scoped_wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_info_string)); + + EXPECT_EQ(kPcp, scoped_wifi_lan_service_info->GetPcp()); + EXPECT_EQ(kVersion, scoped_wifi_lan_service_info->GetVersion()); + EXPECT_EQ(kEndPointID, scoped_wifi_lan_service_info->GetEndpointId()); + EXPECT_EQ(*scoped_service_id_hash, + *(scoped_wifi_lan_service_info->GetServiceIdHash())); +} + +TEST(WifiLanServiceInfoTest, + SerializationDeserializationWorksWithEmptyEndpointName) { + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = WifiLanServiceInfo::AsString( + kVersion, kPcp, kEndPointID, ConstifyPtr(scoped_service_id_hash.get())); + ScopedPtr > scoped_wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_info_string)); + + EXPECT_EQ(kPcp, scoped_wifi_lan_service_info->GetPcp()); + EXPECT_EQ(kVersion, scoped_wifi_lan_service_info->GetVersion()); + EXPECT_EQ(kEndPointID, scoped_wifi_lan_service_info->GetEndpointId()); + EXPECT_EQ(*scoped_service_id_hash, + *(scoped_wifi_lan_service_info->GetServiceIdHash())); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithBadVersion) { + WifiLanServiceInfo::Version bad_version = + static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = + WifiLanServiceInfo::AsString(bad_version, kPcp, kEndPointID, + ConstifyPtr(scoped_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithBadPCP) { + PCP::Value bad_pcp = static_cast(666); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = + WifiLanServiceInfo::AsString(kVersion, bad_pcp, kEndPointID, + ConstifyPtr(scoped_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = + WifiLanServiceInfo::AsString(kVersion, kPcp, short_endpoint_id, + ConstifyPtr(scoped_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + ScopedPtr > scoped_service_id_hash(new ByteArray( + kServiceIDHashBytes, sizeof(kServiceIDHashBytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = + WifiLanServiceInfo::AsString(kVersion, kPcp, long_endpoint_id, + ConstifyPtr(scoped_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + ScopedPtr > scoped_short_service_id_hash( + new ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = WifiLanServiceInfo::AsString( + kVersion, kPcp, kEndPointID, + ConstifyPtr(scoped_short_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, SerializationFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + ScopedPtr > scoped_long_service_id_hash( + new ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char))); + + std::string wifi_lan_service_info_string = WifiLanServiceInfo::AsString( + kVersion, kPcp, kEndPointID, + ConstifyPtr(scoped_long_service_id_hash.get())); + + EXPECT_TRUE(wifi_lan_service_info_string.empty()); +} + +TEST(WifiLanServiceInfoTest, DeserializationFailsWithShortLength) { + char wifi_lan_service_info_bytes[] = {'X'}; + + ScopedPtr > scoped_wifi_lan_service_info_bytes( + new ByteArray(wifi_lan_service_info_bytes, + sizeof(wifi_lan_service_info_bytes) / sizeof(char))); + + ScopedPtr > scoped_wifi_lan_service_info( + WifiLanServiceInfo::FromString(Base64Utils::encode( + ConstifyPtr(scoped_wifi_lan_service_info_bytes.get())))); + + EXPECT_TRUE(scoped_wifi_lan_service_info.isNull()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index e7482ef0..72d19bc6 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -26,7 +26,6 @@ cc_library( ":types", "//platform/api", "//platform/port:string", - "//strings", "//absl/strings", "//absl/time", ], @@ -34,15 +33,11 @@ cc_library( cc_library( name = "types", - srcs = [ - "ptr.cc", - ], hdrs = [ "byte_array.h", "callable.h", "cancelable.h", "container_of.h", - "exception.cc", "exception.h", "ptr.h", "runnable.h", @@ -113,6 +108,15 @@ cc_test( ], ) +cc_test( + name = "exception_test", + srcs = ["exception_test.cc"], + deps = [ + ":types", + "//testing/base/public:gunit_main", + ], +) + cc_test( name = "pipe_test", timeout = "short", diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index 1b474e13..f1c769b7 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -20,23 +20,28 @@ cc_library( "hash_utils.h", "input_file.h", "input_stream.h", + "listenable_future.h", "lock.h", "multi_thread_executor.h", "output_file.h", "output_stream.h", "scheduled_executor.h", + "server_sync.h", "settable_future.h", "single_thread_executor.h", "socket.h", "submittable_executor.h", "system_clock.h", "thread_utils.h", + "webrtc.h", "wifi.h", + "wifi_lan.h", ], deps = [ "//platform:types", "//platform/port:down_cast", "//platform/port:string", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h index 93607126..06a88288 100644 --- a/cpp/platform/api/ble_v2.h +++ b/cpp/platform/api/ble_v2.h @@ -39,7 +39,7 @@ struct BLEAdvertisementData { std::set service_uuids; // Maps service UUIDs to their service data. // Ownership of the map values is tied to ownership of BLEAdvertisementData. - std::map > service_data; + std::map> service_data; }; // Opaque wrapper over a BLE peripheral. Must be able to uniquely identify a @@ -217,7 +217,8 @@ class GATTServer { // about this descriptor, please go to: // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml virtual Ptr createCharacteristic( - const std::string& service_uuid, const std::string& characteristic_uuid, + const std::string& service_uuid, + const std::string& characteristic_uuid, const std::set& permissions, const std::set& properties) = 0; @@ -384,7 +385,9 @@ class BLEMediumV2 { // HIGH: // - Connection interval = ~100ms - 125ms virtual Ptr connectToGATTServer( - Ptr peripheral, MTU mtu, PowerMode::Value power_mode, + Ptr peripheral, + MTU mtu, + PowerMode::Value power_mode, Ptr connection_lifecycle_callback) = 0; diff --git a/cpp/platform/api/bluetooth_classic.h b/cpp/platform/api/bluetooth_classic.h index 0ba04b11..154c7f0f 100644 --- a/cpp/platform/api/bluetooth_classic.h +++ b/cpp/platform/api/bluetooth_classic.h @@ -60,7 +60,7 @@ class BluetoothServerSocket { // // The returned Ptr will be owned (and destroyed) by the caller. Returns // Exception::IO on error. - virtual ExceptionOr > accept() = 0; + virtual ExceptionOr> accept() = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() // @@ -116,8 +116,9 @@ class BluetoothClassicMedium { // // The returned Ptr will be owned (and destroyed) by the caller. Returns // Exception::IO on error. - virtual ExceptionOr > connectToService( - Ptr remote_device, const std::string& service_uuid) = 0; + virtual ExceptionOr> connectToService( + Ptr remote_device, + const std::string& service_uuid) = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord // @@ -129,8 +130,9 @@ class BluetoothClassicMedium { // // The returned Ptr will be owned (and destroyed) by the caller. Returns // Exception::IO on error. - virtual ExceptionOr > listenForService( - const std::string& service_name, const std::string& service_uuid) = 0; + virtual ExceptionOr> listenForService( + const std::string& service_name, + const std::string& service_uuid) = 0; }; } // namespace nearby diff --git a/cpp/platform/api/executor.h b/cpp/platform/api/executor.h index 2c425d15..2755af36 100644 --- a/cpp/platform/api/executor.h +++ b/cpp/platform/api/executor.h @@ -1,6 +1,9 @@ #ifndef PLATFORM_API_EXECUTOR_H_ #define PLATFORM_API_EXECUTOR_H_ +#include "platform/ptr.h" +#include "platform/runnable.h" + namespace location { namespace nearby { @@ -12,6 +15,9 @@ class Executor { // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- virtual void shutdown() = 0; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- + virtual void execute(Ptr runnable) = 0; }; } // namespace nearby diff --git a/cpp/platform/api/future.h b/cpp/platform/api/future.h index 34f0bbed..166a4ed9 100644 --- a/cpp/platform/api/future.h +++ b/cpp/platform/api/future.h @@ -1,6 +1,8 @@ #ifndef PLATFORM_API_FUTURE_H_ #define PLATFORM_API_FUTURE_H_ +#include + #include "platform/exception.h" namespace location { @@ -16,6 +18,11 @@ class Future { virtual ExceptionOr get() = 0; // throws Exception::INTERRUPTED, Exception::EXECUTION + + // throws Exception::INTERRUPTED, Exception::EXECUTION + // throws Exception::TIMEOUT if |timeout_ms| is exceeded while waiting for + // result. + virtual ExceptionOr get(std::int64_t timeout_ms) = 0; }; } // namespace nearby diff --git a/cpp/platform/api/input_file.h b/cpp/platform/api/input_file.h index 28615919..ed2c782a 100644 --- a/cpp/platform/api/input_file.h +++ b/cpp/platform/api/input_file.h @@ -16,7 +16,7 @@ class InputFile { // The returned ConstPtr will be owned (and destroyed) by the caller. // When we have exhausted reading the file and no bytes remain, read will // always return an empty ConstPtr for which isNull() is true. - virtual ExceptionOr > read( + virtual ExceptionOr> read( std::int64_t size) = 0; // throws Exception::IO when the file cannot be // opened or read. virtual std::string getFilePath() const = 0; diff --git a/cpp/platform/api/input_stream.h b/cpp/platform/api/input_stream.h index 08bc4a50..02eb6502 100644 --- a/cpp/platform/api/input_stream.h +++ b/cpp/platform/api/input_stream.h @@ -18,9 +18,9 @@ class InputStream { virtual ~InputStream() {} // The returned ConstPtr will be owned (and destroyed) by the caller. - virtual ExceptionOr > read() = 0; // throws Exception::IO + virtual ExceptionOr> read() = 0; // throws Exception::IO // The returned ConstPtr will be owned (and destroyed) by the caller. - virtual ExceptionOr > read( + virtual ExceptionOr> read( std::int64_t size) = 0; // throws Exception::IO virtual Exception::Value close() = 0; // throws Exception::IO }; diff --git a/cpp/platform/api/listenable_future.h b/cpp/platform/api/listenable_future.h new file mode 100644 index 00000000..3cd306e7 --- /dev/null +++ b/cpp/platform/api/listenable_future.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_API_LISTENABLE_FUTURE_H_ +#define PLATFORM_API_LISTENABLE_FUTURE_H_ + +#include "platform/api/executor.h" +#include "platform/api/future.h" +#include "platform/exception.h" +#include "platform/ptr.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +// A Future that accepts completion listeners. +// +// https://guava.dev/releases/20.0/api/docs/com/google/common/util/concurrent/ListenableFuture.html +template +class ListenableFuture : public Future { + public: + ~ListenableFuture() override {} + + // Executor is shared among multiple runnables. It is not owned by any future. + virtual void addListener(Ptr runnable, Executor* executor) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h index 52a261b5..3770fda4 100644 --- a/cpp/platform/api/multi_thread_executor.h +++ b/cpp/platform/api/multi_thread_executor.h @@ -11,8 +11,8 @@ namespace nearby { // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- template -class MultiThreadExecutor : - public SubmittableExecutor { +class MultiThreadExecutor + : public SubmittableExecutor { public: ~MultiThreadExecutor() override {} }; diff --git a/cpp/platform/api/server_sync.h b/cpp/platform/api/server_sync.h new file mode 100644 index 00000000..e6b01aa9 --- /dev/null +++ b/cpp/platform/api/server_sync.h @@ -0,0 +1,64 @@ +#ifndef PLATFORM_API_SERVER_SYNC_H_ +#define PLATFORM_API_SERVER_SYNC_H_ + +#include + +#include "platform/byte_array.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Abstraction that represents a Nearby endpoint exchanging data through +// ServerSync Medium. +class ServerSyncDevice { + public: + virtual ~ServerSyncDevice() {} + + virtual std::string getName() = 0; + + virtual std::string getGuid() = 0; + + virtual std::string getOwnGuid() = 0; +}; + +// Container of operations that can be performed over the Chrome Sync medium. +class ServerSyncMedium { + public: + virtual ~ServerSyncMedium() {} + + // Takes ownership of (and is responsible for destroying) the passed-in + // 'endpoint_info'. + virtual bool startAdvertising(const std::string& service_id, + const std::string& endpoint_id, + ConstPtr endpoint_info) = 0; + virtual void stopAdvertising(const std::string& service_id) = 0; + + class DiscoveredDeviceCallback { + public: + virtual ~DiscoveredDeviceCallback() {} + + // Called on a new ServerSyncDevice discovery. + virtual void onDeviceDiscovered(Ptr device, + const std::string& service_id, + const std::string& endpoint_id, + ConstPtr endpoint_info) = 0; + // Called when ServerSyncDevice is no longer reachable. + virtual void onDeviceLost(Ptr device, + const std::string& service_id) = 0; + }; + + // Returns true once the Chrome Sync scan has been initiated. + virtual bool startDiscovery( + const std::string& service_id, + Ptr discovered_device_callback) = 0; + // Returns true once Chrome Sync scan for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredDeviceCallback passed in to startScanning() for service_id. + virtual void stopDiscovery(const std::string& service_id) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SERVER_SYNC_H_ diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h index 253a5005..f9a5e35c 100644 --- a/cpp/platform/api/settable_future.h +++ b/cpp/platform/api/settable_future.h @@ -1,7 +1,7 @@ #ifndef PLATFORM_API_SETTABLE_FUTURE_H_ #define PLATFORM_API_SETTABLE_FUTURE_H_ -#include "platform/api/future.h" +#include "platform/api/listenable_future.h" namespace location { namespace nearby { @@ -10,11 +10,13 @@ namespace nearby { // // https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html template -class SettableFuture : public Future { +class SettableFuture : public ListenableFuture { public: ~SettableFuture() override {} virtual bool set(T value) = 0; + + virtual bool setException(Exception exception) = 0; }; } // namespace nearby diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h index 7e2f9c6d..e3338648 100644 --- a/cpp/platform/api/single_thread_executor.h +++ b/cpp/platform/api/single_thread_executor.h @@ -11,8 +11,8 @@ namespace nearby { // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- template -class SingleThreadExecutor : - public SubmittableExecutor { +class SingleThreadExecutor + : public SubmittableExecutor { public: ~SingleThreadExecutor() override {} }; diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h index 4775d8ee..3d7bd625 100644 --- a/cpp/platform/api/submittable_executor.h +++ b/cpp/platform/api/submittable_executor.h @@ -6,7 +6,6 @@ #include "platform/callable.h" #include "platform/port/down_cast.h" #include "platform/ptr.h" -#include "platform/runnable.h" namespace location { namespace nearby { @@ -29,12 +28,9 @@ class SubmittableExecutor : public Executor { ~SubmittableExecutor() override {} template - Ptr > submit(Ptr > callable) { + Ptr> submit(Ptr> callable) { return DOWN_CAST(this)->submit(callable); } - - // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- - virtual void execute(Ptr runnable) = 0; }; } // namespace nearby diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h new file mode 100644 index 00000000..35f53e60 --- /dev/null +++ b/cpp/platform/api/webrtc.h @@ -0,0 +1,46 @@ +#ifndef PLATFORM_API_WEBRTC_H_ +#define PLATFORM_API_WEBRTC_H_ + +#include + +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { + +class WebRtcSignalingMessenger { + public: + virtual ~WebRtcSignalingMessenger() = default; + + /** Called whenever we receive an inbox message from tachyon. */ + class SignalingMessageListener { + public: + virtual ~SignalingMessageListener() = default; + + virtual void onSignalingMessage(ConstPtr message) = 0; + }; + + class IceServersListener { + public: + virtual ~IceServersListener() = default; + + virtual void OnIceServersFetched( + std::vector> + ice_servers) = 0; + }; + + virtual bool registerSignaling() = 0; + virtual bool unregisterSignaling() = 0; + virtual bool sendMessage(const string& peer_id, + ConstPtr message) = 0; + virtual bool startReceivingMessages( + Ptr listener) = 0; + virtual void getIceServers(Ptr ice_servers_listener) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_WEBRTC_H_ diff --git a/cpp/platform/api/wifi.h b/cpp/platform/api/wifi.h index 0cb2566a..6631d036 100644 --- a/cpp/platform/api/wifi.h +++ b/cpp/platform/api/wifi.h @@ -58,7 +58,7 @@ class WifiMedium { // owned (and destroyed) by the recipient of the callback methods (i.e. the // creator of the concrete ScanResultCallback object). virtual void onScanResults( - const std::vector >& scan_results) = 0; + const std::vector>& scan_results) = 0; }; // Does not take ownership of the passed-in scan_result_callback -- destroying @@ -69,7 +69,8 @@ class WifiMedium { // WifiConnectionStatus::CONNECTED on success, or the appropriate failure code // otherwise. virtual WifiConnectionStatus::Value connectToNetwork( - const std::string& ssid, const std::string& password, + const std::string& ssid, + const std::string& password, WifiAuthType::Value auth_type) = 0; // Blocks until it's certain of there being a connection to the internet, or diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h new file mode 100644 index 00000000..1b13b393 --- /dev/null +++ b/cpp/platform/api/wifi_lan.h @@ -0,0 +1,94 @@ +#ifndef PLATFORM_API_WIFI_LAN_H_ +#define PLATFORM_API_WIFI_LAN_H_ + +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Opaque wrapper over a WifiLan service which contains encoded service name. +class WifiLanService { + public: + virtual ~WifiLanService() = default; + + virtual std::string GetName() = 0; +}; + +class WifiLanSocket { + public: + virtual ~WifiLanSocket() = default; + + // Returns the InputStream of the WifiLanSocket, or a null Ptr + // on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + virtual Ptr GetInputStream() = 0; + + // Returns the OutputStream of the WifiLanSocket, or a null + // Ptr on error. + // + // The returned Ptr is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + virtual Ptr GetOutputStream() = 0; + + // Returns Exception::IO on error, Exception::NONE otherwise. + virtual Exception::Value Close() = 0; + + // The returned Ptr is not owned by the caller, and can be invalidated once + // the WifiLanSocket object is destroyed. + virtual Ptr GetRemoteWifiLanService() = 0; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium { + public: + virtual ~WifiLanMedium() = default; + + virtual bool StartAdvertising(const std::string& service_id, + const string& wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(const std::string& service_id) = 0; + + // Callback for WifiLan discover results. + class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(Ptr wifi_lan_service) = 0; + virtual void OnServiceLost(Ptr wifi_lan_service) = 0; + }; + + virtual bool StartDiscovery( + const std::string& service_id, + Ptr discovered_service_callback) = 0; + virtual void StopDiscovery(const std::string& service_id) = 0; + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + // The Ptr provided in this callback method will be owned (and + // destroyed) by the recipient of the callback methods (i.e. the creator of + // the concrete AcceptedConnectionCallback object). + virtual void OnConnectionAccepted(Ptr socket, + const string& service_id) = 0; + }; + + virtual bool StartAcceptingConnections( + const std::string& service_id, + Ptr accepted_connection_callback) = 0; + virtual void StopAcceptingConnections(const std::string& service_id) = 0; + + virtual Ptr Connect(Ptr wifi_lan_service, + const std::string& service_id) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_WIFI_LAN_H_ diff --git a/cpp/platform/api2/BUILD b/cpp/platform/api2/BUILD new file mode 100644 index 00000000..5313b366 --- /dev/null +++ b/cpp/platform/api2/BUILD @@ -0,0 +1,65 @@ +package(default_visibility = [ + "//core:__subpackages__", + "//platform:__subpackages__", + "//location/nearby/setup/core:__subpackages__", +]) + +cc_library( + name = "api2", + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "ble.h", + "ble_v2.h", + "bluetooth_adapter.h", + "bluetooth_classic.h", + "condition_variable.h", + "count_down_latch.h", + "executor.h", + "future.h", + "hash_utils.h", + "input_file.h", + "input_stream.h", + "listenable_future.h", + "multi_thread_executor.h", + "mutex.h", + "output_file.h", + "output_stream.h", + "scheduled_executor.h", + "server_sync.h", + "settable_future.h", + "single_thread_executor.h", + "socket.h", + "submittable_executor.h", + "system_clock.h", + "thread_utils.h", + "webrtc.h", + "wifi.h", + ], + deps = [ + "//platform:types", + "//absl/strings", + "//absl/time", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_library( + name = "mutex", + hdrs = ["mutex.h"], + visibility = [ + "//platform:__subpackages__", + ], +) + +cc_library( + name = "condition_variable", + hdrs = ["condition_variable.h"], + visibility = [ + "//platform:__subpackages__", + ], + deps = [ + "//platform:types", + "//absl/time", + ], +) diff --git a/cpp/platform/api2/atomic_boolean.h b/cpp/platform/api2/atomic_boolean.h new file mode 100644 index 00000000..b5e729fa --- /dev/null +++ b/cpp/platform/api2/atomic_boolean.h @@ -0,0 +1,21 @@ +#ifndef PLATFORM_API2_ATOMIC_BOOLEAN_H_ +#define PLATFORM_API2_ATOMIC_BOOLEAN_H_ + +namespace location { +namespace nearby { + +// A boolean value that may be updated atomically. +// +// https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html +class AtomicBoolean { + public: + virtual ~AtomicBoolean() {} + + virtual bool Get() = 0; + virtual void Set(bool value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/atomic_reference.h b/cpp/platform/api2/atomic_reference.h new file mode 100644 index 00000000..8740be0d --- /dev/null +++ b/cpp/platform/api2/atomic_reference.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API2_ATOMIC_REFERENCE_H_ +#define PLATFORM_API2_ATOMIC_REFERENCE_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 { + public: + virtual ~AtomicReference() {} + + virtual T Get() = 0; + virtual void Set(const T& value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api2/ble.h b/cpp/platform/api2/ble.h new file mode 100644 index 00000000..337f0717 --- /dev/null +++ b/cpp/platform/api2/ble.h @@ -0,0 +1,111 @@ +#ifndef PLATFORM_API2_BLE_H_ +#define PLATFORM_API2_BLE_H_ + +#include "platform/api2/bluetooth_classic.h" +#include "platform/api2/input_stream.h" +#include "platform/api2/output_stream.h" +#include "platform/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// Opaque wrapper over a BLE peripheral. Must contain enough data about a +// particular BLE device to connect to its GATT server. +class BlePeripheral { + public: + virtual ~BlePeripheral() {} + + // The returned Ptr is not owned by the caller, and can be invalidated once + // the corresponding BLEPeripheral object is destroyed. + virtual BluetoothDevice& GetBluetoothDevice() = 0; +}; + +class BleSocket { + public: + virtual ~BleSocket() {} + + // Returns the InputStream of the BleSocket. + // 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 BleSocket object is destroyed. + virtual InputStream& GetInputStream() = 0; + + // Returns the OutputStream of the BleSocket. + // 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 BleSocket object is destroyed. + virtual OutputStream& GetOutputStream() = 0; + + // Conforms to the same contract as + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close(). + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; + + // The returned object is not owned by the caller, and can be invalidated once + // the BleSocket object is destroyed. + virtual BlePeripheral& GetRemotePeripheral() = 0; +}; + +// Container of operations that can be performed over the BLE medium. +class BleMedium { + public: + virtual ~BleMedium() {} + + virtual bool StartAdvertising(absl::string_view service_id, + const ByteArray& advertisement) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; + + class DiscoveredPeripheralCallback { + public: + virtual ~DiscoveredPeripheralCallback() {} + + // The BlePeripheral* 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. + virtual void OnPeripheralDiscovered(BlePeripheral* ble_peripheral, + absl::string_view service_id, + const ByteArray& advertisement) = 0; + virtual void OnPeripheralLost(BlePeripheral* ble_peripheral, + absl::string_view service_id) = 0; + }; + + // Returns true once the BLE scan has been initiated. + virtual bool StartScanning( + absl::string_view service_id, + const DiscoveredPeripheralCallback& discovered_peripheral_callback) = 0; + + // Returns true once BLE scanning for service_id is well and truly stopped; + // after this returns, there must be no more invocations of the + // DiscoveredPeripheralCallback passed in to StartScanning() for service_id. + virtual void StopScanning(absl::string_view service_id) = 0; + + // Callback that is invoked when a new connection is accepted. + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() {} + + virtual void OnConnectionAccepted(std::unique_ptr socket, + absl::string_view service_id) = 0; + }; + + // Returns true once BLE socket connection requests to service_id can be + // accepted. + virtual bool StartAcceptingConnections( + absl::string_view service_id, + const AcceptedConnectionCallback& accepted_connection_callback) = 0; + virtual void StopAcceptingConnections(const std::string& service_id) = 0; + + // BlePeripheral* is not owned by this call; + // it must remain valid for the duration of a call. + virtual std::unique_ptr Connect(BlePeripheral* ble_peripheral, + absl::string_view service_id) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_BLE_H_ diff --git a/cpp/platform/api2/ble_v2.h b/cpp/platform/api2/ble_v2.h new file mode 100644 index 00000000..e0573c55 --- /dev/null +++ b/cpp/platform/api2/ble_v2.h @@ -0,0 +1,390 @@ +#ifndef PLATFORM_API2_BLE_V2_H_ +#define PLATFORM_API2_BLE_V2_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace v2 { + +// https://developer.android.com/reference/android/bluetooth/le/AdvertiseData +// +// Bundle of data found in a BLE advertisement. +// +// All service UUIDs will conform to the 16-bit Bluetooth base UUID, +// 0000xxxx-0000-1000-8000-00805F9B34FB. This makes it possible to store two +// byte service UUIDs in the advertisement. +struct BleAdvertisementData { + using TxPowerLevel = int8_t; + + static const TxPowerLevel kUnspecifiedTxPowerLevel = + std::numeric_limits::min(); + + bool is_connectable; + // When set to kUnspecifiedTxPowerLevel, TX power should not be included in + // the advertisement data. + TxPowerLevel tx_power_level; + // When set to an empty string, local name should not be included in the + // advertisement data. + std::string local_name; + // When set to an empty vector, the set of 16-bit service class UUIDs should + // not be included in the advertisement data. + std::set service_uuids; + // Maps service UUIDs to their service data. + std::map service_data; +}; + +// Opaque wrapper over a BLE peripheral. Must be able to uniquely identify a +// peripheral so that we can connect to its GATT server. +class BlePeripheral { + public: + virtual ~BlePeripheral() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getAddress() + // + // This should be the MAC address when possible. If the implementation is + // unable to retrieve that, any unique identifier should suffice. + virtual std::string GetId() const = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic +// +// Representation of a GATT characteristic. +class GattCharacteristic { + public: + virtual ~GattCharacteristic() {} + + // Possible permissions of a GATT characteristic. + enum class Permission { + kUnknown = 0, + kRead = 1, + kWrite = 2, + kLast, + }; + + // Possible properties of a GATT characteristic. + enum class Property { + kUnknown = 0, + kRead = 1, + kWrite = 2, + kIndicate = 3, + kLast, + }; + + // Returns the UUID of this characteristic. + virtual std::string GetUuid() = 0; + + // Returns the UUID of the containing GATT service. + virtual std::string GetServiceUuid() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGatt +// +// Representation of a client GATT connection to a remote GATT server. +class ClientGattConnection { + public: + virtual ~ClientGattConnection() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getDevice() + // + // Retrieves the BLE peripheral that this connection is tied to. + virtual BlePeripheral& GetPeripheral() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices() + // + // Discovers all available services and characteristics on this connection. + // Returns whether or not discovery finished successfully. + // + // This function should block until discovery has finished. + virtual bool DiscoverServices() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID) + // + // Retrieves a GATT characteristic. On error, does not return a value. + // + // DiscoverServices() should be called before this method to fetch all + // available services and characteristics first. + // + // It is okay for duplicate services to exist, as long as the specified + // characteristic UUID is unique among all services of the same UUID. + virtual std::optional GetCharacteristic( + absl::string_view service_uuid, + absl::string_view characteristic_uuid) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue() + // + // Reads a GATT characteristic. No value is returned upon error. + virtual std::optional ReadCharacteristic( + const GattCharacteristic& characteristic) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // + // Sends a remote characteristic write request to the server and returns + // whether or not it was successful. + virtual bool WriteCharacteristic(const GattCharacteristic& characteristic, + const ByteArray& value) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect() + // + // Disconnects a GATT connection. + virtual void Disconnect() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer +// +// Representation of a server GATT connection to a remote GATT client. +class ServerGattConnection { + public: + virtual ~ServerGattConnection() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattServer.html#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice,%20android.bluetooth.BluetoothGattCharacteristic,%20boolean) + // + // Sends a notification (via indication) to the client that a characteristic + // has changed with the given value. Returns whether or not it was successful. + // + // The value sent does not have to reflect the locally stored characteristic + // value. To update the local value, call GattServer::UpdateCharacteristic. + virtual bool SendCharacteristic(const GattCharacteristic& characteristic, + const ByteArray& value) = 0; +}; + +// Callback for asynchronous events on the client side of a GATT connection. +class ClientGattConnectionLifeCycleCallback { + public: + virtual ~ClientGattConnectionLifeCycleCallback() {} + + // Called when the client is disconnected from the GATT server. + virtual void OnDisconnected(ClientGattConnection* connection) = 0; +}; + +// Callback for asynchronous events on the server side of a GATT connection. +class ServerGattConnectionLifeCycleCallback { + public: + virtual ~ServerGattConnectionLifeCycleCallback() {} + + // Called when a remote peripheral connected to us and subscribed to one of + // our characteristics. + virtual void OnCharacteristicSubscription( + ServerGattConnection* connection, + const GattCharacteristic& characteristic) = 0; + + // Called when a remote peripheral unsubscribed from one of our + // characteristics. + virtual void OnCharacteristicUnsubscription( + ServerGattConnection* connection, + const GattCharacteristic& characteristic) = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer +// +// Representation of a BLE GATT server. +class GattServer { + public: + virtual ~GattServer() {} + + // Creates a characteristic and adds it to the GATT server under the given + // characteristic and service UUIDs. Returns no value upon error. + // + // Characteristics of the same service UUID should be put under one + // service rather than many services with the same UUID. + // + // If the INDICATE property is included, the characteristic should include the + // official Bluetooth Client Characteristic Configuration descriptor with UUID + // 0x2902 and a WRITE permission. This allows remote clients to write to this + // descriptor and subscribe for characteristic changes. For more information + // about this descriptor, please go to: + // https://www.bluetooth.com/specifications/Gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.Gatt.client_characteristic_configuration.xml + virtual std::optional CreateCharacteristic( + absl::string_view service_uuid, absl::string_view characteristic_uuid, + const std::set& permissions, + const std::set& properties) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // + // Locally updates the value of a characteristic and returns whether or not it + // was successful. + // Takes ownership of (and is responsible for destroying) the passed-in + // 'value'. + virtual bool UpdateCharacteristic(const GattCharacteristic& characteristic, + const ByteArray& value) = 0; + + // Stops a GATT server. + virtual void Stop() = 0; +}; + +// A BLE socket representation. +class BleSocket { + public: + virtual ~BleSocket() {} + + // Returns the remote BLE peripheral tied to this socket. + virtual BlePeripheral& GetRemotePeripheral() = 0; + + // Writes a message on the socket and blocks until finished. Returns + // Exception::kIo upon error, and Exception::kSuccess otherwise. + virtual Exception Write(const ByteArray& message) = 0; + + // Closes the socket and blocks until finished. Returns Exception::kIo upon + // error, and Exception::kSuccess otherwise. + virtual Exception Close() = 0; +}; + +// Callback for asynchronous events on a BleSocket object. +class BleSocketLifeCycleCallback { + public: + virtual ~BleSocketLifeCycleCallback() {} + + // Called when a message arrives on a socket. + virtual void OnMessageReceived(BleSocket* socket, + const ByteArray& message) = 0; + + // Called when a socket gets disconnected. + virtual void OnDisconnected(BleSocket* socket) = 0; +}; + +// Callback for asynchronous events on the server side of a BleSocket object. +class ServerBleSocketLifeCycleCallback : public BleSocketLifeCycleCallback { + public: + ~ServerBleSocketLifeCycleCallback() override {} + + // Called when a new incoming socket has been established. + virtual void OnSocketEstablished(BleSocket* socket) = 0; +}; + +// The main BLE medium used inside of Nearby. This serves as the entry point for +// all BLE and GATT related operations. +class BleMedium { + public: + using Mtu = uint32_t; + + virtual ~BleMedium() {} + + // Coarse representation of power settings throughout all BLE operations. + enum class PowerMode { + kUnknown = 0, + kLow = 1, + kHigh = 2, + kLast, + }; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#startAdvertising(android.bluetooth.le.AdvertiseSettings,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseCallback) + // + // Starts BLE advertising and returns whether or not it was successful. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Advertising interval = ~1000ms + // - TX power = low + // HIGH: + // - Advertising interval = ~100ms + // - TX power = high + virtual bool StartAdvertising(const BleAdvertisementData& advertisement_data, + const BleAdvertisementData& scan_response, + PowerMode power_mode) = 0; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#stopAdvertising(android.bluetooth.le.AdvertiseCallback) + // + // Stops advertising. + virtual void StopAdvertising() = 0; + + // https://developer.android.com/reference/android/bluetooth/le/ScanCallback + // + // Callback for BLE scan results. + class ScanCallback { + public: + virtual ~ScanCallback() {} + + // https://developer.android.com/reference/android/bluetooth/le/ScanCallback.html#onScanResult(int,%20android.bluetooth.le.ScanResult) + // + // Called when a BLE advertisement is discovered. + // + // The passed in advertisement_data is the merged combination of both + // advertisement data and scan response. + // + // Every discovery of an advertisement should be reported, even if the + // advertisement was discovered before. + // + // Ownership of the BleAdvertisementData transfers to the caller at this + // point. + virtual void OnAdvertisementFound( + BlePeripheral* peripheral, + const BleAdvertisementData& advertisement_data) = 0; + }; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(java.util.List%3Candroid.bluetooth.le.ScanFilter%3E,%20android.bluetooth.le.ScanSettings,%20android.bluetooth.le.ScanCallback) + // + // Starts scanning and returns whether or not it was successful. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Scan window = ~512ms + // - Scan interval = ~5120ms + // HIGH: + // - Scan window = ~4096ms + // - Scan interval = ~4096ms + virtual bool StartScanning(const std::set& service_uuids, + PowerMode power_mode, + const ScanCallback& scan_callback) = 0; + + // https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#stopScan(android.bluetooth.le.ScanCallback) + // + // Stops scanning. + virtual void StopScanning() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothManager#openGattServer(android.content.Context,%20android.bluetooth.BluetoothGattServerCallback) + // + // Starts a GATT server. Returns a nullptr upon error. + virtual std::unique_ptr StartGattServer( + const ServerGattConnectionLifeCycleCallback& callback) = 0; + + // Starts listening for incoming BLE sockets and returns false upon error. + virtual bool StartListeningForIncomingBleSockets( + const ServerBleSocketLifeCycleCallback& callback) = 0; + + // Stops listening for incoming BLE sockets. + virtual void StopListeningForIncomingBleSockets() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context,%20boolean,%20android.bluetooth.BluetoothGattCallback) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestConnectionPriority(int) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestMtu(int) + // + // Connects to a GATT server and negotiates the specified connection + // parameters. Returns nullptr upon error. + // + // Both connection interval and MTU can be negotiated on a best-effort basis. + // + // Power mode should be interpreted in the following way: + // LOW: + // - Connection interval = ~11.25ms - 15ms + // HIGH: + // - Connection interval = ~100ms - 125ms + virtual std::unique_ptr ConnectToGattServer( + BlePeripheral* peripheral, Mtu mtu, PowerMode power_mode, + const ClientGattConnectionLifeCycleCallback& callback) = 0; + + // Establishes a BLE socket to the specified remote peripheral. Returns + // nullptr on error. + virtual std::unique_ptr EstablishBleSocket( + BlePeripheral* peripheral, + const BleSocketLifeCycleCallback& callback) = 0; +}; + +} // namespace v2 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_BLE_V2_H_ diff --git a/cpp/platform/api2/bluetooth_adapter.h b/cpp/platform/api2/bluetooth_adapter.h new file mode 100644 index 00000000..21171a01 --- /dev/null +++ b/cpp/platform/api2/bluetooth_adapter.h @@ -0,0 +1,55 @@ +#ifndef PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_API2_BLUETOOTH_ADAPTER_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter { + public: + virtual ~BluetoothAdapter() {} + + // Eligible statuses of the BluetoothAdapter. + enum class Status { + kDisabled, + kEnabled, + }; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + virtual bool SetStatus(Status status) = 0; + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + virtual bool IsEnabled() = 0; + + // Scan modes of a BluetoothAdapter, as described at + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). + enum class ScanMode { + kUnknown, + kConnectableDiscoverable, + }; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + virtual ScanMode GetScanMode() = 0; + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + virtual bool SetScanMode(ScanMode scan_mode) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + virtual std::string GetName() const = 0; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + virtual bool SetName(absl::string_view name) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api2/bluetooth_classic.h b/cpp/platform/api2/bluetooth_classic.h new file mode 100644 index 00000000..57de4ddc --- /dev/null +++ b/cpp/platform/api2/bluetooth_classic.h @@ -0,0 +1,124 @@ +#ifndef PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_API2_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "platform/api2/input_stream.h" +#include "platform/api2/output_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice { + public: + virtual ~BluetoothDevice() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + virtual std::string GetName() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket { + public: + virtual ~BluetoothSocket() {} + + // Returns the InputStream of the BluetoothSocket. + virtual InputStream& GetInputStream() = 0; + + // Returns the OutputStream of the BluetoothSocket. + virtual OutputStream& GetOutputStream() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + virtual BluetoothDevice& GetRemoteDevice() = 0; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket { + public: + virtual ~BluetoothServerSocket() {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // returns Exception::kIo on error. + virtual ExceptionOr> Accept() = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium { + public: + virtual ~BluetoothClassicMedium() {} + + class DiscoveryCallback { + public: + virtual ~DiscoveryCallback() {} + + // BluetoothDevice* is not owned by callbacks. + // Pointer is guaranteed to remain valid for the duration of a call. + virtual void OnDeviceDiscovered(BluetoothDevice* device) = 0; + virtual void OnDeviceNameChanged(BluetoothDevice* device) = 0; + virtual void OnDeviceLost(BluetoothDevice* device) = 0; + }; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + // + // Does not take ownership of the passed-in discovery_callback -- destroying + // that is up to the caller. + virtual bool StartDiscovery(const DiscoveryCallback& discovery_callback) = 0; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // startDiscovery(). + virtual bool StopDiscovery() = 0; + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // On success, returns a new BluetoothSocket, wrapped in a ExceptionOr object. + // On error, returns Exception object. + virtual ExceptionOr> ConnectToService( + BluetoothDevice* remote_device, absl::string_view service_uuid) = 0; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns Exception::kIo on error. + virtual ExceptionOr> ListenForService( + absl::string_view service_name, absl::string_view service_uuid) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform/api2/condition_variable.h b/cpp/platform/api2/condition_variable.h new file mode 100644 index 00000000..936a3c36 --- /dev/null +++ b/cpp/platform/api2/condition_variable.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_API2_CONDITION_VARIABLE_H_ +#define PLATFORM_API2_CONDITION_VARIABLE_H_ + +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// The ConditionVariable class is a synchronization primitive that can be used +// to block a thread, or multiple threads at the same time, until another thread +// both modifies a shared variable (the condition), and notifies the +// ConditionVariable. +class ConditionVariable { + public: + virtual ~ConditionVariable() {} + + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + virtual void Notify() = 0; + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- + virtual Exception Wait() = 0; // throws Exception::kInterrupted +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api2/count_down_latch.h b/cpp/platform/api2/count_down_latch.h new file mode 100644 index 00000000..ae0dfc86 --- /dev/null +++ b/cpp/platform/api2/count_down_latch.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#define PLATFORM_API2_COUNT_DOWN_LATCH_H_ + +#include + +#include "platform/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch { + public: + virtual ~CountDownLatch() {} + + virtual Exception Await() = 0; // throws Exception::kInterrupted + virtual ExceptionOr Await( + absl::Duration timeout) = 0; // throws Exception::kInterrupted + virtual void CountDown() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api2/executor.h b/cpp/platform/api2/executor.h new file mode 100644 index 00000000..ee561894 --- /dev/null +++ b/cpp/platform/api2/executor.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_API2_EXECUTOR_H_ +#define PLATFORM_API2_EXECUTOR_H_ + +#include + +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +// This abstract class is the superclass of all classes representing an +// Executor. +class Executor { + public: + virtual ~Executor() = default; + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- + virtual void Execute(std::unique_ptr runnable) = 0; + + // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- + virtual void Shutdown() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_EXECUTOR_H_ diff --git a/cpp/platform/api2/future.h b/cpp/platform/api2/future.h new file mode 100644 index 00000000..7f46c484 --- /dev/null +++ b/cpp/platform/api2/future.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_API2_FUTURE_H_ +#define PLATFORM_API2_FUTURE_H_ + +#include "platform/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// A Future represents the result of an asynchronous computation. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html +template +class Future { + public: + virtual ~Future() = default; + + // throws Exception::kInterrupted, Exception::kExecution + virtual ExceptionOr Get() = 0; + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + virtual ExceptionOr Get(absl::Duration timeout) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_FUTURE_H_ diff --git a/cpp/platform/api2/hash_utils.h b/cpp/platform/api2/hash_utils.h new file mode 100644 index 00000000..fab68f32 --- /dev/null +++ b/cpp/platform/api2/hash_utils.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_API2_HASH_UTILS_H_ +#define PLATFORM_API2_HASH_UTILS_H_ + +#include "platform/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// A provider of standard hashing algorithms. +class HashUtils { + public: + static ByteArray Md5(absl::string_view input); + static ByteArray Sha256(absl::string_view input); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_HASH_UTILS_H_ diff --git a/cpp/platform/api2/input_file.h b/cpp/platform/api2/input_file.h new file mode 100644 index 00000000..0191aff8 --- /dev/null +++ b/cpp/platform/api2/input_file.h @@ -0,0 +1,24 @@ +#ifndef PLATFORM_API2_INPUT_FILE_H_ +#define PLATFORM_API2_INPUT_FILE_H_ + +#include + +#include "platform/api2/input_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// An InputFile represents a readable file on the system. +class InputFile : public InputStream { + public: + ~InputFile() override = default; + virtual std::string GetFilePath() const = 0; + virtual size_t GetTotalSize() const = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_INPUT_FILE_H_ diff --git a/cpp/platform/api2/input_stream.h b/cpp/platform/api2/input_stream.h new file mode 100644 index 00000000..f91a5466 --- /dev/null +++ b/cpp/platform/api2/input_stream.h @@ -0,0 +1,27 @@ +#ifndef PLATFORM_API2_INPUT_STREAM_H_ +#define PLATFORM_API2_INPUT_STREAM_H_ + +#include + +#include "platform/byte_array.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// An InputStream represents an input stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html +class InputStream { + public: + virtual ~InputStream() {} + + virtual ExceptionOr Read( + size_t size) = 0; // throws Exception::kIo + virtual Exception Close() = 0; // throws Exception::kIo +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_INPUT_STREAM_H_ diff --git a/cpp/platform/api2/listenable_future.h b/cpp/platform/api2/listenable_future.h new file mode 100644 index 00000000..2993bc88 --- /dev/null +++ b/cpp/platform/api2/listenable_future.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_API2_LISTENABLE_FUTURE_H_ +#define PLATFORM_API2_LISTENABLE_FUTURE_H_ + +#include + +#include "platform/api2/executor.h" +#include "platform/api2/future.h" +#include "platform/exception.h" +#include "platform/runnable.h" + +namespace location { +namespace nearby { + +// A Future that accepts completion listeners. +// +// https://guava.dev/releases/20.0/api/docs/com/google/common/util/concurrent/ListenableFuture.html +template +class ListenableFuture : public Future { + public: + ~ListenableFuture() override = default; + + virtual void AddListener(std::unique_ptr runnable, + Executor* executor) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform/api2/multi_thread_executor.h b/cpp/platform/api2/multi_thread_executor.h new file mode 100644 index 00000000..f910bbc4 --- /dev/null +++ b/cpp/platform/api2/multi_thread_executor.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ + +#include "platform/api2/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- +template +class MultiThreadExecutor + : public SubmittableExecutor { + public: + ~MultiThreadExecutor() override {} +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/mutex.h b/cpp/platform/api2/mutex.h new file mode 100644 index 00000000..d4dbaf61 --- /dev/null +++ b/cpp/platform/api2/mutex.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API2_MUTEX_H_ +#define PLATFORM_API2_MUTEX_H_ + +namespace location { +namespace nearby { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class Mutex { + public: + virtual ~Mutex() {} + + virtual void Lock() = 0; + virtual void Unlock() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_MUTEX_H_ diff --git a/cpp/platform/api2/output_file.h b/cpp/platform/api2/output_file.h new file mode 100644 index 00000000..4ac962e8 --- /dev/null +++ b/cpp/platform/api2/output_file.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_API2_OUTPUT_FILE_H_ +#define PLATFORM_API2_OUTPUT_FILE_H_ + +#include "platform/api2/output_stream.h" +#include "platform/byte_array.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// An OutputFile represents a writable file on the system. +class OutputFile : public OutputStream { + public: + ~OutputFile() override = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_OUTPUT_FILE_H_ diff --git a/cpp/platform/api2/output_stream.h b/cpp/platform/api2/output_stream.h new file mode 100644 index 00000000..b9336ad1 --- /dev/null +++ b/cpp/platform/api2/output_stream.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_API2_OUTPUT_STREAM_H_ +#define PLATFORM_API2_OUTPUT_STREAM_H_ + +#include "platform/byte_array.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// An OutputStream represents an output stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html +class OutputStream { + public: + virtual ~OutputStream() {} + + virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo + virtual Exception Flush() = 0; // throws Exception::kIo + virtual Exception Close() = 0; // throws Exception::kIo +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api2/scheduled_executor.h b/cpp/platform/api2/scheduled_executor.h new file mode 100644 index 00000000..ae773ee1 --- /dev/null +++ b/cpp/platform/api2/scheduled_executor.h @@ -0,0 +1,29 @@ +#ifndef PLATFORM_API2_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_API2_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform/api2/executor.h" +#include "platform/cancelable.h" +#include "platform/runnable.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public Executor { + public: + ~ScheduledExecutor() override = default; + virtual std::unique_ptr Schedule( + std::unique_ptr runnable, absl::Duration duration) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/server_sync.h b/cpp/platform/api2/server_sync.h new file mode 100644 index 00000000..47bc3aa5 --- /dev/null +++ b/cpp/platform/api2/server_sync.h @@ -0,0 +1,60 @@ +#ifndef PLATFORM_API2_SERVER_SYNC_H_ +#define PLATFORM_API2_SERVER_SYNC_H_ + +#include + +#include "platform/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// Abstraction that represents a Nearby endpoint exchanging data through +// ServerSync Medium. +class ServerSyncDevice { + public: + virtual ~ServerSyncDevice() = default; + + virtual std::string GetName() const = 0; + virtual std::string GetGuid() const = 0; + virtual std::string GetOwnGuid() const = 0; +}; + +// Container of operations that can be performed over the Chrome Sync medium. +class ServerSyncMedium { + public: + virtual ~ServerSyncMedium() = default; + + virtual bool StartAdvertising(absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; + + class DiscoveredDeviceCallback { + public: + virtual ~DiscoveredDeviceCallback() = default; + + // Called on a new ServerSyncDevice discovery. + virtual void OnDeviceDiscovered(ServerSyncDevice* device, + absl::string_view service_id, + absl::string_view endpoint_id, + const ByteArray& endpoint_info) = 0; + // Called when ServerSyncDevice is no longer reachable. + virtual void OnDeviceLost(ServerSyncDevice* device, + absl::string_view service_id) = 0; + }; + + // Returns true once the Chrome Sync scan has been initiated. + virtual bool StartDiscovery( + absl::string_view service_id, + const DiscoveredDeviceCallback& discovered_device_callback) = 0; + // Returns true once Chrome Sync scan for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredDeviceCallback passed in to startScanning() for service_id. + virtual void StopDiscovery(absl::string_view service_id) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SERVER_SYNC_H_ diff --git a/cpp/platform/api2/settable_future.h b/cpp/platform/api2/settable_future.h new file mode 100644 index 00000000..2089173c --- /dev/null +++ b/cpp/platform/api2/settable_future.h @@ -0,0 +1,24 @@ +#ifndef PLATFORM_API2_SETTABLE_FUTURE_H_ +#define PLATFORM_API2_SETTABLE_FUTURE_H_ + +#include "platform/api2/listenable_future.h" + +namespace location { +namespace nearby { + +// A SettableFuture is a type of Future whose result can be set. +// +// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +template +class SettableFuture : public ListenableFuture { + public: + ~SettableFuture() override = default; + + virtual bool Set(const T& value) = 0; + virtual bool SetException(Exception exception) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SETTABLE_FUTURE_H_ diff --git a/cpp/platform/api2/single_thread_executor.h b/cpp/platform/api2/single_thread_executor.h new file mode 100644 index 00000000..990f2fe7 --- /dev/null +++ b/cpp/platform/api2/single_thread_executor.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform/api2/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- +template +class SingleThreadExecutor + : public SubmittableExecutor { + public: + ~SingleThreadExecutor() override {} +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/socket.h b/cpp/platform/api2/socket.h new file mode 100644 index 00000000..0f855609 --- /dev/null +++ b/cpp/platform/api2/socket.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_API2_SOCKET_H_ +#define PLATFORM_API2_SOCKET_H_ + +#include "platform/api2/input_stream.h" +#include "platform/api2/output_stream.h" + +namespace location { +namespace nearby { + +// A socket is an endpoint for communication between two machines. +// +// https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html +class Socket { + public: + virtual ~Socket() {} + + virtual InputStream& GetInputStream() = 0; + virtual OutputStream& GetOutputStream() = 0; + virtual void Close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SOCKET_H_ diff --git a/cpp/platform/api2/submittable_executor.h b/cpp/platform/api2/submittable_executor.h new file mode 100644 index 00000000..43f16f56 --- /dev/null +++ b/cpp/platform/api2/submittable_executor.h @@ -0,0 +1,42 @@ +#ifndef PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ + +#include + +#include "platform/api2/executor.h" +#include "platform/api2/future.h" +#include "platform/callable.h" + +namespace location { +namespace nearby { + +// Each per-platform concrete implementation is expected to extend from +// SubmittableExecutor and provide an override of its submit() method. +// +// e.g. +// class XyzSubmittableExecutor +// : public SubmittableExecutor { +// public: +// template +// std::unique_ptr> submit(std::unique_ptr> callable) { +// ... +// } +// } +template +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override {} + + template + std::unique_ptr> Submit(std::unique_ptr> callable) { + static_assert( + std::is_base_of_v, + "Class template type is not derived from SubmittableExecutor"); + return static_cast(this)->submit(callable); + } +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api2/system_clock.h b/cpp/platform/api2/system_clock.h new file mode 100644 index 00000000..3b0b8090 --- /dev/null +++ b/cpp/platform/api2/system_clock.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API2_SYSTEM_CLOCK_H_ +#define PLATFORM_API2_SYSTEM_CLOCK_H_ + +#include + +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +class SystemClock final { + public: + // Returns the time (in milliseconds) since the system was booted, and + // includes deep sleep. This clock should be guaranteed to be monotonic, and + // should continue to tick even when the CPU is in power saving modes. + static absl::Time ElapsedRealtime(); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/thread_utils.h b/cpp/platform/api2/thread_utils.h new file mode 100644 index 00000000..990c0ec2 --- /dev/null +++ b/cpp/platform/api2/thread_utils.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_API2_THREAD_UTILS_H_ +#define PLATFORM_API2_THREAD_UTILS_H_ + +#include + +#include "platform/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +class ThreadUtils final { + public: + // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long) + // throws Exception::kInterrupted + static Exception Sleep(absl::Duration timeout); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_THREAD_UTILS_H_ diff --git a/cpp/platform/api2/webrtc.h b/cpp/platform/api2/webrtc.h new file mode 100644 index 00000000..e1dbde9e --- /dev/null +++ b/cpp/platform/api2/webrtc.h @@ -0,0 +1,46 @@ +#ifndef PLATFORM_API2_WEBRTC_H_ +#define PLATFORM_API2_WEBRTC_H_ + +#include + +#include "platform/byte_array.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { + +class WebRtcSignalingMessenger { + public: + virtual ~WebRtcSignalingMessenger() = default; + + /** Called whenever we receive an inbox message from tachyon. */ + class SignalingMessageListener { + public: + virtual ~SignalingMessageListener() = default; + + virtual void OnSignalingMessage(const ByteArray& message) = 0; + }; + + class IceServersListener { + public: + virtual ~IceServersListener() = default; + + virtual void OnIceServersFetched( + std::vector + ice_servers) = 0; + }; + + virtual bool RegisterSignaling() = 0; + virtual bool UnregisterSignaling() = 0; + virtual bool SendMessage(std::string_view peer_id, + const ByteArray& message) = 0; + virtual bool StartReceivingMessages( + const SignalingMessageListener& listener) = 0; + virtual void GetIceServers( + const IceServersListener& ice_servers_listener) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_WEBRTC_H_ diff --git a/cpp/platform/api2/wifi.h b/cpp/platform/api2/wifi.h new file mode 100644 index 00000000..74f0e5c9 --- /dev/null +++ b/cpp/platform/api2/wifi.h @@ -0,0 +1,88 @@ +#ifndef PLATFORM_API2_WIFI_H_ +#define PLATFORM_API2_WIFI_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// Possible authentication types for a WiFi network. +enum class WifiAuthType { + // WiFi Authentication type; either none (non-secured a.k.a. open) link, or + // WPA PSK (WiFi Protected Access PreShared Key), or + // see https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access + // WEP (Wired Equivalent Privacy); + // see https://en.wikipedia.org/wiki/Wired_Equivalent_Privacy + kUnknown = 0, + kOpen = 1, + kWpaPsk = 2, + kWep = 3, +}; + +// Possible statuses of a device's connection to a WiFi network. +enum class WifiConnectionStatus { + kUnknown = 0, + kConnected = 1, + kConnectionFailure = 2, + kAuthFailure = 3, +}; + +// Represents a WiFi network found during a call to WifiMedium#scan(). +class WifiScanResult { + public: + virtual ~WifiScanResult() {} + + // Gets the SSID of this WiFi network. + virtual std::string GetSsid() const = 0; + // Gets the signal strength of this WiFi network in dBm. + virtual std::int32_t GetSignalStrengthDbm() const = 0; + // Gets the frequency band of this WiFi network in MHz. + virtual std::int32_t GetFrequencyMhz() const = 0; + // Gets the authentication type of this WiFi network. + virtual WifiAuthType GetAuthType() const = 0; +}; + +// Container of operations that can be performed over the WiFi medium. +class WifiMedium { + public: + virtual ~WifiMedium() {} + + class ScanResultCallback { + public: + virtual ~ScanResultCallback() {} + + virtual void OnScanResults( + const std::vector& scan_results) = 0; + }; + + // Does not take ownership of the passed-in scan_result_callback -- destroying + // that is up to the caller. + virtual bool Scan(const ScanResultCallback& scan_result_callback) = 0; + + // If 'password' is an empty string, none has been provided. Returns + // WifiConnectionStatus::CONNECTED on success, or the appropriate failure code + // otherwise. + virtual WifiConnectionStatus ConnectToNetwork(absl::string_view ssid, + absl::string_view password, + WifiAuthType auth_type) = 0; + + // Blocks until it's certain of there being a connection to the internet, or + // returns false if it fails to do so. + // + // How this method wants to verify said connection is totally up to it (so it + // can feel free to ping whatever server, download whatever resource, etc. + // that it needs to gain confidence that the internet is reachable hereon in). + virtual bool VerifyInternetConnectivity() = 0; + + // Returns the local device's IP address in the IPv4 dotted-quad format. + virtual std::string GetIpAddress() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API2_WIFI_H_ diff --git a/cpp/platform/base64_utils.cc b/cpp/platform/base64_utils.cc index 51cb5635..4ac6e6d6 100644 --- a/cpp/platform/base64_utils.cc +++ b/cpp/platform/base64_utils.cc @@ -1,6 +1,5 @@ #include "platform/base64_utils.h" -#include "strings/escaping.h" #include "absl/strings/escaping.h" namespace location { @@ -25,7 +24,7 @@ std::string Base64Utils::encode(const ByteArray& bytes) { return base64_string; } -std::string Base64Utils::encode(const std::string& input) { +std::string Base64Utils::encode(absl::string_view input) { std::string base64_string; absl::WebSafeBase64Escape(input, &base64_string); @@ -33,7 +32,7 @@ std::string Base64Utils::encode(const std::string& input) { } template<> -Ptr Base64Utils::decode(const std::string& base64_string) { +Ptr Base64Utils::decode(absl::string_view base64_string) { std::string decoded_string; if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { return Ptr(); @@ -43,7 +42,7 @@ Ptr Base64Utils::decode(const std::string& base64_string) { } template<> -ByteArray Base64Utils::decode(const std::string& base64_string) { +ByteArray Base64Utils::decode(absl::string_view base64_string) { std::string decoded_string; if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { return ByteArray(); diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h index 76b8cb7d..cdfee91e 100644 --- a/cpp/platform/base64_utils.h +++ b/cpp/platform/base64_utils.h @@ -4,23 +4,24 @@ #include "platform/byte_array.h" #include "platform/port/string.h" #include "platform/ptr.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { class Base64Utils { public: - static std::string encode(const std::string& input); + static std::string encode(absl::string_view input); static std::string encode(const ByteArray& bytes); static std::string encode(ConstPtr bytes); template - static T decode(const std::string& base64_string); + static T decode(absl::string_view base64_string); template <> - Ptr decode(const std::string& base64_string); + Ptr decode(absl::string_view base64_string); template <> - ByteArray decode(const std::string& base64_string); - static Ptr decode(const std::string& base64_string) { + ByteArray decode(absl::string_view base64_string); + static Ptr decode(absl::string_view base64_string) { return decode>(base64_string); } }; diff --git a/cpp/platform/exception.cc b/cpp/platform/exception.cc deleted file mode 100644 index c5dd53a4..00000000 --- a/cpp/platform/exception.cc +++ /dev/null @@ -1,30 +0,0 @@ -#include "platform/exception.h" - -namespace location { -namespace nearby { - -template -ExceptionOr::ExceptionOr(T result) - : result_(result), exception_(Exception::NONE) {} - -template -ExceptionOr::ExceptionOr(Exception::Value exception) - : result_(), exception_(exception) {} - -template -bool ExceptionOr::ok() const { - return Exception::NONE == exception_; -} - -template -T ExceptionOr::result() const { - return result_; -} - -template -Exception::Value ExceptionOr::exception() const { - return exception_; -} - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h index 8b07e565..485f03a3 100644 --- a/cpp/platform/exception.h +++ b/cpp/platform/exception.h @@ -1,21 +1,34 @@ #ifndef PLATFORM_EXCEPTION_H_ #define PLATFORM_EXCEPTION_H_ +#include + namespace location { namespace nearby { struct Exception { - enum Value { + enum Value : int { NONE, IO, INTERRUPTED, INVALID_PROTOCOL_BUFFER, EXECUTION, + // New code should use the kConstants. + // Old CONSTANTS are deprecated, and should not be used. + kFailed = -1, // Initial value of Exception; any unknown error. + kSuccess = NONE, // No exception. + kIo = IO, // IO Error happened. + kInterrupted = INTERRUPTED, // Operation was interrupted. + kInvalidProtocolBuffer = INVALID_PROTOCOL_BUFFER, // Couldn't parse. + kExecution = EXECUTION, // Couldn't execute. + kTimeout, // Operarion did not finish within specified time. }; + Value value {kFailed}; }; -// ExceptionOr models the concept of the return value of a function that might -// throw an exception. +// ExceptionOr provides experience similar to StatusOr used in +// Google Cloud API, see: +// https://googleapis.github.io/google-cloud-cpp/0.7.0/common/status__or_8h_source.html // // If ok() returns true, result() is a usable return value. Otherwise, // exception() explains why such a value is not present. @@ -36,22 +49,31 @@ struct Exception { template class ExceptionOr { public: - explicit ExceptionOr(T result); - explicit ExceptionOr(Exception::Value exception); + ExceptionOr() = default; + ExceptionOr(T&& result) : result_{std::move(result)}, // NOLINT + exception_{Exception::kSuccess} {} + ExceptionOr(const T& result) : result_{result}, // NOLINT + exception_{Exception::kSuccess} {} + ExceptionOr(Exception::Value exception) : exception_{exception} {} // NOLINT - bool ok() const; + bool ok() const { return exception_.value == Exception::kSuccess; } - T result() const; - Exception::Value exception() const; + T& result() & { return result_; } + const T& result() const & { return result_; } + T&& result() && { return std::move(result_); } + const T&& result() const && { return std::move(result_); } + + Exception::Value exception() const { return exception_.value; } + + T GetResult() const; + Exception GetException() const; private: T result_; - Exception::Value exception_; + Exception exception_ {Exception::kFailed}; }; } // namespace nearby } // namespace location -#include "platform/exception.cc" - #endif // PLATFORM_EXCEPTION_H_ diff --git a/cpp/platform/exception_test.cc b/cpp/platform/exception_test.cc new file mode 100644 index 00000000..d36e2d85 --- /dev/null +++ b/cpp/platform/exception_test.cc @@ -0,0 +1,76 @@ +#include "platform/exception.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby { + +TEST(ExceptionOr, Result_Copy_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should not affect the copy. + exception_or_vector.result().clear(); + EXPECT_FALSE(copy.empty()); +} + +TEST(ExceptionOr, Result_Copy_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Reference_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should reflect in the reference. + exception_or_vector.result().clear(); + EXPECT_TRUE(reference.empty()); +} + +TEST(ExceptionOr, Result_Reference_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + const std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Move_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + ASSERT_FALSE(exception_or_vector.result().empty()); + + // Moving the result should clear the source. + std::vector moved = std::move(exception_or_vector).result(); + ASSERT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, Result_Move_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + ASSERT_FALSE(exception_or_vector.result().empty()); + + // Moving const rvalue reference will result in a copy. + std::vector moved = std::move(exception_or_vector).result(); + ASSERT_FALSE(moved.empty()); +} + +} // namespace location::nearby diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/default/BUILD index 24d48ca7..87f28f9c 100644 --- a/cpp/platform/impl/default/BUILD +++ b/cpp/platform/impl/default/BUILD @@ -1,8 +1,6 @@ cc_library( name = "default", srcs = [ - "default_condition_variable.cc", - "default_lock.cc", "default_platform.cc", ], hdrs = [ @@ -15,6 +13,8 @@ cc_library( "//core:__subpackages__", ], deps = [ + ":condition_variable", + ":lock", "//platform:types", "//platform/api", ], @@ -38,7 +38,7 @@ cc_library( "//platform:__subpackages__", ], deps = [ - ":default", + ":lock", "//platform:types", "//platform/api:condition_variable", ], diff --git a/cpp/platform/ptr.cc b/cpp/platform/ptr.cc deleted file mode 100644 index 64cbe3c0..00000000 --- a/cpp/platform/ptr.cc +++ /dev/null @@ -1,13 +0,0 @@ -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -namespace ptr_impl { - -const std::int32_t RefCount::kInitialCount = 0; - -} // namespace ptr_impl - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h index caa9093a..6527db19 100644 --- a/cpp/platform/ptr.h +++ b/cpp/platform/ptr.h @@ -4,78 +4,15 @@ #include #include #include +#include +#include -#include "platform/impl/default/default_lock.h" #include "platform/logging.h" #include "platform/port/down_cast.h" namespace location { namespace nearby { -namespace ptr_impl { - -class RefCount { - public: - RefCount() : lock_(), count_(kInitialCount) {} - - // Returns false if this operation doesn't make conceptual sense any more - // (for example, if it leads to bringing count_ back from the dead). - bool increment() { - bool result; - - lock_.lock(); - { - // Avoid coming back from the dead. - if (count_ < kInitialCount) { - result = false; - } else { - count_++; - result = true; - } - } - lock_.unlock(); - - return result; - } - - // Returns true if after this operation, count_ is 0. - bool decrement() { - bool result; - - lock_.lock(); - { - // It's alright for count_ to go negative because it will only be exactly - // 0 once (since increment() makes sure that once you go negative, you - // can't come back from the dead). - count_--; - result = (count_ == 0); - } - lock_.unlock(); - - return result; - } - - private: - static const std::int32_t kInitialCount; - - DefaultLock lock_; - std::int32_t count_; -}; - -} // namespace ptr_impl - -template -class ObjectDestroyer { - public: - static void destroy(T* t) { delete t; } -}; - -template -class ArrayDestroyer { - public: - static void destroy(T* t) { delete[] t; } -}; - // Forward declarations to make it possible for Ptr (a class template) to // declare ConstifyPtr, DowncastPtr, and DowncastConstPtr (function templates) // as friends. @@ -85,7 +22,7 @@ class ArrayDestroyer { // Ptr (which is what one might reasonably expect). // // See https://isocpp.org/wiki/faq/templates#template-friends for more. -template class Destroyer = ObjectDestroyer> +template class Ptr; template class ConstPtr; @@ -96,128 +33,74 @@ Ptr DowncastPtr(Ptr base_ptr); template ConstPtr DowncastConstPtr(ConstPtr base_ptr); -// A layer of indirection over a raw pointer, to buy flexibility in the -// future to use, for instance: -// -// a) the in-built shared_ptr in modern implementations of C++, -// b) a custom reference-counting mechanism, etc. -// -// , all without having to touch every line of our codebase that uses -// pointers. -// -// Destroyer defines how the owned pointee should be destroyed, and is -// expected to be a class template that provides at least a destroy() -// method, like so: -// -// template -// class MyDestroyer { -// public: -// static void destroy(T* t); -// }; -// -// It defaults to ObjectDestroyer. -template class Destroyer> +// A layer of indirection over a raw pointer. +// It is being deprecated in favor of standard c++ smart pointers. +// For transion period, Ptr will behave similar to shared_ptr. +// New code should use shrared_ptr or unique_ptr and not Ptr. +template class Ptr { public: // Provide an alias for use as a dependent name. typedef T PointeeType; - Ptr() : pointee_(nullptr), ref_count_(nullptr) {} - explicit Ptr(T* pointee, bool is_ref_counted = false, - ptr_impl::RefCount* ref_count = nullptr) - : pointee_(pointee), - ref_count_( - is_ref_counted - ? (ref_count != nullptr ? ref_count : new ptr_impl::RefCount()) - : nullptr) { - init(); - } - Ptr(const Ptr& that) : pointee_(that.pointee_), ref_count_(that.ref_count_) { - init(); - } + Ptr() = default; + explicit Ptr(T* pointee) : ptr_(pointee) {} + Ptr(const Ptr& that) = default; - Ptr& operator=(const Ptr& other) { - if (pointee_ != other.pointee_) { - // If we're not currently ref-counted, then an assignment shouldn't lead - // to any destruction of our past state -- that's the responsibility of - // whichever instance of Ptr believes it owns pointee_. - destroy(false); + Ptr(std::shared_ptr ptr) : ptr_(ptr) {} // NOLINT - pointee_ = other.pointee_; - ref_count_ = other.ref_count_; - - init(); - } + template + Ptr& operator=(T2* ptr) { + Ptr tmp(ptr); + this->ptr_.swap(tmp); return *this; } + Ptr& operator=(const Ptr& other) = default; + // Conversion to Ptr, where T is trivially convertible to T2. E.g. // conversion from derived to base class. template - operator Ptr() { - return Ptr(pointee_, isRefCounted(), ref_count_); + operator Ptr() { // NOLINT + return Ptr(std::static_pointer_cast(this->ptr_)); + } + operator Ptr() { // NOLINT + return Ptr(*this); } - ~Ptr() { - if (isRefCounted()) { - destroy(); - } else { - // Left empty on purpose. - } - } + explicit operator std::shared_ptr() { return this->ptr_; } + + ~Ptr() = default; bool operator==(const Ptr& other) const { - assert(!(this->isNull())); - assert(!(other.isNull())); - - return ((*(this->pointee_) == *(other.pointee_)) && - (this->isRefCounted() == other.isRefCounted())); + return *(this->ptr_) == *(other.ptr_); } - bool operator!=(const Ptr& other) const { return !(*this == other); } bool operator<(const Ptr& other) const { - assert(!(this->isNull())); - assert(!(other.isNull())); - - return *(this->pointee_) < *(other.pointee_); + return *(this->ptr_) < *(other.ptr_); } - // Calls Destroyer::destroy() to perform deallocation of pointee_. - void destroy(bool should_destroy_if_not_ref_counted = true) { - bool need_to_destroy = isRefCounted() ? ref_count_->decrement() - : should_destroy_if_not_ref_counted; - if (need_to_destroy) { - delete ref_count_; - Destroyer::destroy(pointee_); - } + // No-op: refcounted objects will be destroyed correctly + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + void destroy(bool = true) {} - ref_count_ = NULL; // NOLINT - pointee_ = NULL; // NOLINT - } + // No-op: refcounted objects will be destroyed correctly + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + void clear() {} - // Use this function only when the ownership is held by someone else, and this - // Ptr object has no responsibility to destroy it. - void clear() { - if (isRefCounted()) { - NEARBY_LOG(FATAL, "Attempting to invoke clear() on a RefCounted Ptr."); - } + T& operator*() const { return *ptr_; } - pointee_ = NULL; // NOLINT - } + T* operator->() const { return ptr_.get(); } + T* get() { return ptr_.get(); } + void reset() { return ptr_.reset(); } - T& operator*() const { - assert(pointee_ != NULL); // NOLINT - return *pointee_; - } + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + bool isNull() const { return !this->ptr_; } - T* operator->() const { - assert(pointee_ != NULL); // NOLINT - return pointee_; - } - - bool isNull() const { return pointee_ == nullptr; } - bool isRefCounted() const { return ref_count_ != nullptr; } + // used by pipe.cc; introduced by cr/295271652 + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + bool isRefCounted() const { return true; } private: template @@ -227,16 +110,7 @@ class Ptr { template friend ConstPtr DowncastConstPtr(ConstPtr base_ptr); - void init() { - if (isRefCounted()) { - if (!ref_count_->increment()) { - NEARBY_LOG(FATAL, "Failed to increment RefCount."); - } - } - } - - T* pointee_; - ptr_impl::RefCount* ref_count_; + std::shared_ptr ptr_; }; // Convenience wrapper for a read-only version of Ptr (in which the pointee @@ -259,9 +133,9 @@ template class ConstPtr : public Ptr { public: ConstPtr() {} - explicit ConstPtr(T* pointee, bool is_ref_counted = false, - ptr_impl::RefCount* ref_count = nullptr) - : Ptr(pointee, is_ref_counted, ref_count) {} + explicit ConstPtr(const T* pointee) : Ptr(pointee) {} + explicit ConstPtr(T* pointee) : Ptr(pointee) {} + explicit ConstPtr(Ptr ptr) : Ptr(ptr) {} }; // RAII wrapper over Ptr and ConstPtr (hereon referred to by the PtrType @@ -291,33 +165,29 @@ class ScopedPtr { public: explicit ScopedPtr(typename PtrType::PointeeType* pointee) : ptr_(pointee) {} explicit ScopedPtr(PtrType ptr) : ptr_(ptr) {} - ~ScopedPtr() { ptr_.destroy(); } + ScopedPtr(const ScopedPtr&) = delete; + ~ScopedPtr() = default; + + ScopedPtr& operator=(const ScopedPtr&) = delete; // Shadow methods for the underlying Ptr. - typename PtrType::PointeeType& operator*() const { return ptr_.operator*(); } + typename PtrType::PointeeType& operator*() const { return *ptr_; } typename PtrType::PointeeType* operator->() const { return ptr_.operator->(); } bool isNull() const { return ptr_.isNull(); } // Accessor for the underlying Ptr. - PtrType get() const { return ptr_; } + PtrType get() const { return this->ptr_; } - // Releases the underlying Ptr from the clutches of this ScopedPtr, - // effectively resetting this ScopedPtr (and making its destructor be a no-op) - // -- useful for transfer of ownership from one ScopedPtr to another across - // scopes. + // Does nothing; + // this is to avoid unintended destruction of a managed pointer. + // TODO(b/149938110): remove this completely. PtrType release() { - PtrType released = ptr_; - ptr_ = PtrType(); - return released; + return ptr_; } private: - // Disallow copy and assignment. - ScopedPtr(const ScopedPtr&); - ScopedPtr& operator=(const ScopedPtr&); - PtrType ptr_; }; @@ -358,19 +228,19 @@ ConstPtr MakeConstPtr(T* raw_ptr) { // reference). template Ptr MakeRefCountedPtr(T* raw_ptr) { - return Ptr(raw_ptr, true); + return Ptr(raw_ptr); } // ConstPtr counterpart to MakeRefCountedPtr(). template ConstPtr MakeRefCountedConstPtr(T* raw_ptr) { - return ConstPtr(raw_ptr, true); + return ConstPtr(raw_ptr); } // Use this function to convert a Ptr object to a ConstPtr object. template ConstPtr ConstifyPtr(Ptr ptr) { - return ConstPtr(ptr.pointee_, ptr.isRefCounted(), ptr.ref_count_); + return ConstPtr(ptr); } // Use this function to downcast from a Ptr to a Ptr. @@ -382,16 +252,16 @@ ConstPtr ConstifyPtr(Ptr ptr) { // Ptr my_child_ptr = DowncastPtr(my_base_ptr); template Ptr DowncastPtr(Ptr base_ptr) { - return Ptr(DOWN_CAST(base_ptr.pointee_), - base_ptr.isRefCounted(), base_ptr.ref_count_); + static_assert(std::is_base_of_v); + return Ptr(std::static_pointer_cast(base_ptr.ptr_)); } // ConstPtr counterpart to DowncastPtr(). template ConstPtr DowncastConstPtr(ConstPtr base_ptr) { + static_assert(std::is_base_of_v); return ConstPtr( - const_cast(DOWN_CAST(base_ptr.pointee_)), - base_ptr.isRefCounted(), base_ptr.ref_count_); + std::static_pointer_cast(base_ptr.ptr_)); } } // namespace nearby diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc index a68a58b9..adc73c08 100644 --- a/cpp/platform/ptr_test.cc +++ b/cpp/platform/ptr_test.cc @@ -22,15 +22,6 @@ TEST(PtrTest, RefCountedPtr_MultipleReferences) { SUCCEED(); } -TEST(PtrTest, RefCountedPtr_IsRefCounted_Works) { - Ptr ref_counted = MakeRefCountedPtr(new int(1234)); - Ptr manually_counted = MakePtr(new int(1234)); - ScopedPtr > scoped_manually_counted(manually_counted); - - ASSERT_TRUE(ref_counted.isRefCounted()); - ASSERT_FALSE(manually_counted.isRefCounted()); -} - TEST(PtrTest, RefCountedPtr_MultipleReferencesWithScoped) { Ptr ref_counted = MakeRefCountedPtr(new int(1234)); ScopedPtr > scoped_ref_counted_1(ref_counted); @@ -51,60 +42,6 @@ TEST(PtrTest, AssignmentOperator_RefCountedToRefCounted) { ASSERT_EQ(1234, *ref_counted_2); } -TEST(PtrTest, AssignmentOperator_ManuallyCountedToManuallyCounted) { - Ptr manually_counted_1 = MakePtr(new int(1234)); - Ptr manually_counted_2 = MakePtr(new int(5678)); - // Avoid leaks. - ScopedPtr > scoped_manually_counted_1(manually_counted_1); - ScopedPtr > scoped_manually_counted_2(manually_counted_2); - - manually_counted_2 = manually_counted_1; - - ASSERT_EQ(1234, *manually_counted_1); - ASSERT_EQ(1234, *manually_counted_2); - ASSERT_EQ(1234, *scoped_manually_counted_1); - ASSERT_EQ(5678, *scoped_manually_counted_2); -} - -TEST(PtrTest, AssignmentOperator_RefCountedToManuallyCounted) { - Ptr ref_counted = MakeRefCountedPtr(new int(1234)); - Ptr manually_counted = MakePtr(new int(5678)); - // Avoid leaks. - ScopedPtr > scoped_manually_counted(manually_counted); - - manually_counted = ref_counted; - - ASSERT_EQ(1234, *ref_counted); - ASSERT_EQ(1234, *manually_counted); - ASSERT_EQ(5678, *scoped_manually_counted); -} - -TEST(PtrTest, AssignmentOperator_ManuallyCountedToRefCounted) { - Ptr manually_counted = MakePtr(new int(1234)); - Ptr ref_counted = MakeRefCountedPtr(new int(5678)); - // Avoid leaks. - ScopedPtr > scoped_manually_counted(manually_counted); - - ref_counted = manually_counted; - - ASSERT_EQ(1234, *ref_counted); - ASSERT_EQ(1234, *manually_counted); - ASSERT_EQ(1234, *scoped_manually_counted); -} - -TEST(PtrTest, AssignmentOperator_SelfAssignment_ManuallyCounted) { - Ptr manually_counted_1 = MakePtr(new int(1234)); - Ptr manually_counted_2(manually_counted_1); - // Avoid leaks. - ScopedPtr > scoped_manually_counted_1(manually_counted_1); - - manually_counted_1 = manually_counted_2; - - ASSERT_EQ(1234, *manually_counted_1); - ASSERT_EQ(1234, *manually_counted_2); - ASSERT_EQ(1234, *scoped_manually_counted_1); -} - TEST(PtrTest, AssignmentOperator_SelfAssignment_RefCounted) { Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); Ptr ref_counted_2(ref_counted_1); @@ -115,19 +52,6 @@ TEST(PtrTest, AssignmentOperator_SelfAssignment_RefCounted) { ASSERT_EQ(1234, *ref_counted_2); } -TEST(PtrTest, EqualityOperator_ManuallyCounted) { - Ptr manually_counted_1 = MakePtr(new int(1234)); - Ptr manually_counted_2(manually_counted_1); - // Avoid leaks. - ScopedPtr > scoped_manually_counted_1(manually_counted_1); - - ASSERT_TRUE(manually_counted_1 == manually_counted_2); - - manually_counted_1 = manually_counted_2; - - ASSERT_TRUE(manually_counted_1 == manually_counted_2); -} - TEST(PtrTest, EqualityOperator_RefCounted) { Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); Ptr ref_counted_2(ref_counted_1); @@ -139,16 +63,6 @@ TEST(PtrTest, EqualityOperator_RefCounted) { ASSERT_TRUE(ref_counted_1 == ref_counted_2); } -TEST(PtrTest, EqualityOperator_ManuallyAndRefCounted) { - int* raw = new int(1234); - Ptr manually_counted = MakePtr(raw); - Ptr ref_counted = MakeRefCountedPtr(raw); - // No need for a ScopedPtr for manually_counted here because we know that - // ref_counted will take care of deallocating 'raw'. - - ASSERT_FALSE(manually_counted == ref_counted); -} - namespace { class Base { @@ -171,17 +85,6 @@ class Derived : public Base { } // namespace -TEST(PtrTest, DerivedToBaseConversion_ManuallyCounted) { - Ptr derived = MakePtr(new Derived(1234)); - Ptr base = derived; - // Avoid leaks. - ScopedPtr > scoped_derived(derived); - - ASSERT_EQ(1234, base->getInt()); - ASSERT_EQ(1234, derived->getInt()); - ASSERT_EQ(1234, scoped_derived->getInt()); -} - TEST(PtrTest, DerivedToBaseConversion_RefCounted) { Ptr derived = MakeRefCountedPtr(new Derived(1234)); Ptr base = derived; @@ -193,17 +96,18 @@ TEST(PtrTest, DerivedToBaseConversion_RefCounted) { ASSERT_EQ(1234, base->getInt()); } -TEST(PtrTest, ScopedPtr_Release_ManuallyCounted) { - Ptr manually_counted_1 = MakePtr(new int(1234)); - ScopedPtr > scoped_manually_counted_1(manually_counted_1); +TEST(PtrTest, DistinctValuesAreNotEqual) { + Ptr value1 = MakePtr(new int(5)); + Ptr value2 = MakePtr(new int(6)); - Ptr manually_counted_2 = scoped_manually_counted_1.release(); - // Avoid leaks. - ScopedPtr > scoped_manually_counted_2(manually_counted_2); + ASSERT_NE(value1, value2); +} - ASSERT_TRUE(scoped_manually_counted_1.isNull()); - ASSERT_EQ(1234, *manually_counted_2); - ASSERT_EQ(1234, *scoped_manually_counted_2); +TEST(PtrTest, SameValuesAreEqual) { + Ptr value1 = MakePtr(new int(5)); + Ptr value2 = MakePtr(new int(5)); + + ASSERT_EQ(value1, value2); } TEST(PtrTest, ScopedPtr_Release_RefCounted) { @@ -212,20 +116,20 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted) { Ptr ref_counted_2 = scoped_ref_counted_1.release(); - ASSERT_TRUE(scoped_ref_counted_1.isNull()); + ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_2); ASSERT_EQ(1234, *ref_counted_2); } -TEST(PtrTest, ConstifyPtr_ManuallyCounted) { - Ptr manually_counted = MakePtr(new int(1234)); - // Avoid leaks. - ScopedPtr > scoped_manually_counted(manually_counted); +TEST(PtrTest, ScopedPtr_Release_RefCounted_Stay_Valid) { + Ptr ref_counted_1 = MakeRefCountedPtr(new int(1234)); + Ptr ref_counted_2 = ref_counted_1; + ScopedPtr > scoped_ref_counted_1(ref_counted_1); - ConstPtr const_manually_counted = ConstifyPtr(manually_counted); + Ptr ref_counted_3 = scoped_ref_counted_1.release(); - ASSERT_EQ(1234, *const_manually_counted); - ASSERT_EQ(1234, *manually_counted); - ASSERT_EQ(1234, *scoped_manually_counted); + ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_3); + ASSERT_EQ(1234, *ref_counted_2); + ASSERT_EQ(1234, *ref_counted_3); } TEST(PtrTest, ConstifyPtr_RefCounted) { @@ -240,19 +144,6 @@ TEST(PtrTest, ConstifyPtr_RefCounted) { ASSERT_EQ(1234, *const_ref_counted); } -TEST(PtrTest, DowncastPtr_ManuallyCounted) { - Ptr derived = MakePtr(new Derived(1234)); - Ptr base = derived; - // Avoid leaks. - ScopedPtr > scoped_derived(derived); - - Ptr derived_from_downcast = DowncastPtr(base); - - ASSERT_EQ(1234, base->getInt()); - ASSERT_EQ(1234, derived->getInt()); - ASSERT_EQ(1234, derived_from_downcast->getInt()); -} - TEST(PtrTest, DowncastPtr_RefCounted) { Ptr derived = MakeRefCountedPtr(new Derived(1234)); Ptr base = derived; diff --git a/proto/BUILD b/proto/BUILD index 62d9d917..6446d8f9 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -13,12 +13,6 @@ proto_library( deps = ["//logs/proto/logs_annotations"], ) -cc_proto_library( - name = "bootstrap_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":bootstrap_enums_proto"], -) - java_lite_proto_library( name = "bootstrap_enums_java_proto_lite", visibility = [ @@ -35,12 +29,6 @@ proto_library( deps = ["//logs/proto/logs_annotations"], ) -cc_proto_library( - name = "discovery_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":discovery_enums_proto"], -) - java_lite_proto_library( name = "discovery_enums_java_proto_lite", deps = [":discovery_enums_proto"], @@ -62,12 +50,6 @@ proto_library( ], ) -cc_proto_library( - name = "connections_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":connections_enums_proto"], -) - java_lite_proto_library( name = "connections_enums_java_proto_lite", deps = [":connections_enums_proto"], @@ -108,12 +90,6 @@ proto_library( ], ) -cc_proto_library( - name = "setup_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":setup_enums_proto"], -) - java_lite_proto_library( name = "setup_enums_java_proto_lite", deps = [":setup_enums_proto"], @@ -129,12 +105,6 @@ proto_library( ], ) -cc_proto_library( - name = "nearby_client_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":nearby_client_enums_proto"], -) - java_lite_proto_library( name = "nearby_client_enums_java_proto_lite", deps = [":nearby_client_enums_proto"], @@ -181,12 +151,6 @@ proto_library( ], ) -cc_proto_library( - name = "sharing_enums_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":sharing_enums_proto"], -) - java_lite_proto_library( name = "sharing_enums_java_proto_lite", deps = [":sharing_enums_proto"], @@ -202,12 +166,6 @@ proto_library( ], ) -cc_proto_library( - name = "nearby_event_codes_cc_proto", - compatible_with = ["//buildenv/target:appengine"], - deps = [":nearby_event_codes_proto"], -) - java_lite_proto_library( name = "nearby_event_codes_java_proto_lite", deps = [":nearby_event_codes_proto"], diff --git a/proto/connections/BUILD b/proto/connections/BUILD index 90411711..c7c295c2 100644 --- a/proto/connections/BUILD +++ b/proto/connections/BUILD @@ -1,4 +1,3 @@ -load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") proto_library( @@ -10,16 +9,6 @@ proto_library( visibility = ["//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__"], ) -cc_proto_library( - name = "offline_wire_formats_cc_proto", - visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/connection:__subpackages__", - "//javatests/com/google/android/gmscore/integ/modules/nearby/robolectric/connections/src/com/google/android/gms/nearby/connection:__subpackages__", - ], - deps = [":offline_wire_formats_proto"], -) - java_lite_proto_library( name = "offline_wire_formats_java_proto_lite", visibility = [ diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index f4eaaa2a..461d312c 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -1,3 +1,14 @@ +// Any changes in this file maybe cause the unmapped result in the PLX tables. +// Please remember to update the table schemas: +// 1. Check your changes are rolled out in the MPM. +// https://mpmbrowse.corp.google.com/package/location/nearby/lingo +// 2. Check the new lingo job is scheduled and completed. +// https://borgcron-dashboard.corp.google.com/#user=social-copresence-batch +// 3. Runs PLX script to update schema. +// https://plx.corp.google.com/scripts2/script_e1._9eb6f3_e3cd_419b_b483_c1e42abc824a +// +// Or you can wait one or two days then run the above Step3. dircetly. + syntax = "proto2"; package location.nearby.proto.connections; @@ -149,6 +160,7 @@ enum PayloadStatus { REMOTE_CANCELLATION = 8; } +// next_id: 16 // Result of an upgrade attempt. enum BandwidthUpgradeResult { UNKNOWN_BANDWIDTH_UPGRADE_RESULT = 0; @@ -177,10 +189,26 @@ enum BandwidthUpgradeResult { // record analytics (e.g. the client disconnected). UNFINISHED_ERROR = 10; - // TODO(mariaines): add a REMOTE_ERROR when we implement a cancellation + // TODO(b/151833661): add a REMOTE_ERROR when we implement a cancellation // message, for the case when the remote endpoint had an error on their end. + + // Error during setting up Bluetooth. + BLUETOOTH_MEDIUM_ERROR = 11; + + // Error during setting up WIFI Aware. + WIFI_AWARE_MEDIUM_ERROR = 12; + + // Error during setting up WIFI Lan. + WIFI_LAN_MEDIUM_ERROR = 13; + + // Error during setting up WIFI Hotspot. + WIFI_HOTSPOT_MEDIUM_ERROR = 14; + + // Error during setting up WIFI Direct. + WIFI_DIRECT_MEDIUM_ERROR = 15; } +// next_id: 34 // The stage at which an error occurred. enum BandwidthUpgradeErrorStage { UNKNOWN_BANDWIDTH_UPGRADE_ERROR_STAGE = 0; @@ -211,12 +239,16 @@ enum BandwidthUpgradeErrorStage { WIFI_LISTEN_INCOMING = 11; // On the outgoing side, connecting to the hotspot. WIFI_CONNECT_TO_HOTSPOT = 12; + // Creating the WIFI Hotspot EndpointChannel + WIFI_HOTSPOT_SOCKET_CREATION = 28; // WIFI_LAN // On the incoming side, listening for incoming wifi connections. WIFI_LAN_LISTEN_INCOMING = 13; // On the incoming side, invalid (null or loopback) Inet Address. WIFI_LAN_IP_ADDRESS = 14; + // Creating the WIFI Lan EndpointChannel + WIFI_LAN_SOCKET_CREATION = 29; // On the outgoing side, connecting to the local wifi socket. WIFI_LAN_SOCKET_CONNECTION = 15; @@ -229,6 +261,8 @@ enum BandwidthUpgradeErrorStage { BLUETOOTH_CONNECT_OUTGOING = 18; // On the outgoing side, parsing the remote Bluetooth MAC address. BLUETOOTH_PARSE_MAC_ADDRESS = 19; + // Creating the BLUETOOTH EndpointChannel + BLUETOOTH_SOCKET_CREATION = 30; // WIFI_AWARE // On the incoming side, listening for incoming Wifi Aware connections. @@ -239,6 +273,8 @@ enum BandwidthUpgradeErrorStage { WIFI_AWARE_SUBSCRIBE = 22; // On the outgoing side, connecting to the Wifi Aware network. WIFI_AWARE_CONNECT_TO_NETWORK = 23; + // Creating the WIFI Aware EndpointChannel + WIFI_AWARE_SOCKET_CREATION = 31; // WIFI_DIRECT // On the incoming side, listening for incoming Wifi Direct connections. @@ -249,4 +285,10 @@ enum BandwidthUpgradeErrorStage { WIFI_DIRECT_CONNECT_OUTGOING = 26; // On the outgoing side, parsing the remote device address. WIFI_DIRECT_PARSE_DEVICE_ADDRESS = 27; + // Creating the WIFI Direct EndpointChannel + WIFI_DIRECT_SOCKET_CREATION = 32; + + // WEB_RTC + // Creating the WEB_RTC EndpointChannel + WEB_RTC_SOCKET_CREATION = 33; } diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index 71492b95..08423b4e 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -9,7 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; -// NEXT ID: 130 +// NEXT ID: 132 enum DiscoveryEvent { UNKNOWN_DISCOVERY_EVENT = 0; @@ -299,7 +299,8 @@ enum DiscoveryEvent { // containing a bloom filter FAST_PAIR_DEVICE_DETECTED_WITH_BLOOM_FILTER = 103; - // Detected model id was found in the local Fast Pair device database. + // Detected model id was found in the local Fast Pair device database which is + // not populated by the offline service (130 is offline populated). FAST_PAIR_LOCAL_DB_CACHE_HIT = 104; // Detected model id was not found in the local Fast Pair device database, @@ -335,7 +336,7 @@ enum DiscoveryEvent { // scan stack. FAST_PAIR_NOTIFICATION_CLICKED = 113; - // User has seen a battery notification. + // User has seen a battery notification (131 for low battery). FAST_PAIR_BATTERY_NOTIFICATION_SHOWN = 114; // User has dismissed the battery notification. @@ -385,6 +386,13 @@ enum DiscoveryEvent { // A user dismissed event of launching a companion app. FAST_PAIR_POST_ACTION_DISMISS_COMPANION_APP = 129; + // Detected model id was found in the cache which is populated by the offline + // service (104 is the local db cache). + FAST_PAIR_OFFLINE_SERVICE_CACHE_HIT = 130; + + // User has seen a low battery notification. + FAST_PAIR_LOW_BATTERY_NOTIFICATION_SHOWN = 131; + // Deprecated. reserved 65, 67 to 72; } diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index a1df8808..fd517938 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -106,8 +106,8 @@ enum EventType { // Receiver accepts a fast initialization. ACCEPT_FAST_INITIALIZATION = 27; - // Set internet preference. - SET_INTERNET_PREFERENCE = 28; + // Set data usage preference. + SET_DATA_USAGE = 28; } // Status of nearby sharing. @@ -127,8 +127,8 @@ enum Visibility { HIDDEN = 4; } -enum InternetPreference { - UNKNOWN_INTERNET_PREFERENCE = 0; +enum DataUsage { + UNKNOWN_DATA_USAGE = 0; ONLINE = 1; WIFI_ONLY = 2; From b0730bb8c90a084326f5f2e1b469f8cec45c71f0 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 1 Apr 2020 20:33:53 -0700 Subject: [PATCH 04/52] Add direct dependencies as submodules Change-Id: I79c59022a45135891c55eceb155eb3d717344e0b --- .gitmodules | 20 ++++++++++++++++++++ third_party/absl | 1 + third_party/gtest | 1 + third_party/protobuf | 1 + third_party/smhasher | 1 + third_party/ukey2 | 1 + 6 files changed, 25 insertions(+) create mode 100644 .gitmodules create mode 160000 third_party/absl create mode 160000 third_party/gtest create mode 160000 third_party/protobuf create mode 160000 third_party/smhasher create mode 160000 third_party/ukey2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..eb391ac3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,20 @@ +[submodule "third_party/ukey2"] + path = third_party/ukey2 + url = sso://team/nearby-eng/ukey2 + branch = master +[submodule "third_party/protobuf"] + path = third_party/protobuf + url = https://github.com/protocolbuffers/protobuf + branch = master +[submodule "third_party/gtest"] + path = third_party/gtest + url = https://github.com/google/googletest + branch = master +[submodule "third_party/absl"] + path = third_party/absl + url = https://github.com/abseil/abseil-cpp + branch = master +[submodule "third_party/smhasher"] + path = third_party/smhasher + url = https://github.com/aappleby/smhasher + branch = master diff --git a/third_party/absl b/third_party/absl new file mode 160000 index 00000000..62f05b1f --- /dev/null +++ b/third_party/absl @@ -0,0 +1 @@ +Subproject commit 62f05b1f57ad660e9c09e02ce7d591dcc4d0ca08 diff --git a/third_party/gtest b/third_party/gtest new file mode 160000 index 00000000..61f010d7 --- /dev/null +++ b/third_party/gtest @@ -0,0 +1 @@ +Subproject commit 61f010d703b32de9bfb20ab90ece38ab2f25977f diff --git a/third_party/protobuf b/third_party/protobuf new file mode 160000 index 00000000..c6493970 --- /dev/null +++ b/third_party/protobuf @@ -0,0 +1 @@ +Subproject commit c6493970296fa5c5b4a81a37248a328579fe9662 diff --git a/third_party/smhasher b/third_party/smhasher new file mode 160000 index 00000000..61a0530f --- /dev/null +++ b/third_party/smhasher @@ -0,0 +1 @@ +Subproject commit 61a0530f28277f2e850bfc39600ce61d02b518de diff --git a/third_party/ukey2 b/third_party/ukey2 new file mode 160000 index 00000000..2fc30c88 --- /dev/null +++ b/third_party/ukey2 @@ -0,0 +1 @@ +Subproject commit 2fc30c8894da17442c476d9416b5a811bfe88e32 From 7466db88c09f3495ba8c0d8fae6a21d92df00778 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 1 Apr 2020 20:25:03 -0700 Subject: [PATCH 05/52] Add CMake support Change-Id: I716e1fa3ec43bfe57326f5a691131505f84b7832 --- .gitignore | 1 + CMakeLists.txt | 48 ++++++++++++++ cmake/CMakeLists-smhasher.txt | 14 +++++ cmake/local_build_protobuf.cmake | 28 +++++++++ cmake/local_build_setup.cmake | 12 ++++ cmake/local_setup_smhasher.cmake | 22 +++++++ cmake/proto_defs.cmake | 28 +++++++++ cpp/core/CMakeLists.txt | 58 +++++++++++++++++ cpp/core/internal/CMakeLists.txt | 80 ++++++++++++++++++++++++ cpp/core/internal/mediums/CMakeLists.txt | 57 +++++++++++++++++ cpp/platform/CMakeLists.txt | 79 +++++++++++++++++++++++ cpp/platform/api/CMakeLists.txt | 32 ++++++++++ cpp/platform/impl/default/CMakeLists.txt | 53 ++++++++++++++++ cpp/platform/impl/sample/CMakeLists.txt | 18 ++++++ cpp/platform/port/CMakeLists.txt | 30 +++++++++ proto/CMakeLists.txt | 49 +++++++++++++++ proto/connections/CMakeLists.txt | 5 ++ 17 files changed, 614 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/CMakeLists-smhasher.txt create mode 100644 cmake/local_build_protobuf.cmake create mode 100644 cmake/local_build_setup.cmake create mode 100644 cmake/local_setup_smhasher.cmake create mode 100644 cmake/proto_defs.cmake create mode 100644 cpp/core/CMakeLists.txt create mode 100644 cpp/core/internal/CMakeLists.txt create mode 100644 cpp/core/internal/mediums/CMakeLists.txt create mode 100644 cpp/platform/CMakeLists.txt create mode 100644 cpp/platform/api/CMakeLists.txt create mode 100644 cpp/platform/impl/default/CMakeLists.txt create mode 100644 cpp/platform/impl/sample/CMakeLists.txt create mode 100644 cpp/platform/port/CMakeLists.txt create mode 100644 proto/CMakeLists.txt create mode 100644 proto/connections/CMakeLists.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f65519e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/** diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..dfdbfc18 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.13) + +project(nearby CXX) + +option(nearby_USE_LOCAL_PROTOBUF + "Use local copy of protobuf library and compiler" OFF) + +option(nearby_USE_LOCAL_ABSL + "Use local copy of abseil-cpp library" OFF) + +# target_sources() may convert relative paths to absolute +cmake_policy(SET CMP0076 NEW) + +set (CMAKE_CXX_STANDARD 17) +set (CMAKE_CXX_STANDARD_REQUIRED ON) + +include(cmake/proto_defs.cmake) +include(cmake/local_build_setup.cmake) + +if (nearby_USE_LOCAL_PROTOBUF) + include(cmake/local_build_protobuf.cmake) +endif() + +include(cmake/local_setup_smhasher.cmake) + +find_package(Protobuf REQUIRED) + +enable_testing() + +if (NOT TARGET ukey2) +add_subdirectory(third_party/ukey2) +endif() +if (NOT TARGET gtest) +add_subdirectory(third_party/gtest) +endif() +if (nearby_USE_LOCAL_ABSL) + if (NOT TARGET absl::base) + add_subdirectory(third_party/absl) + endif() +else() + find_package(absl REQUIRED) +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/cpp) + +add_subdirectory(cpp/core) +add_subdirectory(cpp/platform) +add_subdirectory(proto) diff --git a/cmake/CMakeLists-smhasher.txt b/cmake/CMakeLists-smhasher.txt new file mode 100644 index 00000000..08761ae1 --- /dev/null +++ b/cmake/CMakeLists-smhasher.txt @@ -0,0 +1,14 @@ +project(smhasher CXX) + +cmake_minimum_required(VERSION 3.13) + +add_library(smhasher_murmur3 STATIC + cpp/src/smhasher/MurmurHash3.cpp +) + +target_include_directories(smhasher_murmur3 + PUBLIC + cpp/include + PRIVATE + cpp/include/smhasher +) diff --git a/cmake/local_build_protobuf.cmake b/cmake/local_build_protobuf.cmake new file mode 100644 index 00000000..c10f38d3 --- /dev/null +++ b/cmake/local_build_protobuf.cmake @@ -0,0 +1,28 @@ +if (NOT EXISTS ${TOOLS_INSTALL_PREFIX}/bin/protoc) + set(PKG_BUILD_ROOT ${TOOLS_BUILD_ROOT}/protobuf) + set(PKG_SRC_ROOT ${CMAKE_SOURCE_DIR}/third_party/protobuf) + execute_process( + COMMAND mkdir -p ${PKG_BUILD_ROOT} + ) + execute_process( + COMMAND cmake ${PKG_SRC_ROOT}/cmake + WORKING_DIRECTORY ${PKG_BUILD_ROOT} + ) + execute_process( + COMMAND make -j${N_CPUS} + WORKING_DIRECTORY ${PKG_BUILD_ROOT} + ) + execute_process( + COMMAND make check + WORKING_DIRECTORY ${PKG_BUILD_ROOT} + RESULT_VARIABLE test_exit_code + ERROR_QUIET + ) + if (NOT ${test_exit_code} EQUAL "0") + message(FATAL_ERROR "Protobuf tests failed; can't use this protobuf") + endif() + execute_process( + COMMAND /bin/bash -c "DESTDIR=${TOOLS_INSTALL_ROOT} make install" + WORKING_DIRECTORY ${PKG_BUILD_ROOT} + ) +endif() diff --git a/cmake/local_build_setup.cmake b/cmake/local_build_setup.cmake new file mode 100644 index 00000000..dd85a0a9 --- /dev/null +++ b/cmake/local_build_setup.cmake @@ -0,0 +1,12 @@ +include(ProcessorCount) +ProcessorCount(N_CPUS) + +if (N_CPUS EQUAL 0) + set (N_CPUS 1) +endif() + +set (TOOLS_ROOT ${CMAKE_BINARY_DIR}/stage) +set (TOOLS_BUILD_ROOT ${TOOLS_ROOT}/build) +set (TOOLS_INSTALL_ROOT ${TOOLS_ROOT}/install) +set (TOOLS_INSTALL_PREFIX ${TOOLS_INSTALL_ROOT}/usr/local) +set (CMAKE_FIND_ROOT_PATH ${TOOLS_INSTALL_ROOT}) diff --git a/cmake/local_setup_smhasher.cmake b/cmake/local_setup_smhasher.cmake new file mode 100644 index 00000000..cb4164dd --- /dev/null +++ b/cmake/local_setup_smhasher.cmake @@ -0,0 +1,22 @@ +set(PKG_STAGE_SRC_ROOT ${TOOLS_ROOT}/src/smhasher) +if (NOT EXISTS ${PKG_STAGE_SRC_ROOT}/CMakeLists.txt) + set(PKG_SRC_ROOT ${PROJECT_SOURCE_DIR}/third_party/smhasher) + execute_process( + COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher + ) + execute_process( + COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher + ) + execute_process( + COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.cpp ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher + ) + execute_process( + COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.h ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher + ) + execute_process( + COMMAND cp cmake/CMakeLists-smhasher.txt ${PKG_STAGE_SRC_ROOT}/CMakeLists.txt + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + ) +endif() + +add_subdirectory(${PKG_STAGE_SRC_ROOT}) diff --git a/cmake/proto_defs.cmake b/cmake/proto_defs.cmake new file mode 100644 index 00000000..d9a0d1cb --- /dev/null +++ b/cmake/proto_defs.cmake @@ -0,0 +1,28 @@ +function(add_cc_proto_library NAME) + set(single) + set(multi_args PROTOS INCS DEPS) + cmake_parse_arguments(PARSE_ARGV 1 args "" "${single}" "${multi_args}") + + protobuf_generate( + PROTOS ${args_PROTOS} + LANGUAGE cpp + OUT_VAR ${NAME}_var + ) + + add_library(${NAME} + ${${NAME}_var} + ) + + target_link_libraries(${NAME} + PUBLIC + ${Protobuf_LIBRARIES} + ${args_DEPS} + ) + + target_include_directories(${NAME} + PUBLIC + ${Protobuf_INCLUDE_DIRS} + ${args_INCS} + ${CMAKE_CURRENT_BINARY_DIR} + ) +endfunction() diff --git a/cpp/core/CMakeLists.txt b/cpp/core/CMakeLists.txt new file mode 100644 index 00000000..8f2a70c0 --- /dev/null +++ b/cpp/core/CMakeLists.txt @@ -0,0 +1,58 @@ +add_library(core STATIC) + +target_sources(core + PUBLIC + core.h +) + +target_include_directories(core + PUBLIC + ${PROJECT_SOURCE_DIR}/cpp +) + +target_link_libraries(core + PUBLIC + core_internal + platform_types +) + +add_library(core_types STATIC) + +target_sources(core_types + PRIVATE + payload.cc + strategy.cc + PUBLIC + listeners.h + options.h + params.h + payload.h + status.h + strategy.h +) + +target_link_libraries(core_types + PUBLIC + platform_api + platform_port_string + platform_types + platform_utils +) + +add_executable(core_build_test + check_compilation.cc +) + +target_link_libraries(core_build_test + PUBLIC + absl::strings + core + core_types + platform_impl_default_lock + platform_impl_sample + platform_port_string + platform_types + platform_utils +) + +add_subdirectory(internal) diff --git a/cpp/core/internal/CMakeLists.txt b/cpp/core/internal/CMakeLists.txt new file mode 100644 index 00000000..e9cc7350 --- /dev/null +++ b/cpp/core/internal/CMakeLists.txt @@ -0,0 +1,80 @@ +add_library(core_internal STATIC) + +target_sources(core_internal + PRIVATE + ble_advertisement.cc + bluetooth_device_name.cc + internal_payload.cc + internal_payload.h + loop_runner.cc + loop_runner.h + offline_frames.cc + offline_frames.h + PUBLIC + bandwidth_upgrade_handler.h + bandwidth_upgrade_manager.h + base_bandwidth_upgrade_handler.h + base_endpoint_channel.h + base_pcp_handler.h + ble_advertisement.h + ble_compat.h + ble_endpoint_channel.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_factory.h + medium_manager.h + offline_service_controller.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 + wifi_lan_upgrade_handler.h +) + +target_link_libraries(core_internal + PUBLIC + absl::strings + core_internal_mediums + core_types + platform_api + platform_port_down_cast + platform_port_string + platform_types + platform_utils + proto_connections_enums_cc_proto + proto_offline_wire_formats_cc_proto + ukey2 +) + +add_executable(core_internal_test + bluetooth_device_name_test.cc + ble_advertisement_test.cc +) + +add_test( + NAME core_internal_test + COMMAND core_internal_test +) + +target_link_libraries(core_internal_test + PUBLIC + core_internal + gtest + gtest_main + platform_impl_default_cond_var + platform_impl_default_lock + platform_port_string + platform_utils +) + +add_subdirectory(mediums) diff --git a/cpp/core/internal/mediums/CMakeLists.txt b/cpp/core/internal/mediums/CMakeLists.txt new file mode 100644 index 00000000..e9d3b84b --- /dev/null +++ b/cpp/core/internal/mediums/CMakeLists.txt @@ -0,0 +1,57 @@ +add_library(core_internal_mediums STATIC) + +target_sources(core_internal_mediums + PRIVATE + ble_advertisement.cc + ble_advertisement_header.cc + ble_packet.cc + ble_peripheral.cc + utils.cc + utils.h + PUBLIC + advertisement_read_result.h + ble.h + ble_advertisement.h + ble_advertisement_header.h + ble_packet.h + ble_peripheral.h + ble_v2.h + bloom_filter.h + bluetooth_classic.h + bluetooth_radio.h + discovered_peripheral_callback.h + discovered_peripheral_tracker.h + lost_entity_tracker.h + mediums.h + uuid.h +) + +target_link_libraries(core_internal_mediums + PUBLIC + absl::numeric + absl::strings + platform_api + platform_port_string + platform_types + platform_utils + smhasher_murmur3 +) + +add_executable(core_internal_mediums_test + advertisement_read_result_test.cc + ble_advertisement_header_test.cc + ble_advertisement_test.cc + ble_packet_test.cc + bloom_filter_test.cc + lost_entity_tracker_test.cc +) + +target_link_libraries(core_internal_mediums_test + PUBLIC + absl::time + core_internal_mediums + gtest + gtest_main + platform_impl_default + platform_utils +) diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt new file mode 100644 index 00000000..de8405cc --- /dev/null +++ b/cpp/platform/CMakeLists.txt @@ -0,0 +1,79 @@ +add_library(platform_utils STATIC + base64_utils.cc + file_impl.cc + prng.cc + reliability_utils.cc +) + +target_sources(platform_utils + PUBLIC + base64_utils.h + cancelable_alarm.h + file_impl.h + pipe.h + prng.h + reliability_utils.h + synchronized.h +) + +target_link_libraries(platform_utils + PUBLIC + platform_types + platform_api + absl::strings +) + +add_library(platform_types STATIC + ptr.cc +) + +target_sources(platform_types + PUBLIC + byte_array.h + callable.h + cancelable.h + container_of.h + exception.h + logging.h + ptr.h + runnable.h +) + +target_link_libraries(platform_types + PUBLIC + absl::base + absl::strings +) + +add_executable(platform_test + byte_array_test.cc + container_of_test.cc + file_impl_test.cc + pipe_test.cc + prng_test.cc + ptr_test.cc +) + +target_link_libraries(platform_test + PUBLIC + absl::base + absl::strings + absl::time + gtest + gtest_main + platform_api + platform_impl_default_cond_var + platform_impl_default_lock + platform_types + platform_utils +) + +add_test( + NAME platform_test + COMMAND platform_test +) + +add_subdirectory(api) +add_subdirectory(impl/sample) +add_subdirectory(impl/default) +add_subdirectory(port) diff --git a/cpp/platform/api/CMakeLists.txt b/cpp/platform/api/CMakeLists.txt new file mode 100644 index 00000000..35cd7d06 --- /dev/null +++ b/cpp/platform/api/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(platform_api STATIC + atomic_boolean.h + atomic_reference.h + ble.h + ble_v2.h + bluetooth_adapter.h + bluetooth_classic.h + condition_variable.h + count_down_latch.h + executor.h + future.h + hash_utils.h + input_file.h + input_stream.h + lock.h + multi_thread_executor.h + output_file.h + output_stream.h + scheduled_executor.h + settable_future.h + single_thread_executor.h + socket.h + submittable_executor.h + system_clock.h + thread_utils.h + wifi.h +) + +target_link_libraries(platform_api + PUBLIC + platform_types +) diff --git a/cpp/platform/impl/default/CMakeLists.txt b/cpp/platform/impl/default/CMakeLists.txt new file mode 100644 index 00000000..353b2be0 --- /dev/null +++ b/cpp/platform/impl/default/CMakeLists.txt @@ -0,0 +1,53 @@ +add_library(platform_impl_default STATIC) + +target_sources(platform_impl_default + PRIVATE + default_condition_variable.cc + default_lock.cc + default_platform.cc + PUBLIC + default_condition_variable.h + default_lock.h + default_platform.h +) + +target_include_directories(platform_impl_default + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(platform_impl_default + PUBLIC + platform_api + platform_types +) + +add_library(platform_impl_default_lock STATIC) + +target_sources(platform_impl_default_lock + PRIVATE + default_lock.cc + PUBLIC + default_lock.h +) + +target_link_libraries(platform_impl_default_lock + PUBLIC + platform_api +) + +add_library(platform_impl_default_cond_var STATIC) + +target_sources(platform_impl_default_cond_var + PRIVATE + default_condition_variable.cc + PUBLIC + default_condition_variable.h +) + +target_link_libraries(platform_impl_default_cond_var + PUBLIC + platform_api + platform_impl_default_lock + platform_types +) diff --git a/cpp/platform/impl/sample/CMakeLists.txt b/cpp/platform/impl/sample/CMakeLists.txt new file mode 100644 index 00000000..0944ac4f --- /dev/null +++ b/cpp/platform/impl/sample/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(platform_impl_sample STATIC) + +target_sources(platform_impl_sample + PRIVATE + sample_wifi_medium.cc + PUBLIC + sample_platform.h + sample_wifi_medium.h +) + +target_link_libraries(platform_impl_sample + PUBLIC + absl::time + platform_api + platform_port_string + platform_types + platform_utils +) diff --git a/cpp/platform/port/CMakeLists.txt b/cpp/platform/port/CMakeLists.txt new file mode 100644 index 00000000..18dbe0c3 --- /dev/null +++ b/cpp/platform/port/CMakeLists.txt @@ -0,0 +1,30 @@ +add_library(platform_port_config_private INTERFACE) + +target_sources(platform_port_config_private + INTERFACE + config.h +) + +add_library(platform_port_string INTERFACE) + +target_sources(platform_port_string + INTERFACE + string.h +) + +target_link_libraries(platform_port_string + INTERFACE + platform_port_config_private +) + +add_library(platform_port_down_cast INTERFACE) + +target_sources(platform_port_down_cast + INTERFACE + down_cast.h +) + +target_link_libraries(platform_port_down_cast + INTERFACE + platform_port_config_private +) diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt new file mode 100644 index 00000000..7eb4378b --- /dev/null +++ b/proto/CMakeLists.txt @@ -0,0 +1,49 @@ +add_cc_proto_library( + proto_bootstrap_enums_cc_proto + PROTOS bootstrap_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_connections_enums_cc_proto + PROTOS connections_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_discovery_enums_cc_proto + PROTOS discovery_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_magic_pair_enums_cc_proto + PROTOS magic_pair_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_nearby_client_enums_cc_proto + PROTOS nearby_client_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_nearby_event_codes_cc_proto + PROTOS nearby_event_codes.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_setup_enums_cc_proto + PROTOS setup_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_cc_proto_library( + proto_sharing_enums_cc_proto + PROTOS sharing_enums.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/.. +) + +add_subdirectory(connections) diff --git a/proto/connections/CMakeLists.txt b/proto/connections/CMakeLists.txt new file mode 100644 index 00000000..0b36c455 --- /dev/null +++ b/proto/connections/CMakeLists.txt @@ -0,0 +1,5 @@ +add_cc_proto_library( + proto_offline_wire_formats_cc_proto + PROTOS offline_wire_formats.proto + INCS ${CMAKE_CURRENT_BINARY_DIR}/../.. +) From a067c89eca763b1e0ccb8bcdba7cd291c9fada04 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Sat, 4 Apr 2020 12:40:58 -0700 Subject: [PATCH 06/52] proto: delete incompatible proto options Change-Id: I57aa91e02675891015e108bf3b6a225c823bd4f4 --- proto/bootstrap_enums.proto | 4 ---- proto/connections_enums.proto | 4 ---- proto/discovery_enums.proto | 4 ---- proto/magic_pair_enums.proto | 4 ---- proto/nearby_client_enums.proto | 4 ---- proto/nearby_event_codes.proto | 4 ---- proto/setup_enums.proto | 4 ---- proto/sharing_enums.proto | 4 ---- 8 files changed, 32 deletions(-) diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto index 9c378983..71bcfad6 100644 --- a/proto/bootstrap_enums.proto +++ b/proto/bootstrap_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "BootstrapEnums"; diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index f4eaaa2a..d32d6be2 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto.connections; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "ConnectionsEnums"; option objc_class_prefix = "GNCP"; diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index 71492b95..b9d6d3e7 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 51eef973..275be438 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "MagicPairEnums"; option objc_class_prefix = "GNCP"; diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 59dc9116..38ce9ba6 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyClientEnums"; option objc_class_prefix = "GNCP"; diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto index 91610ae5..9ee412a0 100644 --- a/proto/nearby_event_codes.proto +++ b/proto/nearby_event_codes.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyEventCodes"; diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto index 2bb334ca..79880c0f 100644 --- a/proto/setup_enums.proto +++ b/proto/setup_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto.setup; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SetupEnums"; option objc_class_prefix = "GNSP"; diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index a1df8808..725db3c5 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -2,10 +2,6 @@ syntax = "proto2"; package location.nearby.proto.sharing; -import "logs/proto/logs_annotations/logs_annotations.proto"; - -option (logs_proto.file_not_used_for_logging_except_enums) = true; -option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SharingEnums"; option objc_class_prefix = "GNSHP"; From 18c09c5abe14905cb91f1e05ff36221b0471f527 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 2 Apr 2020 14:35:31 -0700 Subject: [PATCH 07/52] Fix build and tests Signed-off-by: Alexey Polyudov Change-Id: I3671e40fd0f8f028e3c8e46c4a47de0d2cc9aeff --- cpp/core/check_compilation.cc | 5 +++++ cpp/core/internal/ble_advertisement.cc | 2 +- cpp/core/internal/mediums/CMakeLists.txt | 5 +++++ .../mediums/advertisement_read_result_test.cc | 10 ++++++++++ cpp/core/internal/mediums/ble_advertisement.cc | 2 ++ .../internal/mediums/ble_advertisement_header.cc | 2 ++ cpp/core/internal/mediums/ble_packet.cc | 1 + cpp/core/internal/offline_frames.h | 2 +- cpp/platform/CMakeLists.txt | 1 + cpp/platform/base64_utils.cc | 15 ++++++++------- cpp/platform/base64_utils.h | 8 +------- cpp/platform/byte_array.h | 8 ++------ cpp/platform/file_impl_test.cc | 8 ++++---- cpp/platform/pipe_test.cc | 2 +- cpp/platform/port/config.h | 4 ++-- 15 files changed, 46 insertions(+), 29 deletions(-) diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index 23941a86..3d62e379 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -117,3 +117,8 @@ void check_compilation() { } // namespace connections } // namespace nearby } // namespace location + +int main() { + location::nearby::connections::check_compilation(); + return 0; +} diff --git a/cpp/core/internal/ble_advertisement.cc b/cpp/core/internal/ble_advertisement.cc index e97ba86b..aaf602f9 100644 --- a/cpp/core/internal/ble_advertisement.cc +++ b/cpp/core/internal/ble_advertisement.cc @@ -186,7 +186,7 @@ std::string BLEAdvertisement::hexBytesToColonDelimitedString( ConstPtr hex_bytes) { // Convert the hex bytes to a string. std::string colon_delimited_string(absl::BytesToHexString( - std::string(hex_bytes->getData(), hex_bytes->size()))); + hex_bytes->asString())); absl::AsciiStrToUpper(&colon_delimited_string); // Insert the colons. diff --git a/cpp/core/internal/mediums/CMakeLists.txt b/cpp/core/internal/mediums/CMakeLists.txt index e9d3b84b..311f7084 100644 --- a/cpp/core/internal/mediums/CMakeLists.txt +++ b/cpp/core/internal/mediums/CMakeLists.txt @@ -55,3 +55,8 @@ target_link_libraries(core_internal_mediums_test platform_impl_default platform_utils ) + +add_test( + NAME core_internal_mediums_test + COMMAND core_internal_mediums_test +) diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index dd3e7c8b..7ef20ffd 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -38,6 +38,16 @@ const absl::Duration kAdvertisementMaxBackoffDuration = absl::Milliseconds(6000); // 6 seconds const char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; +template<> +const std::int64_t AdvertisementReadResult< + SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = + absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); + +template<> +const std::int64_t AdvertisementReadResult< + SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = + absl::ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); + TEST(AdvertisementReadResultTest, AdvertisementExists) { AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); diff --git a/cpp/core/internal/mediums/ble_advertisement.cc b/cpp/core/internal/mediums/ble_advertisement.cc index 050c51a1..e83d8d3b 100644 --- a/cpp/core/internal/mediums/ble_advertisement.cc +++ b/cpp/core/internal/mediums/ble_advertisement.cc @@ -1,5 +1,7 @@ #include "core/internal/mediums/ble_advertisement.h" +#include + #include "platform/logging.h" namespace location { diff --git a/cpp/core/internal/mediums/ble_advertisement_header.cc b/cpp/core/internal/mediums/ble_advertisement_header.cc index e433877d..9935e9b3 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -1,5 +1,7 @@ #include "core/internal/mediums/ble_advertisement_header.h" +#include + #include "platform/base64_utils.h" #include "platform/byte_array.h" #include "platform/logging.h" diff --git a/cpp/core/internal/mediums/ble_packet.cc b/cpp/core/internal/mediums/ble_packet.cc index 3f5fad02..be7a9bb8 100644 --- a/cpp/core/internal/mediums/ble_packet.cc +++ b/cpp/core/internal/mediums/ble_packet.cc @@ -1,5 +1,6 @@ #include "core/internal/mediums/ble_packet.h" +#include #include #include "platform/logging.h" diff --git a/cpp/core/internal/offline_frames.h b/cpp/core/internal/offline_frames.h index 425bac06..e29699bf 100644 --- a/cpp/core/internal/offline_frames.h +++ b/cpp/core/internal/offline_frames.h @@ -13,7 +13,7 @@ // Detects the right usage. #include "google/protobuf/message_lite.h" -#define proto_ns google3_proto_compat +#define proto_ns google::protobuf namespace location { diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt index de8405cc..f7a3c1d8 100644 --- a/cpp/platform/CMakeLists.txt +++ b/cpp/platform/CMakeLists.txt @@ -59,6 +59,7 @@ target_link_libraries(platform_test absl::base absl::strings absl::time + gmock gtest gtest_main platform_api diff --git a/cpp/platform/base64_utils.cc b/cpp/platform/base64_utils.cc index 51cb5635..2cb03359 100644 --- a/cpp/platform/base64_utils.cc +++ b/cpp/platform/base64_utils.cc @@ -1,6 +1,5 @@ #include "platform/base64_utils.h" -#include "strings/escaping.h" #include "absl/strings/escaping.h" namespace location { @@ -10,8 +9,7 @@ std::string Base64Utils::encode(ConstPtr bytes) { std::string base64_string; if (!bytes.isNull()) { - absl::WebSafeBase64Escape(std::string(bytes->getData(), bytes->size()), - &base64_string); + absl::WebSafeBase64Escape(bytes->asString(), &base64_string); } return base64_string; @@ -19,8 +17,7 @@ std::string Base64Utils::encode(ConstPtr bytes) { std::string Base64Utils::encode(const ByteArray& bytes) { std::string base64_string; - absl::WebSafeBase64Escape(std::string(bytes.getData(), bytes.size()), - &base64_string); + absl::WebSafeBase64Escape(bytes.asString(), &base64_string); return base64_string; } @@ -39,7 +36,7 @@ Ptr Base64Utils::decode(const std::string& base64_string) { return Ptr(); } - return MakePtr(new ByteArray(decoded_string.data(), decoded_string.size())); + return MakePtr(new ByteArray(decoded_string)); } template<> @@ -49,7 +46,11 @@ ByteArray Base64Utils::decode(const std::string& base64_string) { return ByteArray(); } - return ByteArray(decoded_string.data(), decoded_string.size()); + return ByteArray(decoded_string); +} + +Ptr Base64Utils::decode(const std::string& base64_string) { + return decode>(base64_string); } } // namespace nearby diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h index 76b8cb7d..70704b82 100644 --- a/cpp/platform/base64_utils.h +++ b/cpp/platform/base64_utils.h @@ -16,13 +16,7 @@ class Base64Utils { template static T decode(const std::string& base64_string); - template <> - Ptr decode(const std::string& base64_string); - template <> - ByteArray decode(const std::string& base64_string); - static Ptr decode(const std::string& base64_string) { - return decode>(base64_string); - } + static Ptr decode(const std::string& base64_string); }; } // namespace nearby diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h index a3ea830e..49f9bf88 100644 --- a/cpp/platform/byte_array.h +++ b/cpp/platform/byte_array.h @@ -43,15 +43,11 @@ class ByteArray { // Operator overloads when comparing ConstPtr. bool operator==(const ByteArray& rhs) const { - return this->size() == rhs.size() && - memcmp(this->getData(), rhs.getData(), this->size()) == 0; + return this->data_ == rhs.data_; } bool operator!=(const ByteArray& rhs) const { return !(*this == rhs); } bool operator<(const ByteArray& rhs) const { - if (this->size() != rhs.size()) { - return this->size() < rhs.size(); - } - return memcmp(this->getData(), rhs.getData(), this->size()) < 0; + return this->data_ < rhs.data_; } // TODO(b/149869249) : rename according to go/c-style std::string asString() const { return data_; } diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc index f1397d5c..f37e0b91 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/file_impl_test.cc @@ -1,21 +1,22 @@ #include "platform/file_impl.h" +#include #include #include #include #include +#include -#include "file/util/temp_path.h" #include "gtest/gtest.h" namespace location { namespace nearby { + class FileImplTest : public ::testing::Test { protected: void SetUp() override { - temp_path_ = std::make_unique(TempPath::Local); - path_ = temp_path_->path() + "/file.txt"; + path_ = std::tmpnam(nullptr);; std::ofstream output_file(path_); file_ = std::fstream(path_, std::fstream::in | std::fstream::out); } @@ -43,7 +44,6 @@ class FileImplTest : public ::testing::Test { static const int64_t kMaxSize = 3; - std::unique_ptr temp_path_; std::string path_; std::fstream file_; size_t size_ = 0; diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc index 35b97a2d..f4f2799b 100644 --- a/cpp/platform/pipe_test.cc +++ b/cpp/platform/pipe_test.cc @@ -11,7 +11,7 @@ #include "platform/ptr.h" #include "platform/runnable.h" #include "gtest/gtest.h" -#include "absl/time/time.h" +#include "absl/time/clock.h" namespace location { namespace nearby { diff --git a/cpp/platform/port/config.h b/cpp/platform/port/config.h index 841168b0..19857578 100644 --- a/cpp/platform/port/config.h +++ b/cpp/platform/port/config.h @@ -12,11 +12,11 @@ // #endif #ifndef NEARBY_USE_STD_STRING -#define NEARBY_USE_STD_STRING 0 +#define NEARBY_USE_STD_STRING 1 #endif #ifndef NEARBY_USE_RTTI -#define NEARBY_USE_RTTI 1 +#define NEARBY_USE_RTTI 0 #endif #endif // PLATFORM_PORT_CONFIG_H_ From eb3c2499a49a1e3a541ef3272b09aeca957f3226 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Sun, 5 Apr 2020 12:08:37 -0700 Subject: [PATCH 08/52] add depot_tools submodule Signed-off-by: Alexey Polyudov Change-Id: Ic7bdf8d36c39b14a5a0922b01a6ea64df066a1bb --- .gitmodules | 4 ++++ third_party/depot_tools | 1 + 2 files changed, 5 insertions(+) create mode 160000 third_party/depot_tools diff --git a/.gitmodules b/.gitmodules index eb391ac3..92e5c3e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,7 @@ path = third_party/smhasher url = https://github.com/aappleby/smhasher branch = master +[submodule "third_party/depot_tools"] + path = third_party/depot_tools + url = https://chromium.googlesource.com/chromium/tools/depot_tools.git + branch = master diff --git a/third_party/depot_tools b/third_party/depot_tools new file mode 160000 index 00000000..19d4809e --- /dev/null +++ b/third_party/depot_tools @@ -0,0 +1 @@ +Subproject commit 19d4809e112652f918494840bab819603b0a2816 From fef714be4eebaf0461c629eb8aa6539bda2b7ce0 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Sat, 4 Apr 2020 13:05:18 -0700 Subject: [PATCH 09/52] Update build scripts Change-Id: I52f7a35324c6be29d7e80d5cc0e6084b49bcc0eb --- cpp/core/internal/CMakeLists.txt | 8 ++++- cpp/platform/CMakeLists.txt | 6 ++-- cpp/platform/api/CMakeLists.txt | 4 +++ cpp/platform/api2/CMakeLists.txt | 41 ++++++++++++++++++++++++ cpp/platform/impl/default/CMakeLists.txt | 6 ++-- 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 cpp/platform/api2/CMakeLists.txt diff --git a/cpp/core/internal/CMakeLists.txt b/cpp/core/internal/CMakeLists.txt index e9cc7350..752732ed 100644 --- a/cpp/core/internal/CMakeLists.txt +++ b/cpp/core/internal/CMakeLists.txt @@ -9,7 +9,7 @@ target_sources(core_internal loop_runner.cc loop_runner.h offline_frames.cc - offline_frames.h + wifi_lan_service_info.cc PUBLIC bandwidth_upgrade_handler.h bandwidth_upgrade_manager.h @@ -28,6 +28,7 @@ target_sources(core_internal endpoint_manager.h internal_payload_factory.h medium_manager.h + offline_frames.h offline_service_controller.h p2p_cluster_pcp_handler.h p2p_point_to_point_pcp_handler.h @@ -57,8 +58,11 @@ target_link_libraries(core_internal ) add_executable(core_internal_test + base_endpoint_channel_test.cc bluetooth_device_name_test.cc ble_advertisement_test.cc + offline_frames_test.cc + wifi_lan_service_info_test.cc ) add_test( @@ -69,8 +73,10 @@ add_test( target_link_libraries(core_internal_test PUBLIC core_internal + gmock gtest gtest_main + platform_impl_default platform_impl_default_cond_var platform_impl_default_lock platform_port_string diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt index f7a3c1d8..bac031db 100644 --- a/cpp/platform/CMakeLists.txt +++ b/cpp/platform/CMakeLists.txt @@ -23,9 +23,7 @@ target_link_libraries(platform_utils absl::strings ) -add_library(platform_types STATIC - ptr.cc -) +add_library(platform_types STATIC) target_sources(platform_types PUBLIC @@ -48,6 +46,7 @@ target_link_libraries(platform_types add_executable(platform_test byte_array_test.cc container_of_test.cc + exception_test.cc file_impl_test.cc pipe_test.cc prng_test.cc @@ -75,6 +74,7 @@ add_test( ) add_subdirectory(api) +add_subdirectory(api2) add_subdirectory(impl/sample) add_subdirectory(impl/default) add_subdirectory(port) diff --git a/cpp/platform/api/CMakeLists.txt b/cpp/platform/api/CMakeLists.txt index 35cd7d06..59343794 100644 --- a/cpp/platform/api/CMakeLists.txt +++ b/cpp/platform/api/CMakeLists.txt @@ -12,18 +12,22 @@ add_library(platform_api STATIC hash_utils.h input_file.h input_stream.h + listenable_future.h lock.h multi_thread_executor.h output_file.h output_stream.h scheduled_executor.h + server_sync.h settable_future.h single_thread_executor.h socket.h submittable_executor.h system_clock.h thread_utils.h + webrtc.h wifi.h + wifi_lan.h ) target_link_libraries(platform_api diff --git a/cpp/platform/api2/CMakeLists.txt b/cpp/platform/api2/CMakeLists.txt new file mode 100644 index 00000000..64e8a7a7 --- /dev/null +++ b/cpp/platform/api2/CMakeLists.txt @@ -0,0 +1,41 @@ +add_library(platform_api2 STATIC) + +target_sources(platform_api2 + PUBLIC + atomic_boolean.h + atomic_reference.h + ble.h + ble_v2.h + bluetooth_adapter.h + bluetooth_classic.h + condition_variable.h + count_down_latch.h + executor.h + future.h + hash_utils.h + input_file.h + input_stream.h + listenable_future.h + multi_thread_executor.h + mutex.h + output_file.h + output_stream.h + scheduled_executor.h + server_sync.h + settable_future.h + single_thread_executor.h + socket.h + submittable_executor.h + system_clock.h + thread_utils.h + webrtc.h + wifi.h +) + +target_link_libraries(platform_api2 + PUBLIC + absl::strings + absl::time + platform_types + webrtc_api_libjingle_peerconnection_api +) diff --git a/cpp/platform/impl/default/CMakeLists.txt b/cpp/platform/impl/default/CMakeLists.txt index 353b2be0..222d9ae0 100644 --- a/cpp/platform/impl/default/CMakeLists.txt +++ b/cpp/platform/impl/default/CMakeLists.txt @@ -2,12 +2,8 @@ add_library(platform_impl_default STATIC) target_sources(platform_impl_default PRIVATE - default_condition_variable.cc - default_lock.cc default_platform.cc PUBLIC - default_condition_variable.h - default_lock.h default_platform.h ) @@ -19,6 +15,8 @@ target_include_directories(platform_impl_default target_link_libraries(platform_impl_default PUBLIC platform_api + platform_impl_default_cond_var + platform_impl_default_lock platform_types ) From 5aaab9a4c04d4ee0f1389639804352a7608d1fd1 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Mon, 6 Apr 2020 09:55:44 -0700 Subject: [PATCH 10/52] Fix the build Change-Id: Ied63786e8f73cec805123031c26886295f086691 Signed-off-by: Alexey Polyudov --- .../internal/mediums/advertisement_read_result_test.cc | 10 ---------- cpp/core/internal/offline_frames_test.cc | 2 +- cpp/core/internal/wifi_lan_service_info.h | 6 +++--- cpp/platform/base64_utils.cc | 2 +- cpp/platform/base64_utils.h | 4 ++-- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index 4a1987ac..158e01fb 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -47,16 +47,6 @@ const std::int64_t SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); -template<> -const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = - absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); - -template<> -const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = - absl::ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); - TEST(AdvertisementReadResultTest, AdvertisementExists) { AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc index 874b3a74..764514f7 100644 --- a/cpp/core/internal/offline_frames_test.cc +++ b/cpp/core/internal/offline_frames_test.cc @@ -47,7 +47,7 @@ constexpr ConnectionRequestFrame::Medium ToConnectionRequestMedium( TEST(OfflineFramesTest, CanParseMessageFromBytes) { const string endpoint_id{"ABC"}; const string endpoint_name{"XYZ"}; - const int32 nonce{1234}; + const int nonce{1234}; const std::vector mediums{Medium::BLE, Medium::BLUETOOTH}; diff --git a/cpp/core/internal/wifi_lan_service_info.h b/cpp/core/internal/wifi_lan_service_info.h index f1114e5f..eb193c28 100644 --- a/cpp/core/internal/wifi_lan_service_info.h +++ b/cpp/core/internal/wifi_lan_service_info.h @@ -66,9 +66,9 @@ class WifiLanServiceInfo { // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. static constexpr int kMaxEndpointNameLength = 131; - static constexpr uint16 kVersionBitmask = 0x0E0; - static constexpr uint16 kPcpBitmask = 0x01F; - static constexpr uint16 kVersionShift = 5; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kVersionShift = 5; WifiLanServiceInfo(Version version, PCP::Value pcp, absl::string_view endpoint_id, diff --git a/cpp/platform/base64_utils.cc b/cpp/platform/base64_utils.cc index 8072e7b4..9c5f2fa8 100644 --- a/cpp/platform/base64_utils.cc +++ b/cpp/platform/base64_utils.cc @@ -49,7 +49,7 @@ ByteArray Base64Utils::decode(absl::string_view base64_string) { return ByteArray(decoded_string); } -Ptr Base64Utils::decode(const std::string& base64_string) { +Ptr Base64Utils::decode(absl::string_view base64_string) { return decode>(base64_string); } diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h index 3a00dacf..042dc386 100644 --- a/cpp/platform/base64_utils.h +++ b/cpp/platform/base64_utils.h @@ -16,8 +16,8 @@ class Base64Utils { static std::string encode(ConstPtr bytes); template - static T decode(const std::string& base64_string); - static Ptr decode(const std::string& base64_string); + static T decode(absl::string_view base64_string); + static Ptr decode(absl::string_view base64_string); }; } // namespace nearby From d06ec324076f502d58d0d78680f26cde0f457d8b Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 16 Apr 2020 13:20:50 -0700 Subject: [PATCH 11/52] Add OSS headers generation Signed-off-by: Alexey Polyudov Change-Id: I9a846fac22469a6afa32e4eafd5f2be34ff55fda --- script/oss.py | 150 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 136 insertions(+), 14 deletions(-) diff --git a/script/oss.py b/script/oss.py index 2e4b7f43..7010ed46 100755 --- a/script/oss.py +++ b/script/oss.py @@ -1,9 +1,61 @@ #!/usr/bin/python3 +import argparse import os import shutil import sys +copy_header="""Copyright 2020 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.""".split("\n") + +MISSING = 0 +HEADLINE = 1 +PARTIAL = 2 +FULL = 3 + +def has_copyright(lines, max_lookup=3): + pos = 0 + for line in lines: + pos += 1 # points to the next line + if line.find(copy_header[0]) >= 0: + break + if pos > max_lookup: + return MISSING + + result = HEADLINE + + for line in copy_header[1:]: + if lines[pos].find(line) < 0: + return result + else: + result = PARTIAL + + return FULL + +def add_copyright(lines, prefix, offset): + new_lines = lines[0:offset] + if offset: + new_lines.append("\n") + for line in copy_header: + if line: + new_lines.append(prefix + " " + line + "\n") + else: + new_lines.append(prefix + "\n") + new_lines.append("\n") + new_lines.extend(lines[offset:]) + return new_lines + def copy_files_to_oss_project(src_root, dst_root): shutil.rmtree(dst_root + "/cpp", ignore_errors=True) shutil.rmtree(dst_root + "/proto", ignore_errors=True) @@ -12,10 +64,27 @@ def copy_files_to_oss_project(src_root, dst_root): shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") -def post_process_oss_files(path): +def detect_file_copy_header_options(fname, lines): + if not lines: + return None # ignore empty file + suffixes = [".cc", ".cpp", ".cxx", ".c", ".h", ".hpp", ".inc", ".proto"] + for suffix in suffixes: + if fname.endswith(suffix): + return ("//", 0) + if (fname in ["CMakeLists.txt", "BUILD.gn", "BUILD"]) or ( + fname.startswith("CMakeLists") or fname.endswith(".cmake")): + return ("#", 0) + if lines[0].startswith("#!"): + return ("#", 1) + return None + +def post_process_oss_files(path, args): modified_total = 0 top_level = True - top_dirs = ["cpp", "proto"] + if args.all: + top_level = False # no special actions to take at top level + else: + top_dirs = ["cpp", "proto"] transforms = ( ("third_party/", ""), ("location/nearby/connections/core", "core"), @@ -23,7 +92,8 @@ def post_process_oss_files(path): ("security/cryptauth/lib/securegcm", "securegcm"), ("testing/base/public/gmock.h", "gmock/gmock.h"), ("testing/base/public/gunit.h", "gtest/gtest.h"), - ("net/proto2/compat/public/message_lite.h", "google/protobuf/message_lite.h"), + ("net/proto2/compat/public/message_lite.h", + "google/protobuf/message_lite.h"), ("LOCATION_NEARBY_CONNECTIONS_", ""), ("LOCATION_NEARBY_CPP_", ""), ("location/nearby/proto", "proto"), @@ -32,7 +102,7 @@ def post_process_oss_files(path): (".proto.h", ".pb.h"), ) for root, dirs, files in os.walk(path): - if top_level: + if top_level and top_dirs: # we must convert cpp/ and proto/ subtrees. # everything else is not parsed. dirs.clear() @@ -41,32 +111,84 @@ def post_process_oss_files(path): continue for file in files: fname = root + "/" + file + print("parsing: {}".format(fname)) if file in ["METADATA"]: os.remove(fname) continue modified = False lines=[] + google3_ignore = False with open(fname, "r") as f: for line in f: orig = line - for lookup, substitute in transforms: - line = line.replace(lookup, substitute) - if orig != line: - modified = True + + if not args.no_subst: + for lookup, substitute in transforms: + line = line.replace(lookup, substitute) + if orig != line: + modified = True + + if args.google3_filter: + if line.find("nearby:google3-only") >= 0: + modified = True + continue + if line.find("nearby:google3-begin") >= 0: + modified = True + google3_ignore = True + continue + if line.find("nearby:google3-end") >= 0: + modified = True + google3_ignore = False + continue + if google3_ignore: + modified = True + continue lines.append(line) + + if args.fix_oss_headers: + options = detect_file_copy_header_options(file, lines) + if options is not None: + if not has_copyright(lines): + prefix, offset = options + lines = add_copyright(lines, prefix, offset) + modified = True if modified: with open(fname, "w") as f: for line in lines: f.write(line) modified_total += 1 + if args.no_recurse: + break + return modified_total -def main(args): - src = "/google/src/cloud/%s/%s/google3/location/nearby" % (os.environ["USER"], args[1]) - dst = args[2] - copy_files_to_oss_project(src, dst) - total = post_process_oss_files(dst) +def main(): + parser = argparse.ArgumentParser('Opensource Nearby Release Tool') + parser.add_argument('target', action='store', type=str, nargs="+", default=[]) + parser.add_argument('--workspace', action='store', default="") + parser.add_argument('--all', action='store_true', default=False) + parser.add_argument('--fix-oss-headers', action='store_true', default=False) + parser.add_argument('--google3-filter', action='store_true', default=False) + parser.add_argument('--no-copy', action='store_true', default=False) + parser.add_argument('--no-subst', action='store_true', default=False) + parser.add_argument('--no-recurse', action='store_true', default=False) + args = parser.parse_args() + if args.google3_filter: + print("google3-specific code will be removed") + if args.workspace: + src = "/google/src/cloud/%s/%s/google3/location/nearby" % (os.environ["USER"], args.workspace) + else: + args.no_copy = True + if len(args.target) == 1: + dst = args.target[0] + else: + args.no_copy = True + args.all = True + if not args.no_copy: copy_files_to_oss_project(src, dst) + total = 0 + for dst in args.target: + total += post_process_oss_files(dst, args) print("Total modified: {} files".format(total)) if __name__ == "__main__": - sys.exit(main(sys.argv)) + sys.exit(main()) From 4b3f08089a717667c1d856db38308c70672dc6b7 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 16 Apr 2020 10:26:30 -0700 Subject: [PATCH 12/52] Update depot_tools Change-Id: I6bc67d5b6726e137b3dde3531abc47abe3490128 --- third_party/depot_tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/depot_tools b/third_party/depot_tools index 19d4809e..e521cd14 160000 --- a/third_party/depot_tools +++ b/third_party/depot_tools @@ -1 +1 @@ -Subproject commit 19d4809e112652f918494840bab819603b0a2816 +Subproject commit e521cd14da4a02274de5099543f0b30350e41be3 From 271a449d7a60aee985e0c85dc519256b400e66cc Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 16 Apr 2020 10:27:33 -0700 Subject: [PATCH 13/52] Prepare Project for open source Change-Id: Ic26d4915efa2be47ce00ffaab7a474d696e6657c --- LICENSE | 202 ++++++++++++++++++ README.md | 66 ++++++ .../mediums/ble_advertisement_header.cc | 3 +- cpp/core/internal/payload_manager.cc | 4 + docs/code-of-conduct.md | 63 ++++++ docs/contributing.md | 28 +++ proto/connections_enums.proto | 2 + proto/nearby_client_enums.proto | 2 + script/handle_oss.sh | 20 ++ 9 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/code-of-conduct.md create mode 100644 docs/contributing.md create mode 100755 script/handle_oss.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e741a4a3 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Nearby Connections Library + +This is not an officially supported Google product. + +**Coathored by:** +* (Java/C++) Varun Kapoor “reznor” +* (Java) Maria-Ines Carrera “marianines” +* (Java) Will Harmon “xlythe” +* (Java/C++/ObjC) Alex Kang “alexanderkang” +* (Java/C++) Amanda Lee “ahlee” +* (C++) Tracy Zhou “tracyzhou” +* (ObjC) Dan Webb “dwebb” +* (C++) John Kaczor “johngk” +* (C++/ObjC) Edwin Wu “edwinwu” +* (C++) Alexey Polyudov “apolyudov” + +**Status:** Implemented in C++ + +**Design reviewers:** TODO + +**Implementation reviewer**: TODO + +**Last Updated:** TODO + +# Overview + +Nearby Connections is a high level protocol on top of Bluetooth/WiFi that acts +as a medium-agnostic socket. Devices are able to advertise, scan, and connect +with one another over any shared medium (eg. BT <-> BT). +Once connected, the two devices share a list of all supported mediums and +attempt to upgrade to the one with the highest bandwidth (eg. BT -> WiFi). +The connection is encrypted, reliable, and fully duplex. BYTE, FILE, and STREAM +payloads are all supported and will be chunked & transferred internally and +recombined on the receiving device. +See [Nearby Connections Overview](https://developers.google.com/nearby/connections/overview) +for more information. + +# Checkout, build, test instructions +## Checkout +pre-requisites: git +``` +git clone https://github.com/google/nearby-connections +cd nearby-connections +git submodule update --init --recursive +``` + +this is a "source root" directory of the project + +## Build +pre-requisites: +openssl, cmake, c++ toolchain (c++17-capable) + +from "source root", run: + +``` +mkdir build; cd build +cmake -Dnearby_USE_LOCAL_PROTOBUF=ON -Dnearby_USE_LOCAL_ABSL=ON .. +make +``` +## Running unit tests + +from "source root/build", run: + +``` +ctest -V +``` diff --git a/cpp/core/internal/mediums/ble_advertisement_header.cc b/cpp/core/internal/mediums/ble_advertisement_header.cc index 9935e9b3..b5e1d672 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -15,8 +15,7 @@ namespace mediums { // ble_v2.createAdvertisementHeader // LINT.IfChange const std::uint32_t BLEAdvertisementHeader::kServiceIdBloomFilterLength = 10; -// LINT.ThenChange(//depot/google3/core/internal/\ -// mediums/ble_v2.h) +// LINT.ThenChange(cpp/core/internal/mediums/ble_v2.h) const std::uint32_t BLEAdvertisementHeader::kAdvertisementHashLength = 4; const std::uint32_t BLEAdvertisementHeader::kVersionAndNumSlotsLength = 1; diff --git a/cpp/core/internal/payload_manager.cc b/cpp/core/internal/payload_manager.cc index fd749499..383a08ee 100644 --- a/cpp/core/internal/payload_manager.cc +++ b/cpp/core/internal/payload_manager.cc @@ -442,6 +442,7 @@ class HandleSuccessfulOutgoingChunkRunnable : public Runnable { return; } + // nearby:google3-begin // TODO(reznor): The fact that we've sent total_size bytes (which we will // always know 1 frame before we get the SUCCESS frame), also tells us this // is the last chunk - should we add those smarts, or just be simple and @@ -451,6 +452,7 @@ class HandleSuccessfulOutgoingChunkRunnable : public Runnable { // at just that point, so at least consider injecting the smarts. // TODO(reznor): Should we check whether payload_header.total_size == // payload_chunk.offset? + // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( @@ -514,6 +516,7 @@ class HandleSuccessfulIncomingChunkRunnable : public Runnable { return; } + // nearby:google3-begin // TODO(reznor): The fact that we've received total_size bytes (which we // will always know 1 frame before we get the SUCCESS frame), also tells us // this is the last chunk - should we add those smarts, or just be simple @@ -522,6 +525,7 @@ class HandleSuccessfulIncomingChunkRunnable : public Runnable { // get all the bytes and then remain hanging because the remote device // disconnected at just that point, so at least consider injecting the // smarts. + // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md new file mode 100644 index 00000000..f8b12cb5 --- /dev/null +++ b/docs/code-of-conduct.md @@ -0,0 +1,63 @@ +# Google Open Source Community Guidelines + +At Google, we recognize and celebrate the creativity and collaboration of open +source contributors and the diversity of skills, experiences, cultures, and +opinions they bring to the projects and communities they participate in. + +Every one of Google's open source projects and communities are inclusive +environments, based on treating all individuals respectfully, regardless of +gender identity and expression, sexual orientation, disabilities, +neurodiversity, physical appearance, body size, ethnicity, nationality, race, +age, religion, or similar personal characteristic. + +We value diverse opinions, but we value respectful behavior more. + +Respectful behavior includes: + +* Being considerate, kind, constructive, and helpful. +* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or + physically threatening behavior, speech, and imagery. +* Not engaging in unwanted physical contact. + +Some Google open source projects [may adopt][] an explicit project code of +conduct, which may have additional detailed expectations for participants. Most +of those projects will use our [modified Contributor Covenant][]. + +[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct +[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ + +## Resolve peacefully + +We do not believe that all conflict is necessarily bad; healthy debate and +disagreement often yields positive results. However, it is never okay to be +disrespectful. + +If you see someone behaving disrespectfully, you are encouraged to address the +behavior directly with those involved. Many issues can be resolved quickly and +easily, and this gives people more control over the outcome of their dispute. +If you are unable to resolve the matter for any reason, or if the behavior is +threatening or harassing, report it. We are dedicated to providing an +environment where participants feel welcome and safe. + +## Reporting problems + +Some Google open source projects may adopt a project-specific code of conduct. +In those cases, a Google employee will be identified as the Project Steward, +who will receive and handle reports of code of conduct violations. In the event +that a project hasn’t identified a Project Steward, you can report problems by +emailing opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is +taken. The identity of the reporter will be omitted from the details of the +report supplied to the accused. In potentially harmful situations, such as +ongoing harassment or threats to anyone's safety, we may take action without +notice. + +*This document was adapted from the [IndieWeb Code of Conduct][] and can also +be found at .* + +[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..654a0716 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 51be3e31..cbd1d5d6 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -1,3 +1,4 @@ +// nearby:google3-begin // Any changes in this file maybe cause the unmapped result in the PLX tables. // Please remember to update the table schemas: // 1. Check your changes are rolled out in the MPM. @@ -8,6 +9,7 @@ // https://plx.corp.google.com/scripts2/script_e1._9eb6f3_e3cd_419b_b483_c1e42abc824a // // Or you can wait one or two days then run the above Step3. dircetly. +// nearby:google3-end syntax = "proto2"; diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 38ce9ba6..aec58708 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -10,10 +10,12 @@ option objc_class_prefix = "GNCP"; enum UserType { UNKNOWN_USER_TYPE = 0; PRODUCTION = 1; +// nearby:google3-begin MODULEFOOD = 2; TEST = 3; PRESTO_DOGFOOD = 4; AUTO_TEST = 5; +// nearby:google3-end } // The client that is logging. diff --git a/script/handle_oss.sh b/script/handle_oss.sh new file mode 100755 index 00000000..00005a67 --- /dev/null +++ b/script/handle_oss.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./oss.py --all --google3-filter --no-subst --fix-oss-headers ../cmake +./oss.py --all --no-subst --fix-oss-headers . +./oss.py --all --google3-filter --no-subst --fix-oss-headers --no-recurse .. +./oss.py --google3-filter --fix-oss-headers .. From f3c7e9dd105bb77da7becafc77df6f4316f4524c Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 16 Apr 2020 10:27:33 -0700 Subject: [PATCH 14/52] Prepare Project for open source Change-Id: Ic26d4915efa2be47ce00ffaab7a474d696e6657c --- LICENSE | 202 ++++++++++++++++++ README.md | 66 ++++++ .../mediums/ble_advertisement_header.cc | 3 +- cpp/core/internal/payload_manager.cc | 4 + docs/code-of-conduct.md | 63 ++++++ docs/contributing.md | 28 +++ proto/connections_enums.proto | 2 + proto/nearby_client_enums.proto | 2 + script/handle_oss.sh | 20 ++ 9 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/code-of-conduct.md create mode 100644 docs/contributing.md create mode 100755 script/handle_oss.sh diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e741a4a3 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Nearby Connections Library + +This is not an officially supported Google product. + +**Coathored by:** +* (Java/C++) Varun Kapoor “reznor” +* (Java) Maria-Ines Carrera “marianines” +* (Java) Will Harmon “xlythe” +* (Java/C++/ObjC) Alex Kang “alexanderkang” +* (Java/C++) Amanda Lee “ahlee” +* (C++) Tracy Zhou “tracyzhou” +* (ObjC) Dan Webb “dwebb” +* (C++) John Kaczor “johngk” +* (C++/ObjC) Edwin Wu “edwinwu” +* (C++) Alexey Polyudov “apolyudov” + +**Status:** Implemented in C++ + +**Design reviewers:** TODO + +**Implementation reviewer**: TODO + +**Last Updated:** TODO + +# Overview + +Nearby Connections is a high level protocol on top of Bluetooth/WiFi that acts +as a medium-agnostic socket. Devices are able to advertise, scan, and connect +with one another over any shared medium (eg. BT <-> BT). +Once connected, the two devices share a list of all supported mediums and +attempt to upgrade to the one with the highest bandwidth (eg. BT -> WiFi). +The connection is encrypted, reliable, and fully duplex. BYTE, FILE, and STREAM +payloads are all supported and will be chunked & transferred internally and +recombined on the receiving device. +See [Nearby Connections Overview](https://developers.google.com/nearby/connections/overview) +for more information. + +# Checkout, build, test instructions +## Checkout +pre-requisites: git +``` +git clone https://github.com/google/nearby-connections +cd nearby-connections +git submodule update --init --recursive +``` + +this is a "source root" directory of the project + +## Build +pre-requisites: +openssl, cmake, c++ toolchain (c++17-capable) + +from "source root", run: + +``` +mkdir build; cd build +cmake -Dnearby_USE_LOCAL_PROTOBUF=ON -Dnearby_USE_LOCAL_ABSL=ON .. +make +``` +## Running unit tests + +from "source root/build", run: + +``` +ctest -V +``` diff --git a/cpp/core/internal/mediums/ble_advertisement_header.cc b/cpp/core/internal/mediums/ble_advertisement_header.cc index 9935e9b3..b5e1d672 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -15,8 +15,7 @@ namespace mediums { // ble_v2.createAdvertisementHeader // LINT.IfChange const std::uint32_t BLEAdvertisementHeader::kServiceIdBloomFilterLength = 10; -// LINT.ThenChange(//depot/google3/core/internal/\ -// mediums/ble_v2.h) +// LINT.ThenChange(cpp/core/internal/mediums/ble_v2.h) const std::uint32_t BLEAdvertisementHeader::kAdvertisementHashLength = 4; const std::uint32_t BLEAdvertisementHeader::kVersionAndNumSlotsLength = 1; diff --git a/cpp/core/internal/payload_manager.cc b/cpp/core/internal/payload_manager.cc index fd749499..383a08ee 100644 --- a/cpp/core/internal/payload_manager.cc +++ b/cpp/core/internal/payload_manager.cc @@ -442,6 +442,7 @@ class HandleSuccessfulOutgoingChunkRunnable : public Runnable { return; } + // nearby:google3-begin // TODO(reznor): The fact that we've sent total_size bytes (which we will // always know 1 frame before we get the SUCCESS frame), also tells us this // is the last chunk - should we add those smarts, or just be simple and @@ -451,6 +452,7 @@ class HandleSuccessfulOutgoingChunkRunnable : public Runnable { // at just that point, so at least consider injecting the smarts. // TODO(reznor): Should we check whether payload_header.total_size == // payload_chunk.offset? + // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( @@ -514,6 +516,7 @@ class HandleSuccessfulIncomingChunkRunnable : public Runnable { return; } + // nearby:google3-begin // TODO(reznor): The fact that we've received total_size bytes (which we // will always know 1 frame before we get the SUCCESS frame), also tells us // this is the last chunk - should we add those smarts, or just be simple @@ -522,6 +525,7 @@ class HandleSuccessfulIncomingChunkRunnable : public Runnable { // get all the bytes and then remain hanging because the remote device // disconnected at just that point, so at least consider injecting the // smarts. + // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( diff --git a/docs/code-of-conduct.md b/docs/code-of-conduct.md new file mode 100644 index 00000000..f8b12cb5 --- /dev/null +++ b/docs/code-of-conduct.md @@ -0,0 +1,63 @@ +# Google Open Source Community Guidelines + +At Google, we recognize and celebrate the creativity and collaboration of open +source contributors and the diversity of skills, experiences, cultures, and +opinions they bring to the projects and communities they participate in. + +Every one of Google's open source projects and communities are inclusive +environments, based on treating all individuals respectfully, regardless of +gender identity and expression, sexual orientation, disabilities, +neurodiversity, physical appearance, body size, ethnicity, nationality, race, +age, religion, or similar personal characteristic. + +We value diverse opinions, but we value respectful behavior more. + +Respectful behavior includes: + +* Being considerate, kind, constructive, and helpful. +* Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or + physically threatening behavior, speech, and imagery. +* Not engaging in unwanted physical contact. + +Some Google open source projects [may adopt][] an explicit project code of +conduct, which may have additional detailed expectations for participants. Most +of those projects will use our [modified Contributor Covenant][]. + +[may adopt]: https://opensource.google/docs/releasing/preparing/#conduct +[modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ + +## Resolve peacefully + +We do not believe that all conflict is necessarily bad; healthy debate and +disagreement often yields positive results. However, it is never okay to be +disrespectful. + +If you see someone behaving disrespectfully, you are encouraged to address the +behavior directly with those involved. Many issues can be resolved quickly and +easily, and this gives people more control over the outcome of their dispute. +If you are unable to resolve the matter for any reason, or if the behavior is +threatening or harassing, report it. We are dedicated to providing an +environment where participants feel welcome and safe. + +## Reporting problems + +Some Google open source projects may adopt a project-specific code of conduct. +In those cases, a Google employee will be identified as the Project Steward, +who will receive and handle reports of code of conduct violations. In the event +that a project hasn’t identified a Project Steward, you can report problems by +emailing opensource@google.com. + +We will investigate every complaint, but you may not receive a direct response. +We will use our discretion in determining when and how to follow up on reported +incidents, which may range from not taking action to permanent expulsion from +the project and project-sponsored spaces. We will notify the accused of the +report and provide them an opportunity to discuss it before any action is +taken. The identity of the reporter will be omitted from the details of the +report supplied to the accused. In potentially harmful situations, such as +ongoing harassment or threats to anyone's safety, we may take action without +notice. + +*This document was adapted from the [IndieWeb Code of Conduct][] and can also +be found at .* + +[IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..654a0716 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 51be3e31..cbd1d5d6 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -1,3 +1,4 @@ +// nearby:google3-begin // Any changes in this file maybe cause the unmapped result in the PLX tables. // Please remember to update the table schemas: // 1. Check your changes are rolled out in the MPM. @@ -8,6 +9,7 @@ // https://plx.corp.google.com/scripts2/script_e1._9eb6f3_e3cd_419b_b483_c1e42abc824a // // Or you can wait one or two days then run the above Step3. dircetly. +// nearby:google3-end syntax = "proto2"; diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 38ce9ba6..aec58708 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -10,10 +10,12 @@ option objc_class_prefix = "GNCP"; enum UserType { UNKNOWN_USER_TYPE = 0; PRODUCTION = 1; +// nearby:google3-begin MODULEFOOD = 2; TEST = 3; PRESTO_DOGFOOD = 4; AUTO_TEST = 5; +// nearby:google3-end } // The client that is logging. diff --git a/script/handle_oss.sh b/script/handle_oss.sh new file mode 100755 index 00000000..00005a67 --- /dev/null +++ b/script/handle_oss.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./oss.py --all --google3-filter --no-subst --fix-oss-headers ../cmake +./oss.py --all --no-subst --fix-oss-headers . +./oss.py --all --google3-filter --no-subst --fix-oss-headers --no-recurse .. +./oss.py --google3-filter --fix-oss-headers .. From c3a89bb8941d6c6d9899e4e8fdafd6dd8c55c037 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 16 Apr 2020 16:44:24 -0700 Subject: [PATCH 15/52] Generate OSS-ready HEAD from master Signed-off-by: Alexey Polyudov Change-Id: I8ca8ef4727e289f3c6ea62ad73008ad718096e5d --- CMakeLists.txt | 14 ++++++++ cmake/CMakeLists-smhasher.txt | 14 ++++++++ cmake/local_build_protobuf.cmake | 14 ++++++++ cmake/local_build_setup.cmake | 14 ++++++++ cmake/local_setup_smhasher.cmake | 14 ++++++++ cmake/proto_defs.cmake | 14 ++++++++ cpp/core/BUILD | 14 ++++++++ cpp/core/CMakeLists.txt | 14 ++++++++ cpp/core/check_compilation.cc | 14 ++++++++ cpp/core/core.cc | 14 ++++++++ cpp/core/core.h | 14 ++++++++ cpp/core/internal/BUILD | 14 ++++++++ cpp/core/internal/CMakeLists.txt | 14 ++++++++ cpp/core/internal/bandwidth_upgrade_handler.h | 14 ++++++++ .../internal/bandwidth_upgrade_manager.cc | 14 ++++++++ cpp/core/internal/bandwidth_upgrade_manager.h | 14 ++++++++ .../base_bandwidth_upgrade_handler.cc | 14 ++++++++ .../internal/base_bandwidth_upgrade_handler.h | 14 ++++++++ cpp/core/internal/base_endpoint_channel.cc | 14 ++++++++ cpp/core/internal/base_endpoint_channel.h | 14 ++++++++ .../internal/base_endpoint_channel_test.cc | 14 ++++++++ cpp/core/internal/base_pcp_handler.cc | 14 ++++++++ cpp/core/internal/base_pcp_handler.h | 14 ++++++++ cpp/core/internal/ble_advertisement.cc | 14 ++++++++ cpp/core/internal/ble_advertisement.h | 14 ++++++++ cpp/core/internal/ble_advertisement_test.cc | 14 ++++++++ cpp/core/internal/ble_compat.h | 14 ++++++++ cpp/core/internal/ble_endpoint_channel.cc | 14 ++++++++ cpp/core/internal/ble_endpoint_channel.h | 14 ++++++++ cpp/core/internal/bluetooth_device_name.cc | 14 ++++++++ cpp/core/internal/bluetooth_device_name.h | 14 ++++++++ .../internal/bluetooth_device_name_test.cc | 14 ++++++++ .../internal/bluetooth_endpoint_channel.cc | 14 ++++++++ .../internal/bluetooth_endpoint_channel.h | 14 ++++++++ cpp/core/internal/client_proxy.cc | 14 ++++++++ cpp/core/internal/client_proxy.h | 14 ++++++++ cpp/core/internal/encryption_runner.cc | 14 ++++++++ cpp/core/internal/encryption_runner.h | 14 ++++++++ cpp/core/internal/endpoint_channel.h | 14 ++++++++ cpp/core/internal/endpoint_channel_manager.cc | 14 ++++++++ cpp/core/internal/endpoint_channel_manager.h | 14 ++++++++ cpp/core/internal/endpoint_manager.cc | 14 ++++++++ cpp/core/internal/endpoint_manager.h | 14 ++++++++ cpp/core/internal/internal_payload.cc | 14 ++++++++ cpp/core/internal/internal_payload.h | 14 ++++++++ cpp/core/internal/internal_payload_factory.cc | 14 ++++++++ cpp/core/internal/internal_payload_factory.h | 14 ++++++++ cpp/core/internal/loop_runner.cc | 14 ++++++++ cpp/core/internal/loop_runner.h | 14 ++++++++ cpp/core/internal/medium_manager.cc | 14 ++++++++ cpp/core/internal/medium_manager.h | 14 ++++++++ cpp/core/internal/mediums/BUILD | 14 ++++++++ cpp/core/internal/mediums/CMakeLists.txt | 14 ++++++++ .../mediums/advertisement_read_result.cc | 14 ++++++++ .../mediums/advertisement_read_result.h | 14 ++++++++ .../mediums/advertisement_read_result_test.cc | 14 ++++++++ cpp/core/internal/mediums/ble.cc | 14 ++++++++ cpp/core/internal/mediums/ble.h | 14 ++++++++ .../internal/mediums/ble_advertisement.cc | 14 ++++++++ cpp/core/internal/mediums/ble_advertisement.h | 14 ++++++++ .../mediums/ble_advertisement_header.cc | 14 ++++++++ .../mediums/ble_advertisement_header.h | 14 ++++++++ .../mediums/ble_advertisement_header_test.cc | 14 ++++++++ .../mediums/ble_advertisement_test.cc | 14 ++++++++ cpp/core/internal/mediums/ble_packet.cc | 14 ++++++++ cpp/core/internal/mediums/ble_packet.h | 14 ++++++++ cpp/core/internal/mediums/ble_packet_test.cc | 14 ++++++++ cpp/core/internal/mediums/ble_peripheral.cc | 14 ++++++++ cpp/core/internal/mediums/ble_peripheral.h | 14 ++++++++ cpp/core/internal/mediums/ble_v2.cc | 14 ++++++++ cpp/core/internal/mediums/ble_v2.h | 14 ++++++++ cpp/core/internal/mediums/bloom_filter.cc | 14 ++++++++ cpp/core/internal/mediums/bloom_filter.h | 14 ++++++++ .../internal/mediums/bloom_filter_test.cc | 14 ++++++++ .../internal/mediums/bluetooth_classic.cc | 14 ++++++++ cpp/core/internal/mediums/bluetooth_classic.h | 14 ++++++++ cpp/core/internal/mediums/bluetooth_radio.cc | 14 ++++++++ cpp/core/internal/mediums/bluetooth_radio.h | 14 ++++++++ .../mediums/discovered_peripheral_callback.h | 14 ++++++++ .../mediums/discovered_peripheral_tracker.cc | 14 ++++++++ .../mediums/discovered_peripheral_tracker.h | 14 ++++++++ .../internal/mediums/lost_entity_tracker.cc | 14 ++++++++ .../internal/mediums/lost_entity_tracker.h | 14 ++++++++ .../mediums/lost_entity_tracker_test.cc | 14 ++++++++ cpp/core/internal/mediums/mediums.cc | 14 ++++++++ cpp/core/internal/mediums/mediums.h | 14 ++++++++ cpp/core/internal/mediums/utils.cc | 14 ++++++++ cpp/core/internal/mediums/utils.h | 14 ++++++++ cpp/core/internal/mediums/uuid.cc | 14 ++++++++ cpp/core/internal/mediums/uuid.h | 14 ++++++++ cpp/core/internal/offline_frames.cc | 14 ++++++++ cpp/core/internal/offline_frames.h | 14 ++++++++ cpp/core/internal/offline_frames_test.cc | 14 ++++++++ .../internal/offline_service_controller.cc | 14 ++++++++ .../internal/offline_service_controller.h | 14 ++++++++ cpp/core/internal/p2p_cluster_pcp_handler.cc | 14 ++++++++ cpp/core/internal/p2p_cluster_pcp_handler.h | 14 ++++++++ .../p2p_point_to_point_pcp_handler.cc | 14 ++++++++ .../internal/p2p_point_to_point_pcp_handler.h | 14 ++++++++ cpp/core/internal/p2p_star_pcp_handler.cc | 14 ++++++++ cpp/core/internal/p2p_star_pcp_handler.h | 14 ++++++++ cpp/core/internal/payload_manager.cc | 35 ++++++++----------- cpp/core/internal/payload_manager.h | 14 ++++++++ cpp/core/internal/pcp.h | 14 ++++++++ cpp/core/internal/pcp_handler.h | 14 ++++++++ cpp/core/internal/pcp_manager.cc | 14 ++++++++ cpp/core/internal/pcp_manager.h | 14 ++++++++ cpp/core/internal/service_controller.h | 14 ++++++++ .../internal/service_controller_router.cc | 14 ++++++++ cpp/core/internal/service_controller_router.h | 14 ++++++++ cpp/core/internal/wifi_lan_service_info.cc | 14 ++++++++ cpp/core/internal/wifi_lan_service_info.h | 14 ++++++++ .../internal/wifi_lan_service_info_test.cc | 14 ++++++++ cpp/core/internal/wifi_lan_upgrade_handler.cc | 14 ++++++++ cpp/core/internal/wifi_lan_upgrade_handler.h | 14 ++++++++ cpp/core/listeners.h | 14 ++++++++ cpp/core/options.h | 14 ++++++++ cpp/core/params.h | 14 ++++++++ cpp/core/payload.cc | 14 ++++++++ cpp/core/payload.h | 14 ++++++++ cpp/core/status.h | 14 ++++++++ cpp/core/strategy.cc | 14 ++++++++ cpp/core/strategy.h | 14 ++++++++ cpp/platform/BUILD | 14 ++++++++ cpp/platform/CMakeLists.txt | 14 ++++++++ cpp/platform/api/BUILD | 14 ++++++++ cpp/platform/api/CMakeLists.txt | 14 ++++++++ cpp/platform/api/atomic_boolean.h | 14 ++++++++ cpp/platform/api/atomic_reference.h | 14 ++++++++ cpp/platform/api/ble.h | 14 ++++++++ cpp/platform/api/ble_v2.h | 14 ++++++++ cpp/platform/api/bluetooth_adapter.h | 14 ++++++++ cpp/platform/api/bluetooth_classic.h | 14 ++++++++ cpp/platform/api/condition_variable.h | 14 ++++++++ cpp/platform/api/count_down_latch.h | 14 ++++++++ cpp/platform/api/executor.h | 14 ++++++++ cpp/platform/api/future.h | 14 ++++++++ cpp/platform/api/hash_utils.h | 14 ++++++++ cpp/platform/api/input_file.h | 14 ++++++++ cpp/platform/api/input_stream.h | 14 ++++++++ cpp/platform/api/listenable_future.h | 14 ++++++++ cpp/platform/api/lock.h | 14 ++++++++ cpp/platform/api/multi_thread_executor.h | 14 ++++++++ cpp/platform/api/output_file.h | 14 ++++++++ cpp/platform/api/output_stream.h | 14 ++++++++ cpp/platform/api/scheduled_executor.h | 14 ++++++++ cpp/platform/api/server_sync.h | 14 ++++++++ cpp/platform/api/settable_future.h | 14 ++++++++ cpp/platform/api/single_thread_executor.h | 14 ++++++++ cpp/platform/api/socket.h | 14 ++++++++ cpp/platform/api/submittable_executor.h | 14 ++++++++ cpp/platform/api/system_clock.h | 14 ++++++++ cpp/platform/api/thread_utils.h | 14 ++++++++ cpp/platform/api/webrtc.h | 14 ++++++++ cpp/platform/api/wifi.h | 14 ++++++++ cpp/platform/api/wifi_lan.h | 14 ++++++++ cpp/platform/api2/BUILD | 14 ++++++++ cpp/platform/api2/CMakeLists.txt | 14 ++++++++ cpp/platform/api2/atomic_boolean.h | 14 ++++++++ cpp/platform/api2/atomic_reference.h | 14 ++++++++ cpp/platform/api2/ble.h | 14 ++++++++ cpp/platform/api2/ble_v2.h | 14 ++++++++ cpp/platform/api2/bluetooth_adapter.h | 14 ++++++++ cpp/platform/api2/bluetooth_classic.h | 14 ++++++++ cpp/platform/api2/condition_variable.h | 14 ++++++++ cpp/platform/api2/count_down_latch.h | 14 ++++++++ cpp/platform/api2/executor.h | 14 ++++++++ cpp/platform/api2/future.h | 14 ++++++++ cpp/platform/api2/hash_utils.h | 14 ++++++++ cpp/platform/api2/input_file.h | 14 ++++++++ cpp/platform/api2/input_stream.h | 14 ++++++++ cpp/platform/api2/listenable_future.h | 14 ++++++++ cpp/platform/api2/multi_thread_executor.h | 14 ++++++++ cpp/platform/api2/mutex.h | 14 ++++++++ cpp/platform/api2/output_file.h | 14 ++++++++ cpp/platform/api2/output_stream.h | 14 ++++++++ cpp/platform/api2/scheduled_executor.h | 14 ++++++++ cpp/platform/api2/server_sync.h | 14 ++++++++ cpp/platform/api2/settable_future.h | 14 ++++++++ cpp/platform/api2/single_thread_executor.h | 14 ++++++++ cpp/platform/api2/socket.h | 14 ++++++++ cpp/platform/api2/submittable_executor.h | 14 ++++++++ cpp/platform/api2/system_clock.h | 14 ++++++++ cpp/platform/api2/thread_utils.h | 14 ++++++++ cpp/platform/api2/webrtc.h | 14 ++++++++ cpp/platform/api2/wifi.h | 14 ++++++++ cpp/platform/base64_utils.cc | 14 ++++++++ cpp/platform/base64_utils.h | 14 ++++++++ cpp/platform/byte_array.h | 14 ++++++++ cpp/platform/byte_array_test.cc | 14 ++++++++ cpp/platform/callable.h | 14 ++++++++ cpp/platform/cancelable.h | 14 ++++++++ cpp/platform/cancelable_alarm.cc | 14 ++++++++ cpp/platform/cancelable_alarm.h | 14 ++++++++ cpp/platform/container_of.h | 14 ++++++++ cpp/platform/container_of_test.cc | 14 ++++++++ cpp/platform/exception.h | 14 ++++++++ cpp/platform/exception_test.cc | 14 ++++++++ cpp/platform/file_impl.cc | 14 ++++++++ cpp/platform/file_impl.h | 14 ++++++++ cpp/platform/file_impl_test.cc | 14 ++++++++ cpp/platform/impl/default/BUILD | 14 ++++++++ cpp/platform/impl/default/CMakeLists.txt | 14 ++++++++ .../default/default_condition_variable.cc | 14 ++++++++ .../impl/default/default_condition_variable.h | 14 ++++++++ cpp/platform/impl/default/default_lock.cc | 14 ++++++++ cpp/platform/impl/default/default_lock.h | 14 ++++++++ cpp/platform/impl/default/default_platform.cc | 14 ++++++++ cpp/platform/impl/default/default_platform.h | 14 ++++++++ cpp/platform/impl/ios/BUILD | 14 ++++++++ cpp/platform/impl/sample/BUILD | 14 ++++++++ cpp/platform/impl/sample/CMakeLists.txt | 14 ++++++++ cpp/platform/impl/sample/sample_platform.h | 14 ++++++++ .../impl/sample/sample_wifi_medium.cc | 14 ++++++++ cpp/platform/impl/sample/sample_wifi_medium.h | 14 ++++++++ cpp/platform/logging.h | 14 ++++++++ cpp/platform/pipe.cc | 14 ++++++++ cpp/platform/pipe.h | 14 ++++++++ cpp/platform/pipe_test.cc | 14 ++++++++ cpp/platform/port/BUILD | 14 ++++++++ cpp/platform/port/CMakeLists.txt | 14 ++++++++ cpp/platform/port/config.h | 14 ++++++++ cpp/platform/port/down_cast.h | 14 ++++++++ cpp/platform/port/string.h | 14 ++++++++ cpp/platform/prng.cc | 14 ++++++++ cpp/platform/prng.h | 14 ++++++++ cpp/platform/prng_test.cc | 14 ++++++++ cpp/platform/ptr.h | 14 ++++++++ cpp/platform/ptr_test.cc | 14 ++++++++ cpp/platform/reliability_utils.cc | 14 ++++++++ cpp/platform/reliability_utils.h | 14 ++++++++ cpp/platform/runnable.h | 14 ++++++++ cpp/platform/synchronized.h | 14 ++++++++ proto/BUILD | 14 ++++++++ proto/CMakeLists.txt | 14 ++++++++ proto/bootstrap_enums.proto | 14 ++++++++ proto/connections/BUILD | 14 ++++++++ proto/connections/CMakeLists.txt | 14 ++++++++ proto/connections/offline_wire_formats.proto | 14 ++++++++ proto/connections_enums.proto | 24 +++++++------ proto/discovery_enums.proto | 14 ++++++++ proto/magic_pair_enums.proto | 14 ++++++++ proto/nearby_client_enums.proto | 20 +++++++---- proto/nearby_event_codes.proto | 14 ++++++++ proto/setup_enums.proto | 14 ++++++++ proto/sharing_enums.proto | 14 ++++++++ script/oss.py | 15 ++++++++ 247 files changed, 3458 insertions(+), 38 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dfdbfc18..69831361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cmake_minimum_required(VERSION 3.13) project(nearby CXX) diff --git a/cmake/CMakeLists-smhasher.txt b/cmake/CMakeLists-smhasher.txt index 08761ae1..a8fc1fd5 100644 --- a/cmake/CMakeLists-smhasher.txt +++ b/cmake/CMakeLists-smhasher.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + project(smhasher CXX) cmake_minimum_required(VERSION 3.13) diff --git a/cmake/local_build_protobuf.cmake b/cmake/local_build_protobuf.cmake index c10f38d3..3a04d554 100644 --- a/cmake/local_build_protobuf.cmake +++ b/cmake/local_build_protobuf.cmake @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + if (NOT EXISTS ${TOOLS_INSTALL_PREFIX}/bin/protoc) set(PKG_BUILD_ROOT ${TOOLS_BUILD_ROOT}/protobuf) set(PKG_SRC_ROOT ${CMAKE_SOURCE_DIR}/third_party/protobuf) diff --git a/cmake/local_build_setup.cmake b/cmake/local_build_setup.cmake index dd85a0a9..a7917fe7 100644 --- a/cmake/local_build_setup.cmake +++ b/cmake/local_build_setup.cmake @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + include(ProcessorCount) ProcessorCount(N_CPUS) diff --git a/cmake/local_setup_smhasher.cmake b/cmake/local_setup_smhasher.cmake index cb4164dd..b32a6b2e 100644 --- a/cmake/local_setup_smhasher.cmake +++ b/cmake/local_setup_smhasher.cmake @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set(PKG_STAGE_SRC_ROOT ${TOOLS_ROOT}/src/smhasher) if (NOT EXISTS ${PKG_STAGE_SRC_ROOT}/CMakeLists.txt) set(PKG_SRC_ROOT ${PROJECT_SOURCE_DIR}/third_party/smhasher) diff --git a/cmake/proto_defs.cmake b/cmake/proto_defs.cmake index d9a0d1cb..aae0ce9c 100644 --- a/cmake/proto_defs.cmake +++ b/cmake/proto_defs.cmake @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + function(add_cc_proto_library NAME) set(single) set(multi_args PROTOS INCS DEPS) diff --git a/cpp/core/BUILD b/cpp/core/BUILD index fa226c20..549ac92a 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "core", hdrs = [ diff --git a/cpp/core/CMakeLists.txt b/cpp/core/CMakeLists.txt index 8f2a70c0..71077e0e 100644 --- a/cpp/core/CMakeLists.txt +++ b/cpp/core/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(core STATIC) target_sources(core diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index 3d62e379..e65ba161 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include diff --git a/cpp/core/core.cc b/cpp/core/core.cc index 8409d0cc..3727138a 100644 --- a/cpp/core/core.cc +++ b/cpp/core/core.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/core.h" #include diff --git a/cpp/core/core.h b/cpp/core/core.h index d148d6df..6453780e 100644 --- a/cpp/core/core.h +++ b/cpp/core/core.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_CORE_H_ #define CORE_CORE_H_ diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index f54df70d..75800414 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "internal", srcs = [ diff --git a/cpp/core/internal/CMakeLists.txt b/cpp/core/internal/CMakeLists.txt index 752732ed..d10c1062 100644 --- a/cpp/core/internal/CMakeLists.txt +++ b/cpp/core/internal/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(core_internal STATIC) target_sources(core_internal diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h index 6e8c0775..901bdc4a 100644 --- a/cpp/core/internal/bandwidth_upgrade_handler.h +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_ #define CORE_INTERNAL_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/bandwidth_upgrade_manager.cc b/cpp/core/internal/bandwidth_upgrade_manager.cc index 4c502beb..62cf32aa 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.cc +++ b/cpp/core/internal/bandwidth_upgrade_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/bandwidth_upgrade_manager.h" #include "proto/connections_enums.pb.h" diff --git a/cpp/core/internal/bandwidth_upgrade_manager.h b/cpp/core/internal/bandwidth_upgrade_manager.h index 5aab4033..6715c2a9 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.h +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ #define CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.cc b/cpp/core/internal/base_bandwidth_upgrade_handler.cc index 9970bb8e..85650c0f 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.cc +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/base_bandwidth_upgrade_handler.h" namespace location { diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.h b/cpp/core/internal/base_bandwidth_upgrade_handler.h index c4be0a7d..867656cd 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.h +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ #define CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc index 6665a2b5..7d15c17d 100644 --- a/cpp/core/internal/base_endpoint_channel.cc +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/base_endpoint_channel.h" #include diff --git a/cpp/core/internal/base_endpoint_channel.h b/cpp/core/internal/base_endpoint_channel.h index 4e3c112e..b7ffec64 100644 --- a/cpp/core/internal/base_endpoint_channel.h +++ b/cpp/core/internal/base_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ #define CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/base_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc index abdb5dbe..697d480c 100644 --- a/cpp/core/internal/base_endpoint_channel_test.cc +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/base_endpoint_channel.h" #include "platform/impl/default/default_platform.h" diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index e885e42c..b83c2409 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/base_pcp_handler.h" #include diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h index 9019a9b9..17e9223f 100644 --- a/cpp/core/internal/base_pcp_handler.h +++ b/cpp/core/internal/base_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BASE_PCP_HANDLER_H_ #define CORE_INTERNAL_BASE_PCP_HANDLER_H_ diff --git a/cpp/core/internal/ble_advertisement.cc b/cpp/core/internal/ble_advertisement.cc index aaf602f9..0ee84373 100644 --- a/cpp/core/internal/ble_advertisement.cc +++ b/cpp/core/internal/ble_advertisement.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/ble_advertisement.h" #include diff --git a/cpp/core/internal/ble_advertisement.h b/cpp/core/internal/ble_advertisement.h index 64eb6a20..518a2385 100644 --- a/cpp/core/internal/ble_advertisement.h +++ b/cpp/core/internal/ble_advertisement.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BLE_ADVERTISEMENT_H_ #define CORE_INTERNAL_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core/internal/ble_advertisement_test.cc b/cpp/core/internal/ble_advertisement_test.cc index 683aa6b3..959a8716 100644 --- a/cpp/core/internal/ble_advertisement_test.cc +++ b/cpp/core/internal/ble_advertisement_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/ble_advertisement.h" #include diff --git a/cpp/core/internal/ble_compat.h b/cpp/core/internal/ble_compat.h index 264cb39e..a10cb38a 100644 --- a/cpp/core/internal/ble_compat.h +++ b/cpp/core/internal/ble_compat.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BLE_COMPAT_H_ #define CORE_INTERNAL_BLE_COMPAT_H_ diff --git a/cpp/core/internal/ble_endpoint_channel.cc b/cpp/core/internal/ble_endpoint_channel.cc index 8684dcc1..73618165 100644 --- a/cpp/core/internal/ble_endpoint_channel.cc +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/ble_endpoint_channel.h" #include diff --git a/cpp/core/internal/ble_endpoint_channel.h b/cpp/core/internal/ble_endpoint_channel.h index fb92dc4d..a966f433 100644 --- a/cpp/core/internal/ble_endpoint_channel.h +++ b/cpp/core/internal/ble_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ #define CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/bluetooth_device_name.cc b/cpp/core/internal/bluetooth_device_name.cc index d305167d..19d581ca 100644 --- a/cpp/core/internal/bluetooth_device_name.cc +++ b/cpp/core/internal/bluetooth_device_name.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/bluetooth_device_name.h" #include diff --git a/cpp/core/internal/bluetooth_device_name.h b/cpp/core/internal/bluetooth_device_name.h index de81dee7..0da3d5bc 100644 --- a/cpp/core/internal/bluetooth_device_name.h +++ b/cpp/core/internal/bluetooth_device_name.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ #define CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ diff --git a/cpp/core/internal/bluetooth_device_name_test.cc b/cpp/core/internal/bluetooth_device_name_test.cc index 90a789ea..b5640c86 100644 --- a/cpp/core/internal/bluetooth_device_name_test.cc +++ b/cpp/core/internal/bluetooth_device_name_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/bluetooth_device_name.h" #include diff --git a/cpp/core/internal/bluetooth_endpoint_channel.cc b/cpp/core/internal/bluetooth_endpoint_channel.cc index f9525b36..59cfd897 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.cc +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/bluetooth_endpoint_channel.h" #include diff --git a/cpp/core/internal/bluetooth_endpoint_channel.h b/cpp/core/internal/bluetooth_endpoint_channel.h index f9be2269..74d75bb6 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.h +++ b/cpp/core/internal/bluetooth_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ #define CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/client_proxy.cc b/cpp/core/internal/client_proxy.cc index 55d6ea7f..2fa7412e 100644 --- a/cpp/core/internal/client_proxy.cc +++ b/cpp/core/internal/client_proxy.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/client_proxy.h" #include diff --git a/cpp/core/internal/client_proxy.h b/cpp/core/internal/client_proxy.h index 98e76fbb..fbad987a 100644 --- a/cpp/core/internal/client_proxy.h +++ b/cpp/core/internal/client_proxy.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_CLIENT_PROXY_H_ #define CORE_INTERNAL_CLIENT_PROXY_H_ diff --git a/cpp/core/internal/encryption_runner.cc b/cpp/core/internal/encryption_runner.cc index ccd5f8d5..3536d17a 100644 --- a/cpp/core/internal/encryption_runner.cc +++ b/cpp/core/internal/encryption_runner.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/encryption_runner.h" #include diff --git a/cpp/core/internal/encryption_runner.h b/cpp/core/internal/encryption_runner.h index 3a2373f8..c5d2637c 100644 --- a/cpp/core/internal/encryption_runner.h +++ b/cpp/core/internal/encryption_runner.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_ENCRYPTION_RUNNER_H_ #define CORE_INTERNAL_ENCRYPTION_RUNNER_H_ diff --git a/cpp/core/internal/endpoint_channel.h b/cpp/core/internal/endpoint_channel.h index b7e2e52b..76e9c426 100644 --- a/cpp/core/internal/endpoint_channel.h +++ b/cpp/core/internal/endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_H_ #define CORE_INTERNAL_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc index 80222b6a..ebd753de 100644 --- a/cpp/core/internal/endpoint_channel_manager.cc +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/endpoint_channel_manager.h" #include "core/internal/ble_endpoint_channel.h" diff --git a/cpp/core/internal/endpoint_channel_manager.h b/cpp/core/internal/endpoint_channel_manager.h index 059085b7..dff4d7b0 100644 --- a/cpp/core/internal/endpoint_channel_manager.h +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ #define CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc index d4d6c3de..2586533f 100644 --- a/cpp/core/internal/endpoint_manager.cc +++ b/cpp/core/internal/endpoint_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/endpoint_manager.h" #include diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h index 05263f2c..772acf13 100644 --- a/cpp/core/internal/endpoint_manager.h +++ b/cpp/core/internal/endpoint_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_ENDPOINT_MANAGER_H_ #define CORE_INTERNAL_ENDPOINT_MANAGER_H_ diff --git a/cpp/core/internal/internal_payload.cc b/cpp/core/internal/internal_payload.cc index ca485783..fdab80e4 100644 --- a/cpp/core/internal/internal_payload.cc +++ b/cpp/core/internal/internal_payload.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/internal_payload.h" namespace location { diff --git a/cpp/core/internal/internal_payload.h b/cpp/core/internal/internal_payload.h index 33f11860..62f2625d 100644 --- a/cpp/core/internal/internal_payload.h +++ b/cpp/core/internal/internal_payload.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_H_ #define CORE_INTERNAL_INTERNAL_PAYLOAD_H_ diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc index 4deb40b7..616b0ba9 100644 --- a/cpp/core/internal/internal_payload_factory.cc +++ b/cpp/core/internal/internal_payload_factory.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/internal_payload_factory.h" #include diff --git a/cpp/core/internal/internal_payload_factory.h b/cpp/core/internal/internal_payload_factory.h index 0b7086e6..eb1ab1d5 100644 --- a/cpp/core/internal/internal_payload_factory.h +++ b/cpp/core/internal/internal_payload_factory.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ #define CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ diff --git a/cpp/core/internal/loop_runner.cc b/cpp/core/internal/loop_runner.cc index 39414857..9e48338a 100644 --- a/cpp/core/internal/loop_runner.cc +++ b/cpp/core/internal/loop_runner.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/loop_runner.h" #include "platform/exception.h" diff --git a/cpp/core/internal/loop_runner.h b/cpp/core/internal/loop_runner.h index af18a5c9..8f4091c2 100644 --- a/cpp/core/internal/loop_runner.h +++ b/cpp/core/internal/loop_runner.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_LOOP_RUNNER_H_ #define CORE_INTERNAL_LOOP_RUNNER_H_ diff --git a/cpp/core/internal/medium_manager.cc b/cpp/core/internal/medium_manager.cc index be6ca370..40c18139 100644 --- a/cpp/core/internal/medium_manager.cc +++ b/cpp/core/internal/medium_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/medium_manager.h" #include "platform/synchronized.h" diff --git a/cpp/core/internal/medium_manager.h b/cpp/core/internal/medium_manager.h index 93ba82af..002b6e1e 100644 --- a/cpp/core/internal/medium_manager.h +++ b/cpp/core/internal/medium_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUM_MANAGER_H_ #define CORE_INTERNAL_MEDIUM_MANAGER_H_ diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD index b0fca5fa..4916b4a0 100644 --- a/cpp/core/internal/mediums/BUILD +++ b/cpp/core/internal/mediums/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "mediums", srcs = [ diff --git a/cpp/core/internal/mediums/CMakeLists.txt b/cpp/core/internal/mediums/CMakeLists.txt index 311f7084..e991f711 100644 --- a/cpp/core/internal/mediums/CMakeLists.txt +++ b/cpp/core/internal/mediums/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(core_internal_mediums STATIC) target_sources(core_internal_mediums diff --git a/cpp/core/internal/mediums/advertisement_read_result.cc b/cpp/core/internal/mediums/advertisement_read_result.cc index 12cf2e1d..7629e041 100644 --- a/cpp/core/internal/mediums/advertisement_read_result.cc +++ b/cpp/core/internal/mediums/advertisement_read_result.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/advertisement_read_result.h" #include diff --git a/cpp/core/internal/mediums/advertisement_read_result.h b/cpp/core/internal/mediums/advertisement_read_result.h index 9fde9598..36b80a15 100644 --- a/cpp/core/internal/mediums/advertisement_read_result.h +++ b/cpp/core/internal/mediums/advertisement_read_result.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ #define CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index 158e01fb..ecd4923d 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/advertisement_read_result.h" #include "platform/impl/default/default_platform.h" diff --git a/cpp/core/internal/mediums/ble.cc b/cpp/core/internal/mediums/ble.cc index ebcffbf4..82c8c9a2 100644 --- a/cpp/core/internal/mediums/ble.cc +++ b/cpp/core/internal/mediums/ble.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble.h" #include "platform/synchronized.h" diff --git a/cpp/core/internal/mediums/ble.h b/cpp/core/internal/mediums/ble.h index e7db1336..5af2a241 100644 --- a/cpp/core/internal/mediums/ble.h +++ b/cpp/core/internal/mediums/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_H_ #define CORE_INTERNAL_MEDIUMS_BLE_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement.cc b/cpp/core/internal/mediums/ble_advertisement.cc index e83d8d3b..1e4c99b0 100644 --- a/cpp/core/internal/mediums/ble_advertisement.cc +++ b/cpp/core/internal/mediums/ble_advertisement.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_advertisement.h" #include diff --git a/cpp/core/internal/mediums/ble_advertisement.h b/cpp/core/internal/mediums/ble_advertisement.h index 75209336..4683301d 100644 --- a/cpp/core/internal/mediums/ble_advertisement.h +++ b/cpp/core/internal/mediums/ble_advertisement.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ #define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement_header.cc b/cpp/core/internal/mediums/ble_advertisement_header.cc index b5e1d672..b1a48727 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_advertisement_header.h" #include diff --git a/cpp/core/internal/mediums/ble_advertisement_header.h b/cpp/core/internal/mediums/ble_advertisement_header.h index 3cf70e5a..db0d1bca 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header.h +++ b/cpp/core/internal/mediums/ble_advertisement_header.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ #define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core/internal/mediums/ble_advertisement_header_test.cc b/cpp/core/internal/mediums/ble_advertisement_header_test.cc index df9267c6..1ba30371 100644 --- a/cpp/core/internal/mediums/ble_advertisement_header_test.cc +++ b/cpp/core/internal/mediums/ble_advertisement_header_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_advertisement_header.h" #include "platform/base64_utils.h" diff --git a/cpp/core/internal/mediums/ble_advertisement_test.cc b/cpp/core/internal/mediums/ble_advertisement_test.cc index 965cd543..b96c881f 100644 --- a/cpp/core/internal/mediums/ble_advertisement_test.cc +++ b/cpp/core/internal/mediums/ble_advertisement_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_advertisement.h" #include diff --git a/cpp/core/internal/mediums/ble_packet.cc b/cpp/core/internal/mediums/ble_packet.cc index be7a9bb8..bdea3049 100644 --- a/cpp/core/internal/mediums/ble_packet.cc +++ b/cpp/core/internal/mediums/ble_packet.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_packet.h" #include diff --git a/cpp/core/internal/mediums/ble_packet.h b/cpp/core/internal/mediums/ble_packet.h index ec7cf0c7..660b374d 100644 --- a/cpp/core/internal/mediums/ble_packet.h +++ b/cpp/core/internal/mediums/ble_packet.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_ #define CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_ diff --git a/cpp/core/internal/mediums/ble_packet_test.cc b/cpp/core/internal/mediums/ble_packet_test.cc index 90c0d06b..5a247557 100644 --- a/cpp/core/internal/mediums/ble_packet_test.cc +++ b/cpp/core/internal/mediums/ble_packet_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_packet.h" #include "gtest/gtest.h" diff --git a/cpp/core/internal/mediums/ble_peripheral.cc b/cpp/core/internal/mediums/ble_peripheral.cc index ef54ec8c..ea1c2a92 100644 --- a/cpp/core/internal/mediums/ble_peripheral.cc +++ b/cpp/core/internal/mediums/ble_peripheral.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble_peripheral.h" namespace location { diff --git a/cpp/core/internal/mediums/ble_peripheral.h b/cpp/core/internal/mediums/ble_peripheral.h index 0c5acd01..7e1a333e 100644 --- a/cpp/core/internal/mediums/ble_peripheral.h +++ b/cpp/core/internal/mediums/ble_peripheral.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ #define CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc index 32ba762c..7e491814 100644 --- a/cpp/core/internal/mediums/ble_v2.cc +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/ble.h" #include "core/internal/mediums/ble_advertisement_header.h" #include "core/internal/mediums/bloom_filter.h" diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h index d8f07bd2..8f268959 100644 --- a/cpp/core/internal/mediums/ble_v2.h +++ b/cpp/core/internal/mediums/ble_v2.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_H_ #define CORE_INTERNAL_MEDIUMS_BLE_V2_H_ diff --git a/cpp/core/internal/mediums/bloom_filter.cc b/cpp/core/internal/mediums/bloom_filter.cc index 835ed205..e8107237 100644 --- a/cpp/core/internal/mediums/bloom_filter.cc +++ b/cpp/core/internal/mediums/bloom_filter.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/bloom_filter.h" #include "absl/numeric/int128.h" diff --git a/cpp/core/internal/mediums/bloom_filter.h b/cpp/core/internal/mediums/bloom_filter.h index d358cb25..174bba9a 100644 --- a/cpp/core/internal/mediums/bloom_filter.h +++ b/cpp/core/internal/mediums/bloom_filter.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ #define CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ diff --git a/cpp/core/internal/mediums/bloom_filter_test.cc b/cpp/core/internal/mediums/bloom_filter_test.cc index 00ad384a..384e6505 100644 --- a/cpp/core/internal/mediums/bloom_filter_test.cc +++ b/cpp/core/internal/mediums/bloom_filter_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/bloom_filter.h" #include diff --git a/cpp/core/internal/mediums/bluetooth_classic.cc b/cpp/core/internal/mediums/bluetooth_classic.cc index c49348c5..bf374d26 100644 --- a/cpp/core/internal/mediums/bluetooth_classic.cc +++ b/cpp/core/internal/mediums/bluetooth_classic.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/bluetooth_classic.h" #include diff --git a/cpp/core/internal/mediums/bluetooth_classic.h b/cpp/core/internal/mediums/bluetooth_classic.h index dddf3993..05ec5a5d 100644 --- a/cpp/core/internal/mediums/bluetooth_classic.h +++ b/cpp/core/internal/mediums/bluetooth_classic.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ #define CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/core/internal/mediums/bluetooth_radio.cc b/cpp/core/internal/mediums/bluetooth_radio.cc index 9edaa777..0f5b73ba 100644 --- a/cpp/core/internal/mediums/bluetooth_radio.cc +++ b/cpp/core/internal/mediums/bluetooth_radio.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/bluetooth_radio.h" #include "platform/exception.h" diff --git a/cpp/core/internal/mediums/bluetooth_radio.h b/cpp/core/internal/mediums/bluetooth_radio.h index 14dd611b..00e4cf28 100644 --- a/cpp/core/internal/mediums/bluetooth_radio.h +++ b/cpp/core/internal/mediums/bluetooth_radio.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ #define CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ diff --git a/cpp/core/internal/mediums/discovered_peripheral_callback.h b/cpp/core/internal/mediums/discovered_peripheral_callback.h index 1e3fe35f..b3edbda1 100644 --- a/cpp/core/internal/mediums/discovered_peripheral_callback.h +++ b/cpp/core/internal/mediums/discovered_peripheral_callback.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_ #define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_ diff --git a/cpp/core/internal/mediums/discovered_peripheral_tracker.cc b/cpp/core/internal/mediums/discovered_peripheral_tracker.cc index 276ec2cf..4fee1e4c 100644 --- a/cpp/core/internal/mediums/discovered_peripheral_tracker.cc +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/discovered_peripheral_tracker.h" #include "core/internal/mediums/ble_packet.h" diff --git a/cpp/core/internal/mediums/discovered_peripheral_tracker.h b/cpp/core/internal/mediums/discovered_peripheral_tracker.h index 7c23a3d8..b1aa8685 100644 --- a/cpp/core/internal/mediums/discovered_peripheral_tracker.h +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_ #define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_ diff --git a/cpp/core/internal/mediums/lost_entity_tracker.cc b/cpp/core/internal/mediums/lost_entity_tracker.cc index 0122cb57..1b71bb9a 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker.cc +++ b/cpp/core/internal/mediums/lost_entity_tracker.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/lost_entity_tracker.h" #include "platform/synchronized.h" diff --git a/cpp/core/internal/mediums/lost_entity_tracker.h b/cpp/core/internal/mediums/lost_entity_tracker.h index ad4fb7ae..b1d30f8e 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker.h +++ b/cpp/core/internal/mediums/lost_entity_tracker.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ #define CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ diff --git a/cpp/core/internal/mediums/lost_entity_tracker_test.cc b/cpp/core/internal/mediums/lost_entity_tracker_test.cc index ce37d6e0..7da24e22 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker_test.cc +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/lost_entity_tracker.h" #include "platform/impl/default/default_platform.h" diff --git a/cpp/core/internal/mediums/mediums.cc b/cpp/core/internal/mediums/mediums.cc index 22638499..69c1d166 100644 --- a/cpp/core/internal/mediums/mediums.cc +++ b/cpp/core/internal/mediums/mediums.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/mediums.h" namespace location { diff --git a/cpp/core/internal/mediums/mediums.h b/cpp/core/internal/mediums/mediums.h index f6d57d75..68c6c72d 100644 --- a/cpp/core/internal/mediums/mediums.h +++ b/cpp/core/internal/mediums/mediums.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_MEDIUMS_H_ #define CORE_INTERNAL_MEDIUMS_MEDIUMS_H_ diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc index 125359c7..0d76a440 100644 --- a/cpp/core/internal/mediums/utils.cc +++ b/cpp/core/internal/mediums/utils.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/utils.h" #include diff --git a/cpp/core/internal/mediums/utils.h b/cpp/core/internal/mediums/utils.h index 665716a9..264f1fa7 100644 --- a/cpp/core/internal/mediums/utils.h +++ b/cpp/core/internal/mediums/utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_UTILS_H_ #define CORE_INTERNAL_MEDIUMS_UTILS_H_ diff --git a/cpp/core/internal/mediums/uuid.cc b/cpp/core/internal/mediums/uuid.cc index df6bed62..549c8c2b 100644 --- a/cpp/core/internal/mediums/uuid.cc +++ b/cpp/core/internal/mediums/uuid.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/mediums/uuid.h" #include diff --git a/cpp/core/internal/mediums/uuid.h b/cpp/core/internal/mediums/uuid.h index 5742e6c4..bb99460d 100644 --- a/cpp/core/internal/mediums/uuid.h +++ b/cpp/core/internal/mediums/uuid.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_MEDIUMS_UUID_H_ #define CORE_INTERNAL_MEDIUMS_UUID_H_ diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index 232a6c89..36407111 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/offline_frames.h" #include diff --git a/cpp/core/internal/offline_frames.h b/cpp/core/internal/offline_frames.h index e29699bf..d82773bb 100644 --- a/cpp/core/internal/offline_frames.h +++ b/cpp/core/internal/offline_frames.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_OFFLINE_FRAMES_H_ #define CORE_INTERNAL_OFFLINE_FRAMES_H_ diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc index 764514f7..1eddd6eb 100644 --- a/cpp/core/internal/offline_frames_test.cc +++ b/cpp/core/internal/offline_frames_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/offline_frames.h" #include diff --git a/cpp/core/internal/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc index b5e45cfb..c7578e26 100644 --- a/cpp/core/internal/offline_service_controller.cc +++ b/cpp/core/internal/offline_service_controller.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/offline_service_controller.h" #include diff --git a/cpp/core/internal/offline_service_controller.h b/cpp/core/internal/offline_service_controller.h index 743e7852..69d34c0c 100644 --- a/cpp/core/internal/offline_service_controller.h +++ b/cpp/core/internal/offline_service_controller.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ #define CORE_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index 84881eef..c98d8041 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/p2p_cluster_pcp_handler.h" #include "platform/api/hash_utils.h" diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h index 78d5c757..0be713c0 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ #define CORE_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc index 4e48a42c..6dd600d0 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/p2p_point_to_point_pcp_handler.h" namespace location { diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.h b/cpp/core/internal/p2p_point_to_point_pcp_handler.h index 56f7104b..e73c3140 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ #define CORE_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ diff --git a/cpp/core/internal/p2p_star_pcp_handler.cc b/cpp/core/internal/p2p_star_pcp_handler.cc index a3bf50d6..0e37e616 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.cc +++ b/cpp/core/internal/p2p_star_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/p2p_star_pcp_handler.h" #include diff --git a/cpp/core/internal/p2p_star_pcp_handler.h b/cpp/core/internal/p2p_star_pcp_handler.h index 4a7c110f..f7c635ae 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.h +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_ #define CORE_INTERNAL_P2P_STAR_PCP_HANDLER_H_ diff --git a/cpp/core/internal/payload_manager.cc b/cpp/core/internal/payload_manager.cc index 383a08ee..6325244b 100644 --- a/cpp/core/internal/payload_manager.cc +++ b/cpp/core/internal/payload_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/payload_manager.h" #include @@ -442,17 +456,6 @@ class HandleSuccessfulOutgoingChunkRunnable : public Runnable { return; } - // nearby:google3-begin - // TODO(reznor): The fact that we've sent total_size bytes (which we will - // always know 1 frame before we get the SUCCESS frame), also tells us this - // is the last chunk - should we add those smarts, or just be simple and - // always have the last IN_PROGRESS have the same numbers as the following - // SUCCESS? I prefer the simplicity, but it'll look stupid if we send all - // the bytes and then remain hanging because the remote device disconnected - // at just that point, so at least consider injecting the smarts. - // TODO(reznor): Should we check whether payload_header.total_size == - // payload_chunk.offset? - // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( @@ -516,16 +519,6 @@ class HandleSuccessfulIncomingChunkRunnable : public Runnable { return; } - // nearby:google3-begin - // TODO(reznor): The fact that we've received total_size bytes (which we - // will always know 1 frame before we get the SUCCESS frame), also tells us - // this is the last chunk - should we add those smarts, or just be simple - // and always have the last IN_PROGRESS have the same numbers as the - // following SUCCESS? I prefer the simplicity, but it'll look stupid if we - // get all the bytes and then remain hanging because the remote device - // disconnected at just that point, so at least consider injecting the - // smarts. - // nearby:google3-end bool is_last_chunk = (payload_chunk_flags_ & PayloadTransferFrame::PayloadChunk::LAST_CHUNK) != 0; PayloadTransferUpdate update( diff --git a/cpp/core/internal/payload_manager.h b/cpp/core/internal/payload_manager.h index 4058ec6f..3f427499 100644 --- a/cpp/core/internal/payload_manager.h +++ b/cpp/core/internal/payload_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_PAYLOAD_MANAGER_H_ #define CORE_INTERNAL_PAYLOAD_MANAGER_H_ diff --git a/cpp/core/internal/pcp.h b/cpp/core/internal/pcp.h index 427d974f..3a4fe92e 100644 --- a/cpp/core/internal/pcp.h +++ b/cpp/core/internal/pcp.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_PCP_H_ #define CORE_INTERNAL_PCP_H_ diff --git a/cpp/core/internal/pcp_handler.h b/cpp/core/internal/pcp_handler.h index 4babed34..d016c2c3 100644 --- a/cpp/core/internal/pcp_handler.h +++ b/cpp/core/internal/pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_PCP_HANDLER_H_ #define CORE_INTERNAL_PCP_HANDLER_H_ diff --git a/cpp/core/internal/pcp_manager.cc b/cpp/core/internal/pcp_manager.cc index 50500e2e..eb618cfe 100644 --- a/cpp/core/internal/pcp_manager.cc +++ b/cpp/core/internal/pcp_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/pcp_manager.h" #include "core/internal/p2p_cluster_pcp_handler.h" diff --git a/cpp/core/internal/pcp_manager.h b/cpp/core/internal/pcp_manager.h index 8bb77a32..b5695796 100644 --- a/cpp/core/internal/pcp_manager.h +++ b/cpp/core/internal/pcp_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_PCP_MANAGER_H_ #define CORE_INTERNAL_PCP_MANAGER_H_ diff --git a/cpp/core/internal/service_controller.h b/cpp/core/internal/service_controller.h index 05f37071..6c7aef31 100644 --- a/cpp/core/internal/service_controller.h +++ b/cpp/core/internal/service_controller.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_SERVICE_CONTROLLER_H_ #define CORE_INTERNAL_SERVICE_CONTROLLER_H_ diff --git a/cpp/core/internal/service_controller_router.cc b/cpp/core/internal/service_controller_router.cc index aa76330e..3a7eb23f 100644 --- a/cpp/core/internal/service_controller_router.cc +++ b/cpp/core/internal/service_controller_router.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/service_controller_router.h" #include "core/internal/offline_service_controller.h" diff --git a/cpp/core/internal/service_controller_router.h b/cpp/core/internal/service_controller_router.h index 73e2784c..11d3ba63 100644 --- a/cpp/core/internal/service_controller_router.h +++ b/cpp/core/internal/service_controller_router.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ #define CORE_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ diff --git a/cpp/core/internal/wifi_lan_service_info.cc b/cpp/core/internal/wifi_lan_service_info.cc index 7cfb9b3e..8c22627b 100644 --- a/cpp/core/internal/wifi_lan_service_info.cc +++ b/cpp/core/internal/wifi_lan_service_info.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/wifi_lan_service_info.h" #include diff --git a/cpp/core/internal/wifi_lan_service_info.h b/cpp/core/internal/wifi_lan_service_info.h index eb193c28..97f81b52 100644 --- a/cpp/core/internal/wifi_lan_service_info.h +++ b/cpp/core/internal/wifi_lan_service_info.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ #define CORE_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ diff --git a/cpp/core/internal/wifi_lan_service_info_test.cc b/cpp/core/internal/wifi_lan_service_info_test.cc index 7b7c5ced..79c4b094 100644 --- a/cpp/core/internal/wifi_lan_service_info_test.cc +++ b/cpp/core/internal/wifi_lan_service_info_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/wifi_lan_service_info.h" #include diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc index 7df38ed7..8ddd1ed5 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.cc +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/internal/wifi_lan_upgrade_handler.h" #include "proto/connections_enums.pb.h" diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.h b/cpp/core/internal/wifi_lan_upgrade_handler.h index 26781c47..54f89804 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.h +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_ #define CORE_INTERNAL_WIFI_LAN_UPGRADE_HANDLER_H_ diff --git a/cpp/core/listeners.h b/cpp/core/listeners.h index 28ab7a30..3831408a 100644 --- a/cpp/core/listeners.h +++ b/cpp/core/listeners.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_LISTENERS_H_ #define CORE_LISTENERS_H_ diff --git a/cpp/core/options.h b/cpp/core/options.h index 222f0060..05ba1d88 100644 --- a/cpp/core/options.h +++ b/cpp/core/options.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_OPTIONS_H_ #define CORE_OPTIONS_H_ diff --git a/cpp/core/params.h b/cpp/core/params.h index 754d0ec6..a1299dc2 100644 --- a/cpp/core/params.h +++ b/cpp/core/params.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_PARAMS_H_ #define CORE_PARAMS_H_ diff --git a/cpp/core/payload.cc b/cpp/core/payload.cc index cafabcee..5b878465 100644 --- a/cpp/core/payload.cc +++ b/cpp/core/payload.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/payload.h" #include diff --git a/cpp/core/payload.h b/cpp/core/payload.h index 5dd3436f..e07b5894 100644 --- a/cpp/core/payload.h +++ b/cpp/core/payload.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_PAYLOAD_H_ #define CORE_PAYLOAD_H_ diff --git a/cpp/core/status.h b/cpp/core/status.h index ea0de7b6..cce5f41d 100644 --- a/cpp/core/status.h +++ b/cpp/core/status.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_STATUS_H_ #define CORE_STATUS_H_ diff --git a/cpp/core/strategy.cc b/cpp/core/strategy.cc index dfa1637c..a5c46a8e 100644 --- a/cpp/core/strategy.cc +++ b/cpp/core/strategy.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core/strategy.h" namespace location { diff --git a/cpp/core/strategy.h b/cpp/core/strategy.h index 2cd1dacd..24c64454 100644 --- a/cpp/core/strategy.h +++ b/cpp/core/strategy.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_STRATEGY_H_ #define CORE_STRATEGY_H_ diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 72d19bc6..fea39046 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "utils", srcs = [ diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt index bac031db..c346e859 100644 --- a/cpp/platform/CMakeLists.txt +++ b/cpp/platform/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_utils STATIC base64_utils.cc file_impl.cc diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index f1c769b7..80ee2bf1 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + package(default_visibility = [ "//core:__subpackages__", "//platform:__subpackages__", diff --git a/cpp/platform/api/CMakeLists.txt b/cpp/platform/api/CMakeLists.txt index 59343794..935d2d24 100644 --- a/cpp/platform/api/CMakeLists.txt +++ b/cpp/platform/api/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_api STATIC atomic_boolean.h atomic_reference.h diff --git a/cpp/platform/api/atomic_boolean.h b/cpp/platform/api/atomic_boolean.h index 41f94165..84949079 100644 --- a/cpp/platform/api/atomic_boolean.h +++ b/cpp/platform/api/atomic_boolean.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_ATOMIC_BOOLEAN_H_ #define PLATFORM_API_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api/atomic_reference.h b/cpp/platform/api/atomic_reference.h index 52a8b14e..61ee8c9f 100644 --- a/cpp/platform/api/atomic_reference.h +++ b/cpp/platform/api/atomic_reference.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_ATOMIC_REFERENCE_H_ #define PLATFORM_API_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api/ble.h b/cpp/platform/api/ble.h index e9d44250..460daf3a 100644 --- a/cpp/platform/api/ble.h +++ b/cpp/platform/api/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_BLE_H_ #define PLATFORM_API_BLE_H_ diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h index 06a88288..b620ea32 100644 --- a/cpp/platform/api/ble_v2.h +++ b/cpp/platform/api/ble_v2.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_BLE_V2_H_ #define PLATFORM_API_BLE_V2_H_ diff --git a/cpp/platform/api/bluetooth_adapter.h b/cpp/platform/api/bluetooth_adapter.h index 04223492..170afde6 100644 --- a/cpp/platform/api/bluetooth_adapter.h +++ b/cpp/platform/api/bluetooth_adapter.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_BLUETOOTH_ADAPTER_H_ #define PLATFORM_API_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api/bluetooth_classic.h b/cpp/platform/api/bluetooth_classic.h index 154c7f0f..fdf448a3 100644 --- a/cpp/platform/api/bluetooth_classic.h +++ b/cpp/platform/api/bluetooth_classic.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_BLUETOOTH_CLASSIC_H_ #define PLATFORM_API_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform/api/condition_variable.h b/cpp/platform/api/condition_variable.h index b40aa7f5..d0351065 100644 --- a/cpp/platform/api/condition_variable.h +++ b/cpp/platform/api/condition_variable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_CONDITION_VARIABLE_H_ #define PLATFORM_API_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api/count_down_latch.h b/cpp/platform/api/count_down_latch.h index d5b99f95..ed2cbb45 100644 --- a/cpp/platform/api/count_down_latch.h +++ b/cpp/platform/api/count_down_latch.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_COUNT_DOWN_LATCH_H_ #define PLATFORM_API_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api/executor.h b/cpp/platform/api/executor.h index 2755af36..3c1f68cc 100644 --- a/cpp/platform/api/executor.h +++ b/cpp/platform/api/executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_EXECUTOR_H_ #define PLATFORM_API_EXECUTOR_H_ diff --git a/cpp/platform/api/future.h b/cpp/platform/api/future.h index 166a4ed9..7ba3d8db 100644 --- a/cpp/platform/api/future.h +++ b/cpp/platform/api/future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_FUTURE_H_ #define PLATFORM_API_FUTURE_H_ diff --git a/cpp/platform/api/hash_utils.h b/cpp/platform/api/hash_utils.h index 12083380..4bbe1ae7 100644 --- a/cpp/platform/api/hash_utils.h +++ b/cpp/platform/api/hash_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_HASH_UTILS_H_ #define PLATFORM_API_HASH_UTILS_H_ diff --git a/cpp/platform/api/input_file.h b/cpp/platform/api/input_file.h index ed2c782a..8de2ba5c 100644 --- a/cpp/platform/api/input_file.h +++ b/cpp/platform/api/input_file.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_INPUT_FILE_H_ #define PLATFORM_API_INPUT_FILE_H_ diff --git a/cpp/platform/api/input_stream.h b/cpp/platform/api/input_stream.h index 02eb6502..49a65921 100644 --- a/cpp/platform/api/input_stream.h +++ b/cpp/platform/api/input_stream.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_INPUT_STREAM_H_ #define PLATFORM_API_INPUT_STREAM_H_ diff --git a/cpp/platform/api/listenable_future.h b/cpp/platform/api/listenable_future.h index 3cd306e7..1f5d47ca 100644 --- a/cpp/platform/api/listenable_future.h +++ b/cpp/platform/api/listenable_future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_LISTENABLE_FUTURE_H_ #define PLATFORM_API_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform/api/lock.h b/cpp/platform/api/lock.h index 1c93aa8c..f1256c73 100644 --- a/cpp/platform/api/lock.h +++ b/cpp/platform/api/lock.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_LOCK_H_ #define PLATFORM_API_LOCK_H_ diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h index 3770fda4..3ffdc46e 100644 --- a/cpp/platform/api/multi_thread_executor.h +++ b/cpp/platform/api/multi_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_MULTI_THREAD_EXECUTOR_H_ #define PLATFORM_API_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api/output_file.h b/cpp/platform/api/output_file.h index b600d539..90d31002 100644 --- a/cpp/platform/api/output_file.h +++ b/cpp/platform/api/output_file.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_OUTPUT_FILE_H_ #define PLATFORM_API_OUTPUT_FILE_H_ diff --git a/cpp/platform/api/output_stream.h b/cpp/platform/api/output_stream.h index fd4d8ea3..a94febff 100644 --- a/cpp/platform/api/output_stream.h +++ b/cpp/platform/api/output_stream.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_OUTPUT_STREAM_H_ #define PLATFORM_API_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api/scheduled_executor.h b/cpp/platform/api/scheduled_executor.h index 2100058b..38410ffd 100644 --- a/cpp/platform/api/scheduled_executor.h +++ b/cpp/platform/api/scheduled_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SCHEDULED_EXECUTOR_H_ #define PLATFORM_API_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api/server_sync.h b/cpp/platform/api/server_sync.h index e6b01aa9..8c20b368 100644 --- a/cpp/platform/api/server_sync.h +++ b/cpp/platform/api/server_sync.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SERVER_SYNC_H_ #define PLATFORM_API_SERVER_SYNC_H_ diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h index f9a5e35c..3aed3d83 100644 --- a/cpp/platform/api/settable_future.h +++ b/cpp/platform/api/settable_future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SETTABLE_FUTURE_H_ #define PLATFORM_API_SETTABLE_FUTURE_H_ diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h index e3338648..ed92e0fa 100644 --- a/cpp/platform/api/single_thread_executor.h +++ b/cpp/platform/api/single_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SINGLE_THREAD_EXECUTOR_H_ #define PLATFORM_API_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api/socket.h b/cpp/platform/api/socket.h index e6e69775..915a85ba 100644 --- a/cpp/platform/api/socket.h +++ b/cpp/platform/api/socket.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SOCKET_H_ #define PLATFORM_API_SOCKET_H_ diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h index 3d7bd625..3554165b 100644 --- a/cpp/platform/api/submittable_executor.h +++ b/cpp/platform/api/submittable_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ #define PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api/system_clock.h b/cpp/platform/api/system_clock.h index d85ae1ca..60e3e2ed 100644 --- a/cpp/platform/api/system_clock.h +++ b/cpp/platform/api/system_clock.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_SYSTEM_CLOCK_H_ #define PLATFORM_API_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api/thread_utils.h b/cpp/platform/api/thread_utils.h index e477662b..50dd3ea2 100644 --- a/cpp/platform/api/thread_utils.h +++ b/cpp/platform/api/thread_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_THREAD_UTILS_H_ #define PLATFORM_API_THREAD_UTILS_H_ diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index 35f53e60..fd73f19d 100644 --- a/cpp/platform/api/webrtc.h +++ b/cpp/platform/api/webrtc.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_WEBRTC_H_ #define PLATFORM_API_WEBRTC_H_ diff --git a/cpp/platform/api/wifi.h b/cpp/platform/api/wifi.h index 6631d036..3eeb83e5 100644 --- a/cpp/platform/api/wifi.h +++ b/cpp/platform/api/wifi.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_WIFI_H_ #define PLATFORM_API_WIFI_H_ diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h index 1b13b393..744813f8 100644 --- a/cpp/platform/api/wifi_lan.h +++ b/cpp/platform/api/wifi_lan.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API_WIFI_LAN_H_ #define PLATFORM_API_WIFI_LAN_H_ diff --git a/cpp/platform/api2/BUILD b/cpp/platform/api2/BUILD index 5313b366..6051f5c4 100644 --- a/cpp/platform/api2/BUILD +++ b/cpp/platform/api2/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + package(default_visibility = [ "//core:__subpackages__", "//platform:__subpackages__", diff --git a/cpp/platform/api2/CMakeLists.txt b/cpp/platform/api2/CMakeLists.txt index 64e8a7a7..9639502f 100644 --- a/cpp/platform/api2/CMakeLists.txt +++ b/cpp/platform/api2/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_api2 STATIC) target_sources(platform_api2 diff --git a/cpp/platform/api2/atomic_boolean.h b/cpp/platform/api2/atomic_boolean.h index b5e729fa..52ac5831 100644 --- a/cpp/platform/api2/atomic_boolean.h +++ b/cpp/platform/api2/atomic_boolean.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_ATOMIC_BOOLEAN_H_ #define PLATFORM_API2_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/atomic_reference.h b/cpp/platform/api2/atomic_reference.h index 8740be0d..7e6b6d96 100644 --- a/cpp/platform/api2/atomic_reference.h +++ b/cpp/platform/api2/atomic_reference.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_ATOMIC_REFERENCE_H_ #define PLATFORM_API2_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api2/ble.h b/cpp/platform/api2/ble.h index 337f0717..a9e79823 100644 --- a/cpp/platform/api2/ble.h +++ b/cpp/platform/api2/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_BLE_H_ #define PLATFORM_API2_BLE_H_ diff --git a/cpp/platform/api2/ble_v2.h b/cpp/platform/api2/ble_v2.h index e0573c55..58c433fb 100644 --- a/cpp/platform/api2/ble_v2.h +++ b/cpp/platform/api2/ble_v2.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_BLE_V2_H_ #define PLATFORM_API2_BLE_V2_H_ diff --git a/cpp/platform/api2/bluetooth_adapter.h b/cpp/platform/api2/bluetooth_adapter.h index 21171a01..58bf9dad 100644 --- a/cpp/platform/api2/bluetooth_adapter.h +++ b/cpp/platform/api2/bluetooth_adapter.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_BLUETOOTH_ADAPTER_H_ #define PLATFORM_API2_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api2/bluetooth_classic.h b/cpp/platform/api2/bluetooth_classic.h index 57de4ddc..b693e671 100644 --- a/cpp/platform/api2/bluetooth_classic.h +++ b/cpp/platform/api2/bluetooth_classic.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_BLUETOOTH_CLASSIC_H_ #define PLATFORM_API2_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform/api2/condition_variable.h b/cpp/platform/api2/condition_variable.h index 936a3c36..f0fd7573 100644 --- a/cpp/platform/api2/condition_variable.h +++ b/cpp/platform/api2/condition_variable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_CONDITION_VARIABLE_H_ #define PLATFORM_API2_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api2/count_down_latch.h b/cpp/platform/api2/count_down_latch.h index ae0dfc86..8ba4a3c0 100644 --- a/cpp/platform/api2/count_down_latch.h +++ b/cpp/platform/api2/count_down_latch.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_COUNT_DOWN_LATCH_H_ #define PLATFORM_API2_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api2/executor.h b/cpp/platform/api2/executor.h index ee561894..0ed336c3 100644 --- a/cpp/platform/api2/executor.h +++ b/cpp/platform/api2/executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_EXECUTOR_H_ #define PLATFORM_API2_EXECUTOR_H_ diff --git a/cpp/platform/api2/future.h b/cpp/platform/api2/future.h index 7f46c484..4d566d30 100644 --- a/cpp/platform/api2/future.h +++ b/cpp/platform/api2/future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_FUTURE_H_ #define PLATFORM_API2_FUTURE_H_ diff --git a/cpp/platform/api2/hash_utils.h b/cpp/platform/api2/hash_utils.h index fab68f32..fc692ad3 100644 --- a/cpp/platform/api2/hash_utils.h +++ b/cpp/platform/api2/hash_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_HASH_UTILS_H_ #define PLATFORM_API2_HASH_UTILS_H_ diff --git a/cpp/platform/api2/input_file.h b/cpp/platform/api2/input_file.h index 0191aff8..29aafb72 100644 --- a/cpp/platform/api2/input_file.h +++ b/cpp/platform/api2/input_file.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_INPUT_FILE_H_ #define PLATFORM_API2_INPUT_FILE_H_ diff --git a/cpp/platform/api2/input_stream.h b/cpp/platform/api2/input_stream.h index f91a5466..4caf598f 100644 --- a/cpp/platform/api2/input_stream.h +++ b/cpp/platform/api2/input_stream.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_INPUT_STREAM_H_ #define PLATFORM_API2_INPUT_STREAM_H_ diff --git a/cpp/platform/api2/listenable_future.h b/cpp/platform/api2/listenable_future.h index 2993bc88..0007e98c 100644 --- a/cpp/platform/api2/listenable_future.h +++ b/cpp/platform/api2/listenable_future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_LISTENABLE_FUTURE_H_ #define PLATFORM_API2_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform/api2/multi_thread_executor.h b/cpp/platform/api2/multi_thread_executor.h index f910bbc4..4f4bb951 100644 --- a/cpp/platform/api2/multi_thread_executor.h +++ b/cpp/platform/api2/multi_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ #define PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/mutex.h b/cpp/platform/api2/mutex.h index d4dbaf61..a097da40 100644 --- a/cpp/platform/api2/mutex.h +++ b/cpp/platform/api2/mutex.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_MUTEX_H_ #define PLATFORM_API2_MUTEX_H_ diff --git a/cpp/platform/api2/output_file.h b/cpp/platform/api2/output_file.h index 4ac962e8..1375c65c 100644 --- a/cpp/platform/api2/output_file.h +++ b/cpp/platform/api2/output_file.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_OUTPUT_FILE_H_ #define PLATFORM_API2_OUTPUT_FILE_H_ diff --git a/cpp/platform/api2/output_stream.h b/cpp/platform/api2/output_stream.h index b9336ad1..95be4cd4 100644 --- a/cpp/platform/api2/output_stream.h +++ b/cpp/platform/api2/output_stream.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_OUTPUT_STREAM_H_ #define PLATFORM_API2_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api2/scheduled_executor.h b/cpp/platform/api2/scheduled_executor.h index ae773ee1..2bc068a5 100644 --- a/cpp/platform/api2/scheduled_executor.h +++ b/cpp/platform/api2/scheduled_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SCHEDULED_EXECUTOR_H_ #define PLATFORM_API2_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/server_sync.h b/cpp/platform/api2/server_sync.h index 47bc3aa5..46d5c5e2 100644 --- a/cpp/platform/api2/server_sync.h +++ b/cpp/platform/api2/server_sync.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SERVER_SYNC_H_ #define PLATFORM_API2_SERVER_SYNC_H_ diff --git a/cpp/platform/api2/settable_future.h b/cpp/platform/api2/settable_future.h index 2089173c..73617ae5 100644 --- a/cpp/platform/api2/settable_future.h +++ b/cpp/platform/api2/settable_future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SETTABLE_FUTURE_H_ #define PLATFORM_API2_SETTABLE_FUTURE_H_ diff --git a/cpp/platform/api2/single_thread_executor.h b/cpp/platform/api2/single_thread_executor.h index 990f2fe7..56319d3e 100644 --- a/cpp/platform/api2/single_thread_executor.h +++ b/cpp/platform/api2/single_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ #define PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/socket.h b/cpp/platform/api2/socket.h index 0f855609..29113ca8 100644 --- a/cpp/platform/api2/socket.h +++ b/cpp/platform/api2/socket.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SOCKET_H_ #define PLATFORM_API2_SOCKET_H_ diff --git a/cpp/platform/api2/submittable_executor.h b/cpp/platform/api2/submittable_executor.h index 43f16f56..c55f7a5a 100644 --- a/cpp/platform/api2/submittable_executor.h +++ b/cpp/platform/api2/submittable_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ #define PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api2/system_clock.h b/cpp/platform/api2/system_clock.h index 3b0b8090..cf1442e9 100644 --- a/cpp/platform/api2/system_clock.h +++ b/cpp/platform/api2/system_clock.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_SYSTEM_CLOCK_H_ #define PLATFORM_API2_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/thread_utils.h b/cpp/platform/api2/thread_utils.h index 990c0ec2..013f3a3d 100644 --- a/cpp/platform/api2/thread_utils.h +++ b/cpp/platform/api2/thread_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_THREAD_UTILS_H_ #define PLATFORM_API2_THREAD_UTILS_H_ diff --git a/cpp/platform/api2/webrtc.h b/cpp/platform/api2/webrtc.h index e1dbde9e..23ab20ed 100644 --- a/cpp/platform/api2/webrtc.h +++ b/cpp/platform/api2/webrtc.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_WEBRTC_H_ #define PLATFORM_API2_WEBRTC_H_ diff --git a/cpp/platform/api2/wifi.h b/cpp/platform/api2/wifi.h index 74f0e5c9..93552823 100644 --- a/cpp/platform/api2/wifi.h +++ b/cpp/platform/api2/wifi.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_API2_WIFI_H_ #define PLATFORM_API2_WIFI_H_ diff --git a/cpp/platform/base64_utils.cc b/cpp/platform/base64_utils.cc index 9c5f2fa8..2210970d 100644 --- a/cpp/platform/base64_utils.cc +++ b/cpp/platform/base64_utils.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/base64_utils.h" #include "absl/strings/escaping.h" diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h index 042dc386..615b4e82 100644 --- a/cpp/platform/base64_utils.h +++ b/cpp/platform/base64_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_BASE64_UTILS_H_ #define PLATFORM_BASE64_UTILS_H_ diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h index 49f9bf88..79bbd52b 100644 --- a/cpp/platform/byte_array.h +++ b/cpp/platform/byte_array.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_BYTE_ARRAY_H_ #define PLATFORM_BYTE_ARRAY_H_ diff --git a/cpp/platform/byte_array_test.cc b/cpp/platform/byte_array_test.cc index 8bd5ee97..3989c4a4 100644 --- a/cpp/platform/byte_array_test.cc +++ b/cpp/platform/byte_array_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/byte_array.h" #include "gmock/gmock.h" diff --git a/cpp/platform/callable.h b/cpp/platform/callable.h index 792a207d..79465ad9 100644 --- a/cpp/platform/callable.h +++ b/cpp/platform/callable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_CALLABLE_H_ #define PLATFORM_CALLABLE_H_ diff --git a/cpp/platform/cancelable.h b/cpp/platform/cancelable.h index 74a2d634..2a506896 100644 --- a/cpp/platform/cancelable.h +++ b/cpp/platform/cancelable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_CANCELABLE_H_ #define PLATFORM_CANCELABLE_H_ diff --git a/cpp/platform/cancelable_alarm.cc b/cpp/platform/cancelable_alarm.cc index 326a893e..faa729fa 100644 --- a/cpp/platform/cancelable_alarm.cc +++ b/cpp/platform/cancelable_alarm.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/cancelable_alarm.h" #include "platform/synchronized.h" diff --git a/cpp/platform/cancelable_alarm.h b/cpp/platform/cancelable_alarm.h index d5549cf6..3551ecca 100644 --- a/cpp/platform/cancelable_alarm.h +++ b/cpp/platform/cancelable_alarm.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_CANCELABLE_ALARM_H_ #define PLATFORM_CANCELABLE_ALARM_H_ diff --git a/cpp/platform/container_of.h b/cpp/platform/container_of.h index ccdccd7a..e6f61127 100644 --- a/cpp/platform/container_of.h +++ b/cpp/platform/container_of.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_CONTAINER_OF_H_ #define PLATFORM_CONTAINER_OF_H_ diff --git a/cpp/platform/container_of_test.cc b/cpp/platform/container_of_test.cc index 72c5d8c6..46dba31e 100644 --- a/cpp/platform/container_of_test.cc +++ b/cpp/platform/container_of_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/container_of.h" #include "gmock/gmock.h" diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h index 485f03a3..9976e838 100644 --- a/cpp/platform/exception.h +++ b/cpp/platform/exception.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_EXCEPTION_H_ #define PLATFORM_EXCEPTION_H_ diff --git a/cpp/platform/exception_test.cc b/cpp/platform/exception_test.cc index d36e2d85..31058398 100644 --- a/cpp/platform/exception_test.cc +++ b/cpp/platform/exception_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/exception.h" #include diff --git a/cpp/platform/file_impl.cc b/cpp/platform/file_impl.cc index 67bab338..91c596db 100644 --- a/cpp/platform/file_impl.cc +++ b/cpp/platform/file_impl.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/file_impl.h" #include diff --git a/cpp/platform/file_impl.h b/cpp/platform/file_impl.h index db522c23..9945afe7 100644 --- a/cpp/platform/file_impl.h +++ b/cpp/platform/file_impl.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_FILE_IMPL_H_ #define PLATFORM_FILE_IMPL_H_ diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc index f37e0b91..b6b03784 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/file_impl_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/file_impl.h" #include diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/default/BUILD index 87f28f9c..3e5dbe6d 100644 --- a/cpp/platform/impl/default/BUILD +++ b/cpp/platform/impl/default/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "default", srcs = [ diff --git a/cpp/platform/impl/default/CMakeLists.txt b/cpp/platform/impl/default/CMakeLists.txt index 222d9ae0..a542481c 100644 --- a/cpp/platform/impl/default/CMakeLists.txt +++ b/cpp/platform/impl/default/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_impl_default STATIC) target_sources(platform_impl_default diff --git a/cpp/platform/impl/default/default_condition_variable.cc b/cpp/platform/impl/default/default_condition_variable.cc index d7e3811f..a48d77a3 100644 --- a/cpp/platform/impl/default/default_condition_variable.cc +++ b/cpp/platform/impl/default/default_condition_variable.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/impl/default/default_condition_variable.h" namespace location { diff --git a/cpp/platform/impl/default/default_condition_variable.h b/cpp/platform/impl/default/default_condition_variable.h index 4aa1343f..76d7cf8d 100644 --- a/cpp/platform/impl/default/default_condition_variable.h +++ b/cpp/platform/impl/default/default_condition_variable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ #define PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_lock.cc b/cpp/platform/impl/default/default_lock.cc index bfd3cf0b..df67c6c1 100644 --- a/cpp/platform/impl/default/default_lock.cc +++ b/cpp/platform/impl/default/default_lock.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/impl/default/default_lock.h" namespace location { diff --git a/cpp/platform/impl/default/default_lock.h b/cpp/platform/impl/default/default_lock.h index 18d50e44..3e1b2e41 100644 --- a/cpp/platform/impl/default/default_lock.h +++ b/cpp/platform/impl/default/default_lock.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ #define PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ diff --git a/cpp/platform/impl/default/default_platform.cc b/cpp/platform/impl/default/default_platform.cc index 3d41d42b..876ad0f3 100644 --- a/cpp/platform/impl/default/default_platform.cc +++ b/cpp/platform/impl/default/default_platform.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/impl/default/default_platform.h" #include "platform/impl/default/default_condition_variable.h" diff --git a/cpp/platform/impl/default/default_platform.h b/cpp/platform/impl/default/default_platform.h index 0d001825..54132b19 100644 --- a/cpp/platform/impl/default/default_platform.h +++ b/cpp/platform/impl/default/default_platform.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ #define PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ diff --git a/cpp/platform/impl/ios/BUILD b/cpp/platform/impl/ios/BUILD index a75790f9..089b92d6 100644 --- a/cpp/platform/impl/ios/BUILD +++ b/cpp/platform/impl/ios/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + objc_library( name = "ios", visibility = [ diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index 892ccd51..e75257c7 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "sample", srcs = [ diff --git a/cpp/platform/impl/sample/CMakeLists.txt b/cpp/platform/impl/sample/CMakeLists.txt index 0944ac4f..c5929ffe 100644 --- a/cpp/platform/impl/sample/CMakeLists.txt +++ b/cpp/platform/impl/sample/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_impl_sample STATIC) target_sources(platform_impl_sample diff --git a/cpp/platform/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h index 113f4636..78b154c5 100644 --- a/cpp/platform/impl/sample/sample_platform.h +++ b/cpp/platform/impl/sample/sample_platform.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ #define PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ diff --git a/cpp/platform/impl/sample/sample_wifi_medium.cc b/cpp/platform/impl/sample/sample_wifi_medium.cc index 89b68391..2031cc8e 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.cc +++ b/cpp/platform/impl/sample/sample_wifi_medium.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/impl/sample/sample_wifi_medium.h" #include diff --git a/cpp/platform/impl/sample/sample_wifi_medium.h b/cpp/platform/impl/sample/sample_wifi_medium.h index e64f1b8e..46acfc2b 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.h +++ b/cpp/platform/impl/sample/sample_wifi_medium.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ #define PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ diff --git a/cpp/platform/logging.h b/cpp/platform/logging.h index 836ce62f..96511073 100644 --- a/cpp/platform/logging.h +++ b/cpp/platform/logging.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_LOGGING_H_ #define PLATFORM_LOGGING_H_ diff --git a/cpp/platform/pipe.cc b/cpp/platform/pipe.cc index e5572127..52779b95 100644 --- a/cpp/platform/pipe.cc +++ b/cpp/platform/pipe.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/pipe.h" #include "platform/synchronized.h" diff --git a/cpp/platform/pipe.h b/cpp/platform/pipe.h index 29e06d11..c4a8ca72 100644 --- a/cpp/platform/pipe.h +++ b/cpp/platform/pipe.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PIPE_H_ #define PLATFORM_PIPE_H_ diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc index f4f2799b..7d511c8a 100644 --- a/cpp/platform/pipe_test.cc +++ b/cpp/platform/pipe_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/pipe.h" #include diff --git a/cpp/platform/port/BUILD b/cpp/platform/port/BUILD index 80447569..6b0293d6 100644 --- a/cpp/platform/port/BUILD +++ b/cpp/platform/port/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + cc_library( name = "config", hdrs = [ diff --git a/cpp/platform/port/CMakeLists.txt b/cpp/platform/port/CMakeLists.txt index 18dbe0c3..ac2b8f16 100644 --- a/cpp/platform/port/CMakeLists.txt +++ b/cpp/platform/port/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_library(platform_port_config_private INTERFACE) target_sources(platform_port_config_private diff --git a/cpp/platform/port/config.h b/cpp/platform/port/config.h index 19857578..f7da653c 100644 --- a/cpp/platform/port/config.h +++ b/cpp/platform/port/config.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PORT_CONFIG_H_ #define PLATFORM_PORT_CONFIG_H_ diff --git a/cpp/platform/port/down_cast.h b/cpp/platform/port/down_cast.h index 161884c8..6cc2fcb7 100644 --- a/cpp/platform/port/down_cast.h +++ b/cpp/platform/port/down_cast.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PORT_DOWN_CAST_H_ #define PLATFORM_PORT_DOWN_CAST_H_ diff --git a/cpp/platform/port/string.h b/cpp/platform/port/string.h index d9a0cdff..e0cfda4a 100644 --- a/cpp/platform/port/string.h +++ b/cpp/platform/port/string.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PORT_STRING_H_ #define PLATFORM_PORT_STRING_H_ diff --git a/cpp/platform/prng.cc b/cpp/platform/prng.cc index 7f98870c..e4512346 100644 --- a/cpp/platform/prng.cc +++ b/cpp/platform/prng.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/prng.h" #include diff --git a/cpp/platform/prng.h b/cpp/platform/prng.h index 9a7ff34a..4ed2198d 100644 --- a/cpp/platform/prng.h +++ b/cpp/platform/prng.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PRNG_H_ #define PLATFORM_PRNG_H_ diff --git a/cpp/platform/prng_test.cc b/cpp/platform/prng_test.cc index e4115944..328e3687 100644 --- a/cpp/platform/prng_test.cc +++ b/cpp/platform/prng_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/prng.h" #include "gtest/gtest.h" diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h index 6527db19..45c5e55d 100644 --- a/cpp/platform/ptr.h +++ b/cpp/platform/ptr.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_PTR_H_ #define PLATFORM_PTR_H_ diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc index adc73c08..2ce2f9b0 100644 --- a/cpp/platform/ptr_test.cc +++ b/cpp/platform/ptr_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/ptr.h" #include "gtest/gtest.h" diff --git a/cpp/platform/reliability_utils.cc b/cpp/platform/reliability_utils.cc index 1f296eb8..917a2fa7 100644 --- a/cpp/platform/reliability_utils.cc +++ b/cpp/platform/reliability_utils.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform/reliability_utils.h" namespace location { diff --git a/cpp/platform/reliability_utils.h b/cpp/platform/reliability_utils.h index a4262e89..4c387b60 100644 --- a/cpp/platform/reliability_utils.h +++ b/cpp/platform/reliability_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_RELIABILITY_UTILS_H_ #define PLATFORM_RELIABILITY_UTILS_H_ diff --git a/cpp/platform/runnable.h b/cpp/platform/runnable.h index e70bd512..76d0d4be 100644 --- a/cpp/platform/runnable.h +++ b/cpp/platform/runnable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_RUNNABLE_H_ #define PLATFORM_RUNNABLE_H_ diff --git a/cpp/platform/synchronized.h b/cpp/platform/synchronized.h index 81b37789..c95a1dd5 100644 --- a/cpp/platform/synchronized.h +++ b/cpp/platform/synchronized.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_SYNCHRONIZED_H_ #define PLATFORM_SYNCHRONIZED_H_ diff --git a/proto/BUILD b/proto/BUILD index 6446d8f9..ae34afb9 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Proto for Nearby products load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 7eb4378b..4c6002d9 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_cc_proto_library( proto_bootstrap_enums_cc_proto PROTOS bootstrap_enums.proto diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto index 71bcfad6..777ba3b0 100644 --- a/proto/bootstrap_enums.proto +++ b/proto/bootstrap_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto; diff --git a/proto/connections/BUILD b/proto/connections/BUILD index c7c295c2..03567b3d 100644 --- a/proto/connections/BUILD +++ b/proto/connections/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") proto_library( diff --git a/proto/connections/CMakeLists.txt b/proto/connections/CMakeLists.txt index 0b36c455..9195a301 100644 --- a/proto/connections/CMakeLists.txt +++ b/proto/connections/CMakeLists.txt @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + add_cc_proto_library( proto_offline_wire_formats_cc_proto PROTOS offline_wire_formats.proto diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index b5d2901e..5e2f3360 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.connections; diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index cbd1d5d6..1961c20b 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -1,15 +1,17 @@ -// nearby:google3-begin -// Any changes in this file maybe cause the unmapped result in the PLX tables. -// Please remember to update the table schemas: -// 1. Check your changes are rolled out in the MPM. -// https://mpmbrowse.corp.google.com/package/location/nearby/lingo -// 2. Check the new lingo job is scheduled and completed. -// https://borgcron-dashboard.corp.google.com/#user=social-copresence-batch -// 3. Runs PLX script to update schema. -// https://plx.corp.google.com/scripts2/script_e1._9eb6f3_e3cd_419b_b483_c1e42abc824a +// Copyright 2020 Google LLC // -// Or you can wait one or two days then run the above Step3. dircetly. -// nearby:google3-end +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index c8b0848f..9e9adffb 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto; diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 275be438..c74dc399 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto; diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index aec58708..602e338d 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto; @@ -10,12 +24,6 @@ option objc_class_prefix = "GNCP"; enum UserType { UNKNOWN_USER_TYPE = 0; PRODUCTION = 1; -// nearby:google3-begin - MODULEFOOD = 2; - TEST = 3; - PRESTO_DOGFOOD = 4; - AUTO_TEST = 5; -// nearby:google3-end } // The client that is logging. diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto index 9ee412a0..3981e09c 100644 --- a/proto/nearby_event_codes.proto +++ b/proto/nearby_event_codes.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto; diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto index 79880c0f..a2399207 100644 --- a/proto/setup_enums.proto +++ b/proto/setup_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto.setup; diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index 2447a135..bbb792c5 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.proto.sharing; diff --git a/script/oss.py b/script/oss.py index 7010ed46..be08927c 100755 --- a/script/oss.py +++ b/script/oss.py @@ -1,5 +1,20 @@ #!/usr/bin/python3 +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import argparse import os import shutil From 81a46c68c60b165550198ba207f3bf57d41111af Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Thu, 21 May 2020 17:56:28 +0100 Subject: [PATCH 16/52] Modify webrtc header files This helps in using webrtc in other open source projects which might have a different location for the webrtc headers. Change-Id: Ifa9aea95a26fa542544d83343ef90144835960c3 --- script/oss.py | 1 + 1 file changed, 1 insertion(+) diff --git a/script/oss.py b/script/oss.py index 7010ed46..f188b3b7 100755 --- a/script/oss.py +++ b/script/oss.py @@ -86,6 +86,7 @@ def post_process_oss_files(path, args): else: top_dirs = ["cpp", "proto"] transforms = ( + ("third_party/webrtc/files/stable/", ""), ("third_party/", ""), ("location/nearby/connections/core", "core"), ("location/nearby/cpp/platform", "platform"), From 667bf4ee3bbcd1fb1c1d1c7bd83a198be6ad390f Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 27 May 2020 23:56:16 -0700 Subject: [PATCH 17/52] update oss.py to include v2 code Signed-off-by: Alexey Polyudov Change-Id: I75f5e12cc947ed9767d49fc4598de75921073a5a --- script/oss.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/script/oss.py b/script/oss.py index 7010ed46..c4672f25 100755 --- a/script/oss.py +++ b/script/oss.py @@ -61,7 +61,9 @@ def copy_files_to_oss_project(src_root, dst_root): shutil.rmtree(dst_root + "/proto", ignore_errors=True) shutil.copytree(src_root + "/proto", dst_root + "/proto/") shutil.copytree(src_root + "/cpp/platform/", dst_root + "/cpp/platform/") + shutil.copytree(src_root + "/cpp/platform_v2/", dst_root + "/cpp/platform_v2/") shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") + shutil.copytree(src_root + "/connections/core_v2/", dst_root + "/cpp/core_v2/") shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") def detect_file_copy_header_options(fname, lines): @@ -87,7 +89,9 @@ def post_process_oss_files(path, args): top_dirs = ["cpp", "proto"] transforms = ( ("third_party/", ""), + ("location/nearby/connections/core_v2", "core_v2"), ("location/nearby/connections/core", "core"), + ("location/nearby/cpp/platform_v2", "platform_v2"), ("location/nearby/cpp/platform", "platform"), ("security/cryptauth/lib/securegcm", "securegcm"), ("testing/base/public/gmock.h", "gmock/gmock.h"), From ae1c427b9933c979321ba0c1ca8363af475f7d5e Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 28 May 2020 00:03:22 -0700 Subject: [PATCH 18/52] nearby: snapshot of cl/313536507 Signed-off-by: Alexey Polyudov Change-Id: I8936b527079074d5c3af5531b9245063767cc4a7 --- cpp/core/BUILD | 4 +- cpp/core/check_compilation.cc | 8 +- cpp/core/core.h | 19 +- cpp/core/internal/BUILD | 41 +- cpp/core/internal/bandwidth_upgrade_handler.h | 4 +- .../internal/bandwidth_upgrade_manager.cc | 20 +- cpp/core/internal/bandwidth_upgrade_manager.h | 21 +- .../base_bandwidth_upgrade_handler.cc | 105 ++-- .../internal/base_bandwidth_upgrade_handler.h | 34 +- cpp/core/internal/base_endpoint_channel.cc | 94 ++-- cpp/core/internal/base_endpoint_channel.h | 8 +- .../internal/base_endpoint_channel_test.cc | 20 +- cpp/core/internal/base_pcp_handler.cc | 25 +- cpp/core/internal/base_pcp_handler.h | 13 +- cpp/core/internal/ble_endpoint_channel.cc | 33 +- cpp/core/internal/ble_endpoint_channel.h | 12 +- .../internal/bluetooth_endpoint_channel.cc | 31 +- .../internal/bluetooth_endpoint_channel.h | 12 +- cpp/core/internal/encryption_runner.cc | 44 +- cpp/core/internal/endpoint_channel_manager.cc | 100 ++-- cpp/core/internal/endpoint_channel_manager.h | 14 +- cpp/core/internal/endpoint_manager.cc | 2 +- cpp/core/internal/endpoint_manager.h | 4 +- cpp/core/internal/internal_payload_factory.cc | 15 +- cpp/core/internal/medium_manager.cc | 129 ++++- cpp/core/internal/medium_manager.h | 40 ++ cpp/core/internal/mediums/BUILD | 39 +- .../mediums/advertisement_read_result_test.cc | 54 +- cpp/core/internal/mediums/ble_v2.cc | 4 +- cpp/core/internal/mediums/ble_v2.h | 6 +- .../mediums/lost_entity_tracker_test.cc | 18 +- cpp/core/internal/mediums/mediums.cc | 8 +- cpp/core/internal/mediums/mediums.h | 4 + cpp/core/internal/mediums/utils.cc | 22 + cpp/core/internal/mediums/utils.h | 2 + cpp/core/internal/mediums/webrtc/BUILD | 77 +++ cpp/core/internal/mediums/webrtc/peer_id.cc | 41 ++ cpp/core/internal/mediums/webrtc/peer_id.h | 36 ++ .../internal/mediums/webrtc/peer_id_test.cc | 76 +++ .../mediums/webrtc/signaling_frames.cc | 125 +++++ .../mediums/webrtc/signaling_frames.h | 49 ++ .../mediums/webrtc/signaling_frames_test.cc | 184 +++++++ .../internal/mediums/webrtc/webrtc_socket.cc | 139 +++++ .../internal/mediums/webrtc/webrtc_socket.h | 104 ++++ .../mediums/webrtc/webrtc_socket_test.cc | 155 ++++++ cpp/core/internal/mediums/wifi_lan.cc | 213 ++++++++ cpp/core/internal/mediums/wifi_lan.h | 160 ++++++ cpp/core/internal/message_lite.h | 6 + cpp/core/internal/offline_frames.cc | 5 +- cpp/core/internal/offline_frames_test.cc | 4 +- .../internal/offline_service_controller.cc | 4 +- .../internal/offline_service_controller.h | 5 +- cpp/core/internal/p2p_cluster_pcp_handler.cc | 316 +++++++++++- cpp/core/internal/p2p_cluster_pcp_handler.h | 270 +++++++--- .../p2p_point_to_point_pcp_handler.cc | 4 +- .../internal/p2p_point_to_point_pcp_handler.h | 4 +- cpp/core/internal/p2p_star_pcp_handler.cc | 4 +- cpp/core/internal/p2p_star_pcp_handler.h | 9 +- cpp/core/internal/pcp_manager.cc | 4 +- cpp/core/internal/pcp_manager.h | 4 +- .../internal/wifi_lan_endpoint_channel.cc | 49 ++ cpp/core/internal/wifi_lan_endpoint_channel.h | 46 ++ cpp/core/internal/wifi_lan_upgrade_handler.cc | 4 +- cpp/core/internal/wifi_lan_upgrade_handler.h | 27 +- cpp/core_v2/BUILD | 73 +++ cpp/core_v2/core.cc | 107 ++++ cpp/core_v2/core.h | 208 ++++++++ cpp/core_v2/core_test.cc | 44 ++ cpp/core_v2/internal/BUILD | 101 ++++ cpp/core_v2/internal/base_endpoint_channel.cc | 270 ++++++++++ cpp/core_v2/internal/base_endpoint_channel.h | 113 +++++ .../internal/base_endpoint_channel_test.cc | 342 +++++++++++++ cpp/core_v2/internal/base_pcp_handler.cc | 143 ++++++ cpp/core_v2/internal/base_pcp_handler.h | 323 ++++++++++++ cpp/core_v2/internal/base_pcp_handler_test.cc | 287 +++++++++++ cpp/core_v2/internal/ble_advertisement.cc | 222 ++++++++ cpp/core_v2/internal/ble_advertisement.h | 90 ++++ .../internal/ble_advertisement_test.cc | 258 ++++++++++ cpp/core_v2/internal/client_proxy.cc | 461 +++++++++++++++++ cpp/core_v2/internal/client_proxy.h | 217 ++++++++ cpp/core_v2/internal/client_proxy_test.cc | 357 +++++++++++++ cpp/core_v2/internal/encryption_runner.cc | 368 ++++++++++++++ cpp/core_v2/internal/encryption_runner.h | 72 +++ .../internal/encryption_runner_test.cc | 128 +++++ cpp/core_v2/internal/endpoint_channel.h | 74 +++ .../internal/endpoint_channel_manager.cc | 137 +++++ .../internal/endpoint_channel_manager.h | 155 ++++++ .../internal/endpoint_channel_manager_test.cc | 17 + cpp/core_v2/internal/endpoint_manager.cc | 477 ++++++++++++++++++ cpp/core_v2/internal/endpoint_manager.h | 218 ++++++++ cpp/core_v2/internal/endpoint_manager_test.cc | 242 +++++++++ cpp/core_v2/internal/mediums/BUILD | 70 +++ .../mediums/advertisement_read_result.cc | 125 +++++ .../mediums/advertisement_read_result.h | 90 ++++ .../mediums/advertisement_read_result_test.cc | 129 +++++ .../internal/mediums/ble_advertisement.cc | 201 ++++++++ .../internal/mediums/ble_advertisement.h | 100 ++++ .../mediums/ble_advertisement_header.cc | 118 +++++ .../mediums/ble_advertisement_header.h | 84 +++ .../mediums/ble_advertisement_header_test.cc | 176 +++++++ .../mediums/ble_advertisement_test.cc | 223 ++++++++ cpp/core_v2/internal/mediums/ble_packet.cc | 59 +++ cpp/core_v2/internal/mediums/ble_packet.h | 51 ++ .../internal/mediums/ble_packet_test.cc | 97 ++++ cpp/core_v2/internal/mediums/ble_peripheral.h | 36 ++ .../internal/mediums/ble_peripheral_test.cc | 33 ++ .../internal/mediums/bluetooth_radio.cc | 104 ++++ .../internal/mediums/bluetooth_radio.h | 80 +++ .../internal/mediums/bluetooth_radio_test.cc | 45 ++ .../internal/mediums/lost_entity_tracker.h | 80 +++ .../mediums/lost_entity_tracker_test.cc | 123 +++++ cpp/core_v2/internal/mediums/utils.cc | 41 ++ cpp/core_v2/internal/mediums/utils.h | 22 + cpp/core_v2/internal/mediums/uuid.cc | 75 +++ cpp/core_v2/internal/mediums/uuid.h | 45 ++ cpp/core_v2/internal/mediums/uuid_test.cc | 56 ++ cpp/core_v2/internal/mediums/webrtc/BUILD | 76 +++ .../internal/mediums/webrtc/peer_id.cc | 38 ++ cpp/core_v2/internal/mediums/webrtc/peer_id.h | 35 ++ .../internal/mediums/webrtc/peer_id_test.cc | 42 ++ .../mediums/webrtc/signaling_frames.cc | 120 +++++ .../mediums/webrtc/signaling_frames.h | 44 ++ .../mediums/webrtc/signaling_frames_test.cc | 182 +++++++ .../internal/mediums/webrtc/webrtc_socket.cc | 101 ++++ .../internal/mediums/webrtc/webrtc_socket.h | 101 ++++ .../mediums/webrtc/webrtc_socket_test.cc | 154 ++++++ .../internal/mock_service_controller.h | 71 +++ cpp/core_v2/internal/offline_frames.cc | 251 +++++++++ cpp/core_v2/internal/offline_frames.h | 61 +++ cpp/core_v2/internal/offline_frames_test.cc | 252 +++++++++ cpp/core_v2/internal/pcp.h | 26 + cpp/core_v2/internal/pcp_handler.h | 88 ++++ cpp/core_v2/internal/service_controller.h | 77 +++ .../internal/service_controller_router.cc | 383 ++++++++++++++ .../internal/service_controller_router.h | 111 ++++ .../service_controller_router_test.cc | 376 ++++++++++++++ cpp/core_v2/internal/wifi_lan_service_info.cc | 180 +++++++ cpp/core_v2/internal/wifi_lan_service_info.h | 81 +++ .../internal/wifi_lan_service_info_test.cc | 143 ++++++ cpp/core_v2/listeners.h | 180 +++++++ cpp/core_v2/listeners_test.cc | 45 ++ cpp/core_v2/options.h | 30 ++ cpp/core_v2/params.h | 27 + cpp/core_v2/payload.h | 85 ++++ cpp/core_v2/payload_test.cc | 76 +++ cpp/core_v2/status.h | 45 ++ cpp/core_v2/status_test.cc | 44 ++ cpp/core_v2/strategy.cc | 47 ++ cpp/core_v2/strategy.h | 62 +++ cpp/core_v2/strategy_test.cc | 41 ++ cpp/platform/BUILD | 83 +-- cpp/platform/api/BUILD | 6 + cpp/platform/api/atomic_reference.h | 41 +- cpp/platform/api/atomic_reference_def.h | 27 + cpp/platform/api/ble_v2.h | 2 +- cpp/platform/api/multi_thread_executor.h | 6 +- cpp/platform/api/platform.h | 106 ++++ cpp/platform/api/scheduled_executor.h | 6 +- cpp/platform/api/server_sync.h | 2 +- cpp/platform/api/settable_future.h | 57 ++- cpp/platform/api/settable_future_def.h | 31 ++ cpp/platform/api/single_thread_executor.h | 6 +- cpp/platform/api/submittable_executor.h | 51 +- cpp/platform/api/submittable_executor_def.h | 35 ++ cpp/platform/api/webrtc.h | 2 +- cpp/platform/api/wifi_lan.h | 20 +- cpp/platform/api2/atomic_boolean.h | 21 - cpp/platform/api2/input_file.h | 24 - cpp/platform/api2/input_stream.h | 27 - cpp/platform/api2/multi_thread_executor.h | 23 - cpp/platform/api2/mutex.h | 22 - cpp/platform/api2/output_file.h | 20 - cpp/platform/api2/output_stream.h | 25 - cpp/platform/api2/scheduled_executor.h | 29 -- cpp/platform/api2/single_thread_executor.h | 23 - cpp/platform/api2/submittable_executor.h | 42 -- cpp/platform/api2/system_clock.h | 22 - cpp/platform/api2/thread_utils.h | 22 - cpp/platform/atomic_reference_test.cc | 80 +++ cpp/platform/byte_array.h | 2 +- cpp/platform/cancelable_alarm.cc | 22 +- cpp/platform/cancelable_alarm.h | 12 +- cpp/platform/exception.h | 8 +- cpp/platform/file_impl.h | 2 +- cpp/platform/file_impl_test.cc | 2 +- cpp/platform/impl/default/BUILD | 45 -- .../default/default_condition_variable.cc | 28 - .../impl/default/default_condition_variable.h | 30 -- cpp/platform/impl/default/default_platform.cc | 17 - cpp/platform/impl/default/default_platform.h | 26 - cpp/platform/impl/g3/BUILD | 26 + cpp/platform/impl/g3/atomic_reference_impl.h | 39 ++ cpp/platform/impl/g3/platform.cc | 137 +++++ cpp/platform/impl/g3/settable_future_impl.h | 94 ++++ cpp/platform/impl/g3/system_clock_impl.h | 23 + cpp/platform/impl/sample/BUILD | 10 +- .../impl/sample/atomic_reference_impl.h | 25 + cpp/platform/impl/sample/sample_platform.cc | 125 +++++ cpp/platform/impl/sample/sample_platform.h | 141 ------ .../impl/sample/settable_future_impl.h | 35 ++ cpp/platform/impl/shared/BUILD | 45 ++ .../impl/shared/atomic_boolean_impl.h | 33 ++ .../impl/shared/posix_condition_variable.cc | 28 + .../impl/shared/posix_condition_variable.h | 30 ++ .../default_lock.cc => shared/posix_lock.cc} | 10 +- .../default_lock.h => shared/posix_lock.h} | 14 +- cpp/platform/impl/shared/sample/BUILD | 22 + .../{ => shared}/sample/sample_wifi_medium.cc | 2 +- .../{ => shared}/sample/sample_wifi_medium.h | 6 +- cpp/platform/pipe.cc | 59 +-- cpp/platform/pipe.h | 7 - cpp/platform/pipe_test.cc | 14 +- cpp/platform/ptr.h | 27 +- cpp/platform/ptr_test.cc | 5 +- cpp/platform/settable_future_test.cc | 84 +++ cpp/{platform/api2 => platform_v2/api}/BUILD | 48 +- cpp/platform_v2/api/atomic_boolean.h | 24 + .../api}/atomic_reference.h | 14 +- cpp/{platform/api2 => platform_v2/api}/ble.h | 19 +- .../api2 => platform_v2/api}/ble_v2.h | 16 +- .../api}/bluetooth_adapter.h | 17 +- .../api}/bluetooth_classic.h | 18 +- cpp/platform_v2/api/cancelable.h | 21 + .../api}/condition_variable.h | 10 +- .../api}/count_down_latch.h | 12 +- .../hash_utils.h => platform_v2/api/crypto.h} | 14 +- .../api2 => platform_v2/api}/executor.h | 16 +- .../api2 => platform_v2/api}/future.h | 12 +- cpp/platform_v2/api/input_file.h | 26 + .../api}/listenable_future.h | 19 +- cpp/platform_v2/api/mutex.h | 41 ++ cpp/platform_v2/api/output_file.h | 22 + cpp/platform_v2/api/platform.h | 78 +++ cpp/platform_v2/api/scheduled_executor.h | 36 ++ .../api2 => platform_v2/api}/server_sync.h | 10 +- .../api}/settable_future.h | 12 +- cpp/platform_v2/api/submittable_executor.h | 33 ++ cpp/platform_v2/api/system_clock.h | 23 + .../api2 => platform_v2/api}/webrtc.h | 10 +- cpp/{platform/api2 => platform_v2/api}/wifi.h | 12 +- cpp/platform_v2/api/wifi_lan.h | 87 ++++ cpp/platform_v2/base/BUILD | 73 +++ cpp/platform_v2/base/base64_utils.cc | 27 + cpp/platform_v2/base/base64_utils.h | 19 + cpp/platform_v2/base/base_mutex_lock.h | 26 + cpp/platform_v2/base/base_pipe.cc | 96 ++++ cpp/platform_v2/base/base_pipe.h | 128 +++++ cpp/platform_v2/base/byte_array.h | 81 +++ cpp/platform_v2/base/byte_array_test.cc | 68 +++ cpp/platform_v2/base/callable.h | 23 + cpp/platform_v2/base/exception.h | 97 ++++ cpp/platform_v2/base/exception_test.cc | 106 ++++ cpp/platform_v2/base/input_stream.h | 28 + cpp/platform_v2/base/listeners.h | 20 + cpp/platform_v2/base/output_stream.h | 25 + cpp/platform_v2/base/prng.cc | 45 ++ cpp/platform_v2/base/prng.h | 23 + cpp/platform_v2/base/prng_test.cc | 27 + cpp/platform_v2/base/runnable.h | 19 + .../api2 => platform_v2/base}/socket.h | 12 +- cpp/platform_v2/config/BUILD | 21 + cpp/platform_v2/config/config.h | 22 + cpp/platform_v2/config/string.h | 12 + cpp/platform_v2/impl/g3/BUILD | 57 +++ cpp/platform_v2/impl/g3/atomic_boolean.h | 30 ++ .../impl/g3/atomic_reference_any.h | 46 ++ cpp/platform_v2/impl/g3/bluetooth_adapter.cc | 65 +++ cpp/platform_v2/impl/g3/bluetooth_adapter.h | 90 ++++ cpp/platform_v2/impl/g3/condition_variable.h | 33 ++ cpp/platform_v2/impl/g3/count_down_latch.h | 59 +++ cpp/platform_v2/impl/g3/crypto.cc | 39 ++ cpp/platform_v2/impl/g3/medium_environment.cc | 32 ++ cpp/platform_v2/impl/g3/medium_environment.h | 47 ++ .../impl/g3/multi_thread_executor.h | 54 ++ cpp/platform_v2/impl/g3/mutex.h | 47 ++ cpp/platform_v2/impl/g3/pipe.h | 30 ++ cpp/platform_v2/impl/g3/platform.cc | 136 +++++ cpp/platform_v2/impl/g3/scheduled_executor.cc | 65 +++ cpp/platform_v2/impl/g3/scheduled_executor.h | 42 ++ cpp/platform_v2/impl/g3/settable_future_any.h | 104 ++++ .../impl/g3/single_thread_executor.h | 22 + cpp/platform_v2/impl/g3/system_clock.cc | 16 + cpp/platform_v2/impl/shared/BUILD | 34 ++ .../impl/shared/posix_condition_variable.cc | 30 ++ .../impl/shared/posix_condition_variable.h | 31 ++ cpp/platform_v2/impl/shared/posix_mutex.cc | 26 + cpp/platform_v2/impl/shared/posix_mutex.h | 31 ++ cpp/platform_v2/public/BUILD | 86 ++++ cpp/platform_v2/public/atomic_boolean.h | 34 ++ cpp/platform_v2/public/atomic_boolean_test.cc | 24 + cpp/platform_v2/public/atomic_reference.h | 40 ++ .../public/atomic_reference_test.cc | 75 +++ cpp/platform_v2/public/bluetooth_adapter.h | 63 +++ .../public/bluetooth_adapter_test.cc | 44 ++ cpp/platform_v2/public/cancelable.h | 36 ++ cpp/platform_v2/public/cancelable_alarm.h | 56 ++ cpp/platform_v2/public/condition_variable.h | 36 ++ cpp/platform_v2/public/count_down_latch.h | 40 ++ .../public/count_down_latch_test.cc | 48 ++ cpp/platform_v2/public/crypto.h | 6 + cpp/platform_v2/public/crypto_test.cc | 34 ++ cpp/platform_v2/public/file.cc | 79 +++ cpp/platform_v2/public/file.h | 51 ++ cpp/platform_v2/public/file_test.cc | 131 +++++ cpp/platform_v2/public/future.h | 63 +++ cpp/platform_v2/public/future_test.cc | 102 ++++ cpp/platform_v2/public/logging.h | 6 + cpp/platform_v2/public/logging_test.cc | 12 + .../public/multi_thread_executor.h | 28 + .../public/multi_thread_executor_test.cc | 94 ++++ cpp/platform_v2/public/mutex.h | 64 +++ cpp/platform_v2/public/mutex_lock.h | 31 ++ cpp/platform_v2/public/mutex_test.cc | 103 ++++ cpp/platform_v2/public/pipe.cc | 21 + cpp/platform_v2/public/pipe.h | 23 + cpp/platform_v2/public/pipe_test.cc | 332 ++++++++++++ cpp/platform_v2/public/scheduled_executor.h | 75 +++ .../public/scheduled_executor_test.cc | 100 ++++ .../public/single_thread_executor.h | 26 + .../public/single_thread_executor_test.cc | 71 +++ cpp/platform_v2/public/submittable_executor.h | 96 ++++ cpp/platform_v2/public/system_clock.h | 6 + proto/BUILD | 20 + proto/bootstrap_enums.proto | 1 + proto/connections/offline_wire_formats.proto | 2 + proto/connections_enums.proto | 1 + proto/connections_enums_proto_config.asciipb | 2 + proto/discovery_enums.proto | 1 + proto/error_code_enums.proto | 133 +++++ 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 | 17 + 334 files changed, 20315 insertions(+), 1487 deletions(-) create mode 100644 cpp/core/internal/mediums/webrtc/BUILD create mode 100644 cpp/core/internal/mediums/webrtc/peer_id.cc create mode 100644 cpp/core/internal/mediums/webrtc/peer_id.h create mode 100644 cpp/core/internal/mediums/webrtc/peer_id_test.cc create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames.cc create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames.h create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames_test.cc create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket.cc create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket.h create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc create mode 100644 cpp/core/internal/mediums/wifi_lan.cc create mode 100644 cpp/core/internal/mediums/wifi_lan.h create mode 100644 cpp/core/internal/message_lite.h create mode 100644 cpp/core/internal/wifi_lan_endpoint_channel.cc create mode 100644 cpp/core/internal/wifi_lan_endpoint_channel.h create mode 100644 cpp/core_v2/BUILD create mode 100644 cpp/core_v2/core.cc create mode 100644 cpp/core_v2/core.h create mode 100644 cpp/core_v2/core_test.cc create mode 100644 cpp/core_v2/internal/BUILD create mode 100644 cpp/core_v2/internal/base_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/base_endpoint_channel.h create mode 100644 cpp/core_v2/internal/base_endpoint_channel_test.cc create mode 100644 cpp/core_v2/internal/base_pcp_handler.cc create mode 100644 cpp/core_v2/internal/base_pcp_handler.h create mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc create mode 100644 cpp/core_v2/internal/ble_advertisement.cc create mode 100644 cpp/core_v2/internal/ble_advertisement.h create mode 100644 cpp/core_v2/internal/ble_advertisement_test.cc create mode 100644 cpp/core_v2/internal/client_proxy.cc create mode 100644 cpp/core_v2/internal/client_proxy.h create mode 100644 cpp/core_v2/internal/client_proxy_test.cc create mode 100644 cpp/core_v2/internal/encryption_runner.cc create mode 100644 cpp/core_v2/internal/encryption_runner.h create mode 100644 cpp/core_v2/internal/encryption_runner_test.cc create mode 100644 cpp/core_v2/internal/endpoint_channel.h create mode 100644 cpp/core_v2/internal/endpoint_channel_manager.cc create mode 100644 cpp/core_v2/internal/endpoint_channel_manager.h create mode 100644 cpp/core_v2/internal/endpoint_channel_manager_test.cc create mode 100644 cpp/core_v2/internal/endpoint_manager.cc create mode 100644 cpp/core_v2/internal/endpoint_manager.h create mode 100644 cpp/core_v2/internal/endpoint_manager_test.cc create mode 100644 cpp/core_v2/internal/mediums/BUILD create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result.cc create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result.h create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement.h create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header.h create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_packet.cc create mode 100644 cpp/core_v2/internal/mediums/ble_packet.h create mode 100644 cpp/core_v2/internal/mediums/ble_packet_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_peripheral.h create mode 100644 cpp/core_v2/internal/mediums/ble_peripheral_test.cc create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio.cc create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio.h create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio_test.cc create mode 100644 cpp/core_v2/internal/mediums/lost_entity_tracker.h create mode 100644 cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc create mode 100644 cpp/core_v2/internal/mediums/utils.cc create mode 100644 cpp/core_v2/internal/mediums/utils.h create mode 100644 cpp/core_v2/internal/mediums/uuid.cc create mode 100644 cpp/core_v2/internal/mediums/uuid.h create mode 100644 cpp/core_v2/internal/mediums/uuid_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/BUILD create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc create mode 100644 cpp/core_v2/internal/mock_service_controller.h create mode 100644 cpp/core_v2/internal/offline_frames.cc create mode 100644 cpp/core_v2/internal/offline_frames.h create mode 100644 cpp/core_v2/internal/offline_frames_test.cc create mode 100644 cpp/core_v2/internal/pcp.h create mode 100644 cpp/core_v2/internal/pcp_handler.h create mode 100644 cpp/core_v2/internal/service_controller.h create mode 100644 cpp/core_v2/internal/service_controller_router.cc create mode 100644 cpp/core_v2/internal/service_controller_router.h create mode 100644 cpp/core_v2/internal/service_controller_router_test.cc create mode 100644 cpp/core_v2/internal/wifi_lan_service_info.cc create mode 100644 cpp/core_v2/internal/wifi_lan_service_info.h create mode 100644 cpp/core_v2/internal/wifi_lan_service_info_test.cc create mode 100644 cpp/core_v2/listeners.h create mode 100644 cpp/core_v2/listeners_test.cc create mode 100644 cpp/core_v2/options.h create mode 100644 cpp/core_v2/params.h create mode 100644 cpp/core_v2/payload.h create mode 100644 cpp/core_v2/payload_test.cc create mode 100644 cpp/core_v2/status.h create mode 100644 cpp/core_v2/status_test.cc create mode 100644 cpp/core_v2/strategy.cc create mode 100644 cpp/core_v2/strategy.h create mode 100644 cpp/core_v2/strategy_test.cc create mode 100644 cpp/platform/api/atomic_reference_def.h create mode 100644 cpp/platform/api/platform.h create mode 100644 cpp/platform/api/settable_future_def.h create mode 100644 cpp/platform/api/submittable_executor_def.h delete mode 100644 cpp/platform/api2/atomic_boolean.h delete mode 100644 cpp/platform/api2/input_file.h delete mode 100644 cpp/platform/api2/input_stream.h delete mode 100644 cpp/platform/api2/multi_thread_executor.h delete mode 100644 cpp/platform/api2/mutex.h delete mode 100644 cpp/platform/api2/output_file.h delete mode 100644 cpp/platform/api2/output_stream.h delete mode 100644 cpp/platform/api2/scheduled_executor.h delete mode 100644 cpp/platform/api2/single_thread_executor.h delete mode 100644 cpp/platform/api2/submittable_executor.h delete mode 100644 cpp/platform/api2/system_clock.h delete mode 100644 cpp/platform/api2/thread_utils.h create mode 100644 cpp/platform/atomic_reference_test.cc delete mode 100644 cpp/platform/impl/default/BUILD delete mode 100644 cpp/platform/impl/default/default_condition_variable.cc delete mode 100644 cpp/platform/impl/default/default_condition_variable.h delete mode 100644 cpp/platform/impl/default/default_platform.cc delete mode 100644 cpp/platform/impl/default/default_platform.h create mode 100644 cpp/platform/impl/g3/atomic_reference_impl.h create mode 100644 cpp/platform/impl/g3/platform.cc create mode 100644 cpp/platform/impl/g3/settable_future_impl.h create mode 100644 cpp/platform/impl/g3/system_clock_impl.h create mode 100644 cpp/platform/impl/sample/atomic_reference_impl.h create mode 100644 cpp/platform/impl/sample/sample_platform.cc delete mode 100644 cpp/platform/impl/sample/sample_platform.h create mode 100644 cpp/platform/impl/sample/settable_future_impl.h create mode 100644 cpp/platform/impl/shared/BUILD create mode 100644 cpp/platform/impl/shared/atomic_boolean_impl.h create mode 100644 cpp/platform/impl/shared/posix_condition_variable.cc create mode 100644 cpp/platform/impl/shared/posix_condition_variable.h rename cpp/platform/impl/{default/default_lock.cc => shared/posix_lock.cc} (55%) rename cpp/platform/impl/{default/default_lock.h => shared/posix_lock.h} (51%) create mode 100644 cpp/platform/impl/shared/sample/BUILD rename cpp/platform/impl/{ => shared}/sample/sample_wifi_medium.cc (98%) rename cpp/platform/impl/{ => shared}/sample/sample_wifi_medium.h (90%) create mode 100644 cpp/platform/settable_future_test.cc rename cpp/{platform/api2 => platform_v2/api}/BUILD (51%) create mode 100644 cpp/platform_v2/api/atomic_boolean.h rename cpp/{platform/api2 => platform_v2/api}/atomic_reference.h (53%) rename cpp/{platform/api2 => platform_v2/api}/ble.h (90%) rename cpp/{platform/api2 => platform_v2/api}/ble_v2.h (98%) rename cpp/{platform/api2 => platform_v2/api}/bluetooth_adapter.h (82%) rename cpp/{platform/api2 => platform_v2/api}/bluetooth_classic.h (91%) create mode 100644 cpp/platform_v2/api/cancelable.h rename cpp/{platform/api2 => platform_v2/api}/condition_variable.h (75%) rename cpp/{platform/api2 => platform_v2/api}/count_down_latch.h (70%) rename cpp/{platform/api2/hash_utils.h => platform_v2/api/crypto.h} (50%) rename cpp/{platform/api2 => platform_v2/api}/executor.h (59%) rename cpp/{platform/api2 => platform_v2/api}/future.h (74%) create mode 100644 cpp/platform_v2/api/input_file.h rename cpp/{platform/api2 => platform_v2/api}/listenable_future.h (52%) create mode 100644 cpp/platform_v2/api/mutex.h create mode 100644 cpp/platform_v2/api/output_file.h create mode 100644 cpp/platform_v2/api/platform.h create mode 100644 cpp/platform_v2/api/scheduled_executor.h rename cpp/{platform/api2 => platform_v2/api}/server_sync.h (90%) rename cpp/{platform/api2 => platform_v2/api}/settable_future.h (62%) create mode 100644 cpp/platform_v2/api/submittable_executor.h create mode 100644 cpp/platform_v2/api/system_clock.h rename cpp/{platform/api2 => platform_v2/api}/webrtc.h (85%) rename cpp/{platform/api2 => platform_v2/api}/wifi.h (92%) create mode 100644 cpp/platform_v2/api/wifi_lan.h create mode 100644 cpp/platform_v2/base/BUILD create mode 100644 cpp/platform_v2/base/base64_utils.cc create mode 100644 cpp/platform_v2/base/base64_utils.h create mode 100644 cpp/platform_v2/base/base_mutex_lock.h create mode 100644 cpp/platform_v2/base/base_pipe.cc create mode 100644 cpp/platform_v2/base/base_pipe.h create mode 100644 cpp/platform_v2/base/byte_array.h create mode 100644 cpp/platform_v2/base/byte_array_test.cc create mode 100644 cpp/platform_v2/base/callable.h create mode 100644 cpp/platform_v2/base/exception.h create mode 100644 cpp/platform_v2/base/exception_test.cc create mode 100644 cpp/platform_v2/base/input_stream.h create mode 100644 cpp/platform_v2/base/listeners.h create mode 100644 cpp/platform_v2/base/output_stream.h create mode 100644 cpp/platform_v2/base/prng.cc create mode 100644 cpp/platform_v2/base/prng.h create mode 100644 cpp/platform_v2/base/prng_test.cc create mode 100644 cpp/platform_v2/base/runnable.h rename cpp/{platform/api2 => platform_v2/base}/socket.h (62%) create mode 100644 cpp/platform_v2/config/BUILD create mode 100644 cpp/platform_v2/config/config.h create mode 100644 cpp/platform_v2/config/string.h create mode 100644 cpp/platform_v2/impl/g3/BUILD create mode 100644 cpp/platform_v2/impl/g3/atomic_boolean.h create mode 100644 cpp/platform_v2/impl/g3/atomic_reference_any.h create mode 100644 cpp/platform_v2/impl/g3/bluetooth_adapter.cc create mode 100644 cpp/platform_v2/impl/g3/bluetooth_adapter.h create mode 100644 cpp/platform_v2/impl/g3/condition_variable.h create mode 100644 cpp/platform_v2/impl/g3/count_down_latch.h create mode 100644 cpp/platform_v2/impl/g3/crypto.cc create mode 100644 cpp/platform_v2/impl/g3/medium_environment.cc create mode 100644 cpp/platform_v2/impl/g3/medium_environment.h create mode 100644 cpp/platform_v2/impl/g3/multi_thread_executor.h create mode 100644 cpp/platform_v2/impl/g3/mutex.h create mode 100644 cpp/platform_v2/impl/g3/pipe.h create mode 100644 cpp/platform_v2/impl/g3/platform.cc create mode 100644 cpp/platform_v2/impl/g3/scheduled_executor.cc create mode 100644 cpp/platform_v2/impl/g3/scheduled_executor.h create mode 100644 cpp/platform_v2/impl/g3/settable_future_any.h create mode 100644 cpp/platform_v2/impl/g3/single_thread_executor.h create mode 100644 cpp/platform_v2/impl/g3/system_clock.cc create mode 100644 cpp/platform_v2/impl/shared/BUILD create mode 100644 cpp/platform_v2/impl/shared/posix_condition_variable.cc create mode 100644 cpp/platform_v2/impl/shared/posix_condition_variable.h create mode 100644 cpp/platform_v2/impl/shared/posix_mutex.cc create mode 100644 cpp/platform_v2/impl/shared/posix_mutex.h create mode 100644 cpp/platform_v2/public/BUILD create mode 100644 cpp/platform_v2/public/atomic_boolean.h create mode 100644 cpp/platform_v2/public/atomic_boolean_test.cc create mode 100644 cpp/platform_v2/public/atomic_reference.h create mode 100644 cpp/platform_v2/public/atomic_reference_test.cc create mode 100644 cpp/platform_v2/public/bluetooth_adapter.h create mode 100644 cpp/platform_v2/public/bluetooth_adapter_test.cc create mode 100644 cpp/platform_v2/public/cancelable.h create mode 100644 cpp/platform_v2/public/cancelable_alarm.h create mode 100644 cpp/platform_v2/public/condition_variable.h create mode 100644 cpp/platform_v2/public/count_down_latch.h create mode 100644 cpp/platform_v2/public/count_down_latch_test.cc create mode 100644 cpp/platform_v2/public/crypto.h create mode 100644 cpp/platform_v2/public/crypto_test.cc create mode 100644 cpp/platform_v2/public/file.cc create mode 100644 cpp/platform_v2/public/file.h create mode 100644 cpp/platform_v2/public/file_test.cc create mode 100644 cpp/platform_v2/public/future.h create mode 100644 cpp/platform_v2/public/future_test.cc create mode 100644 cpp/platform_v2/public/logging.h create mode 100644 cpp/platform_v2/public/logging_test.cc create mode 100644 cpp/platform_v2/public/multi_thread_executor.h create mode 100644 cpp/platform_v2/public/multi_thread_executor_test.cc create mode 100644 cpp/platform_v2/public/mutex.h create mode 100644 cpp/platform_v2/public/mutex_lock.h create mode 100644 cpp/platform_v2/public/mutex_test.cc create mode 100644 cpp/platform_v2/public/pipe.cc create mode 100644 cpp/platform_v2/public/pipe.h create mode 100644 cpp/platform_v2/public/pipe_test.cc create mode 100644 cpp/platform_v2/public/scheduled_executor.h create mode 100644 cpp/platform_v2/public/scheduled_executor_test.cc create mode 100644 cpp/platform_v2/public/single_thread_executor.h create mode 100644 cpp/platform_v2/public/single_thread_executor_test.cc create mode 100644 cpp/platform_v2/public/submittable_executor.h create mode 100644 cpp/platform_v2/public/system_clock.h create mode 100644 proto/error_code_enums.proto diff --git a/cpp/core/BUILD b/cpp/core/BUILD index fa226c20..f0bb59b5 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -49,7 +49,9 @@ cc_library( ":types", "//platform:types", "//platform:utils", - "//platform/impl/sample", + "//platform/api", + "//platform/impl/g3", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", ], ) diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index 23941a86..7bca2966 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -1,4 +1,3 @@ - #include #include "core/core.h" @@ -6,9 +5,10 @@ #include "core/params.h" #include "core/payload.h" #include "core/status.h" +#include "platform/api/platform.h" #include "platform/byte_array.h" #include "platform/file_impl.h" -#include "platform/impl/sample/sample_platform.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -16,6 +16,8 @@ namespace location { namespace nearby { namespace connections { +using TestPlatform = platform::ImplementationPlatform; + class ResultListenerImpl : public ResultListener { public: void onResult(Status::Value status) override {} @@ -53,7 +55,7 @@ class PayloadListenerImpl : public PayloadListener { }; void check_compilation() { - Core core; + Core core; const string name = "name"; const string service_id = "service_id"; diff --git a/cpp/core/core.h b/cpp/core/core.h index d148d6df..4643dea8 100644 --- a/cpp/core/core.h +++ b/cpp/core/core.h @@ -32,15 +32,20 @@ namespace connections { * SystemClock * ConditionVariable * - * The Platform class must also provide typedefs for the following subset of - * primitives to identify the concrete classes: + * A sample Platform definitions can be found at + * //platform/impl/shared/sample/sample_platform.cc * - * SingleThreadExecutorType - * MultiThreadExecutorType - * ScheduledExecutorType + * It is no longer necessary to parametrize system types with a platform type. + * New, recommended approach is to define platform support by implementing + * static methods of "location::nearby::platform::ImplementationPlatform" class. + * every library class that needs platform support, must include platform + * header "platform/api/platform.h" and use it. + * To keep textual compatibility, one could define the following alias + * "using Platform = platform::ImplementationPlatform;". + * this will replace the "template " declaration. * - * A sample Platform class can be found at - * //platform/impl/sample/sample_platform.h + * As an added benefit, this will allow to not include *.cc files from *.h, + * and let more static analysis happen at compiler stage. */ template class Core { diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index f54df70d..9c7f303b 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -1,38 +1,39 @@ cc_library( name = "internal", srcs = [ + "bandwidth_upgrade_manager.cc", + "base_bandwidth_upgrade_handler.cc", + "base_endpoint_channel.cc", "ble_advertisement.cc", + "ble_endpoint_channel.cc", "bluetooth_device_name.cc", + "bluetooth_endpoint_channel.cc", + "endpoint_channel_manager.cc", "internal_payload.cc", "internal_payload.h", "loop_runner.cc", "loop_runner.h", "offline_frames.cc", + "wifi_lan_endpoint_channel.cc", "wifi_lan_service_info.cc", ], hdrs = [ "bandwidth_upgrade_handler.h", - "bandwidth_upgrade_manager.cc", "bandwidth_upgrade_manager.h", - "base_bandwidth_upgrade_handler.cc", "base_bandwidth_upgrade_handler.h", - "base_endpoint_channel.cc", "base_endpoint_channel.h", "base_pcp_handler.cc", "base_pcp_handler.h", "ble_advertisement.h", "ble_compat.h", - "ble_endpoint_channel.cc", "ble_endpoint_channel.h", "bluetooth_device_name.h", - "bluetooth_endpoint_channel.cc", "bluetooth_endpoint_channel.h", "client_proxy.cc", "client_proxy.h", "encryption_runner.cc", "encryption_runner.h", "endpoint_channel.h", - "endpoint_channel_manager.cc", "endpoint_channel_manager.h", "endpoint_manager.cc", "endpoint_manager.h", @@ -58,6 +59,7 @@ cc_library( "service_controller.h", "service_controller_router.cc", "service_controller_router.h", + "wifi_lan_endpoint_channel.h", "wifi_lan_service_info.h", "wifi_lan_upgrade_handler.cc", "wifi_lan_upgrade_handler.h", @@ -73,7 +75,6 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", - "//platform/port:down_cast", "//platform/port:string", "//proto:connections_enums_portable_proto", "//net/proto2/compat/public:proto2_lite", @@ -82,13 +83,29 @@ cc_library( ], ) +# TODO(apolyudov): remove when api v2 rework is done. +cc_library( + name = "message_lite", + hdrs = [ + "message_lite.h", + ], + visibility = [ + "//core:__subpackages__", + "//core_v2:__subpackages__", + ], + deps = [ + "//net/proto2/compat/public:proto2_lite", + ], +) + cc_test( name = "base_endpoint_channel_test", srcs = ["base_endpoint_channel_test.cc"], deps = [ ":internal", "//platform:utils", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//proto:connections_enums_portable_proto", "//testing/base/public:gunit_main", ], @@ -100,6 +117,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -110,6 +129,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":internal", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -121,6 +142,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -135,6 +158,8 @@ cc_test( ":internal", "//proto/connections:offline_wire_formats_portable_proto", "//platform:types", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h index 6e8c0775..2ce07b7a 100644 --- a/cpp/core/internal/bandwidth_upgrade_handler.h +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -4,6 +4,7 @@ #include "core/internal/client_proxy.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform/api/count_down_latch.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "proto/connections_enums.pb.h" @@ -13,9 +14,10 @@ namespace connections { // Defines the set of methods that need to be implemented to handle the // per-Medium-specific operations needed to upgrade an EndpointChannel. -template class BandwidthUpgradeHandler { public: + using Platform = platform::ImplementationPlatform; + virtual ~BandwidthUpgradeHandler() {} // Reverts any changes made to the device in the process of upgrading diff --git a/cpp/core/internal/bandwidth_upgrade_manager.cc b/cpp/core/internal/bandwidth_upgrade_manager.cc index 4c502beb..4eabbabe 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.cc +++ b/cpp/core/internal/bandwidth_upgrade_manager.cc @@ -6,38 +6,32 @@ namespace location { namespace nearby { namespace connections { -template -BandwidthUpgradeManager::BandwidthUpgradeManager( +BandwidthUpgradeManager::BandwidthUpgradeManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager) : endpoint_manager_(endpoint_manager), bandwidth_upgrade_handlers_(), current_bandwidth_upgrade_handler_() {} -template -BandwidthUpgradeManager::~BandwidthUpgradeManager() { +BandwidthUpgradeManager::~BandwidthUpgradeManager() { // TODO(ahlee): Make sure we don't repeat the mistake fixed in cl/201883908. } -template -void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( +void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id, proto::connections::Medium medium) {} -template -void BandwidthUpgradeManager::processIncomingOfflineFrame( +void BandwidthUpgradeManager::processIncomingOfflineFrame( ConstPtr offline_frame, const string& from_endpoint_id, Ptr > to_client_proxy, proto::connections::Medium current_medium) {} -template -void BandwidthUpgradeManager::processEndpointDisconnection( +void BandwidthUpgradeManager::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( +bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( proto::connections::Medium medium) { return false; } diff --git a/cpp/core/internal/bandwidth_upgrade_manager.h b/cpp/core/internal/bandwidth_upgrade_manager.h index 5aab4033..da7a3e8f 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.h +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -9,6 +9,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -19,14 +20,15 @@ namespace connections { // Manages all known {@link BandwidthUpgradeHandler} implementations, delegating // operations to the appropriate one as per the parameters passed in. -template class BandwidthUpgradeManager - : public EndpointManager::IncomingOfflineFrameProcessor { + : public EndpointManager< + platform::ImplementationPlatform>::IncomingOfflineFrameProcessor { public: - BandwidthUpgradeManager( - Ptr > medium_manager, - Ptr > endpoint_channel_manager, - Ptr > endpoint_manager); + using Platform = platform::ImplementationPlatform; + + BandwidthUpgradeManager(Ptr> medium_manager, + Ptr endpoint_channel_manager, + Ptr> endpoint_manager); ~BandwidthUpgradeManager() override; // This is the point on the initiator side where the @@ -50,17 +52,14 @@ class BandwidthUpgradeManager bool setCurrentBandwidthUpgradeHandler(proto::connections::Medium medium); Ptr > endpoint_manager_; - typedef std::map > > + typedef std::map> BandwidthUpgradeHandlersMap; BandwidthUpgradeHandlersMap bandwidth_upgrade_handlers_; - Ptr > current_bandwidth_upgrade_handler_; + Ptr current_bandwidth_upgrade_handler_; }; } // namespace connections } // namespace nearby } // namespace location -#include "core/internal/bandwidth_upgrade_manager.cc" - #endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.cc b/cpp/core/internal/base_bandwidth_upgrade_handler.cc index 9970bb8e..e58c6301 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.cc +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -4,138 +4,115 @@ namespace location { namespace nearby { namespace connections { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace base_bandwidth_upgrade_handler { -template class RevertRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class InitiateBandwidthUpgradeForEndpointRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessEndpointDisconnectionRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable : public Runnable { public: - void run() {} + void run() override {} }; } // namespace base_bandwidth_upgrade_handler -template -BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager) +BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager) : endpoint_channel_manager_(endpoint_channel_manager), - alarm_executor_(), - serial_executor_(), + alarm_executor_(nullptr), + serial_executor_(nullptr), previous_endpoint_channels_(), in_progress_upgrades_(), safe_to_close_write_timestamps_() {} -template -BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} +BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} -template -void BaseBandwidthUpgradeHandler::revert() {} +void BaseBandwidthUpgradeHandler::revert() {} -template -void BaseBandwidthUpgradeHandler::processEndpointDisconnection( +void BaseBandwidthUpgradeHandler::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( +void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradeNegotiationFrame( - ConstPtr - bandwidth_upgrade_negotiation, - Ptr > to_client_proxy, - const string& from_endpoint_id, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradeNegotiationFrame( + ConstPtr bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, const string& from_endpoint_id, + proto::connections::Medium current_medium) {} -template -Ptr > -BaseBandwidthUpgradeHandler::getEndpointChannelManager() { +Ptr +BaseBandwidthUpgradeHandler::getEndpointChannelManager() { return endpoint_channel_manager_; } -template -void BaseBandwidthUpgradeHandler::onIncomingConnection( +void BaseBandwidthUpgradeHandler::onIncomingConnection( Ptr incoming_socket_connection) {} -template -void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( +void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( Ptr runnable) {} -template -void BaseBandwidthUpgradeHandler::runUpgradeProtocol( +void BaseBandwidthUpgradeHandler::runUpgradeProtocol( Ptr > client_proxy, const string& endpoint_id, Ptr new_endpoint_channel) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEvent( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEvent( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info, + proto::connections::Medium current_medium) {} -template -Ptr BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEventInternal( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info) { +Ptr +BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEventInternal( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info) { return Ptr(); } -template -void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( +void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( +void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( +std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( const string& endpoint_id) { return 0; } -template -std::int64_t -BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( +std::int64_t BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( const string& endpoint_id) { return 0; } // TODO(ahlee): This will differ from the Java code as we don't have to handle // analytics in the C++ code. -template -void BaseBandwidthUpgradeHandler:: +void BaseBandwidthUpgradeHandler:: attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( proto::connections::BandwidthUpgradeResult result, proto::connections::BandwidthUpgradeErrorStage error_stage) {} // TODO(ahlee): This will differ from the Java code (previously threw an // UpgradeException). -template Ptr -BaseBandwidthUpgradeHandler::readClientIntroductionFrame( +BaseBandwidthUpgradeHandler::readClientIntroductionFrame( Ptr endpoint_channel) { return Ptr(); } diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.h b/cpp/core/internal/base_bandwidth_upgrade_handler.h index c4be0a7d..78320abe 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.h +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -19,13 +19,9 @@ namespace connections { namespace base_bandwidth_upgrade_handler { -template class RevertRunnable; -template class InitiateBandwidthUpgradeForEndpointRunnable; -template class ProcessEndpointDisconnectionRunnable; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable; } // namespace base_bandwidth_upgrade_handler @@ -55,26 +51,28 @@ class ProcessBandwidthUpgradeNegotiationFrameRunnable; // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the // other, and upon doing so, close the prior EndpointChannel. // -template -class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { +class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { public: - BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager); - ~BaseBandwidthUpgradeHandler(); + using Platform = platform::ImplementationPlatform; - void revert(); + explicit BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager); + ~BaseBandwidthUpgradeHandler() override; + + void revert() override; void processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, - Ptr process_disconnection_barrier); + Ptr process_disconnection_barrier) override; // Initiates the bandwidth upgrade and sends an UPGRADE_PATH_AVAILABLE // OfflineFrame. void initiateBandwidthUpgradeForEndpoint( - Ptr > client_proxy, const string& endpoint_id); + Ptr > client_proxy, + const string& endpoint_id) override; void processBandwidthUpgradeNegotiationFrame( ConstPtr bandwidth_upgrade_negotiation, Ptr > to_client_proxy, const string& from_endpoint_id, - proto::connections::Medium current_medium); + proto::connections::Medium current_medium) override; protected: // Represents the incoming Socket the Initiator has gotten after initializing @@ -117,7 +115,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { // @BandwidthUpgradeHandlerThread virtual proto::connections::Medium getUpgradeMedium() = 0; - Ptr > getEndpointChannelManager(); + Ptr getEndpointChannelManager(); // Common functionality to take an incoming connection and go through the // upgrade process. // @BandwidthUpgradeHandlerThread @@ -126,15 +124,11 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { void runOnBandwidthUpgradeHandlerThread(Ptr runnable); private: - template friend class base_bandwidth_upgrade_handler::RevertRunnable; - template friend class base_bandwidth_upgrade_handler:: InitiateBandwidthUpgradeForEndpointRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessEndpointDisconnectionRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessBandwidthUpgradeNegotiationFrameRunnable; @@ -162,7 +156,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { Ptr readClientIntroductionFrame(Ptr endpoint_channel); - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; ScopedPtr > alarm_executor_; ScopedPtr > serial_executor_; // Stores each upgraded endpoint's previous EndpointChannel (that was @@ -184,6 +178,4 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { } // namespace nearby } // namespace location -#include "core/internal/base_bandwidth_upgrade_handler.cc" - #endif // CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc index 6665a2b5..8df7d4bc 100644 --- a/cpp/core/internal/base_endpoint_channel.cc +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -2,6 +2,7 @@ #include +#include "platform/api/platform.h" #include "platform/synchronized.h" #include "proto/connections_enums.pb.h" @@ -11,6 +12,8 @@ namespace connections { namespace { +using Platform = platform::ImplementationPlatform; + std::int32_t bytesToInt(ConstPtr bytes) { const char* int_bytes = bytes->getData(); @@ -33,36 +36,36 @@ ConstPtr intToBytes(std::int32_t value) { return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes))); } -ExceptionOr > readExactly(Ptr reader, - std::int64_t size) { +ExceptionOr> readExactly(Ptr reader, + std::int64_t size) { string buffer; std::int64_t remaining_size = size; while (remaining_size > 0) { - ExceptionOr > read_bytes = reader->read(remaining_size); + ExceptionOr> read_bytes = reader->read(remaining_size); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); // In Java, EOFException is a sub-variant of IOException. if (scoped_read_bytes.isNull() || scoped_read_bytes->size() == 0) { - return ExceptionOr >(Exception::IO); + return ExceptionOr>(Exception::IO); } buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size()); remaining_size -= scoped_read_bytes->size(); } - return ExceptionOr >( + return ExceptionOr>( MakeConstPtr(new ByteArray(buffer.data(), buffer.size()))); } ExceptionOr readInt(Ptr reader) { - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader, sizeof(std::int32_t)); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { @@ -70,7 +73,7 @@ ExceptionOr readInt(Ptr reader) { } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); return ExceptionOr(bytesToInt(scoped_read_bytes.get())); } @@ -82,10 +85,9 @@ Exception::Value writeInt(Ptr writer, std::int32_t value) { } // namespace // TODO(b/150763574): Move implementatiopn to header or .inc file. -template -BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, - Ptr reader, - Ptr writer) +BaseEndpointChannel::BaseEndpointChannel(absl::string_view channel_name, + Ptr reader, + Ptr writer) : last_read_timestamp_(-1), channel_name_(channel_name), system_clock_(Platform::createSystemClock()), @@ -100,8 +102,7 @@ BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, Platform::createConditionVariable(is_paused_lock_.get())), is_paused_(Platform::createAtomicBoolean(false)) {} -template -BaseEndpointChannel::~BaseEndpointChannel() { +BaseEndpointChannel::~BaseEndpointChannel() { // WARNING: Make sure to never access reader_ and writer_ from here. // // They're owned by the specialized *Socket classes that are in turn @@ -114,14 +115,13 @@ BaseEndpointChannel::~BaseEndpointChannel() { // of this class). } -template -ExceptionOr > BaseEndpointChannel::read() { +ExceptionOr> BaseEndpointChannel::read() { Synchronized s(reader_lock_.get()); ExceptionOr read_int = readInt(reader_); if (!read_int.ok()) { if (Exception::IO == read_int.exception()) { - return ExceptionOr >(read_int.exception()); + return ExceptionOr>(read_int.exception()); } } @@ -131,11 +131,11 @@ ExceptionOr > BaseEndpointChannel::read() { return ExceptionOr>(Exception::IO); } - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader_, read_int.result()); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } @@ -154,7 +154,7 @@ ExceptionOr > BaseEndpointChannel::read() { // short-circuit out of here on error. read_bytes_result.destroy(); if (decoded_bytes == nullptr) { - return ExceptionOr >( + return ExceptionOr>( Exception::INVALID_PROTOCOL_BUFFER); } read_bytes_result = MakeConstPtr( @@ -162,16 +162,14 @@ ExceptionOr > BaseEndpointChannel::read() { } last_read_timestamp_ = system_clock_->elapsedRealtime(); - return ExceptionOr >(read_bytes_result); + return ExceptionOr>(read_bytes_result); } -template -Exception::Value BaseEndpointChannel::write( - ConstPtr data) { +Exception::Value BaseEndpointChannel::write(ConstPtr data) { Synchronized s(writer_lock_.get()); // Avoid leaks. - ScopedPtr > scoped_data(data); + ScopedPtr> scoped_data(data); if (isPaused()) { blockUntilUnpaused(); @@ -191,7 +189,7 @@ Exception::Value BaseEndpointChannel::write( data_to_write = scoped_data.release(); } // Avoid leaks. - ScopedPtr > scoped_data_to_write(data_to_write); + ScopedPtr> scoped_data_to_write(data_to_write); Exception::Value write_exception = writeInt( writer_, static_cast(scoped_data_to_write->size())); @@ -218,8 +216,7 @@ Exception::Value BaseEndpointChannel::write( return Exception::NONE; } -template -void BaseEndpointChannel::close() { +void BaseEndpointChannel::close() { // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. @@ -246,8 +243,7 @@ void BaseEndpointChannel::close() { // TODO(tracyzhou): Add logging. } -template -void BaseEndpointChannel::close( +void BaseEndpointChannel::close( proto::connections::DisconnectionReason reason) { // WARNING WARNING WARNING // @@ -259,8 +255,7 @@ void BaseEndpointChannel::close( // TODO(tracyzhou): Add logging. } -template -string BaseEndpointChannel::getType() { +string BaseEndpointChannel::getType() { string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : ""; switch (getMedium()) { case proto::connections::Medium::BLUETOOTH: @@ -278,46 +273,32 @@ string BaseEndpointChannel::getType() { } } -template -string BaseEndpointChannel::getName() { - return channel_name_; -} +string BaseEndpointChannel::getName() { return channel_name_; } -template -void BaseEndpointChannel::enableEncryption( +void BaseEndpointChannel::enableEncryption( Ptr encryption_context) { assert(!encryption_context.isNull()); encryption_context_->set(encryption_context); } -template -bool BaseEndpointChannel::isPaused() { - return is_paused_->get(); -} +bool BaseEndpointChannel::isPaused() { return is_paused_->get(); } -template -void BaseEndpointChannel::pause() { - is_paused_->set(true); -} +void BaseEndpointChannel::pause() { is_paused_->set(true); } -template -void BaseEndpointChannel::resume() { +void BaseEndpointChannel::resume() { is_paused_->set(false); unblockPausedWriter(); } -template -std::int64_t BaseEndpointChannel::getLastReadTimestamp() { +std::int64_t BaseEndpointChannel::getLastReadTimestamp() { return last_read_timestamp_; } -template -bool BaseEndpointChannel::isEncryptionEnabled() { +bool BaseEndpointChannel::isEncryptionEnabled() { return !encryption_context_->get().isNull(); } -template -void BaseEndpointChannel::unblockPausedWriter() { +void BaseEndpointChannel::unblockPausedWriter() { Synchronized s(is_paused_lock_.get()); // Notify to tell the thread calling wait() to check again. @@ -329,8 +310,7 @@ void BaseEndpointChannel::unblockPausedWriter() { is_paused_condition_variable_->notify(); } -template -void BaseEndpointChannel::blockUntilUnpaused() { +void BaseEndpointChannel::blockUntilUnpaused() { Synchronized s(is_paused_lock_.get()); // For more on how this works, see diff --git a/cpp/core/internal/base_endpoint_channel.h b/cpp/core/internal/base_endpoint_channel.h index 4e3c112e..e5b60160 100644 --- a/cpp/core/internal/base_endpoint_channel.h +++ b/cpp/core/internal/base_endpoint_channel.h @@ -16,15 +16,15 @@ #include "platform/ptr.h" #include "proto/connections_enums.pb.h" #include "securegcm/d2d_connection_context_v1.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { namespace connections { -template class BaseEndpointChannel : public EndpointChannel { public: - BaseEndpointChannel(const string& channel_name, Ptr reader, + BaseEndpointChannel(absl::string_view channel_name, Ptr reader, Ptr writer); ~BaseEndpointChannel() override; @@ -69,7 +69,7 @@ class BaseEndpointChannel : public EndpointChannel { private: // Used to sanity check that our frame sizes are reasonable. - static const std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB bool isEncryptionEnabled(); void unblockPausedWriter(); @@ -107,6 +107,4 @@ class BaseEndpointChannel : public EndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/base_endpoint_channel.cc" - #endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/base_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc index abdb5dbe..f954758a 100644 --- a/cpp/core/internal/base_endpoint_channel_test.cc +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -1,6 +1,6 @@ #include "core/internal/base_endpoint_channel.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "platform/pipe.h" #include "proto/connections_enums.pb.h" #include "gmock/gmock.h" @@ -11,21 +11,7 @@ namespace nearby { namespace connections { namespace { -class TestPlatform : public DefaultPlatform { - public: - static SystemClock* createSystemClock() { return nullptr; } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr> createAtomicReference(const T& initial_value) { - return Ptr>(); - } -}; - -class TestEndpointChannel : public BaseEndpointChannel { +class TestEndpointChannel : public BaseEndpointChannel { public: explicit TestEndpointChannel(Ptr input_stream) : BaseEndpointChannel("channel", input_stream, Ptr()) {} @@ -34,7 +20,7 @@ class TestEndpointChannel : public BaseEndpointChannel { MOCK_METHOD(void, closeImpl, (), (override)); }; -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index e885e42c..03235508 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -48,12 +48,7 @@ class StartAdvertisingCallable : public Callable { service_id_(service_id), local_endpoint_name_(local_endpoint_name), options_(options), - // Convert the passed in connection_lifecycle_listener Ptr into a - // reference counted one. The advertising session and any connected - // endpoints need a handle to the same connection_lifecycle_listener, so - // there is no clear model of who actually owns the listener. - connection_lifecycle_listener_( - MakeRefCountedPtr(&(*connection_lifecycle_listener))) {} + connection_lifecycle_listener_(connection_lifecycle_listener) {} ExceptionOr call() override { // Ask the implementation to attempt to start advertising. @@ -675,8 +670,8 @@ const std::int64_t template BasePCPHandler::BasePCPHandler( Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : endpoint_manager_(endpoint_manager), endpoint_channel_manager_(endpoint_channel_manager), bandwidth_upgrade_manager_(bandwidth_upgrade_manager), @@ -1137,6 +1132,14 @@ Exception::Value BasePCPHandler::onIncomingConnection( return Exception::IO; } + // The ConnectionRequest frame has two fields that both contain the + // EndpointInfo. The legacy field stores it as a string while the newer field + // stores it as a byte array. We'll attempt to grab from the newer field, but + // will accept the older string if it's all that exists. + const std::string& endpoint_name = connection_request.has_endpoint_info() + ? connection_request.endpoint_info() + : connection_request.endpoint_name(); + // We've successfully connected to the device, and are now about to jump on to // the EncryptionRunner thread to start running our encryption protocol. We'll // mark ourselves as pending in case we get another call to requestConnection @@ -1146,7 +1149,7 @@ Exception::Value BasePCPHandler::onIncomingConnection( .insert(std::make_pair( connection_request.endpoint_id(), PendingConnectionInfo::newIncomingPendingConnectionInfo( - client_proxy, connection_request.endpoint_name(), + client_proxy, endpoint_name, scoped_endpoint_channel.release(), connection_request.nonce(), start_time_millis, advertising_connection_lifecycle_listener_, OfflineFrames::connectionRequestMediumsToMediums( @@ -1378,7 +1381,7 @@ void BasePCPHandler::evaluateConnectionResult( } else { pending_rejected_connection_close_alarms_.insert(std::make_pair( endpoint_id, - MakePtr(new CancelableAlarm( + MakePtr(new CancelableAlarm( "BasePCPHandler.evaluateConnectionResult() delayed close", MakePtr( new base_pcp_handler:: @@ -1407,7 +1410,7 @@ BasePCPHandler::readConnectionRequestFrame( // To avoid a device connecting but never sending their introductory frame, we // time out the connection after a certain amount of time. - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "PCPHandler(" + this->getStrategy().getName() + ").readConnectionRequestFrame", MakePtr( diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h index 9019a9b9..8415cae0 100644 --- a/cpp/core/internal/base_pcp_handler.h +++ b/cpp/core/internal/base_pcp_handler.h @@ -69,10 +69,9 @@ class BasePCPHandler public EndpointManager::IncomingOfflineFrameProcessor { public: // TODO(tracyzhou): Add SecureRandom. - BasePCPHandler( - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + BasePCPHandler(Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~BasePCPHandler() override; // We have been asked by the client to start advertising. Once we successfully @@ -239,8 +238,8 @@ class BasePCPHandler virtual proto::connections::Medium getDefaultUpgradeMedium() = 0; Ptr > endpoint_manager_; - Ptr > endpoint_channel_manager_; - Ptr > bandwidth_upgrade_manager_; + Ptr endpoint_channel_manager_; + Ptr bandwidth_upgrade_manager_; private: template @@ -473,7 +472,7 @@ class BasePCPHandler // reading the message (in which case, this alarm should be cancelled as it's // no longer needed), but this alarm is the fallback in case that doesn't // happen. - typedef std::map > > + typedef std::map > PendingRejectedConnectionCloseAlarmsMap; PendingRejectedConnectionCloseAlarmsMap pending_rejected_connection_close_alarms_; diff --git a/cpp/core/internal/ble_endpoint_channel.cc b/cpp/core/internal/ble_endpoint_channel.cc index 8684dcc1..35924f94 100644 --- a/cpp/core/internal/ble_endpoint_channel.cc +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -6,42 +6,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BLEEndpointChannel::createOutgoing( +Ptr BLEEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -Ptr > -BLEEndpointChannel::createIncoming( +Ptr BLEEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -BLEEndpointChannel::BLEEndpointChannel( - const string& channel_name, Ptr ble_socket) - : BaseEndpointChannel(channel_name, - ble_socket->getInputStream(), - ble_socket->getOutputStream()), +BLEEndpointChannel::BLEEndpointChannel(const string& channel_name, + Ptr ble_socket) + : BaseEndpointChannel(channel_name, ble_socket->getInputStream(), + ble_socket->getOutputStream()), ble_socket_(ble_socket) {} -template -BLEEndpointChannel::~BLEEndpointChannel() {} +BLEEndpointChannel::~BLEEndpointChannel() {} -template -proto::connections::Medium BLEEndpointChannel::getMedium() { +proto::connections::Medium BLEEndpointChannel::getMedium() { return proto::connections::Medium::BLE; } -template -void BLEEndpointChannel::closeImpl() { +void BLEEndpointChannel::closeImpl() { Exception::Value exception = ble_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/ble_endpoint_channel.h b/cpp/core/internal/ble_endpoint_channel.h index fb92dc4d..fe336b9b 100644 --- a/cpp/core/internal/ble_endpoint_channel.h +++ b/cpp/core/internal/ble_endpoint_channel.h @@ -4,6 +4,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/ble.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -12,13 +13,14 @@ namespace location { namespace nearby { namespace connections { -template -class BLEEndpointChannel : public BaseEndpointChannel { +class BLEEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); @@ -39,6 +41,4 @@ class BLEEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/ble_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/bluetooth_endpoint_channel.cc b/cpp/core/internal/bluetooth_endpoint_channel.cc index f9525b36..9fa8069a 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.cc +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -6,42 +6,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BluetoothEndpointChannel::createOutgoing( +Ptr BluetoothEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -Ptr > -BluetoothEndpointChannel::createIncoming( +Ptr BluetoothEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -BluetoothEndpointChannel::BluetoothEndpointChannel( +BluetoothEndpointChannel::BluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) - : BaseEndpointChannel(channel_name, - bluetooth_socket->getInputStream(), - bluetooth_socket->getOutputStream()), + : BaseEndpointChannel(channel_name, bluetooth_socket->getInputStream(), + bluetooth_socket->getOutputStream()), bluetooth_socket_(bluetooth_socket) {} -template -BluetoothEndpointChannel::~BluetoothEndpointChannel() {} +BluetoothEndpointChannel::~BluetoothEndpointChannel() {} -template -proto::connections::Medium BluetoothEndpointChannel::getMedium() { +proto::connections::Medium BluetoothEndpointChannel::getMedium() { return proto::connections::Medium::BLUETOOTH; } -template -void BluetoothEndpointChannel::closeImpl() { +void BluetoothEndpointChannel::closeImpl() { Exception::Value exception = bluetooth_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/bluetooth_endpoint_channel.h b/cpp/core/internal/bluetooth_endpoint_channel.h index f9be2269..31c84216 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.h +++ b/cpp/core/internal/bluetooth_endpoint_channel.h @@ -4,6 +4,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/bluetooth_classic.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -12,13 +13,14 @@ namespace location { namespace nearby { namespace connections { -template -class BluetoothEndpointChannel : public BaseEndpointChannel { +class BluetoothEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); @@ -40,6 +42,4 @@ class BluetoothEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/bluetooth_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/encryption_runner.cc b/cpp/core/internal/encryption_runner.cc index ccd5f8d5..9e463442 100644 --- a/cpp/core/internal/encryption_runner.cc +++ b/cpp/core/internal/encryption_runner.cc @@ -101,7 +101,7 @@ class ServerRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startServer() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -112,7 +112,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -121,7 +121,7 @@ class ServerRunnable : public Runnable { if (!client_init.ok()) { if (Exception::IO == client_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -138,7 +138,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -151,7 +151,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -160,7 +160,7 @@ class ServerRunnable : public Runnable { if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -174,7 +174,7 @@ class ServerRunnable : public Runnable { if (!client_finish.ok()) { if (Exception::IO == client_finish.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -189,7 +189,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -202,7 +202,7 @@ class ServerRunnable : public Runnable { MakePtr(server.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -213,8 +213,8 @@ class ServerRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } @@ -258,7 +258,7 @@ class ClientRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startClient() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -270,7 +270,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -280,7 +280,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -289,7 +289,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_init_exception) { if (Exception::IO == write_init_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -303,7 +303,7 @@ class ClientRunnable : public Runnable { if (!server_init.ok()) { if (Exception::IO == server_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -319,7 +319,7 @@ class ClientRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -332,7 +332,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_finish == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -342,7 +342,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_finish_exception) { if (Exception::IO == write_finish_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -356,7 +356,7 @@ class ClientRunnable : public Runnable { MakePtr(client.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -367,8 +367,8 @@ class ClientRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc index 80222b6a..b366fff6 100644 --- a/cpp/core/internal/endpoint_channel_manager.cc +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -2,21 +2,20 @@ #include "core/internal/ble_endpoint_channel.h" #include "core/internal/bluetooth_endpoint_channel.h" +#include "core/internal/wifi_lan_endpoint_channel.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { -template -EndpointChannelManager::EndpointChannelManager( +EndpointChannelManager::EndpointChannelManager( Ptr > medium_manager) : lock_(Platform::createLock()), medium_manager_(medium_manager), channel_state_(new ChannelState()) {} -template -EndpointChannelManager::~EndpointChannelManager() { +EndpointChannelManager::~EndpointChannelManager() { Synchronized s(lock_.get()); // TODO(tracyzhou): logger.atDebug().log("Initiating shutdown of @@ -26,40 +25,47 @@ EndpointChannelManager::~EndpointChannelManager() { // down."); } -template Ptr -EndpointChannelManager::createOutgoingBluetoothEndpointChannel( +EndpointChannelManager::createOutgoingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createOutgoing( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createOutgoing(medium_manager_, channel_name, + bluetooth_socket); } -template Ptr -EndpointChannelManager::createIncomingBluetoothEndpointChannel( +EndpointChannelManager::createIncomingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createIncoming( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createIncoming(medium_manager_, channel_name, + bluetooth_socket); } -template -Ptr -EndpointChannelManager::createOutgoingBLEEndpointChannel( +Ptr EndpointChannelManager::createOutgoingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createOutgoing(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createOutgoing(medium_manager_, channel_name, + ble_socket); } -template -Ptr -EndpointChannelManager::createIncomingBLEEndpointChannel( +Ptr EndpointChannelManager::createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createIncoming(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createIncoming(medium_manager_, channel_name, + ble_socket); } -template -void EndpointChannelManager::registerChannelForEndpoint( +Ptr +EndpointChannelManager::CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateOutgoing( + medium_manager_, channel_name, wifi_lan_socket); +} + +Ptr +EndpointChannelManager::CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateIncoming( + medium_manager_, channel_name, wifi_lan_socket); +} + +void EndpointChannelManager::registerChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -74,9 +80,7 @@ void EndpointChannelManager::registerChannelForEndpoint( } #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED -template -Ptr -EndpointChannelManager::replaceChannelForEndpoint( +Ptr EndpointChannelManager::replaceChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -96,8 +100,7 @@ EndpointChannelManager::replaceChannelForEndpoint( } #endif -template -bool EndpointChannelManager::encryptChannelForEndpoint( +bool EndpointChannelManager::encryptChannelForEndpoint( const string& endpoint_id, Ptr encryption_context) { Synchronized s(lock_.get()); @@ -125,16 +128,14 @@ bool EndpointChannelManager::encryptChannelForEndpoint( return true; } -template -Ptr EndpointChannelManager::getChannelForEndpoint( +Ptr EndpointChannelManager::getChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); return channel_state_->getChannelForEndpoint(endpoint_id); } -template -void EndpointChannelManager::setActiveEndpointChannel( +void EndpointChannelManager::setActiveEndpointChannel( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED @@ -155,8 +156,7 @@ void EndpointChannelManager::setActiveEndpointChannel( channel_state_->updateChannelForEndpoint(endpoint_id, endpoint_channel)); } -template -void EndpointChannelManager::encryptChannel( +void EndpointChannelManager::encryptChannel( const string& endpoint_id, Ptr endpoint_channel, Ptr encryption_context) { // TODO(tracyzhou): Add logging. @@ -165,8 +165,7 @@ void EndpointChannelManager::encryptChannel( ///////////////////////////////// ChannelState ///////////////////////////////// -template -EndpointChannelManager::ChannelState::~ChannelState() { +EndpointChannelManager::ChannelState::~ChannelState() { while (!endpoint_id_to_metadata_.empty()) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.begin(); @@ -176,15 +175,13 @@ EndpointChannelManager::ChannelState::~ChannelState() { } } -template -bool EndpointChannelManager::ChannelState::isEndpointEncrypted( +bool EndpointChannelManager::ChannelState::isEndpointEncrypted( const string& endpoint_id) { return !getEncryptionContextForEndpoint(endpoint_id).isNull(); } -template Ptr -EndpointChannelManager::ChannelState::updateChannelForEndpoint( +EndpointChannelManager::ChannelState::updateChannelForEndpoint( const string& endpoint_id, Ptr endpoint_channel) { Ptr previous_endpoint_channel; Ptr endpoint_metadata; @@ -208,11 +205,10 @@ EndpointChannelManager::ChannelState::updateChannelForEndpoint( return scoped_previous_endpoint_channel.release(); } -template -Ptr EndpointChannelManager:: - ChannelState::updateEncryptionContextForEndpoint( - const string& endpoint_id, - Ptr encryption_context) { +Ptr +EndpointChannelManager::ChannelState::updateEncryptionContextForEndpoint( + const string& endpoint_id, + Ptr encryption_context) { Ptr previous_encryption_context; Ptr endpoint_metadata; @@ -234,8 +230,7 @@ Ptr EndpointChannelManager:: return scoped_previous_encryption_context.release(); } -template -bool EndpointChannelManager::ChannelState::removeEndpoint( +bool EndpointChannelManager::ChannelState::removeEndpoint( const string& endpoint_id, proto::connections::DisconnectionReason reason) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -249,9 +244,8 @@ bool EndpointChannelManager::ChannelState::removeEndpoint( return true; } -template Ptr -EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( +EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -262,9 +256,8 @@ EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( return it->second->encryption_context; } -template Ptr -EndpointChannelManager::ChannelState::getChannelForEndpoint( +EndpointChannelManager::ChannelState::getChannelForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -275,8 +268,7 @@ EndpointChannelManager::ChannelState::getChannelForEndpoint( return it->second->endpoint_channel; } -template -bool EndpointChannelManager::unregisterChannelForEndpoint( +bool EndpointChannelManager::unregisterChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); diff --git a/cpp/core/internal/endpoint_channel_manager.h b/cpp/core/internal/endpoint_channel_manager.h index 059085b7..18ec3641 100644 --- a/cpp/core/internal/endpoint_channel_manager.h +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -9,6 +9,8 @@ #include "platform/api/ble.h" #include "platform/api/bluetooth_classic.h" #include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "securegcm/d2d_connection_context_v1.h" @@ -22,10 +24,11 @@ namespace connections { // // The factory methods would be static, but for the fact that they need to use // the MediumManager. -template class EndpointChannelManager { public: - explicit EndpointChannelManager(Ptr > medium_manager); + using Platform = platform::ImplementationPlatform; + + explicit EndpointChannelManager(Ptr> medium_manager); ~EndpointChannelManager(); Ptr createOutgoingBluetoothEndpointChannel( @@ -38,6 +41,11 @@ class EndpointChannelManager { Ptr createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket); + Ptr CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket); + Ptr CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket); + // Registers the initial EndpointChannel to be associated with an endpoint; // if there already exists a previously-associated EndpointChannel, that will // be closed before continuing the registration. @@ -138,6 +146,4 @@ class EndpointChannelManager { } // namespace nearby } // namespace location -#include "core/internal/endpoint_channel_manager.cc" - #endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc index d4d6c3de..f6757d06 100644 --- a/cpp/core/internal/endpoint_manager.cc +++ b/cpp/core/internal/endpoint_manager.cc @@ -466,7 +466,7 @@ const std::int32_t EndpointManager::kMaxConcurrentEndpoints = 50; template EndpointManager::EndpointManager( - Ptr> endpoint_channel_manager) + Ptr endpoint_channel_manager) : thread_utils_(Platform::createThreadUtils()), system_clock_(Platform::createSystemClock()), endpoint_channel_manager_(endpoint_channel_manager), diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h index 05263f2c..7fd5b5df 100644 --- a/cpp/core/internal/endpoint_manager.h +++ b/cpp/core/internal/endpoint_manager.h @@ -95,7 +95,7 @@ class EndpointManager { }; explicit EndpointManager( - Ptr > endpoint_channel_manager); + Ptr endpoint_channel_manager); ~EndpointManager(); // Invoked from the constructors of the various *Manager components that make @@ -211,7 +211,7 @@ class EndpointManager { ScopedPtr > thread_utils_; ScopedPtr > system_clock_; - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; typedef std::map > IncomingOfflineFrameProcessorsMap; diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc index 4deb40b7..c7cd5a45 100644 --- a/cpp/core/internal/internal_payload_factory.cc +++ b/cpp/core/internal/internal_payload_factory.cc @@ -108,7 +108,7 @@ class OutgoingStreamInternalPayload : public InternalPayload { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; template @@ -191,7 +191,7 @@ class OutgoingFileInternalPayload : public InternalPayload { void close() override { payload_->asFile()->asInputFile()->close(); } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; class IncomingFileInternalPayload : public InternalPayload { @@ -277,14 +277,13 @@ Ptr InternalPayloadFactory::createIncoming( case PayloadTransferFrame::PayloadHeader::STREAM: { // pipe will be auto-destroyed when it is no longer referenced. - auto pipe = MakeRefCountedPtr(new Pipe()); + auto pipe = MakeRefCountedPtr(new Pipe()); return MakePtr(new IncomingStreamInternalPayload( - MakeConstPtr(new Payload( - payload_id, - MakeConstPtr(new Payload::Stream( - Pipe::createInputStream(pipe))))), - Pipe::createOutputStream(pipe))); + MakeConstPtr( + new Payload(payload_id, MakeConstPtr(new Payload::Stream( + Pipe::createInputStream(pipe))))), + Pipe::createOutputStream(pipe))); } case PayloadTransferFrame::PayloadHeader::FILE: { diff --git a/cpp/core/internal/medium_manager.cc b/cpp/core/internal/medium_manager.cc index be6ca370..4035bd20 100644 --- a/cpp/core/internal/medium_manager.cc +++ b/cpp/core/internal/medium_manager.cc @@ -10,13 +10,15 @@ template MediumManager::MediumManager() : mediums_(new Mediums()), bluetooth_classic_lock_(Platform::createLock()), - ble_lock_(Platform::createLock()) {} + ble_lock_(Platform::createLock()), + wifi_lan_lock_(Platform::createLock()) {} template MediumManager::~MediumManager() { // TODO(reznor): log.atDebug().log("Initiating shutdown of MediumManager."); Synchronized s1(bluetooth_classic_lock_.get()); Synchronized s2(ble_lock_.get()); + Synchronized s3(wifi_lan_lock_.get()); mediums_.destroy(); // TODO(reznor): log.atDebug().log("MediumManager has shut down."); @@ -356,6 +358,131 @@ Ptr MediumManager::connectToBlePeripheral( #endif } +// ~~~~~~~~~~~~~~~~~~~~~~~~ WIFILAN ~~~~~~~~~~~~~~~~~~~~~~~~ +template +bool MediumManager::IsWifiLanAvailable() { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAvailable(); +} + +template +bool MediumManager::StartWifiLanAdvertising( + absl::string_view service_id, absl::string_view service_info_name) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAdvertising(service_id, service_info_name); +} + +template +void MediumManager::StopWifiLanAdvertising( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAdvertising(service_id); +} + +template +class DiscoveredServiceCallback : public mediums::DiscoveredServiceCallback { + public: + typedef typename MediumManager::FoundWifiLanServiceProcessor + FoundWifiLanServiceProcessor; + + explicit DiscoveredServiceCallback( + Ptr found_wifi_lan_service_processor) + : found_wifi_lan_service_processor_(found_wifi_lan_service_processor) {} + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnFoundWifiLanService(wifi_lan_service); + } + + void OnServiceLost(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnLostWifiLanService(wifi_lan_service); + } + + private: + ScopedPtr > + found_wifi_lan_service_processor_; +}; + +template +bool MediumManager::StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartDiscovery( + service_id, MakePtr(new DiscoveredServiceCallback( + found_wifi_lan_service_processor))); +} + +template +void MediumManager::StopWifiLanDiscovery( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopDiscovery(service_id); +} + +template +class WifiLanAcceptedConnectionCallback + : public mediums::WifiLan::AcceptedConnectionCallback { + public: + typedef typename MediumManager::IncomingWifiLanConnectionProcessor + IncomingWifiLanConnectionProcessor; + + explicit WifiLanAcceptedConnectionCallback( + Ptr + incoming_wifi_lan_connection_processor) + : incoming_wifi_lan_connection_processor_( + incoming_wifi_lan_connection_processor) {} + + void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) override { + incoming_wifi_lan_connection_processor_->OnIncomingWifiLanConnection( + socket); + } + + private: + ScopedPtr > + incoming_wifi_lan_connection_processor_; +}; + +template +bool MediumManager::IsListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAcceptingConnections(service_id); +} + +template +bool MediumManager::StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAcceptingConnections( + service_id, MakePtr(new WifiLanAcceptedConnectionCallback( + incoming_wifi_lan_connection_processor))); +} + +template +void MediumManager::StopListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAcceptingConnections(service_id); +} + +template +Ptr MediumManager::ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->Connect(wifi_lan_service, service_id); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/medium_manager.h b/cpp/core/internal/medium_manager.h index 93ba82af..b102865a 100644 --- a/cpp/core/internal/medium_manager.h +++ b/cpp/core/internal/medium_manager.h @@ -122,6 +122,45 @@ class MediumManager { Ptr connectToBlePeripheral(Ptr ble_peripheral, const string& service_id); + // ~~~~~~~~~~~~~~~~~~~~~~~~ WIFI-LAN ~~~~~~~~~~~~~~~~~~~~~~~~ + + bool IsWifiLanAvailable(); + + bool StartWifiLanAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopWifiLanAdvertising(absl::string_view service_id); + + class FoundWifiLanServiceProcessor { + public: + virtual ~FoundWifiLanServiceProcessor() {} + + virtual void OnFoundWifiLanService( + Ptr wifi_lan_service) = 0; + virtual void OnLostWifiLanService(Ptr wifi_lan_service) = 0; + }; + + bool StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor); + void StopWifiLanDiscovery(absl::string_view service_id); + + class IncomingWifiLanConnectionProcessor { + public: + virtual ~IncomingWifiLanConnectionProcessor() {} + + virtual void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) = 0; + }; + + bool IsListeningForIncomingWifiLanConnections(absl::string_view service_id); + bool StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor); + void StopListeningForIncomingWifiLanConnections(absl::string_view service_id); + + Ptr ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id); + private: // The destructor for this needs to be manually invoked after the locks below // are acquired, so it cannot be a ScopedPtr. @@ -129,6 +168,7 @@ class MediumManager { ScopedPtr > bluetooth_classic_lock_; ScopedPtr > ble_lock_; + ScopedPtr > wifi_lan_lock_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD index b0fca5fa..1cf244df 100644 --- a/cpp/core/internal/mediums/BUILD +++ b/cpp/core/internal/mediums/BUILD @@ -1,3 +1,23 @@ +cc_library( + name = "utils", + srcs = [ + "utils.cc", + ], + hdrs = [ + "utils.h", + ], + visibility = [ + "//core/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + cc_library( name = "mediums", srcs = [ @@ -5,8 +25,6 @@ cc_library( "ble_advertisement_header.cc", "ble_packet.cc", "ble_peripheral.cc", - "utils.cc", - "utils.h", ], hdrs = [ "advertisement_read_result.cc", @@ -34,9 +52,12 @@ cc_library( "mediums.h", "uuid.cc", "uuid.h", + "wifi_lan.cc", + "wifi_lan.h", ], visibility = ["//core/internal:__pkg__"], deps = [ + ":utils", "//platform:logging", "//platform:types", "//platform:utils", @@ -53,7 +74,8 @@ cc_test( srcs = ["advertisement_read_result_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", "//absl/time", ], @@ -65,6 +87,8 @@ cc_test( deps = [ ":mediums", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -74,6 +98,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -83,6 +109,8 @@ cc_test( srcs = ["ble_packet_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -92,6 +120,8 @@ cc_test( srcs = ["bloom_filter_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -101,7 +131,8 @@ cc_test( srcs = ["lost_entity_tracker_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index 158e01fb..db251240 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -1,6 +1,6 @@ #include "core/internal/mediums/advertisement_read_result.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -10,23 +10,7 @@ namespace nearby { namespace connections { namespace mediums { -class SampleSystemClock : public SystemClock { - public: - SampleSystemClock() {} - ~SampleSystemClock() override {} - - std::int64_t elapsedRealtime() override { - return absl::ToUnixMillis(absl::Now()); - } -}; - -class SamplePlatform { - public: - static Ptr createLock() { return DefaultPlatform::createLock(); } - static Ptr createSystemClock() { - return MakePtr(new SampleSystemClock()); - } -}; +using TestPlatform = platform::ImplementationPlatform; constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; @@ -39,16 +23,16 @@ const absl::Duration kAdvertisementMaxBackoffDuration = template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = + TestPlatform>::kAdvertisementMaxBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = + TestPlatform>::kAdvertisementBaseBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); TEST(AdvertisementReadResultTest, AdvertisementExists) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -61,7 +45,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) { } TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -70,23 +54,23 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { } TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), AdvertisementReadResult< - SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); + TestPlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Sleep for some time, but not long enough to warrant a retry. @@ -94,22 +78,22 @@ TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2)); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Sleep long enough to warrant a retry. absl::SleepFor(kAdvertisementBaseBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an additional failure so our backoff duration increases. @@ -120,11 +104,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { absl::SleepFor(kAdvertisementBaseBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an absurd amount of failures so we hit the maximum backoff duration. @@ -137,11 +121,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { absl::SleepFor(kAdvertisementMaxBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, GetDurationSinceRead) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int64_t sleepTime = 420; diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc index 32ba762c..4b38ecd4 100644 --- a/cpp/core/internal/mediums/ble_v2.cc +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -460,8 +460,8 @@ void BLEV2::stopScanning() { // TODO(b/112199086) Change to RecurringCancelableAlarm template -Ptr> BLEV2::createOnLostAlarm() { - return Ptr>(); +Ptr BLEV2::createOnLostAlarm() { + return Ptr(); } // Returns true if the device is currently accepting incoming BLE socket diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h index d8f07bd2..2e13802b 100644 --- a/cpp/core/internal/mediums/ble_v2.h +++ b/cpp/core/internal/mediums/ble_v2.h @@ -166,7 +166,7 @@ class BLEV2 { struct ScanningInfo { ScanningInfo(const string& service_id, Ptr scan_callback_facade, - Ptr> on_lost_alarm) + Ptr on_lost_alarm) : service_id(service_id), scan_callback_facade(scan_callback_facade), on_lost_alarm(on_lost_alarm) {} @@ -177,7 +177,7 @@ class BLEV2 { const string service_id; ScopedPtr> scan_callback_facade; // TODO(ahlee): Change to recurring cancelable alarm - ScopedPtr>> on_lost_alarm; + ScopedPtr> on_lost_alarm; }; struct AdvertisingInfo { @@ -236,7 +236,7 @@ class BLEV2 { Ptr ble_peripheral, ConstPtr advertisement_data); void processOnLostTimeout(); - Ptr> createOnLostAlarm(); + Ptr createOnLostAlarm(); bool isAdvertisementGattServerRunning(); bool startAdvertisementGattServer(const string& service_id, diff --git a/cpp/core/internal/mediums/lost_entity_tracker_test.cc b/cpp/core/internal/mediums/lost_entity_tracker_test.cc index ce37d6e0..bb4b2ef9 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker_test.cc +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -1,6 +1,6 @@ #include "core/internal/mediums/lost_entity_tracker.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" namespace location { @@ -9,6 +9,8 @@ namespace connections { namespace mediums { namespace { +using TestPlatform = platform::ImplementationPlatform; + struct TestEntity { int id; @@ -18,7 +20,7 @@ struct TestEntity { }; TEST(LostEntityTracker, NoEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -41,7 +43,7 @@ TEST(LostEntityTracker, NoEntitiesLost) { } TEST(LostEntityTracker, AllEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -55,7 +57,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); // Go through a round without rediscovering any entities. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); @@ -63,7 +65,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { } TEST(LostEntityTracker, SomeEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -80,7 +82,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { // was lost after the check. lost_entity_tracker.recordFoundEntity(entity_1.get()); lost_entity_tracker.recordFoundEntity(entity_3.get()); - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end()); ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); @@ -88,7 +90,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { } TEST(LostEntityTracker, SameEntityMultipleCopies) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_1_copy( MakeConstPtr(new TestEntity(1))); @@ -107,7 +109,7 @@ TEST(LostEntityTracker, SameEntityMultipleCopies) { // Go through a round without rediscovering any entities and verify that we // lost an entity equivalent to both copies of it. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_EQ(lost_entities.size(), 1); ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); diff --git a/cpp/core/internal/mediums/mediums.cc b/cpp/core/internal/mediums/mediums.cc index 22638499..65d19764 100644 --- a/cpp/core/internal/mediums/mediums.cc +++ b/cpp/core/internal/mediums/mediums.cc @@ -10,7 +10,8 @@ Mediums::Mediums() bluetooth_classic_( new BluetoothClassic(bluetooth_radio_.get())), ble_(new BLE(bluetooth_radio_.get())), - ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())) {} + ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())), + wifi_lan_(new mediums::WifiLan()) {} template Mediums::~Mediums() { @@ -37,6 +38,11 @@ Ptr > Mediums::bleV2() const { return ble_v2_.get(); } +template +Ptr > Mediums::wifi_lan() const { + return wifi_lan_.get(); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/mediums/mediums.h b/cpp/core/internal/mediums/mediums.h index f6d57d75..fed971fa 100644 --- a/cpp/core/internal/mediums/mediums.h +++ b/cpp/core/internal/mediums/mediums.h @@ -5,6 +5,7 @@ #include "core/internal/mediums/ble_v2.h" #include "core/internal/mediums/bluetooth_classic.h" #include "core/internal/mediums/bluetooth_radio.h" +#include "core/internal/mediums/wifi_lan.h" #include "platform/ptr.h" namespace location { @@ -27,6 +28,8 @@ class Mediums { Ptr > ble() const; // Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium. Ptr > bleV2() const; + // Returns a handle to the Wifi-Lan medium. + Ptr > wifi_lan() const; private: // The order of declaration is critical for both construction and @@ -41,6 +44,7 @@ class Mediums { ScopedPtr > > bluetooth_classic_; ScopedPtr > > ble_; ScopedPtr > > ble_v2_; + ScopedPtr > > wifi_lan_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc index 125359c7..72f08ef7 100644 --- a/cpp/core/internal/mediums/utils.cc +++ b/cpp/core/internal/mediums/utils.cc @@ -1,8 +1,10 @@ #include "core/internal/mediums/utils.h" +#include #include #include "platform/exception.h" +#include "platform/prng.h" #include "absl/strings/escaping.h" namespace location { @@ -48,6 +50,26 @@ ConstPtr Utils::legacySha256HashOnlyForPrinting( return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length); } +ConstPtr Utils::generateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.nextUInt32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return MakeConstPtr(new ByteArray(data)); +} + std::string Utils::bytesToPrintableHexString(ConstPtr bytes) { std::string hex_string( absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); diff --git a/cpp/core/internal/mediums/utils.h b/cpp/core/internal/mediums/utils.h index 665716a9..bb5e7704 100644 --- a/cpp/core/internal/mediums/utils.h +++ b/cpp/core/internal/mediums/utils.h @@ -21,6 +21,8 @@ class Utils { static ConstPtr legacySha256HashOnlyForPrinting( Ptr hash_utils, ConstPtr source, size_t length); + static ConstPtr generateRandomBytes(size_t length); + private: static std::string bytesToPrintableHexString(ConstPtr bytes); }; diff --git a/cpp/core/internal/mediums/webrtc/BUILD b/cpp/core/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..5ab6e446 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/BUILD @@ -0,0 +1,77 @@ +cc_library( + name = "webrtc", + hdrs = [ + "webrtc_socket.cc", + "webrtc_socket.h", + ], + deps = [ + "//platform:utils", + "//platform/api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_library( + name = "peer_id", + srcs = ["peer_id.cc"], + hdrs = ["peer_id.h"], + deps = [ + "//core/internal/mediums:utils", + "//platform:types", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + +cc_library( + name = "signaling_frames", + srcs = ["signaling_frames.cc"], + hdrs = ["signaling_frames.h"], + deps = [ + ":peer_id", + "//platform:types", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//absl/strings", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform:types", + "//platform/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep + ], +) diff --git a/cpp/core/internal/mediums/webrtc/peer_id.cc b/cpp/core/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..d6c03fe6 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,41 @@ +#include "core/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(ConstPtr bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +ConstPtr PeerId::FromRandom(Ptr hash_utils) { + return FromSeed(Utils::generateRandomBytes(kPeerIdLength), hash_utils); +} + +ConstPtr PeerId::FromSeed(ConstPtr seed, + Ptr hash_utils) { + ScopedPtr> full_hash( + Utils::sha256Hash(hash_utils, seed, kPeerIdLength)); + ScopedPtr> hashedSeed( + MakeConstPtr(new ByteArray(full_hash->getData(), kPeerIdLength / 2))); + return MakeConstPtr(new PeerId(BytesToStringUppercase(hashedSeed.get()))); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/peer_id.h b/cpp/core/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..984ed34c --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,36 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static ConstPtr FromRandom(Ptr hash_utils); + static ConstPtr FromSeed(ConstPtr seed, + Ptr hash_utils); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core/internal/mediums/webrtc/peer_id_test.cc b/cpp/core/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..de1235e9 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,76 @@ +#include "core/internal/mediums/webrtc/peer_id.h" + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +class MockHashUtils : public HashUtils { + public: + MOCK_METHOD(ConstPtr, md5, (const std::string& input), (override)); + MOCK_METHOD(ConstPtr, sha256, (const std::string& input), + (override)); +}; + +} // namespace + +TEST(PeerIdTest, GenerateRandomPeerId) { + // These are actual SHA-256 values for |seed| = "seed". + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::_)) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::_)); + + ConstPtr peer_id = PeerId::FromRandom(mock_hash_utils); + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "sesdfed"; + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))); + + ConstPtr peer_id = + PeerId::FromSeed(MakeConstPtr(new ByteArray(seed)), mock_hash_utils); + + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + ASSERT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.cc b/cpp/core/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..6af39230 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,125 @@ +#include "core/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ConstPtr FrameToByteArray( + const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return MakeConstPtr(new ByteArray(message.c_str(), message.size())); +} + +void SetSenderId(ConstPtr sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id->GetId()); +} + +ConstPtr DecodeIceCandidate( + const location::nearby::mediums::IceCandidate& ice_candidate_proto) { + webrtc::SdpParseError error; + return ConstPtr(webrtc::CreateIceCandidate( + ice_candidate_proto.sdp_mid(), ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.mutable_ready_for_signaling_poke(); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +Ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()) + .release()); +} + +Ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()) + .release()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.h b/cpp/core/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..fec7046c --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,49 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id); + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer); +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +Ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +Ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..3e468d23 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,184 @@ +#include "core/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/ptr.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + ConstPtr sender_id(new PeerId("abc")); + ConstPtr encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_poke->getData(), encoded_poke->size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ConstPtr encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer->getData(), encoded_offer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignalingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + Ptr decoded_offer = DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr answer = + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp); + ConstPtr encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer->getData(), encoded_answer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + Ptr decoded_answer = DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + ConstPtr sender_id(new PeerId("abc")); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate.get())); + } + ConstPtr encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates->getData(), encoded_candidates->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> decoded_candidates = + DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..49d76110 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,139 @@ +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +template +Exception::Value WebRtcSocket::OutputStreamImpl::write( + ConstPtr data) { + ScopedPtr> scoped_data(data); + + if (scoped_data->size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return Exception::IO; + } + + socket_->BlockUntilSufficientSpaceInBuffer(scoped_data->size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return Exception::IO; + } + + if (!socket_->SendMessage(scoped_data.release())) { + return Exception::IO; + } + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::flush() { + // Java implementation is empty. + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::close() { + socket_->close(); + return Exception::NONE; +} + +// WebRtcSocket +template +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), + data_channel_(std::move(data_channel)), + pipe_(MakeRefCountedPtr(new Pipe())), + incoming_data_piped_input_stream_(Pipe::createInputStream(pipe_)), + incoming_data_piped_output_stream_(Pipe::createOutputStream(pipe_)), + output_stream_(MakePtr(new OutputStreamImpl(this))), + closed_(Platform::createAtomicBoolean(false)), + backpressure_lock_(Platform::createLock()), + buffer_variable_( + Platform::createConditionVariable(backpressure_lock_.get())) {} + +template +Ptr WebRtcSocket::getInputStream() { + return incoming_data_piped_input_stream_.get(); +} + +template +Ptr WebRtcSocket::getOutputStream() { + return output_stream_.get(); +} + +template +void WebRtcSocket::close() { + if (IsClosed()) return; + + closed_->set(true); + incoming_data_piped_output_stream_->close(); + incoming_data_piped_input_stream_->close(); + data_channel_->Close(); + WakeUpWriter(); + if (!socket_closed_listener_.isNull()) { + socket_closed_listener_->OnSocketClosed(); + } +} + +template +void WebRtcSocket::NotifyDataChannelMsgReceived( + ConstPtr message) { + Exception::Value exception = + incoming_data_piped_output_stream_->write(message); + if (exception != Exception::NONE) close(); + + exception = incoming_data_piped_output_stream_->flush(); + if (exception != Exception::NONE) close(); +} + +template +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { + WakeUpWriter(); +} + +template +bool WebRtcSocket::SendMessage(ConstPtr data) { + ScopedPtr> scoped_data(data); + return data_channel_->Send(webrtc::DataBuffer( + std::string(scoped_data->getData(), scoped_data->size()))); +} + +template +bool WebRtcSocket::IsClosed() { + return closed_->get(); +} + +template +void WebRtcSocket::WakeUpWriter() { + Synchronized s(backpressure_lock_.get()); + buffer_variable_->notify(); +} + +template +void WebRtcSocket::SetOnSocketClosedListener( + Ptr listener) { + socket_closed_listener_ = listener; +} + +template +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + Synchronized s(backpressure_lock_.get()); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_->wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..5a55e9d9 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,104 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include "platform/api/atomic_boolean.h" +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/api/socket.h" +#include "platform/pipe.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +template +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + Ptr getInputStream() override; + Ptr getOutputStream() override; + void close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(ConstPtr message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + class SocketClosedListener { + public: + virtual ~SocketClosedListener() = default; + virtual void OnSocketClosed() = 0; + }; + void SetOnSocketClosedListener(Ptr listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) + : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception::Value write(ConstPtr data) override; + Exception::Value flush() override; + Exception::Value close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(ConstPtr data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Ptr pipe_; + ScopedPtr> incoming_data_piped_input_stream_; + ScopedPtr> incoming_data_piped_output_stream_; + + ScopedPtr> output_stream_; + + ScopedPtr> closed_; + + Ptr socket_closed_listener_; + + ScopedPtr> backpressure_lock_; + ScopedPtr> buffer_variable_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/webrtc/webrtc_socket.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..503b8cd8 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,155 @@ +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/api/platform.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +class MockSocketClosedListener + : public WebRtcSocket::SocketClosedListener { + public: + MOCK_METHOD(void, OnSocketClosed, ()); +}; + +TEST(WebRtcSocketTest, ReadFromSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr> result = + webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("Me"))); + webrtc_socket.NotifyDataChannelMsgReceived( + MakeConstPtr(new ByteArray("ssa"))); + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("ge"))); + ExceptionOr> result; + + // This behaviour is different from the Java code + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "Me"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ssa"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ge"); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::NONE); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + ConstPtr kMessage = MakeConstPtr(new ByteArray(kMaxDataSize + 1)); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + ScopedPtr> mock_listener( + MakePtr(new MockSocketClosedListener())); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.SetOnSocketClosedListener(mock_listener.get()); + + EXPECT_CALL(*mock_listener, OnSocketClosed()); + EXPECT_CALL(*mock_data_channel, Close()); + webrtc_socket.close(); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.getOutputStream()->write(kMessage); + webrtc_socket.close(); + + EXPECT_EQ(webrtc_socket.getInputStream()->read().exception(), Exception::IO); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.cc b/cpp/core/internal/mediums/wifi_lan.cc new file mode 100644 index 00000000..bc69220a --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.cc @@ -0,0 +1,213 @@ +#include "core/internal/mediums/wifi_lan.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +template +WifiLan::WifiLan() + : lock_(Platform::createLock()), + wifi_lan_medium_(Platform::createWifiLanMedium()) {} + +template +bool WifiLan::IsAvailable() { + Synchronized s(lock_.get()); + + return !wifi_lan_medium_.isNull(); +} + +template +bool WifiLan::StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) { + Synchronized s(lock_.get()); + + if (!IsAvailable()) { + return false; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StartAdvertising(service_id, + // wifi_lan_service_info_name)); + + advertising_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAdvertising(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAdvertising()) { + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopAdvertising(advertising_info_.service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.service_id.clear(); +} + +template +bool WifiLan::IsAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.service_id.empty(); +} + +template +bool WifiLan::StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback) { + Synchronized s(lock_.get()); + + if (discovered_service_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because a null parameter was passed in."); + return false; + } + + if (IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because we are already discovering."); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start WifiLan discovering + // because WifiLan isn't available."); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_discovered_service_callback_bridge( + new DiscoveredServiceCallbackBridge(discovered_service_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartDiscovery( + // service_id, Ptr( + // discovered_service_callback_bridge.release())); + + discovering_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopDiscovery(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't turn off WifiLan + // discovering because we never started discovering."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopDiscovery(discovering_info_.service_id); + // Reset our bundle of scanning state to mark that we're no longer scanning. + discovering_info_.service_id.clear(); +} + +template +bool WifiLan::IsDiscovering(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !discovering_info_.service_id.empty(); +} + +template +bool WifiLan::StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + if (accepted_connection_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections because a null parameter was passed in."); + return false; + } + + if (IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections for %s because another WifiLan service socket is + // already in-progress.", service_id); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start accepting WifiLan + // connections for %s because WifiLan isn't available.", serviceId); + return false; + } + + ScopedPtr> + scoped_wifi_lan_accepted_connection_callback( + new WifiLanAcceptedConnectionCallback( + accepted_connection_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartAcceptingConnections( + // service_id, Ptr( + // wifi_lan_accepted_connection_callback.release())); + + accepting_connections_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't stop accepting WifiLan + // connections because it was never started."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium.); + // A possible implementation is: + // wifi_lan_medium_->StopAcceptingConnections( + // accepting_connections_info_.service_id); + + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.service_id.clear(); +} + +template +bool WifiLan::IsAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !accepting_connections_info_.service_id.empty(); +} + +template +Ptr WifiLan::Connect( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (wifi_lan_service.isNull() || service_id.empty()) { + return Ptr(); + } + + if (!IsAvailable()) { + return Ptr(); + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // return wifi_lan_medium_->Connect(wifi_lan_service, service_id); + return Ptr(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.h b/cpp/core/internal/mediums/wifi_lan.h new file mode 100644 index 00000000..953cbf1f --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.h @@ -0,0 +1,160 @@ +#ifndef CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ +#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ + +#include + +#include "platform/api/lock.h" +#include "platform/api/wifi_lan.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(Ptr wifi_lan_service) = 0; + virtual void OnServiceLost(Ptr wifi_lan_service) = 0; +}; + +template +class WifiLan { + public: + WifiLan(); + virtual ~WifiLan() = default; + + bool IsAvailable(); + + bool StartAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopAdvertising(absl::string_view service_id); + bool IsAdvertising(); + + bool StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback); + void StopDiscovery(absl::string_view service_id); + bool IsDiscovering(absl::string_view service_id); + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) = 0; + }; + + bool StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback); + void StopAcceptingConnections(absl::string_view service_id); + bool IsAcceptingConnections(absl::string_view service_id); + + Ptr Connect(Ptr wifi_lan_service, + absl::string_view service_id); + + private: + class DiscoveredServiceCallbackBridge + : public WifiLanMedium::DiscoveredServiceCallback { + public: + explicit DiscoveredServiceCallbackBridge( + Ptr discovered_service_callback) + : discovered_service_callback_(discovered_service_callback) {} + ~DiscoveredServiceCallbackBridge() override = default; + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceDiscovered(wifi_lan_service); + } + void OnServiceLost(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceLost(wifi_lan_service); + } + + private: + ScopedPtr> + discovered_service_callback_; + }; + + class WifiLanAcceptedConnectionCallback + : public WifiLanMedium::AcceptedConnectionCallback { + public: + explicit WifiLanAcceptedConnectionCallback( + Ptr accepted_connection_callback) + : accepted_connection_callback_(accepted_connection_callback) {} + ~WifiLanAcceptedConnectionCallback() override = default; + + void OnConnectionAccepted(Ptr wifi_lan_socket, + absl::string_view service_id) override { + accepted_connection_callback_->OnConnectionAccepted(wifi_lan_socket, + service_id); + } + + private: + ScopedPtr> + accepted_connection_callback_; + }; + + struct DiscoveringInfo { + DiscoveringInfo() = default; + explicit DiscoveringInfo(absl::string_view service_id) + : service_id(service_id) {} + ~DiscoveringInfo() = default; + + string service_id; + }; + + struct AdvertisingInfo { + AdvertisingInfo() = default; + explicit AdvertisingInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AdvertisingInfo() = default; + + string service_id; + }; + + struct AcceptingConnectionsInfo { + AcceptingConnectionsInfo() = default; + explicit AcceptingConnectionsInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AcceptingConnectionsInfo() = default; + + string service_id; + }; + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + + // ---------- CORE WIFILAN------------ + + // The underlying, per-platform implementation. + ScopedPtr> wifi_lan_medium_; + + // ------------ DISCOVERY ------------ + + // discovering_info_ is not scoped because it's nullable. + DiscoveringInfo discovering_info_; + + // ------------ ADVERTISING ------------ + + // A bundle of state required to start/stop WifiLan service publishing. + AdvertisingInfo advertising_info_; + + // A bundle of state required to start/stop accepting WifiLan service + /// connections. + AcceptingConnectionsInfo accepting_connections_info_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/wifi_lan.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ diff --git a/cpp/core/internal/message_lite.h b/cpp/core/internal/message_lite.h new file mode 100644 index 00000000..4ca8a0b2 --- /dev/null +++ b/cpp/core/internal/message_lite.h @@ -0,0 +1,6 @@ +#ifndef CORE_INTERNAL_MESSAGE_LITE_H_ +#define CORE_INTERNAL_MESSAGE_LITE_H_ + +#include "google/protobuf/message_lite.h" + +#endif // CORE_INTERNAL_MESSAGE_LITE_H_ diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index 232a6c89..d077205d 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -61,7 +61,8 @@ ExceptionOrOfflineFrame OfflineFrames::fromBytes( ConstPtr offline_frame_bytes) { auto offline_frame = std::make_unique(); - if (!offline_frame->ParseFromString(offline_frame_bytes->asString())) { + if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(), + offline_frame_bytes->size())) { return ExceptionOrOfflineFrame(Exception::INVALID_PROTOCOL_BUFFER); } @@ -78,6 +79,7 @@ V1Frame::FrameType OfflineFrames::getFrameType( return V1Frame::UNKNOWN_FRAME_TYPE; } +// TODO(b/155752436): Use byte array endpoint_info instead of endpoint_name. ConstPtr OfflineFrames::forConnectionRequest( const std::string &endpoint_id, const std::string &endpoint_name, std::int32_t nonce, @@ -85,6 +87,7 @@ ConstPtr OfflineFrames::forConnectionRequest( auto connection_request = std::make_unique(); connection_request->set_endpoint_id(endpoint_id); connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); connection_request->set_nonce(nonce); for (std::vector::const_iterator it = diff --git a/cpp/core/internal/offline_frames_test.cc b/cpp/core/internal/offline_frames_test.cc index 874b3a74..66460a1b 100644 --- a/cpp/core/internal/offline_frames_test.cc +++ b/cpp/core/internal/offline_frames_test.cc @@ -45,8 +45,8 @@ constexpr ConnectionRequestFrame::Medium ToConnectionRequestMedium( } // namespace TEST(OfflineFramesTest, CanParseMessageFromBytes) { - const string endpoint_id{"ABC"}; - const string endpoint_name{"XYZ"}; + const std::string endpoint_id{"ABC"}; + const std::string endpoint_name{"XYZ"}; const int32 nonce{1234}; const std::vector mediums{Medium::BLE, Medium::BLUETOOTH}; diff --git a/cpp/core/internal/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc index b5e45cfb..386eb171 100644 --- a/cpp/core/internal/offline_service_controller.cc +++ b/cpp/core/internal/offline_service_controller.cc @@ -11,11 +11,11 @@ OfflineServiceController::OfflineServiceController() : ServiceController(), medium_manager_(new MediumManager()), endpoint_channel_manager_( - new EndpointChannelManager(medium_manager_.get())), + new EndpointChannelManager(medium_manager_.get())), endpoint_manager_( new EndpointManager(endpoint_channel_manager_.get())), payload_manager_(new PayloadManager(endpoint_manager_.get())), - bandwidth_upgrade_manager_(new BandwidthUpgradeManager( + bandwidth_upgrade_manager_(new BandwidthUpgradeManager( medium_manager_.get(), endpoint_channel_manager_.get(), endpoint_manager_.get())), pcp_manager_(new PCPManager( diff --git a/cpp/core/internal/offline_service_controller.h b/cpp/core/internal/offline_service_controller.h index 743e7852..cbf1b13b 100644 --- a/cpp/core/internal/offline_service_controller.h +++ b/cpp/core/internal/offline_service_controller.h @@ -68,11 +68,10 @@ class OfflineServiceController : public ServiceController { // on the destructors running (strictly) in the reverse order; a deviation // from that will lead to crashes at runtime. ScopedPtr > > medium_manager_; - ScopedPtr > > endpoint_channel_manager_; + ScopedPtr> endpoint_channel_manager_; ScopedPtr > > endpoint_manager_; ScopedPtr > > payload_manager_; - ScopedPtr > > - bandwidth_upgrade_manager_; + ScopedPtr> bandwidth_upgrade_manager_; ScopedPtr > > pcp_manager_; }; diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index 84881eef..bd75a030 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -1,5 +1,4 @@ #include "core/internal/p2p_cluster_pcp_handler.h" - #include "platform/api/hash_utils.h" namespace location { @@ -16,6 +15,11 @@ const BLEAdvertisement::Version::Value P2PClusterPCPHandler::kBleAdvertisementVersion = BLEAdvertisement::Version::V1; +template +const WifiLanServiceInfo::Version + P2PClusterPCPHandler::kWifiLanServiceInfoVersion = + WifiLanServiceInfo::Version::kV1; + template ConstPtr P2PClusterPCPHandler::generateHash( const string& source, size_t size) { @@ -35,8 +39,8 @@ template P2PClusterPCPHandler::P2PClusterPCPHandler( Ptr> medium_manager, Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : BasePCPHandler(endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), medium_manager_(medium_manager) {} @@ -58,6 +62,9 @@ template std::vector P2PClusterPCPHandler::getConnectionMediumsByPriority() { std::vector mediums; + if (medium_manager_->IsWifiLanAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } if (medium_manager_->isBluetoothAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } @@ -81,6 +88,15 @@ P2PClusterPCPHandler::startAdvertisingImpl( const AdvertisingOptions& options) { std::vector mediums_started_successfully; + ScopedPtr> scoped_wifi_lan_service_id_hash( + generateHash(service_id, WifiLanServiceInfo::kServiceIdHashLength)); + proto::connections::Medium wifi_lan_medium = StartWifiLanAdvertising( + client_proxy, service_id, scoped_wifi_lan_service_id_hash.get(), + local_endpoint_id, local_endpoint_name); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + ScopedPtr> scoped_bluetooth_service_id_hash( generateHash(service_id, BluetoothDeviceName::kServiceIdHashLength)); proto::connections::Medium bluetooth_medium = startBluetoothAdvertising( @@ -118,10 +134,14 @@ Status::Value P2PClusterPCPHandler::stopAdvertisingImpl( Ptr> client_proxy) { medium_manager_->stopBleAdvertising(client_proxy->getAdvertisingServiceId()); medium_manager_->turnOffBluetoothDiscoverability(); + medium_manager_->StopWifiLanAdvertising( + client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBleConnections( client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBluetoothConnections( client_proxy->getAdvertisingServiceId()); + medium_manager_->StopListeningForIncomingWifiLanConnections( + client_proxy->getAdvertisingServiceId()); return Status::SUCCESS; } @@ -132,6 +152,14 @@ P2PClusterPCPHandler::startDiscoveryImpl( const DiscoveryOptions& options) { std::vector mediums_started_successfully; + proto::connections::Medium wifi_lan_medium = + StartWifiLanDiscovery(MakePtr(new FoundWifiLanServiceProcessor( + self_, client_proxy, service_id)), + client_proxy, service_id); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + proto::connections::Medium bluetooth_medium = startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor( self_, client_proxy, service_id)), @@ -170,6 +198,12 @@ typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::connectImpl( Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) { + Ptr wifi_lan_endpoint = + DowncastPtr(endpoint); + if (!wifi_lan_endpoint.isNull()) { + return WifiLanConnectImpl(client_proxy, wifi_lan_endpoint); + } + Ptr bluetooth_endpoint = DowncastPtr(endpoint); if (!bluetooth_endpoint.isNull()) { @@ -295,6 +329,60 @@ void P2PClusterPCPHandler::IncomingBleConnectionProcessor:: proto::connections::Medium::BLE); } +//////////// P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor ///////// +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnection(Ptr wifi_lan_socket) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnIncomingWifiLanConnectionRunnable( + pcp_handler_, client_proxy_, wifi_lan_socket))); +} + +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + wifi_lan_socket_(wifi_lan_socket) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::run() { + string remote_service_name = + wifi_lan_socket_->GetRemoteWifiLanService()->GetName(); + ScopedPtr> scoped_wifi_lan_endpoint_channel( + pcp_handler_->endpoint_channel_manager_ + ->CreateIncomingWifiLanEndpointChannel(remote_service_name, + wifi_lan_socket_)); + if (!scoped_wifi_lan_endpoint_channel.isNull()) { + // TODO(b/149806065): Add logging. + } else { + Exception::Value exception = wifi_lan_socket_->Close(); + wifi_lan_socket_.destroy(); + if (Exception::NONE != exception) { + if (Exception::IO == exception) { + // TODO(b/149806065): Add logging. + } + } + } + pcp_handler_->onIncomingConnection(client_proxy_, remote_service_name, + scoped_wifi_lan_endpoint_channel.release(), + proto::connections::Medium::WIFI_LAN); +} + ///////// P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor ////////// template P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: @@ -582,6 +670,137 @@ void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: } } +////////// P2PClusterPCPHandler::FoundWifiLanServiceProcessor /////////// +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, absl::string_view service_id) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +bool P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + IsRecognizedWifiLanEndpoint(Ptr wifi_lan_service_info) { + if (wifi_lan_service_info.isNull()) { + return false; + } + + if (wifi_lan_service_info->GetPcp() != pcp_handler_->getPCP()) { + return false; + } + + if (*(wifi_lan_service_info->GetServiceIdHash()) != + *(expected_service_id_hash_.get())) { + return false; + } + + return true; +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the discovered endpoint to the client. + pcp_handler_->onEndpointFound( + client_proxy_, + MakePtr(new WifiLanEndpoint( + wifi_lan_service_.release(), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service.operator->()) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(b/149806065): Add logging. + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the endpoint as lost to the client. + // TODO(b/149806065): Add logging. + pcp_handler_->onEndpointLost( + client_proxy_, + MakePtr(new WifiLanEndpoint( + Ptr(wifi_lan_service_.release()), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + //////////////////// END IMPLEMENTATIONS FOR NESTED CLASSES //////////////////// template @@ -710,6 +929,65 @@ proto::connections::Medium P2PClusterPCPHandler::startBleDiscovery( return proto::connections::BLE; } +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + if (!medium_manager_->IsListeningForIncomingWifiLanConnections(service_id)) { + if (!medium_manager_->StartListeningForIncomingWifiLanConnections( + service_id, MakePtr(new IncomingWifiLanConnectionProcessor( + self_, client_proxy, local_endpoint_name)))) { + // TODO(b/149806065): logger.atWarning().log("In + // StartWifiLanAdvertising(%s), client %d failed to start listening for + // incoming WifiLan connections to ServiceId %s", local_endpoint_name, + // clientProxy.getClientId(), service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(b/149806065): Add logging. + } + + // Generate a WifiLanServiceInfo. + const string wifi_lan_service_info = + WifiLanServiceInfo::AsString(kWifiLanServiceInfoVersion, + getPCP(), + local_endpoint_id, + service_id_hash); + if (wifi_lan_service_info.empty()) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(b/149806065): Add logging. + } + + // TODO(b/149806065): Add logging + + if (!medium_manager_->StartWifiLanAdvertising( + service_id, wifi_lan_service_info)) { + // TODO(b/149806065): Add logging + medium_manager_->StopWifiLanAdvertising(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + return proto::connections::WIFI_LAN; +} + +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanDiscovery( + Ptr processor, + Ptr > client_proxy, absl::string_view service_id) { + if (!medium_manager_->StartWifiLanDiscovery(service_id, processor)) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } + + return proto::connections::WIFI_LAN; +} + template typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::bluetoothConnectImpl( @@ -783,6 +1061,38 @@ string P2PClusterPCPHandler::getBlePeripheralId( #endif } +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint) { + Ptr remote_wifi_lan_service = + wifi_lan_endpoint->GetWifiLanService(); + + Ptr wifi_lan_socket = medium_manager_->ConnectToWifiLanService( + remote_wifi_lan_service, wifi_lan_endpoint->getServiceId()); + + if (wifi_lan_socket.isNull()) { + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::BLUETOOTH_ERROR); + } + + ScopedPtr> scoped_wifi_lan_endpoint_channel( + this->endpoint_channel_manager_->CreateOutgoingWifiLanEndpointChannel( + wifi_lan_endpoint->getEndpointId(), wifi_lan_socket)); + + if (scoped_wifi_lan_endpoint_channel.isNull()) { + wifi_lan_socket->Close(); + wifi_lan_socket.destroy(); // Avoid leaks. + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::ERROR); + } + + // TODO(b/149806065): Add logging. + return typename BasePCPHandler::ConnectImplResult( + scoped_wifi_lan_endpoint_channel.release()); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h index 78d5c757..26d60b6e 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -13,6 +13,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "core/internal/pcp.h" +#include "core/internal/wifi_lan_service_info.h" #include "core/options.h" #include "core/strategy.h" #include "platform/api/bluetooth_classic.h" @@ -35,11 +36,10 @@ namespace connections { template class P2PClusterPCPHandler : public BasePCPHandler { public: - P2PClusterPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PClusterPCPHandler(Ptr> medium_manager, + Ptr> endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PClusterPCPHandler() override; Strategy getStrategy() override; @@ -52,27 +52,29 @@ class P2PClusterPCPHandler : public BasePCPHandler { // @PCPHandlerThread Ptr::StartOperationResult> - startAdvertisingImpl(Ptr > client_proxy, + startAdvertisingImpl(Ptr> client_proxy, const string& service_id, const string& local_endpoint_id, const string& local_endpoint_name, const AdvertisingOptions& options) override; + // @PCPHandlerThread Status::Value stopAdvertisingImpl( - Ptr > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread Ptr::StartOperationResult> - startDiscoveryImpl(Ptr > client_proxy, + startDiscoveryImpl(Ptr> client_proxy, const string& service_id, const DiscoveryOptions& options) override; + // @PCPHandlerThread Status::Value stopDiscoveryImpl( - Ptr > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread typename BasePCPHandler::ConnectImplResult connectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) override; @@ -82,16 +84,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { template friend class IncomingBleConnectionProcessor; template + friend class IncomingWifiLanConnectionProcessor; + template friend class FoundBluetoothAdvertisementProcessor; template friend class FoundBleAdvertisementProcessor; + template + friend class FoundWifiLanServiceProcessor; class IncomingBluetoothConnectionProcessor : public MediumManager::IncomingBluetoothConnectionProcessor { public: IncomingBluetoothConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBluetoothConnection( @@ -101,20 +107,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBluetoothConnectionRunnable : public Runnable { public: OnIncomingBluetoothConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr bluetooth_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr bluetooth_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -122,8 +128,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::IncomingBleConnectionProcessor { public: IncomingBleConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBleConnection(Ptr ble_socket, @@ -133,19 +139,51 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBleConnectionRunnable : public Runnable { public: OnIncomingBleConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, Ptr ble_socket); + Ptr> pcp_handler, + Ptr> client_proxy, Ptr ble_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr ble_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string local_endpoint_name_; + }; + + class IncomingWifiLanConnectionProcessor + : public MediumManager::IncomingWifiLanConnectionProcessor { + public: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name); + + void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) override; + + private: + class OnIncomingWifiLanConnectionRunnable : public Runnable { + public: + OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr wifi_lan_socket_; + }; + + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -153,8 +191,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBluetoothDeviceProcessor { public: FoundBluetoothAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, const string& service_id); + Ptr> pcp_handler, + Ptr> client_proxy, const string& service_id); void onFoundBluetoothDevice(Ptr bluetooth_device) override; void onLostBluetoothDevice(Ptr bluetooth_device) override; @@ -163,8 +201,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBluetoothDeviceRunnable : public Runnable { public: OnFoundBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -172,19 +210,19 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; class OnLostBluetoothDeviceRunnable : public Runnable { public: OnLostBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -192,22 +230,22 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; bool isRecognizedBluetoothEndpoint( const string& found_bluetooth_device_name, Ptr bluetooth_device_name); - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string service_id_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> expected_service_id_hash_; std::shared_ptr self_{this, [](void*) {}}; }; @@ -216,8 +254,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBlePeripheralProcessor { public: FoundBleAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy); + Ptr> pcp_handler, + Ptr> client_proxy); void onFoundBlePeripheral(Ptr ble_peripheral, const string& service_id, @@ -229,8 +267,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBlePeripheralRunnable : public Runnable { public: OnFoundBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral, ConstPtr advertisement_bytes); @@ -238,31 +276,31 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; - ScopedPtr > advertisement_bytes_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> ble_peripheral_; + ScopedPtr> advertisement_bytes_; + ScopedPtr> expected_service_id_hash_; }; class OnLostBlePeripheralRunnable : public Runnable { public: OnLostBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; }; // Holds the state required to re-create a BLEEndpoint we see on a @@ -278,14 +316,74 @@ class P2PClusterPCPHandler : public BasePCPHandler { const string endpoint_name; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + // Maps a BLEPeripheral to its corresponding BLEEndpointState. typedef std::map FoundBLEEndpointsMap; FoundBLEEndpointsMap found_ble_endpoints_; std::shared_ptr self_{this, [](void*) {}}; }; + class FoundWifiLanServiceProcessor + : public MediumManager::FoundWifiLanServiceProcessor { + public: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view service_id); + + void OnFoundWifiLanService(Ptr wifi_lan_service) override; + void OnLostWifiLanService(Ptr wifi_lan_service) override; + + private: + class OnFoundWifiLanServiceRunnable : public Runnable { + public: + OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + ScopedPtr> expected_service_id_hash_; + }; + + class OnLostWifiLanServiceRunnable : public Runnable { + public: + OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + }; + + bool IsRecognizedWifiLanEndpoint( + Ptr wifi_lan_service_info); + + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string service_id_; + ScopedPtr> expected_service_id_hash_; + std::shared_ptr self_{this, [](void*) {}}; + }; + class BluetoothEndpoint : public BasePCPHandler::DiscoveredEndpoint { public: @@ -310,7 +408,7 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBluetoothAdvertisementProcessor; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -336,7 +434,35 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBleAdvertisementProcessor; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; + const string endpoint_id_; + const string endpoint_name_; + const string service_id_; + }; + + class WifiLanEndpoint : public BasePCPHandler::DiscoveredEndpoint { + public: + Ptr GetWifiLanService() { return wifi_lan_service_.get(); } + string getEndpointId() override { return endpoint_id_; } + string getEndpointName() override { return endpoint_name_; } + string getServiceId() override { return service_id_; } + proto::connections::Medium getMedium() override { + return proto::connections::Medium::WIFI_LAN; + } + + private: + WifiLanEndpoint(Ptr wifi_lan_service, + absl::string_view endpoint_id, + absl::string_view endpoint_name, + absl::string_view service_id) + : wifi_lan_service_(wifi_lan_service), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + service_id_(service_id) {} + + friend class FoundWifiLanServiceProcessor; + + ScopedPtr> wifi_lan_service_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -344,32 +470,44 @@ class P2PClusterPCPHandler : public BasePCPHandler { static const BluetoothDeviceName::Version::Value kBluetoothDeviceNameVersion; static const BLEAdvertisement::Version::Value kBleAdvertisementVersion; + static const WifiLanServiceInfo::Version kWifiLanServiceInfoVersion; static ConstPtr generateHash(const string& source, size_t size); static string getBlePeripheralId(Ptr ble_peripheral); proto::connections::Medium startBluetoothAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBluetoothDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bluetoothConnectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr bluetooth_endpoint); proto::connections::Medium startBleAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBleDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bleConnectImpl( - Ptr > client_proxy, Ptr ble_endpoint); + Ptr> client_proxy, Ptr ble_endpoint); - Ptr > medium_manager_; + proto::connections::Medium StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name); + proto::connections::Medium StartWifiLanDiscovery( + Ptr processor, + Ptr> client_proxy, absl::string_view service_id); + typename BasePCPHandler::ConnectImplResult WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint); + + Ptr> medium_manager_; std::shared_ptr self_{this, [](void*) {}}; }; diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc index 4e48a42c..7623f490 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -8,8 +8,8 @@ template P2PPointToPointPCPHandler::P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PStarPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.h b/cpp/core/internal/p2p_point_to_point_pcp_handler.h index 56f7104b..0b75dbef 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -27,8 +27,8 @@ class P2PPointToPointPCPHandler : public P2PStarPCPHandler { P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); Strategy getStrategy() override; PCP::Value getPCP() override; diff --git a/cpp/core/internal/p2p_star_pcp_handler.cc b/cpp/core/internal/p2p_star_pcp_handler.cc index a3bf50d6..320bc1a0 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.cc +++ b/cpp/core/internal/p2p_star_pcp_handler.cc @@ -10,8 +10,8 @@ template P2PStarPCPHandler::P2PStarPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PClusterPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_star_pcp_handler.h b/cpp/core/internal/p2p_star_pcp_handler.h index 4a7c110f..b16a5a48 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.h +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -27,11 +27,10 @@ namespace connections { template class P2PStarPCPHandler : public P2PClusterPCPHandler { public: - P2PStarPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PStarPCPHandler(Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PStarPCPHandler() override; Strategy getStrategy() override; diff --git a/cpp/core/internal/pcp_manager.cc b/cpp/core/internal/pcp_manager.cc index 50500e2e..2411ee39 100644 --- a/cpp/core/internal/pcp_manager.cc +++ b/cpp/core/internal/pcp_manager.cc @@ -11,9 +11,9 @@ namespace connections { template PCPManager::PCPManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager) + Ptr bandwidth_upgrade_manager) : pcp_handlers_(), current_pcp_handler_() { pcp_handlers_[PCP::P2P_CLUSTER] = MakePtr(new P2PClusterPCPHandler( medium_manager, endpoint_manager, endpoint_channel_manager, diff --git a/cpp/core/internal/pcp_manager.h b/cpp/core/internal/pcp_manager.h index 8bb77a32..731f6951 100644 --- a/cpp/core/internal/pcp_manager.h +++ b/cpp/core/internal/pcp_manager.h @@ -29,9 +29,9 @@ template class PCPManager { public: PCPManager(Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager); + Ptr bandwidth_upgrade_manager); ~PCPManager(); Status::Value startAdvertising( diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.cc b/cpp/core/internal/wifi_lan_endpoint_channel.cc new file mode 100644 index 00000000..ca2589a5 --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.cc @@ -0,0 +1,49 @@ +#include "core/internal/wifi_lan_endpoint_channel.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +Ptr +WifiLanEndpointChannel::CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +Ptr +WifiLanEndpointChannel::CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +WifiLanEndpointChannel::WifiLanEndpointChannel( + absl::string_view channel_name, Ptr wifi_lan_socket) + : BaseEndpointChannel(channel_name, + wifi_lan_socket->GetInputStream(), + wifi_lan_socket->GetOutputStream()), + wifi_lan_socket_(wifi_lan_socket) {} + +WifiLanEndpointChannel::~WifiLanEndpointChannel() {} + +proto::connections::Medium WifiLanEndpointChannel::getMedium() { + return proto::connections::Medium::WIFI_LAN; +} + +void WifiLanEndpointChannel::closeImpl() { + Exception::Value exception = wifi_lan_socket_->Close(); + if (exception != Exception::NONE) { + if (exception == Exception::IO) { + // TODO(b/149806065): Add logging. + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.h b/cpp/core/internal/wifi_lan_endpoint_channel.h new file mode 100644 index 00000000..8d31df0f --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.h @@ -0,0 +1,46 @@ +#ifndef CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ + +#include "core/internal/base_endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiLanEndpointChannel : public BaseEndpointChannel { + public: + using Platform = platform::ImplementationPlatform; + + static Ptr CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + static Ptr CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + + ~WifiLanEndpointChannel() override; + + proto::connections::Medium getMedium() override; + + protected: + void closeImpl() override; + + private: + WifiLanEndpointChannel(absl::string_view channel_name, + Ptr wifi_lan_socket); + + ScopedPtr > wifi_lan_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc index 7df38ed7..00639406 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.cc +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -18,8 +18,8 @@ class OnIncomingWifiConnectionRunnable : public Runnable { template WifiLanUpgradeHandler::WifiLanUpgradeHandler( Ptr > medium_manager, - Ptr > endpoint_channel_manager) - : BaseBandwidthUpgradeHandler(endpoint_channel_manager), + Ptr endpoint_channel_manager) + : BaseBandwidthUpgradeHandler(endpoint_channel_manager), medium_manager_(medium_manager) {} template diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.h b/cpp/core/internal/wifi_lan_upgrade_handler.h index 26781c47..1b4d4d1a 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.h +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -23,36 +23,35 @@ class OnIncomingWifiConnectionRunnable; // Manages the WIFI_LAN-specific methods needed to upgrade an EndpointChannel template -class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { +class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // TODO(ahlee): Uncomment when WIFI_LAN plumbing is done. // public MediumManager::IncomingWifiConnectionProcessor { public: - WifiLanUpgradeHandler( - Ptr > medium_manager_, - Ptr > endpoint_channel_manager); - ~WifiLanUpgradeHandler(); + WifiLanUpgradeHandler(Ptr > medium_manager_, + Ptr endpoint_channel_manager); + ~WifiLanUpgradeHandler() override; void onIncomingWifiConnection(Ptr socket); protected: // @BandwidthUpgradeHandlerThread ConstPtr initializeUpgradedMediumForEndpoint( - const string& endpoint_id); + const string& endpoint_id) override; // @BandwidthUpgradeHandlerThread Ptr createUpgradedEndpointChannel( const string& endpoint_id, ConstPtr - upgrade_path_info); + upgrade_path_info) override; // TODO(ahlee): Change the java counterparts of these methods to private. - proto::connections::Medium getUpgradeMedium(); + proto::connections::Medium getUpgradeMedium() override; // @BandwidthUpgradeHandlerThread - void revertImpl(); + void revertImpl() override; private: class IncomingWifiLanSocketConnection - : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { + : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { public: - IncomingWifiLanSocketConnection(Ptr socket) + explicit IncomingWifiLanSocketConnection(Ptr socket) : new_endpoint_channel_(Ptr()), // TODO(ahlee): Uncomment when plumbing for WIFI_LAN is done. // new_endpoint_channel_(getEndpointChannelManager() @@ -61,15 +60,15 @@ class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // TODO(ahlee): This is only used for logging which is not currently // implemented. If we want to match the Java code in the future, we'll need // to add toString() to socket.h. - string socketToString() { return string(); } - void closeSocket() { + string socketToString() override { return string(); } + void closeSocket() override { // Ignore the potential Exception returned by close(), as a counterpart // to Java's closeQuietly(). wifi_socket_->close(); } // TODO(ahlee): Double check that the ownership of this is correct when // this is fully implemented. - Ptr getEndpointChannel() { + Ptr getEndpointChannel() override { return new_endpoint_channel_.release(); } diff --git a/cpp/core_v2/BUILD b/cpp/core_v2/BUILD new file mode 100644 index 00000000..12a7a8fe --- /dev/null +++ b/cpp/core_v2/BUILD @@ -0,0 +1,73 @@ +cc_library( + name = "core_v2", + srcs = [ + "core.cc", + ], + hdrs = [ + "core.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":core_types", + "//core_v2/internal", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "core_types", + srcs = [ + "strategy.cc", + ], + hdrs = [ + "listeners.h", + "options.h", + "params.h", + "payload.h", + "status.h", + "strategy.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/types:variant", + ], +) + +cc_test( + name = "core_v2_test", + size = "small", + srcs = [ + "core_test.cc", + "listeners_test.cc", + "payload_test.cc", + "status_test.cc", + "strategy_test.cc", + ], + shard_count = 16, + deps = [ + ":core_types", + ":core_v2", + "//core_v2/internal", + "//core_v2/internal:internal_test", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/time", + "//absl/types:variant", + ], +) diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc new file mode 100644 index 00000000..c7848047 --- /dev/null +++ b/cpp/core_v2/core.cc @@ -0,0 +1,107 @@ +#include "core_v2/core.h" + +#include +#include + +#include "core_v2/options.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +Core::~Core() { + CountDownLatch latch(1); + router_.ClientDisconnecting( + &client_, { + .result_cb = [&latch](Status) { latch.CountDown(); }, + }); + if (!latch.Await(kWaitForDisconnect).result()) { + NEARBY_LOG(FATAL, "Unable to shutdown"); + } +} + +void Core::StartAdvertising(absl::string_view service_id, + ConnectionOptions options, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartAdvertising(&client_, service_id, options, info, callback); +} + +void Core::StopAdvertising(const ResultCallback callback) { + router_.StopAdvertising(&client_, callback); +} + +void Core::StartDiscovery(absl::string_view service_id, + ConnectionOptions options, DiscoveryListener listener, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartDiscovery(&client_, service_id, options, listener, callback); +} + +void Core::StopDiscovery(ResultCallback callback) { + router_.StopDiscovery(&client_, callback); +} + +void Core::RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RequestConnection(&client_, endpoint_id, info, callback); +} + +void Core::AcceptConnection(absl::string_view endpoint_id, + PayloadListener listener, ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.AcceptConnection(&client_, endpoint_id, listener, callback); +} + +void Core::RejectConnection(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RejectConnection(&client_, endpoint_id, callback); +} + +void Core::InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback) { + router_.InitiateBandwidthUpgrade(&client_, endpoint_id, callback); +} + +void Core::SendPayload(absl::Span endpoint_ids, + Payload payload, ResultCallback callback) { + assert(payload.GetType() != Payload::Type::kUnknown); + assert(!endpoint_ids.empty()); + + router_.SendPayload(&client_, endpoint_ids, std::move(payload), callback); +} + +void Core::CancelPayload(std::int64_t payload_id, ResultCallback callback) { + assert(payload_id != 0); + + router_.CancelPayload(&client_, payload_id, callback); +} + +void Core::DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.DisconnectFromEndpoint(&client_, endpoint_id, callback); +} + +void Core::StopAllEndpoints(ResultCallback callback) { + router_.StopAllEndpoints(&client_, callback); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/core.h b/cpp/core_v2/core.h new file mode 100644 index 00000000..60021671 --- /dev/null +++ b/cpp/core_v2/core.h @@ -0,0 +1,208 @@ +#ifndef CORE_V2_CORE_H_ +#define CORE_V2_CORE_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/internal/service_controller_router.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// This class defines the API of the Nearby Connections Core library. +class Core { + public: + explicit Core(std::function factory) + : router_(factory) {} + ~Core(); + Core(Core&&) = default; + Core& operator=(Core&&) = default; + + // Starts advertising an endpoint for a local app. + // + // service_id - An identifier to advertise your app to other endpoints. + // This can be an arbitrary string, so long as it uniquely + // identifies your service. A good default is to use your + // app's package name. + // options - The options for advertising. + // info - Connection parameters: + // > name - A human readable name for this endpoint, to appear on + // other devices. + // > listener - A callback notified when remote endpoints request a + // connection to this endpoint. + // callback - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if advertising started successfully. + // Status::STATUS_ALREADY_ADVERTISING if the app is already advertising. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartAdvertising(absl::string_view service_id, ConnectionOptions options, + ConnectionRequestInfo info, ResultCallback callback); + + // Stops advertising a local endpoint. Should be called after calling + // StartAdvertising, as soon as the application no longer needs to advertise + // itself or goes inactive. Payloads can still be sent to connected + // endpoints after advertising ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopAdvertising(ResultCallback callback); + + // Starts discovery for remote endpoints with the specified service ID. + // + // service_id - The ID for the service to be discovered, as specified in + // the corresponding call to StartAdvertising. + // listener - A callback notified when a remote endpoint is discovered. + // options - The options for discovery. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if discovery started successfully. + // Status::STATUS_ALREADY_DISCOVERING if the app is already + // discovering the specified service. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartDiscovery(absl::string_view service_id, ConnectionOptions options, + DiscoveryListener listener, ResultCallback callback); + + // Stops discovery for remote endpoints, after a previous call to + // StartDiscovery, when the client no longer needs to discover endpoints or + // goes inactive. Payloads can still be sent to connected endpoints after + // discovery ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopDiscovery(ResultCallback callback); + + // Sends a request to connect to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint to which a + // connection request will be sent. Should match the value + // provided in a call to + // DiscoveryListener::endpoint_found_cb() + // info - Connection parameters: + // > name - A human readable name for the local endpoint, to appear on + // the remote endpoint. + // > listener - A callback notified when the remote endpoint sends a + // response to the connection request. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was sent. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already + // has a connection to the specified endpoint. + // Status::STATUS_RADIO_ERROR if we failed to connect because of an + // issue with Bluetooth/WiFi. + // Status::STATUS_ERROR if we failed to connect for any other reason. + void RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, ResultCallback callback); + + // Accepts a connection to a remote endpoint. This method must be called + // before Payloads can be exchanged with the remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated. + // listener - A callback for payloads exchanged with the remote endpoint. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was accepted. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already. + // has a connection to the specified endpoint. + void AcceptConnection(absl::string_view endpoint_id, PayloadListener listener, + ResultCallback callback); + + // Rejects a connection to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK} if the connection request was rejected. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT} if the app already + // has a connection to the specified endpoint. + void RejectConnection(absl::string_view endpoint_id, ResultCallback callback); + + // Sends a Payload to a remote endpoint. Payloads can only be sent to remote + // endpoints once a notice of connection acceptance has been delivered via + // ConnectionListener::onConnectionResult(). + // + // endpoint_ids - Array of remote endpoint identifiers for the to which the + // payload should be sent. + // payload - The Payload to be sent. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OUT_OF_ORDER_API_CALL if the device has not first + // performed advertisement or discovery (to set the Strategy. + // Status::STATUS_ENDPOINT_UNKNOWN if there's no active (or pending) + // connection to the remote endpoint. + // Status::STATUS_OK if none of the above errors occurred. Note that this + // indicates that Nearby Connections will attempt to send the Payload, + // but not that the send has successfully completed yet. Errors might + // still occur during transmission (and at different times for + // different endpoints), and will be delivered via + // PayloadCallback#onPayloadTransferUpdate. + void SendPayload(absl::Span endpoint_ids, Payload payload, + ResultCallback callback); + + // Cancels a Payload currently in-flight to or from remote endpoint(s). + // + // payload_id - The identifier for the Payload to be canceled. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void CancelPayload(std::int64_t payload_id, ResultCallback callback); + + // Disconnects from a remote endpoint. {@link Payload}s can no longer be sent + // to or received from the endpoint after this method is called. + // + // endpoint_id - The identifier for the remote endpoint to disconnect from. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback); + + // Disconnects from, and removes all traces of, all connected and/or + // discovered endpoints. This call is expected to be preceded by a call to + // StopAdvertising or StartDiscovery as needed. After calling + // StopAllEndpoints, no further operations with remote endpoints will be + // possible until a new call to one of StartAdvertising() or StartDiscovery(). + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void StopAllEndpoints(ResultCallback callback); + + // Sends a request to initiate connection bandwidth upgrade. + // + // endpoint_id - The identifier for the remote endpoint which will be + // switching to a higher connection data rate and possibly + // different wireless protocol. On success, calls + // ConnectionListener::bandwidth_changed_cb(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback); + + private: + static constexpr absl::Duration kWaitForDisconnect = absl::Milliseconds(5000); + + ClientProxy client_; + ServiceControllerRouter router_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_CORE_H_ diff --git a/cpp/core_v2/core_test.cc b/cpp/core_v2/core_test.cc new file mode 100644 index 00000000..038383e3 --- /dev/null +++ b/cpp/core_v2/core_test.cc @@ -0,0 +1,44 @@ +#include "core_v2/core.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(CoreTest, ConstructorDestructorWorks) { + MockServiceController mock; + Core core{[&mock]() { return &mock; }}; +} + +TEST(CoreTest, DestructorReportsFatalFailure) { + MockServiceController mock; + ON_CALL(mock, StopDiscovery).WillByDefault([](ClientProxy* client) { + NEARBY_LOG(INFO, "Blocking Endpoint disconnect for 10 sec"); + absl::SleepFor(absl::Milliseconds(10000)); + }); + ASSERT_DEATH( + [&mock]() { + Core core{[&mock]() { return &mock; }}; + EXPECT_CALL(mock, StartDiscovery).Times(1); + EXPECT_CALL(mock, StopAdvertising).Times(1); + core.StartDiscovery("service_id", {.strategy = Strategy::kP2pCluster}, + {}, {.result_cb = [](Status status) { + NEARBY_LOG(INFO, "Discovery status: %d", + static_cast(status.value)); + }}); + }(), + "Unable to shutdown"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD new file mode 100644 index 00000000..e375d8a1 --- /dev/null +++ b/cpp/core_v2/internal/BUILD @@ -0,0 +1,101 @@ +cc_library( + name = "internal", + srcs = [ + "base_endpoint_channel.cc", + "base_pcp_handler.cc", + "ble_advertisement.cc", + "client_proxy.cc", + "encryption_runner.cc", + "endpoint_channel_manager.cc", + "endpoint_manager.cc", + "offline_frames.cc", + "service_controller_router.cc", + "wifi_lan_service_info.cc", + ], + hdrs = [ + "base_endpoint_channel.h", + "base_pcp_handler.h", + "ble_advertisement.h", + "client_proxy.h", + "encryption_runner.h", + "endpoint_channel.h", + "endpoint_channel_manager.h", + "endpoint_manager.h", + "offline_frames.h", + "pcp.h", + "pcp_handler.h", + "service_controller.h", + "service_controller_router.h", + "wifi_lan_service_info.h", + ], + visibility = [ + "//core_v2:__pkg__", + ], + deps = [ + "//core/internal:message_lite", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "internal_test", + testonly = True, + hdrs = [ + "mock_service_controller.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":internal", + "//testing/base/public:gunit", + ], +) + +cc_test( + name = "core_v2_internal_test", + size = "small", + srcs = [ + "base_endpoint_channel_test.cc", + "base_pcp_handler_test.cc", + "ble_advertisement_test.cc", + "client_proxy_test.cc", + "encryption_runner_test.cc", + "endpoint_channel_manager_test.cc", + "endpoint_manager_test.cc", + "offline_frames_test.cc", + "service_controller_router_test.cc", + "wifi_lan_service_info_test.cc", + ], + shard_count = 16, + deps = [ + ":internal", + ":internal_test", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//testing/base/public:gunit", + "//testing/base/public:gunit_main", + "//absl/container:flat_hash_set", + "//absl/synchronization", + "//absl/time", + "//absl/types:span", + ], +) diff --git a/cpp/core_v2/internal/base_endpoint_channel.cc b/cpp/core_v2/internal/base_endpoint_channel.cc new file mode 100644 index 00000000..078224c4 --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.cc @@ -0,0 +1,270 @@ +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +std::int32_t BytesToInt(const ByteArray& bytes) { + const char* int_bytes = bytes.data(); + + std::int32_t result = 0; + result |= (static_cast(int_bytes[0]) & 0x0FF) << 24; + result |= (static_cast(int_bytes[1]) & 0x0FF) << 16; + result |= (static_cast(int_bytes[2]) & 0x0FF) << 8; + result |= (static_cast(int_bytes[3]) & 0x0FF); + + return result; +} + +ByteArray IntToBytes(std::int32_t value) { + char int_bytes[sizeof(std::int32_t)]; + int_bytes[0] = static_cast((value >> 24) & 0x0FF); + int_bytes[1] = static_cast((value >> 16) & 0x0FF); + int_bytes[2] = static_cast((value >> 8) & 0x0FF); + int_bytes[3] = static_cast((value)&0x0FF); + + return ByteArray(int_bytes, sizeof(int_bytes)); +} + +ExceptionOr ReadExactly(InputStream* reader, std::int64_t size) { + ByteArray buffer(size); + std::int64_t current_pos = 0; + + while (current_pos < size) { + ExceptionOr read_bytes = reader->Read(size - current_pos); + if (!read_bytes.ok()) { + return read_bytes; + } + ByteArray result = read_bytes.result(); + + if (result.Empty()) { + return ExceptionOr(Exception::kIo); + } + + buffer.CopyAt(current_pos, result); + current_pos += result.size(); + } + + return ExceptionOr(std::move(buffer)); +} + +ExceptionOr ReadInt(InputStream* reader) { + ExceptionOr read_bytes = ReadExactly(reader, sizeof(std::int32_t)); + if (!read_bytes.ok()) { + return ExceptionOr(read_bytes.exception()); + } + return ExceptionOr(BytesToInt(std::move(read_bytes.result()))); +} + +Exception WriteInt(OutputStream* writer, std::int32_t value) { + return writer->Write(IntToBytes(value)); +} + +} // namespace + +BaseEndpointChannel::BaseEndpointChannel(const std::string& channel_name, + InputStream* reader, + OutputStream* writer) + : channel_name_(channel_name), reader_(reader), writer_(writer) {} + +ExceptionOr BaseEndpointChannel::Read() { + ByteArray result; + { + MutexLock lock(&reader_mutex_); + + ExceptionOr read_int = ReadInt(reader_); + if (!read_int.ok()) { + return ExceptionOr(read_int.exception()); + } + + if (read_int.result() < 0 || read_int.result() > kMaxAllowedReadBytes) { + return ExceptionOr(Exception::kIo); + } + + ExceptionOr read_bytes = ReadExactly(reader_, read_int.result()); + if (!read_bytes.ok()) { + return read_bytes; + } + result = std::move(read_bytes.result()); + } + + // If encryption is enabled, decode the message. + if (IsEncryptionEnabled()) { + MutexLock crypto_lock(&crypto_mutex_); + result = ByteArray(std::move( + *encryption_context_->DecodeMessageFromPeer(std::string(result)))); + if (result.Empty()) { + return ExceptionOr(Exception::kInvalidProtocolBuffer); + } + } + + { + MutexLock lock(&last_read_mutex_); + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + } + return ExceptionOr(result); +} + +Exception BaseEndpointChannel::Write(const ByteArray& data) { + { + MutexLock pause_lock(&is_paused_mutex_); + if (is_paused_) { + BlockUntilUnpaused(); + } + } + + ByteArray encrypted_data; + const ByteArray* data_to_write = &data; + { + MutexLock crypto_lock(&crypto_mutex_); + // If encryption is enabled, encode the message. + if (IsEncryptionEnabled()) { + encrypted_data = ByteArray(std::move( + *encryption_context_->EncodeMessageToPeer(std::string(data)))); + data_to_write = &encrypted_data; + } + } + + { + MutexLock lock(&writer_mutex_); + Exception write_exception = + WriteInt(writer_, static_cast(data_to_write->size())); + if (!write_exception.Ok()) { + return write_exception; + } + + write_exception = writer_->Write(*data_to_write); + if (write_exception.Ok()) { + return write_exception; + } + + Exception flush_exception = writer_->Flush(); + if (!flush_exception.Ok()) { + return flush_exception; + } + } + + return {Exception::kSuccess}; +} + +void BaseEndpointChannel::Close() { + { + // In case channel is paused, resume it first thing. + MutexLock lock(&is_paused_mutex_); + UnblockPausedWriter(); + } + CloseIo(); + CloseImpl(); +} + +void BaseEndpointChannel::CloseIo() { + // Keep this method dedicated to reader and writer handling an nothing else. + { + // Do not take reader_mutex_ here: read may be in progress, and it will + // deadlock. Calling Close() with Read() in progress will terminate the + // IO and Read() will proceed normally (with Exception::kIo). + Exception exception = reader_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } + { + // Do not take writer_mutex_ here: write may be in progress, and it will + // deadlock. Calling Close() with Write() in progress will terminate the + // IO and Write() will proceed normally (with Exception::kIo). + Exception exception = writer_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } +} + +void BaseEndpointChannel::Close( + proto::connections::DisconnectionReason reason) { + Close(); +} + +std::string BaseEndpointChannel::GetType() const { + std::string subtype = IsEncryptionEnabled() ? "ENCRYPTED_" : ""; + + switch (GetMedium()) { + case proto::connections::Medium::BLUETOOTH: + return absl::StrCat(subtype, "BLUETOOTH"); + case proto::connections::Medium::BLE: + return absl::StrCat(subtype, "BLE"); + case proto::connections::Medium::MDNS: + return absl::StrCat(subtype, "MDNS"); + case proto::connections::Medium::WIFI_HOTSPOT: + return absl::StrCat(subtype, "WIFI_HOTSPOT"); + case proto::connections::Medium::WIFI_LAN: + return absl::StrCat(subtype, "WIFI_LAN"); + default: + return "UNKNOWN"; + } +} + +std::string BaseEndpointChannel::GetName() const { return channel_name_; } + +void BaseEndpointChannel::EnableEncryption( + securegcm::D2DConnectionContextV1* encryption_context) { + MutexLock lock(&crypto_mutex_); + encryption_context_ = encryption_context; +} + +bool BaseEndpointChannel::IsPaused() const { + MutexLock lock(&is_paused_mutex_); + return is_paused_; +} + +void BaseEndpointChannel::Pause() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = true; +} + +void BaseEndpointChannel::Resume() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = false; + is_paused_cond_.Notify(); +} + +absl::Time BaseEndpointChannel::GetLastReadTimestamp() const { + MutexLock lock(&last_read_mutex_); + return last_read_timestamp_; +} + +bool BaseEndpointChannel::IsEncryptionEnabled() const { + return encryption_context_ != nullptr; +} + +void BaseEndpointChannel::BlockUntilUnpaused() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + while (is_paused_) { + Exception wait_succeeded = is_paused_cond_.Wait(); + if (!wait_succeeded.Ok()) { + return; + } + } +} + +void BaseEndpointChannel::UnblockPausedWriter() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + is_paused_ = false; + is_paused_cond_.Notify(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_endpoint_channel.h b/cpp/core_v2/internal/base_endpoint_channel.h new file mode 100644 index 00000000..2799e58d --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.h @@ -0,0 +1,113 @@ +#ifndef CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace connections { + +class BaseEndpointChannel : public EndpointChannel { + public: + BaseEndpointChannel(const std::string& channel_name, InputStream* reader, + OutputStream* writer); + ~BaseEndpointChannel() override = default; + + ExceptionOr Read() + ABSL_LOCKS_EXCLUDED(reader_mutex_, crypto_mutex_, + last_read_mutex_) override; + + Exception Write(const ByteArray& data) + ABSL_LOCKS_EXCLUDED(writer_mutex_, crypto_mutex_) override; + + // Closes this EndpointChannel, without tracking the closure in analytics. + void Close() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Closes this EndpointChannel and records the closure with the given reason. + void Close(proto::connections::DisconnectionReason reason) override; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, + // WIFI. + std::string GetType() const override; + + // Returns the name of the EndpointChannel. + std::string GetName() const override; + + // Enables encryption on the EndpointChannel. + // Should be called after connection is accepted by both parties, and + // before entering data phase, where Payloads may be exchanged. + void EnableEncryption(securegcm::D2DConnectionContextV1* context) override; + + // True if the EndpointChannel is currently pausing all writes. + bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Pauses all writes on this EndpointChannel until resume() is called. + void Pause() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + void Resume() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Returns the timestamp (returned by ElapsedRealtime) of the last read from + // this endpoint, or -1 if no reads have occurred. + absl::Time GetLastReadTimestamp() const + ABSL_LOCKS_EXCLUDED(last_read_mutex_) override; + + protected: + virtual void CloseImpl() = 0; + + private: + // Used to sanity check that our frame sizes are reasonable. + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + + bool IsEncryptionEnabled() const; + void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS; + + // We need a separate mutex to pritect read timestamp, because if a read + // blocks on IO, we don't want timestamp read access to block too. + mutable Mutex last_read_mutex_; + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(last_read_mutex_) = + absl::InfinitePast(); + const std::string channel_name_; + + // The reader and writer are synchronized independently since we can't have + // writes waiting on reads that might potentially block forever. + Mutex reader_mutex_; + InputStream* reader_ ABSL_PT_GUARDED_BY(reader_mutex_); + + Mutex writer_mutex_; + OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_); + + // Used by both read and write to protect payload encryption/decryption. + Mutex crypto_mutex_; + // An encryptor/decryptor. May be null. + securegcm::D2DConnectionContextV1* encryption_context_ + ABSL_PT_GUARDED_BY(crypto_mutex_) = nullptr; + + mutable Mutex is_paused_mutex_; + ConditionVariable is_paused_cond_{&is_paused_mutex_}; + // If true, writes should block until this has been set to false. + bool is_paused_ ABSL_GUARDED_BY(is_paused_mutex_) = false; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/base_endpoint_channel_test.cc b/cpp/core_v2/internal/base_endpoint_channel_test.cc new file mode 100644 index 00000000..c96e8f4a --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel_test.cc @@ -0,0 +1,342 @@ +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "core_v2/internal/encryption_runner.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/single_thread_executor.h" +#include "proto/connections_enums.pb.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; + +class TestEndpointChannel : public BaseEndpointChannel { + public: + explicit TestEndpointChannel(InputStream* input, OutputStream* output) + : BaseEndpointChannel("channel", input, output) {} + + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, CloseImpl, (), (override)); +}; + +std::function MakeDataPump( + std::string label, InputStream* input, OutputStream* output, + std::function monitor = nullptr) { + return [label, input, output, monitor]() { + NEARBY_LOG(INFO, "streaming data thorough '%s'", label.c_str()); + while (true) { + auto read_response = input->Read(Pipe::kChunkSize); + if (!read_response.ok()) { + NEARBY_LOG(INFO, "Peer reader closed on '%s'", label.c_str()); + output->Close(); + break; + } + if (monitor) { + monitor(read_response.result()); + } + auto write_response = output->Write(read_response.result()); + if (write_response.Raised()) { + NEARBY_LOG(INFO, "Peer writer closed on '%s'", label.c_str()); + input->Close(); + break; + } + } + NEARBY_LOG(INFO, "streaming terminated on '%s'", label.c_str()); + }; +} + +std::function MakeDataMonitor(const std::string& label, + std::string* capture, + absl::Mutex* mutex) { + return [label, capture, mutex](const ByteArray& input) mutable { + std::string s = std::string(input); + { + absl::MutexLock lock(mutex); + *capture += s; + } + NEARBY_LOG(INFO, "source='%s'; message='%s'", label.c_str(), s.c_str()); + }; +} + +std::pair, + std::unique_ptr> +DoDhKeyExchange(BaseEndpointChannel* channel_a, + BaseEndpointChannel* channel_b) { + std::unique_ptr context_a; + std::unique_ptr context_b; + EncryptionRunner crypto_a; + EncryptionRunner crypto_b; + ClientProxy proxy_a; + ClientProxy proxy_b; + CountDownLatch latch(2); + crypto_a.StartClient( + &proxy_a, "endpoint_id", channel_a, + { + .on_success_cb = + [&latch, &context_a]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-A side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_a = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-A side key negotiation failed"); + latch.CountDown(); + }, + }); + crypto_b.StartServer( + &proxy_b, "endpoint_id", channel_b, + { + .on_success_cb = + [&latch, &context_b]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-B side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_b = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-B side key negotiation failed"); + latch.CountDown(); + }, + }); + EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); + return std::make_pair(std::move(context_a), std::move(context_b)); +} + +TEST(BaseEndpointChannelTest, ConstructorDestructorWorks) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); +} + +TEST(BaseEndpointChannelTest, ReadWrite) { + // Direct not-encrypted IO. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + EXPECT_EQ(rx_message, tx_message); +} + +TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) { + // Not encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { return Medium::BLE; }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { return Medium::BLE; }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + EXPECT_EQ(channel_a.GetType(), "BLE"); + EXPECT_EQ(channel_b.GetType(), "BLE"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) != std::string::npos || + capture_b.find(message) != std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) { + // Encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + // Run DH key exchange; setup encryption contexts for channels. + auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b); + ASSERT_NE(context_a, nullptr); + ASSERT_NE(context_b, nullptr); + channel_a.EnableEncryption(context_a.get()); + channel_b.EnableEncryption(context_b.get()); + + EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH"); + EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) == std::string::npos && + capture_b.find(message) == std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) { + // Setup test communication environment. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + + EXPECT_EQ(channel_a.GetType(), "WIFI_LAN"); + EXPECT_EQ(channel_b.GetType(), "WIFI_LAN"); + + // Start data transfer + ByteArray tx_message{"data message"}; + ByteArray more_message{"more data"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Pause and make sure reader blocks. + MultiThreadExecutor pause_resume_executor(2); + channel_a.Pause(); + pause_resume_executor.Execute([&channel_a, &more_message](){ + // Write will block until channel is resumed, or closed. + EXPECT_TRUE(channel_a.Write(more_message).Ok()); + }); + std::atomic_bool done = false; + ByteArray read_more; + pause_resume_executor.Execute([&channel_b, &read_more, &done](){ + // Read will block until channel is resumed, or closed. + auto response = channel_b.Read(); + EXPECT_TRUE(response.ok()); + read_more = std::move(response.result()); + done = true; + }); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(read_more.Empty()); + + // Resume; verify that data transfer comepleted. + channel_a.Resume(); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(done); + EXPECT_EQ(read_more, more_message); + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); + + // Close the output stream before trying to read from the input. + output_stream.Close(); + + // Trying to read should fail gracefully with an IO error. + ExceptionOr read_data = test_channel.Read(); + + ASSERT_FALSE(read_data.ok()); + ASSERT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc new file mode 100644 index 00000000..99482b77 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -0,0 +1,143 @@ +#include "core_v2/internal/base_pcp_handler.h" + +#include +#include +#include +#include +#include + +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager) + : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} + +BasePcpHandler::~BasePcpHandler() { + // Unregister ourselves from the FrameProcessors. + endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE, + handle_); + + // Stop all the ongoing Runnables (as gracefully as possible). + serial_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +Status BasePcpHandler::StartAdvertising(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) { + Future response; + RunOnPcpHandlerThread( + [this, client, &service_id, &info, &options, &response]() { + auto result = StartAdvertisingImpl(client, service_id, + client->GenerateLocalEndpointId(), + info.name, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartAdvertising(", info.name, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopAdvertising(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopAdvertisingImpl(client); + client->StoppedAdvertising(); + advertising_options_.Clear(); + latch.CountDown(); + }); + WaitForLatch("StopAdvertising", &latch); +} + +Status BasePcpHandler::StartDiscovery(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) { + Future response; + RunOnPcpHandlerThread( + [this, client, service_id, options, listener, &response]() { + // Ask the implementation to attempt to start discovery. + auto result = StartDiscoveryImpl(client, service_id, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as discovering and clear + // out any old endpoints we had discovered. + discovery_options_ = options; + discovered_endpoints_.clear(); + client->StartedDiscovery(service_id, GetStrategy(), listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartDiscovery(", service_id, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopDiscovery(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopDiscoveryImpl(client); + client->StoppedDiscovery(); + discovery_options_.Clear(); + latch.CountDown(); + }); + + WaitForLatch("stopDiscovery", &latch); +} + +void BasePcpHandler::WaitForLatch(const string& method_name, + CountDownLatch* latch) { + Exception await_exception = latch->Await(); + if (!await_exception.Ok()) { + if (await_exception.Raised(Exception::kTimeout)) { + NEARBY_LOG(INFO, "Blocked in %s", method_name.c_str()); + } + } +} + +Status BasePcpHandler::WaitForResult(const string& method_name, + std::int64_t client_id, + Future* future) { + if (!future) { + NEARBY_LOG(INFO, "No future to wait for; return with error"); + return {Status::kError}; + } + NEARBY_LOG(INFO, "waiting for future to complete"); + ExceptionOr result = future->Get(); + if (!result.ok()) { + NEARBY_LOG(INFO, "Future completed with exception: %d", result.exception()); + return {Status::kError}; + } + NEARBY_LOG(INFO, "Future completed with status: %d", result.result().value); + return result.result(); +} + +void BasePcpHandler::RunOnPcpHandlerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h new file mode 100644 index 00000000..e4df32f3 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -0,0 +1,323 @@ +#ifndef CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ + +#include +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/internal/pcp_handler.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Define a class that supports move operation for pointers using std::swap. +// It replicates std::unique_ptr<> behavior, but it does not own the pointer, +// so it does not attempt destroy it. +// This approach was recommended during code review, as a better alternative to +// reuse of std::unique_ptr<> with custom no-op deleter, for the sake of +// readability. +template +class Swapper { + public: + Swapper(T* pointer) : pointer_(pointer) {} // NOLINT. + Swapper(Swapper&& other) { *this = std::move(other); } + Swapper& operator=(Swapper&& other) { + std::swap(pointer_, other.pointer_); + return *this; + } + T* operator->() const { return pointer_; } + T& operator*() { return *pointer_; } + operator T*() { return pointer_; } // NOLINT. + T* get() const { return pointer_; } + void reset() { pointer_ = nullptr; } + + private: + T* pointer_ = nullptr; +}; + +template +Swapper MakeSwapper(T* value) { + return Swapper(value); +} + +// A base implementation of the PcpHandler interface that takes care of all +// bookkeeping and handshake protocols that are common across all PcpHandler +// implementations -- thus, every concrete PcpHandler implementation must extend +// this class, so that they can focus exclusively on the medium-specific +// operations. +class BasePcpHandler : public PcpHandler, + public EndpointManager::FrameProcessor { + public: + using FrameProcessor = EndpointManager::FrameProcessor; + + // TODO(tracyzhou): Add SecureRandom. + BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager); + ~BasePcpHandler() override; + BasePcpHandler(BasePcpHandler&&) = delete; + BasePcpHandler& operator=(BasePcpHandler&&) = delete; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See + // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 + Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) override; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopAdvertising(ClientProxy* client_proxy) override; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) override; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopDiscovery(ClientProxy* client_proxy) override; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // If either party does call it, connection will terminate. + // Update state in ClientProxy. + Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) override { + return Status{Status::kError}; + } + + // @EndpointManagerReaderThread + void OnIncomingFrame(const OfflineFrame& frame, + const std::string& endpoint_id, ClientProxy* client, + proto::connections::Medium medium) override {} + + // Called when an endpoint disconnects while we're waiting for both sides to + // approve/reject the connection. + // @EndpointManagerThread + void OnEndpointDisconnect(ClientProxy* client_proxy, + const std::string& endpoint_id, + CountDownLatch* barrier) override {} + + protected: + // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). + struct StartOperationResult { + Status status; + // If success, the mediums on which we are now advertising/discovering, for + // analytics. + std::vector mediums; + }; + + // Represents an endpoint that we've discovered. Typically, the implementation + // will know how to connect to this endpoint if asked. (eg. It holds on to a + // BluetoothDevice) + class DiscoveredEndpoint { + public: + virtual ~DiscoveredEndpoint() = default; + + virtual std::string GetEndpointId() const = 0; + virtual std::string GetEndpointName() const = 0; + virtual std::string GetServiceId() const = 0; + virtual proto::connections::Medium GetMedium() const = 0; + }; + + struct ConnectImplResult { + proto::connections::Medium medium = + proto::connections::Medium::UNKNOWN_MEDIUM; + Status status = {Status::kError}; + std::unique_ptr endpoint_channel; + }; + + void RunOnPcpHandlerThread(Runnable runnable); + + ConnectionOptions GetConnectionOptions() const; + + // @PcpHandlerThread + void OnEndpointFound(ClientProxy* client_proxy, + std::unique_ptr endpoint); + + // @PcpHandlerThread + void OnEndpointLost(ClientProxy* client_proxy, + const DiscoveredEndpoint* endpoint); + + Exception OnIncomingConnection( + ClientProxy* client_proxy, const std::string& remote_device_name, + std::unique_ptr endpoint_channel, + proto::connections::Medium medium); // throws Exception::IO + + // @PcpHandlerThread + virtual StartOperationResult StartAdvertisingImpl( + ClientProxy* client_proxy, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopAdvertisingImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual StartOperationResult StartDiscoveryImpl( + ClientProxy* client_proxy, const std::string& service_id, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopDiscoveryImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual ConnectImplResult ConnectImpl(ClientProxy* client_proxy, + DiscoveredEndpoint* endpoint) = 0; + + virtual std::vector + GetConnectionMediumsByPriority() = 0; + virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0; + + EndpointManager* endpoint_manager_; + EndpointChannelManager* channel_manager_; + + private: + static Exception WriteConnectionRequestFrame( + EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, + const std::string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums); + + static constexpr absl::Duration kConnectionRequestReadTimeout = + absl::Seconds(2); + static constexpr absl::Duration kRejectedConnectionCloseDelay = + absl::Seconds(2); + + void OnConnectionResponse(ClientProxy* client_proxy, + const std::string& endpoint_id, + const OfflineFrame& frame); + + // Returns true if the new endpoint is preferred over the old endpoint. + bool IsPreferred(const BasePcpHandler::DiscoveredEndpoint& new_endpoint, + const BasePcpHandler::DiscoveredEndpoint& old_endpoint); + + // Called when an incoming connection has been accepted by both sides. + // + // @param client_proxy The client + // @param endpoint_id The id of the remote device + // @param supported_mediums The mediums supported by the remote device. + // Empty + // for outgoing connections and older devices that don't report their + // supported mediums. + void InitiateBandwidthUpgrade( + ClientProxy* client_proxy, const std::string& endpoint_id, + const std::vector& supported_mediums); + + // Returns the optimal medium supported by both devices. + proto::connections::Medium ChooseBestUpgradeMedium( + const std::vector& supported_mediums); + + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, + EndpointChannel* channel, + Status status, + Future* result); + void ProcessPreConnectionResultFailure(ClientProxy* client_proxy, + const std::string& endpoint_id); + DiscoveredEndpoint* GetDiscoveredEndpoint(const std::string& endpoint_id); + + // Called when either side accepts/rejects the connection, but only takes + // effect after both have accepted or one side has rejected. + // + // NOTE: We also take in a 'can_close_immediately' variable. This is because + // any writes in transit are dropped when we close. To avoid having a reject + // write being dropped (which causes the other side to report + // onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our + // close. If the other side behaves properly, we shouldn't even see the + // delay (because they will also close the connection). + void EvaluateConnectionResult(ClientProxy* client_proxy, + const std::string& endpoint_id, + bool can_close_immediately); + + ExceptionOr ReadConnectionRequestFrame( + EndpointChannel* channel); + + void WaitForLatch(const std::string& method_name, CountDownLatch* latch); + Status WaitForResult(const std::string& method_name, std::int64_t client_id, + Future* future); + + AtomicReference bandwidth_upgrade_medium_{ + proto::connections::Medium::UNKNOWN_MEDIUM}; + ScheduledExecutor alarm_executor_; + SingleThreadExecutor serial_executor_; + + // A map of endpoint id -> DiscoveredEndpoint. + absl::flat_hash_map> + discovered_endpoints_; + // A map of endpoint id -> alarm. These alarms delay closing the + // EndpointChannel to give the other side enough time to read the rejection + // message. It's expected that the other side will close the connection + // after reading the message (in which case, this alarm should be cancelled + // as it's no longer needed), but this alarm is the fallback in case that + // doesn't happen. + absl::flat_hash_map pending_alarms_; + + // The active ClientProxy's advertising constraints. Empty() + // returns true if the client hasn't started advertising false otherwise. + // Note: this is not cleared when the client stops advertising because it + // might still be useful downstream of advertising (eg: establishing + // connections, performing bandwidth upgrades, etc.) + ConnectionOptions advertising_options_; + // The active ClientProxy's connection lifecycle listener. Non-null while + // advertising. + ConnectionListener advertising_listener_; + + // The active ClientProxy's discovery constraints. Null if the client + // hasn't started discovering. Note: this is not cleared when the client + // stops discovering because it might still be useful downstream of + // discovery (eg: connection speed, etc.) + ConnectionOptions discovery_options_; + Prng prng_; + EncryptionRunner encryption_runner_; + EndpointManager::FrameProcessor::Handle handle_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc new file mode 100644 index 00000000..756ea76b --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -0,0 +1,287 @@ +#include "core_v2/internal/base_pcp_handler.h" + +#include + +#include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/offline_frames.h" +#include "core_v2/listeners.h" +#include "core_v2/params.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; +using ::testing::_; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public BaseEndpointChannel { + public: + explicit MockEndpointChannel(Pipe* reader, Pipe* writer) + : BaseEndpointChannel("channel", &reader->GetInputStream(), + &writer->GetOutputStream()) {} + + ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } + Exception DoWrite(const ByteArray& data) { + return BaseEndpointChannel::Write(data); + } + absl::Time DoGetLastReadTimestamp() { + return BaseEndpointChannel::GetLastReadTimestamp(); + } + + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, CloseImpl, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); +}; + +class MockPcpHandler : public BasePcpHandler { + public: + MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) + : BasePcpHandler(em, ecm) {} + + // Expose protected inner types of a base type for mocking. + using BasePcpHandler::ConnectImplResult; + using BasePcpHandler::DiscoveredEndpoint; + using BasePcpHandler::StartOperationResult; + + MOCK_METHOD(Strategy, GetStrategy, (), (override)); + MOCK_METHOD(Pcp, GetPcp, (), (override)); + + MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, + (ClientProxy * client, const string& service_id, + const string& local_endpoint_id, + const string& local_endpoint_name, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); + MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, + (ClientProxy * client, const string& service_id, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); + MOCK_METHOD(ConnectImplResult, ConnectImpl, + (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); + MOCK_METHOD(std::vector, + GetConnectionMediumsByPriority, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), + (override)); + + // Mock adapters for protected non-virtual methods of a base class. + void OnEndpointFound(ClientProxy* client, + std::unique_ptr endpoint) { + BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); + } + void OnEndpointLost(ClientProxy* client, DiscoveredEndpoint* endpoint) { + BasePcpHandler::OnEndpointLost(client, endpoint); + } +}; + +class MockDiscoveredEndpoint final : public MockPcpHandler::DiscoveredEndpoint { + public: + MOCK_METHOD(std::string, GetEndpointId, (), (const override)); + MOCK_METHOD(std::string, GetEndpointName, (), (const override)); + MOCK_METHOD(std::string, GetServiceId, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); +}; + +class BasePcpHandlerTest : public ::testing::Test { + protected: + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + StrictMock< + MockFunction> + endpoint_distance_changed_cb; + }; + + void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + ConnectionRequestInfo info{ + .name = "remote_endpoint_name", + .listener = connection_listener_, + }; + EXPECT_CALL(*pcp_handler, + StartAdvertisingImpl(client, service_id, _, info.name, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, + discovery_listener_), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsDiscovering()); + } + + std::pair, + std::unique_ptr> + SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT + auto channel_a = std::make_unique(&pipe_b, &pipe_a); + auto channel_b = std::make_unique(&pipe_a, &pipe_b); + // On initiator (A) side, we drop the first write, since this is a + // connection establishment packet, and we don't have the peer entity, just + // the peer channel. The rest of the exchange must happen for the benefit of + // DH key exchange. + EXPECT_CALL(*channel_a, Read()) + .WillRepeatedly(Invoke( + [channel = channel_a.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_a, Write(_)) + .WillOnce(Return(Exception{Exception::kSuccess})) + .WillRepeatedly( + Invoke([channel = channel_a.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_a, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_a, IsPaused) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_b, Read()) + .WillRepeatedly(Invoke( + [channel = channel_b.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_b, Write(_)) + .WillRepeatedly( + Invoke([channel = channel_b.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_b, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_b, IsPaused) + .WillRepeatedly(Return(false)); + return std::make_pair(std::move(channel_a), std::move(channel_b)); + } + + Pipe pipe_a_; + Pipe pipe_b_; + MockConnectionListener mock_connection_listener_; + MockDiscoveryListener mock_discovery_listener_; + ConnectionListener connection_listener_{ + .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_connection_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = + mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = + mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), + .endpoint_distance_changed_cb = + mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), + }; +}; + +TEST_F(BasePcpHandlerTest, ConstructorDestructorWorks) { + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + SUCCEED(); +} + +TEST_F(BasePcpHandlerTest, StartAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopAdvertisingImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsAdvertising()); + pcp_handler->StopAdvertising(client.get()); + EXPECT_FALSE(client->IsAdvertising()); +} + +TEST_F(BasePcpHandlerTest, StartDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopDiscoveryImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsDiscovering()); + pcp_handler->StopDiscovery(client.get()); + EXPECT_FALSE(client->IsDiscovering()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc new file mode 100644 index 00000000..af266605 --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -0,0 +1,222 @@ +#include "core_v2/internal/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { + +BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, + const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address) { + if (version != Version::kV1 || + service_id_hash.size() != kServiceIdHashLength || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + endpoint_name.length() > kMaxEndpointNameLength) { + return; + } + + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; + endpoint_name_ = endpoint_name; + if (!BluetoothMacAddressHexStringToBytes(bluetooth_mac_address).Empty()) { + bluetooth_mac_address_ = bluetooth_mac_address; + } +} + +BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Start reading the bytes. + auto* ble_advertisement_bytes_read_ptr = ble_advertisement_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV1) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: unsupported Version %d", + version_); + return; + } + + pcp_ = static_cast(*ble_advertisement_bytes_read_ptr & kPcpBitmask); + ble_advertisement_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: { + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength); + ble_advertisement_bytes_read_ptr += kServiceIdHashLength; + + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(ble_advertisement_bytes_read_ptr, kEndpointIdLength); + ble_advertisement_bytes_read_ptr += kEndpointIdLength; + + // The next 8 bits are the length of the endpoint name. + auto expected_endpoint_name_length = static_cast( + *ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask); + ble_advertisement_bytes_read_ptr++; + + // The next x bits are the endpoint name. (Max length is 131 bytes). + // Check that the stated endpoint_name_length is the same as what we + // received (based off of the length of ble_advertisement_bytes). + auto actual_endpoint_name_length = + ComputeEndpointNameLength(ble_advertisement_bytes); + if (actual_endpoint_name_length < expected_endpoint_name_length) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisement: expected endpointName to " + "be %d bytes, got %d bytes", + expected_endpoint_name_length, actual_endpoint_name_length); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string(ble_advertisement_bytes_read_ptr, + expected_endpoint_name_length); + ble_advertisement_bytes_read_ptr += expected_endpoint_name_length; + + // The next 48 bits are the bluetooth mac address. + auto bluetooth_mac_address_bytes = ByteArray( + ble_advertisement_bytes_read_ptr, kBluetoothMacAddressLength); + // If the Bluetooth MAC Address bytes are unset or invalid, leave the + // string empty. Otherwise, convert it to the proper colon delimited + // format. + if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) { + bluetooth_mac_address_ = + HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); + } + break; + } + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d", + pcp_); + break; + } +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_pcp_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the Pcp. + version_and_pcp_byte |= static_cast(pcp_) & kPcpBitmask; + out.reserve(1 + service_id_hash_.size() + kEndpointIdLength + 1 + + endpoint_name_.size() + kBluetoothMacAddressLength); + out.append(1, version_and_pcp_byte); + out.append(std::string(service_id_hash_)); + out.append(endpoint_id_); + out.append(1, endpoint_name_.size()); + out.append(endpoint_name_); + // The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is + // invalid or empty, we get back a null byte array. + auto bluetooth_mac_address_bytes( + BluetoothMacAddressHexStringToBytes(bluetooth_mac_address_)); + if (!bluetooth_mac_address_bytes.Empty()) { + out.append(bluetooth_mac_address_bytes.data(), kBluetoothMacAddressLength); + } + + return ByteArray(std::move(out)); +} + +std::uint32_t BleAdvertisement::ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const { + std::string bt_mac_address(bluetooth_mac_address); + + // Remove the colon delimiters. + bt_mac_address.erase( + std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'), + bt_mac_address.end()); + + // If the bluetooth mac address is invalid (wrong size), return a null byte + // array. + if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) { + return ByteArray(); + } + + // Convert to bytes. If MAC Address bytes are unset, return a null byte array. + auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address)); + auto bt_mac_address_bytes = + ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size()); + if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) { + return ByteArray(); + } + return bt_mac_address_bytes; +} + +std::string BleAdvertisement::HexBytesToColonDelimitedString( + const ByteArray& hex_bytes) const { + // Convert the hex bytes to a string. + std::string colon_delimited_string( + absl::BytesToHexString(std::string(hex_bytes.data(), hex_bytes.size()))); + absl::AsciiStrToUpper(&colon_delimited_string); + + // Insert the colons. + for (int i = colon_delimited_string.length() - 2; i > 0; i -= 2) { + colon_delimited_string.insert(i, ":"); + } + return colon_delimited_string; +} + +bool BleAdvertisement::IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const { + for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) { + if (bluetooth_mac_address_bytes.data()[i] != 0) { + return false; + } + } + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h new file mode 100644 index 00000000..2a86082e --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -0,0 +1,90 @@ +#ifndef CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Connections Ble Advertisement used in +// Advertising + Discovery. +// +//

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

See go/connections-ble-advertisement for more information. +class BleAdvertisement { + public: + // Versions of the BleAdvertisement. + enum class Version { + kUndefined = 0, + kV1 = 1, + // Version is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + static constexpr int kVersionAndPcpLength = 1; + // Should be defined as EndpointManager::kEndpointIdLength, but that + // involves making BleAdvertisement templatized on Platform just for + // that one little thing, so forget it (at least for now). + static constexpr int kEndpointIdLength = 4; + static constexpr int kEndpointNameSizeLength = 1; + static constexpr int kBluetoothMacAddressLength = 6; + static constexpr int kMinAdvertisementLength = + kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + + kEndpointNameSizeLength + kBluetoothMacAddressLength; + static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kEndpointNameLengthBitmask = 0x0FF; + + BleAdvertisement() = default; + BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address); + explicit BleAdvertisement(const ByteArray& ble_advertisement_bytes); + ~BleAdvertisement() = default; + + BleAdvertisement(const BleAdvertisement&) = default; + BleAdvertisement& operator=(const BleAdvertisement&) = default; + BleAdvertisement(BleAdvertisement&&) = default; + BleAdvertisement& operator=(BleAdvertisement&&) = default; + + explicit operator ByteArray() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline ByteArray GetServiceIdHash() const{ return service_id_hash_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline std::string GetBluetoothMacAddress() const { + return bluetooth_mac_address_; + } + + private: + std::uint32_t ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const; + ByteArray BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const; + std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const; + bool IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const; + + Version version_ = Version::kUndefined; + Pcp pcp_ = Pcp::kUnknown; + ByteArray service_id_hash_; + std::string endpoint_id_; + std::string endpoint_name_; + std::string bluetooth_mac_address_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc new file mode 100644 index 00000000..9ff3ffea --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -0,0 +1,258 @@ +#include "core_v2/internal/ble_advertisement.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +const char kEndPointID[] = "AB12"; +const char kEndpointName[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +const char kBluetoothMacAddress[] = "00:00:E6:88:64:13"; + +TEST(BleAdvertisementTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + empty_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(empty_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { + std::string emoji_endpoint_name("\u0001F450 \u0001F450"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + emoji_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(emoji_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { + std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, + 'x'); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + long_endpoint_name, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(bad_version, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, bad_pcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { + std::string empty_bluetooth_mac_address = ""; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, empty_bluetooth_mac_address); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { + std::string bad_bluetooth_mac_address = "022:00"; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, bad_bluetooth_mac_address); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty()); +} + +TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(org_ble_advertisement); + + auto ble_advertisement = BleAdvertisement(ble_advertisement_bytes); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +// Bytes at the end should be ignored so that they can be used as reserve bytes +// in the future. +TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Add bytes to the end of the valid Ble advertisement. + auto long_ble_advertisement_bytes = + ByteArray(BleAdvertisement::kMinAdvertisementLength + 1000); + ASSERT_LE(ble_advertisement_bytes.size(), + long_ble_advertisement_bytes.size()); + memcpy(long_ble_advertisement_bytes.data(), + ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + + auto long_ble_advertisement = BleAdvertisement(long_ble_advertisement_bytes); + auto is_valid = long_ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, long_ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, long_ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, + long_ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + auto ble_advertisement = BleAdvertisement(ByteArray()); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Shorten the valid Ble Advertisement. + auto short_ble_advertisement_bytes( + ByteArray(ble_advertisement_bytes.data(), + BleAdvertisement::kMinAdvertisementLength - 1)); + + auto short_ble_advertisement = + BleAdvertisement(short_ble_advertisement_bytes); + auto is_valid = short_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, + ConstructionFromByesWithWrongEndpointNameLengthFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Corrupt the EndpointNameLength bits. + std::string corrupt_ble_advertisement_string(ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + corrupt_ble_advertisement_string[8] ^= 0x0FF; + auto corrupt_ble_advertisement_bytes = + ByteArray(corrupt_ble_advertisement_string); + + auto corrupt_ble_advertisement = + BleAdvertisement(corrupt_ble_advertisement_bytes); + auto is_valid = corrupt_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc new file mode 100644 index 00000000..aaa67dba --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.cc @@ -0,0 +1,461 @@ +#include "core_v2/internal/client_proxy.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +ClientProxy::ClientProxy() : client_id_(Prng().NextInt64()) {} + +ClientProxy::~ClientProxy() { Reset(); } + +std::int64_t ClientProxy::GetClientId() const { return client_id_; } + +std::string ClientProxy::GenerateLocalEndpointId() { + // 1) Concatenate the DeviceID with this ClientID. + // 2) Compute a hash of that concatenation. + // 3) Base64-encode that hash, to make it human-readable. + // 4) Use only the first 4 bytes of that Base64 encoding. + ByteArray id_hash(Crypto::Sha256( + absl::StrCat(api::ImplementationPlatform::GetDeviceId(), GetClientId()))); + + return Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength); +} + +void ClientProxy::Reset() { + MutexLock lock(&mutex_); + + StoppedAdvertising(); + StoppedDiscovery(); + RemoveAllEndpoints(); +} + +void ClientProxy::StartedAdvertising( + const std::string& service_id, Strategy strategy, + const ConnectionListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + advertising_info_ = {service_id, listener}; +} + +void ClientProxy::StoppedAdvertising() { + MutexLock lock(&mutex_); + + if (IsAdvertising()) { + advertising_info_.Clear(); + } +} + +bool ClientProxy::IsAdvertising() const { + MutexLock lock(&mutex_); + + return !advertising_info_.IsEmpty(); +} + +std::string ClientProxy::GetAdvertisingServiceId() const { + MutexLock lock(&mutex_); + return advertising_info_.service_id; +} + +void ClientProxy::StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + discovery_info_ = DiscoveryInfo{service_id, listener}; +} + +void ClientProxy::StoppedDiscovery() { + MutexLock lock(&mutex_); + + if (IsDiscovering()) { + discovered_endpoint_ids_.clear(); + discovery_info_.Clear(); + } +} + +bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const { + MutexLock lock(&mutex_); + + return IsDiscovering() && service_id == discovery_info_.service_id; +} + +bool ClientProxy::IsDiscovering() const { + MutexLock lock(&mutex_); + + return !discovery_info_.IsEmpty(); +} + +std::string ClientProxy::GetDiscoveryServiceId() const { + MutexLock lock(&mutex_); + + return discovery_info_.service_id; +} + +void ClientProxy::OnEndpointFound(const std::string& service_id, + const std::string& endpoint_id, + const std::string& endpoint_name, + proto::connections::Medium medium) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + if (discovered_endpoint_ids_.count(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + discovered_endpoint_ids_.insert(endpoint_id); + discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_name, + service_id); +} + +void ClientProxy::OnEndpointLost(const std::string& service_id, + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + const auto it = discovered_endpoint_ids_.find(endpoint_id); + if (it == discovered_endpoint_ids_.end()) return; + discovered_endpoint_ids_.erase(it); + discovery_info_.listener.endpoint_lost_cb(endpoint_id); +} + +void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener) { + MutexLock lock(&mutex_); + + // Whether this is incoming or outgoing, the local and remote endpoints both + // still need to accept this connection, so set its establishment status to + // PENDING. + auto result = connections_.emplace( + endpoint_id, Connection{ + .is_incoming = info.is_incoming_connection, + .connection_listener = listener, + }); + // Instead of using structured binding which is nice, but banned + // (can not use c++17 features, until chromium does) we unpack manually. + auto& pair_iter = result.first; + bool& inserted = result.second; + DCHECK(inserted); + const Connection& item = pair_iter->second; + // Notify the client. + // + // Note: we allow devices to connect to an advertiser even after it stops + // advertising, so no need to check IsAdvertising() here. + item.connection_listener.initiated_cb(endpoint_id, info); +} + +void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + // Notify the client. + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.accepted_cb(endpoint_id); + item->status = Connection::kConnected; + } +} + +void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, + const Status& status) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + NEARBY_LOG(INFO, "ClientProxy [Rejected]: no pending connection; id=%s", + endpoint_id.c_str()); + return; + } + + // Notify the client. + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.rejected_cb(endpoint_id, status); + OnDisconnected(endpoint_id, false /* notify */); + } +} + +void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id, + std::int32_t quality) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.bandwidth_changed_cb(endpoint_id, quality); + } +} + +void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + if (notify) { + item->connection_listener.disconnected_cb({endpoint_id}); + } + connections_.erase(endpoint_id); + } +} + +bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status == status; + } + return false; +} + +bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const { + return ConnectionStatusMatches(endpoint_id, Connection::kConnected); +} + +std::vector ClientProxy::GetMatchingEndpoints( + std::function pred) const { + MutexLock lock(&mutex_); + + std::vector connected_endpoints; + + for (const auto& pair : connections_) { + const auto& endpoint_id = pair.first; + const auto& connection = pair.second; + if (pred(connection)) { + connected_endpoints.push_back(endpoint_id); + } + } + return connected_endpoints; +} + +std::vector ClientProxy::GetPendingConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status != Connection::kConnected; + }); +} + +std::vector ClientProxy::GetConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected; + }); +} + +std::int32_t ClientProxy::GetNumOutgoingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + !connection.is_incoming; + }) + .size(); +} + +std::int32_t ClientProxy::GetNumIncomingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + connection.is_incoming; + }) + .size(); +} + +bool ClientProxy::HasPendingConnectionToEndpoint( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status != Connection::kConnected; + } + return false; +} + +bool ClientProxy::HasLocalEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointAccepted | + Connection::kLocalEndpointRejected)); +} + +bool ClientProxy::HasRemoteEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kRemoteEndpointAccepted | + Connection::kRemoteEndpointRejected)); +} + +void ClientProxy::LocalEndpointAcceptedConnection( + const std::string& endpoint_id, const PayloadListener& listener) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointAccepted); + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener = listener; + } +} + +void ClientProxy::LocalEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointRejected); +} + +void ClientProxy::RemoteEndpointAcceptedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::RemoteEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointRejected); +} + +bool ClientProxy::IsConnectionAccepted(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains(endpoint_id, + Connection::kLocalEndpointAccepted) && + ConnectionStatusesContains(endpoint_id, + Connection::kRemoteEndpointAccepted); +} + +bool ClientProxy::IsConnectionRejected(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointRejected | + Connection::kRemoteEndpointRejected)); +} + +bool ClientProxy::LocalConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kLocalEndpointAccepted); +} + +bool ClientProxy::RemoteConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::OnPayload(const std::string& endpoint_id, Payload payload) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_cb(endpoint_id, std::move(payload)); + } + } +} + +const ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) const { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +void ClientProxy::OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_progress_cb(endpoint_id, info); + } + } +} + +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() == rhs.GetClientId(); +} + +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() < rhs.GetClientId(); +} + +void ClientProxy::RemoveAllEndpoints() { + MutexLock lock(&mutex_); + + // Note: we may want to notify the client of onDisconnected() for each + // endpoint, in the case when this is called from stopAllEndpoints(). For now, + // just remove without notifying. + connections_.clear(); +} + +bool ClientProxy::ConnectionStatusesContains( + const std::string& endpoint_id, Connection::Status status_to_match) const { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return (item->status & status_to_match) != 0; + } + return false; +} + +void ClientProxy::AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->status = + static_cast(item->status | status_to_append); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h new file mode 100644 index 00000000..a1013e0c --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.h @@ -0,0 +1,217 @@ +#ifndef CORE_V2_INTERNAL_CLIENT_PROXY_H_ +#define CORE_V2_INTERNAL_CLIENT_PROXY_H_ + +#include +#include +#include + +#include "core_v2/listeners.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "proto/connections_enums.pb.h" +// Prefer using absl:: versions of a set and a map; they tend to be more +// efficient: implementation is using open-addressing hash tables. +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// CLientProxy is tracking state of client's connection, and serves as +// a proxy for notifications sent to this client. +class ClientProxy final { + public: + static constexpr int kEndpointIdLength = 4; + + ClientProxy(); + ~ClientProxy(); + ClientProxy(ClientProxy&&) = default; + ClientProxy& operator=(ClientProxy&&) = default; + + std::int64_t GetClientId() const; + + std::string GenerateLocalEndpointId(); + + // Clears all the runtime state of this client. + void Reset(); + + // Marks this client as advertising with the given callbacks. + void StartedAdvertising( + const std::string& service_id, Strategy strategy, + const ConnectionListener& connection_lifecycle_listener, + absl::Span mediums); + // Marks this client as not advertising. + void StoppedAdvertising(); + bool IsAdvertising() const; + std::string GetAdvertisingServiceId() const; + + // Marks this client as discovering with the given callback. + void StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& discovery_listener, + absl::Span mediums); + // Marks this client as not discovering at all. + void StoppedDiscovery(); + bool IsDiscoveringServiceId(const std::string& service_id) const; + bool IsDiscovering() const; + std::string GetDiscoveryServiceId() const; + + // Proxies to the client's DiscoveryListener::OnEndpointFound() callback. + void OnEndpointFound(const std::string& service_id, + const std::string& endpoint_id, + const std::string& endpoint_name, + proto::connections::Medium medium); + // Proxies to the client's DiscoveryListener::OnEndpointLost() callback. + void OnEndpointLost(const std::string& service_id, + const std::string& endpoint_id); + + // Proxies to the client's ConnectionListener::OnInitiated() callback. + void OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener); + + // Proxies to the client's ConnectionListener::OnAccepted() callback. + void OnConnectionAccepted(const std::string& endpoint_id); + // Proxies to the client's ConnectionListener::OnRejected() callback. + void OnConnectionRejected(const std::string& endpoint_id, + const Status& status); + + void OnBandwidthChanged(const std::string& endpoint_id, std::int32_t quality); + + // Removes the endpoint from this client's list of connected endpoints. If + // notify is true, also calls the client's + // ConnectionListener.disconnected_cb() callback. + void OnDisconnected(const std::string& endpoint_id, bool notify); + + // Returns true if it's safe to send payloads to this endpoint. + bool IsConnectedToEndpoint(const std::string& endpoint_id) const; + // Returns all endpoints that can safely be sent payloads. + std::vector GetConnectedEndpoints() const; + // Returns all endpoints that are still awaiting acceptance. + std::vector GetPendingConnectedEndpoints() const; + // Returns the number of endpoints that are connected and outgoing. + std::int32_t GetNumOutgoingConnections() const; + // Returns the number of endpoints that are connected and incoming. + std::int32_t GetNumIncomingConnections() const; + // If true, then we're in the process of approving (or rejecting) a + // connection. No payloads should be sent until isConnectedToEndpoint() + // returns true. + bool HasPendingConnectionToEndpoint(const std::string& endpoint_id) const; + // Returns true if the local endpoint has already marked itself as + // accepted/rejected. + bool HasLocalEndpointResponded(const std::string& endpoint_id) const; + // Returns true if the remote endpoint has already marked themselves as + // accepted/rejected. + bool HasRemoteEndpointResponded(const std::string& endpoint_id) const; + // Marks the local endpoint as having accepted the connection. + void LocalEndpointAcceptedConnection(const std::string& endpoint_id, + const PayloadListener& listener); + // Marks the local endpoint as having rejected the connection. + void LocalEndpointRejectedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having accepted the connection. + void RemoteEndpointAcceptedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having rejected the connection. + void RemoteEndpointRejectedConnection(const std::string& endpoint_id); + // Returns true if both the local endpoint and the remote endpoint have + // accepted the connection. + bool IsConnectionAccepted(const std::string& endpoint_id) const; + // Returns true if either the local endpoint or the remote endpoint has + // rejected the connection. + bool IsConnectionRejected(const std::string& endpoint_id) const; + + // Proxies to the client's PayloadListener::OnPayload() callback. + void OnPayload(const std::string& endpoint_id, Payload payload); + // Proxies to the client's PayloadListener::OnPayloadProgress() callback. + void OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info); + bool LocalConnectionIsAccepted(std::string endpoint_id) const; + bool RemoteConnectionIsAccepted(std::string endpoint_id) const; + + private: + struct Connection { + // Status: may be either: + // Connection::PENDING, or combination of + // Connection::LOCAL_ENDPOINT_ACCEPTED: + // Connection::LOCAL_ENDPOINT_REJECTED and + // Connection::REMOTE_ENDPOINT_ACCEPTED: + // Connection::REMOTE_ENDPOINT_REJECTED, or + // Connection::CONNECTED. + // Only when this is set to CONNECTED should you allow payload transfers. + // + // We want this enum to be implicitly convertible to int, because + // we perform bit operations on it. + enum Status : uint8_t { + kPending = 0, + kLocalEndpointAccepted = 1 << 0, + kLocalEndpointRejected = 1 << 1, + kRemoteEndpointAccepted = 1 << 2, + kRemoteEndpointRejected = 1 << 3, + kConnected = 1 << 4, + }; + bool is_incoming{false}; + Status status{kPending}; + ConnectionListener connection_listener; + PayloadListener payload_listener; + }; + + struct AdvertisingInfo { + std::string service_id; + ConnectionListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + struct DiscoveryInfo { + std::string service_id; + DiscoveryListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + void RemoveAllEndpoints(); + bool ConnectionStatusesContains(const std::string& endpoint_id, + Connection::Status status_to_match) const; + void AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append); + + const Connection* LookupConnection(const std::string& endpoint_id) const; + Connection* LookupConnection(const std::string& endpoint_id); + bool ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const; + std::vector GetMatchingEndpoints( + std::function pred) const; + + mutable RecursiveMutex mutex_; + std::int64_t client_id_; + + // If not empty, we are currently advertising and accepting connection + // requests for the given service_id. + AdvertisingInfo advertising_info_; + + // If not empty, we are currently discovering for the given service_id. + DiscoveryInfo discovery_info_; + + // Maps endpoint_id to endpoint connection state. + absl::flat_hash_map connections_; + + // A cache of endpoint ids that we've already notified the discoverer of. We + // check this cache before calling onEndpointFound() so that we don't notify + // the client multiple times for the same endpoint. This would otherwise + // happen because some mediums (like Bluetooth) repeatedly give us the same + // endpoints after each scan. + absl::flat_hash_set discovered_endpoint_ids_; +}; + +// Operator overloads when comparing Ptr. +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs); +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_CLIENT_PROXY_H_ diff --git a/cpp/core_v2/internal/client_proxy_test.cc b/cpp/core_v2/internal/client_proxy_test.cc new file mode 100644 index 00000000..88a3e93e --- /dev/null +++ b/cpp/core_v2/internal/client_proxy_test.cc @@ -0,0 +1,357 @@ +#include "core_v2/internal/client_proxy.h" + +#include + +#include "core_v2/listeners.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::testing::MockFunction; +using ::testing::StrictMock; + +class ClientProxyTest : public testing::Test { + protected: + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockPayloadListener { + StrictMock< + MockFunction> + payload_cb; + StrictMock> + payload_progress_cb; + }; + + struct Endpoint { + std::string name; + std::string id; + }; + + Endpoint StartAdvertising(ClientProxy* client, ConnectionListener listener) { + Endpoint endpoint{ + .name = "advertising endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedAdvertising(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + Endpoint StartDiscovery(ClientProxy* client, DiscoveryListener listener) { + Endpoint endpoint{ + .name = "discovery endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedDiscovery(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + void OnDiscoveryEndpointFound(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_found_cb, Call).Times(1); + client->OnEndpointFound(service_id_, endpoint.id, endpoint.name, medium_); + } + + void OnDiscoveryEndpointLost(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_lost_cb, Call).Times(1); + client->OnEndpointLost(service_id_, endpoint.id); + } + + void OnDiscoveryConnectionInitiated(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1); + const std::string auth_token{"auth_token"}; + const ByteArray raw_auth_token{auth_token}; + advertising_connection_info_.remote_endpoint_name = endpoint.name; + client->OnConnectionInitiated(endpoint.id, advertising_connection_info_, + discovery_connection_listener_); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + } + + void OnDiscoveryConnectionLocalAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointAcceptedConnection(endpoint.id, payload_listener_); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointAcceptedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionLocalRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.accepted_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint.id)); + client->OnConnectionAccepted(endpoint.id); + } + + void OnDiscoveryConnectionRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.rejected_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionRejected(endpoint.id)); + client->OnConnectionRejected(endpoint.id, {Status::kConnectionRejected}); + } + + void OnDiscoveryBandwidthChanged(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.bandwidth_changed_cb, Call).Times(1); + client->OnBandwidthChanged(endpoint.id, 1); + } + + void OnDiscoveryConnectionDisconnected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.disconnected_cb, Call).Times(1); + client->OnDisconnected(endpoint.id, true); + } + + void OnPayload(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_cb, Call).Times(1); + client->OnPayload(endpoint.id, Payload(payload_bytes_)); + } + + void OnPayloadProgress(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_progress_cb, Call).Times(1); + client->OnPayloadProgress(endpoint.id, {}); + } + + MockDiscoveryListener mock_discovery_; + MockConnectionListener mock_discovery_connection_; + MockPayloadListener mock_discovery_payload_; + + proto::connections::Medium medium_{proto::connections::Medium::BLUETOOTH}; + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH, + }; + Strategy strategy_{Strategy::kP2pPointToPoint}; + const std::string service_id_{"service"}; + ClientProxy client1_; + ClientProxy client2_; + std::string auth_token_ = "auth_token"; + ByteArray raw_auth_token_ = ByteArray(auth_token_); + ByteArray payload_bytes_{"bytes"}; + ConnectionResponseInfo advertising_connection_info_{ + .authentication_token = auth_token_, + .raw_authentication_token = raw_auth_token_, + .is_incoming_connection = true, + }; + ConnectionListener advertising_connection_listener_; + ConnectionListener discovery_connection_listener_{ + .initiated_cb = mock_discovery_connection_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_discovery_connection_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_discovery_connection_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_discovery_connection_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_discovery_connection_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = mock_discovery_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = mock_discovery_.endpoint_lost_cb.AsStdFunction(), + }; + PayloadListener payload_listener_{ + .payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(), + .payload_progress_cb = + mock_discovery_payload_.payload_progress_cb.AsStdFunction(), + }; +}; + +TEST_F(ClientProxyTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(ClientProxyTest, ClientIdIsUnique) { + EXPECT_NE(client1_.GetClientId(), client2_.GetClientId()); +} + +TEST_F(ClientProxyTest, GeneratedEndpointIdIsUnique) { + EXPECT_NE(client1_.GenerateLocalEndpointId(), + client2_.GenerateLocalEndpointId()); +} + +TEST_F(ClientProxyTest, ResetClearsState) { + client1_.Reset(); + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedAdvertisingChangesStateFromIdle) { + client1_.StartedAdvertising(service_id_, strategy_, {}, {}); + + EXPECT_TRUE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_EQ(client1_.GetAdvertisingServiceId(), service_id_); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedDiscoveryChangesStateFromIdle) { + client1_.StartedDiscovery(service_id_, strategy_, {}, {}); + + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_TRUE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_EQ(client1_.GetDiscoveryServiceId(), service_id_); +} + +TEST_F(ClientProxyTest, OnEndpointFoundFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnEndpointLostFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryEndpointLost(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnConnectionInitiatedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnBandwidthChangedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnDiscoveryBandwidthChanged(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnDisconnectedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionDisconnected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayload(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadProgressChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayloadProgress(&client2_, advertising_endpoint); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.cc b/cpp/core_v2/internal/encryption_runner.cc new file mode 100644 index 00000000..226c0695 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.cc @@ -0,0 +1,368 @@ +#include "core_v2/internal/encryption_runner.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/logging.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/strings/ascii.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::Duration kTimeout = absl::Seconds(15); +constexpr std::int32_t kMaxUkey2VerificationStringLength = 32; +constexpr std::int32_t kTokenLength = 5; +constexpr securegcm::UKey2Handshake::HandshakeCipher kCipher = + securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512; + +// Transforms a raw UKEY2 token (which is a random ByteArray that's +// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only +// uses [A-Z], [0-9], '_', '-' for each character. +std::string ToHumanReadableString(const ByteArray& token) { + std::string result = Base64Utils::Encode(token).substr(0, kTokenLength); + absl::AsciiStrToUpper(&result); + return result; +} + +bool HandleEncryptionSuccess(const std::string& endpoint_id, + std::unique_ptr ukey2, + const EncryptionRunner::ResultListener& listener) { + std::unique_ptr verification_string = + ukey2->GetVerificationString(kMaxUkey2VerificationStringLength); + if (verification_string == nullptr) { + return false; + } + + ByteArray raw_authentication_token(*verification_string); + + listener.on_success_cb(endpoint_id, std::move(ukey2), + ToHumanReadableString(raw_authentication_token), + raw_authentication_token); + + return true; +} + +void CancelableAlarmRunnable(ClientProxy* client_proxy, + const std::string& endpoint_id, + EndpointChannel* endpoint_channel) { + NEARBY_LOG(INFO, + "Timing out encryption for client %" PRId64 + " to endpoint %s after %" PRId64 " ms", + client_proxy->GetClientId(), endpoint_id.c_str(), + static_cast(absl::ToInt64Milliseconds(kTimeout))); + endpoint_channel->Close(); +} + +class ServerRunnable final { + public: + ServerRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startServer() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr server = + securegcm::UKey2Handshake::ForResponder(kCipher); + if (server == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + ExceptionOr client_init = channel_->Read(); + if (!client_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + server->ParseHandshakeMessage(std::string(client_init.result())); + + // Java code throws a HandshakeException / AlertException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 1 from endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + std::unique_ptr server_init = + server->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (server_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_exception = + channel_->Write(ByteArray(std::move(*server_init))); + if (!write_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), wrote UKEY2 Message 2 to endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + ExceptionOr client_finish = channel_->Read(); + + if (!client_finish.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + parse_result = + server->ParseHandshakeMessage(std::string(client_finish.result())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 3 from endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.Cancel(); + + if (!HandleEncryptionSuccess(endpoint_id_, std::move(server), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startServer(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +class ClientRunnable final { + public: + ClientRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startClient() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr crypto = + securegcm::UKey2Handshake::ForInitiator(kCipher); + + // Java code throws a HandshakeException. + if (crypto == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + std::unique_ptr client_init = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_init_exception = channel_->Write(ByteArray(*client_init)); + if (!write_init_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 1 to endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + ExceptionOr server_init = channel_->Read(); + + if (!server_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + crypto->ParseHandshakeMessage(std::string(server_init.result())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), read UKEY2 Message 2 from endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + std::unique_ptr client_finish = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_finish == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_finish_exception = + channel_->Write(ByteArray(*client_finish)); + if (!write_finish_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 3 to endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.Cancel(); + + if (!HandleEncryptionSuccess(endpoint_id_, std::move(crypto), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startClient(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startClient(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +} // namespace + +EncryptionRunner::~EncryptionRunner() { + // Stop all the ongoing Runnables (as gracefully as possible). + client_executor_.Shutdown(); + server_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +void EncryptionRunner::StartServer( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + server_executor_.Execute( + [runnable{ServerRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +void EncryptionRunner::StartClient( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + client_executor_.Execute( + [runnable{ClientRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.h b/cpp/core_v2/internal/encryption_runner.h new file mode 100644 index 00000000..399fb0b5 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.h @@ -0,0 +1,72 @@ +#ifndef CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ +#define CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "securegcm/ukey2_handshake.h" + +namespace location { +namespace nearby { +namespace connections { + +// Encrypts a connection over UKEY2. +// +// NOTE: Stalled EndpointChannels will be disconnected after kTimeout. +// This is to prevent unverified endpoints from maintaining an +// indefinite connection to us. +class EncryptionRunner { + public: + EncryptionRunner() = default; + ~EncryptionRunner(); + + struct ResultListener { + // @EncryptionRunnerThread + std::function ukey2, + const std::string& auth_token, + const ByteArray& raw_auth_token)> + on_success_cb = + DefaultCallback, + const std::string&, const ByteArray&>(); + + // Encryption has failed. The remote_endpoint_id and channel are given so + // that any pending state can be cleaned up. + // + // We return the EndpointChannel because, at this stage, simultaneous + // connections are a possibility. Use this channel to verify that the state + // you're cleaning up is for this EndpointChannel, and not state for another + // channel to the same endpoint. + // + // @EncryptionRunnerThread + std::function + on_failure_cb = DefaultCallback(); + }; + + // @AnyThread + void StartServer(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + // @AnyThread + void StartClient(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + + private: + ScheduledExecutor alarm_executor_; + SingleThreadExecutor server_executor_; + SingleThreadExecutor client_executor_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ diff --git a/cpp/core_v2/internal/encryption_runner_test.cc b/cpp/core_v2/internal/encryption_runner_test.cc new file mode 100644 index 00000000..cc4839db --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner_test.cc @@ -0,0 +1,128 @@ +#include "core_v2/internal/encryption_runner.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; + +class FakeEndpointChannel : public EndpointChannel { + public: + FakeEndpointChannel(InputStream* in, OutputStream* out) + : in_(in), out_(out) {} + ExceptionOr Read() override { + read_timestamp_ = SystemClock::ElapsedRealtime(); + return in_ ? in_->Read(Pipe::kChunkSize) + : ExceptionOr{Exception::kIo}; + } + Exception Write(const ByteArray& data) override { + return out_ ? out_->Write(data) : Exception{Exception::kIo}; + } + void Close() override { + if (in_) in_->Close(); + if (out_) out_->Close(); + } + void Close(proto::connections::DisconnectionReason reason) override { + Close(); + } + std::string GetType() const override { return "fake-channel-type"; } + std::string GetName() const override { return "fake-channel"; } + Medium GetMedium() const override { return Medium::BLE; } + void EnableEncryption( + securegcm::D2DConnectionContextV1* connection_context) override {} + bool IsPaused() const override { return false; } + void Pause() override {} + void Resume() override {} + absl::Time GetLastReadTimestamp() const override { return read_timestamp_; } + + private: + InputStream* in_ = nullptr; + OutputStream* out_ = nullptr; + absl::Time read_timestamp_ = absl::InfinitePast(); +}; + +struct User { + User(Pipe* reader, Pipe* writer) + : channel(&reader->GetInputStream(), &writer->GetOutputStream()) {} + + FakeEndpointChannel channel; + EncryptionRunner crypto; + ClientProxy client; +}; + +struct Response { + enum class Status { + kUnknown = 0, + kDone = 1, + kFailed = 2, + }; + + CountDownLatch latch{2}; + Status server_status = Status::kUnknown; + Status client_status = Status::kUnknown; +}; + +TEST(EncryptionRunnerTest, ConstructorDestructorWorks) { EncryptionRunner enc; } + +TEST(EncryptionRunnerTest, ReadWrite) { + Pipe from_a_to_b; + Pipe from_b_to_a; + User user_a(/*reader=*/&from_b_to_a, /*writer=*/&from_a_to_b); + User user_b(/*reader=*/&from_a_to_b, /*writer=*/&from_b_to_a); + Response response; + + user_a.crypto.StartServer( + &user_a.client, "endpoint_id", &user_a.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.server_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.server_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + user_b.crypto.StartClient( + &user_b.client, "endpoint_id", &user_b.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.client_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.client_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + EXPECT_TRUE(response.latch.Await(absl::Milliseconds(5000)).result()); + EXPECT_EQ(response.server_status, Response::Status::kDone); + EXPECT_EQ(response.client_status, Response::Status::kDone); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel.h b/cpp/core_v2/internal/endpoint_channel.h new file mode 100644 index 00000000..6c441191 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel.h @@ -0,0 +1,74 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +class EndpointChannel { + public: + virtual ~EndpointChannel() = default; + + virtual ExceptionOr + Read() = 0; // throws Exception::IO, Exception::INTERRUPTED + + virtual Exception Write(const ByteArray& data) = 0; // throws Exception::IO + + // Closes this EndpointChannel, without tracking the closure in analytics. + virtual void Close() = 0; + + // Closes this EndpointChannel and records the closure with the given reason. + virtual void Close(proto::connections::DisconnectionReason reason) = 0; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, WIFI. + virtual std::string GetType() const = 0; + + // Returns the name of the EndpointChannel. + virtual std::string GetName() const = 0; + + // Returns the analytics enum representing the medium of this EndpointChannel. + virtual proto::connections::Medium GetMedium() const = 0; + + // Enables encryption on the EndpointChannel. + virtual void EnableEncryption( + securegcm::D2DConnectionContextV1* context) = 0; + + // True if the EndpointChannel is currently pausing all writes. + virtual bool IsPaused() const = 0; + + // Pauses all writes on this EndpointChannel until resume() is called. + virtual void Pause() = 0; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + virtual void Resume() = 0; + + // Returns the timestamp of the last read from this endpoint, or -1 if no + // reads have occurred. + virtual absl::Time GetLastReadTimestamp() const = 0; +}; + +inline bool operator==(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return (lhs.GetType() == rhs.GetType()) && (lhs.GetName() == rhs.GetName()) && + (lhs.GetMedium() == rhs.GetMedium()); +} + +inline bool operator!=(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager.cc b/cpp/core_v2/internal/endpoint_channel_manager.cc new file mode 100644 index 00000000..2e0bdc41 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.cc @@ -0,0 +1,137 @@ +#include "core_v2/internal/endpoint_channel_manager.h" + +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +EndpointChannelManager::~EndpointChannelManager() { + MutexLock lock(&mutex_); + channel_state_.DestroyAll(); +} + +void EndpointChannelManager::RegisterChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); + + NEARBY_LOG(INFO, "Registered channel: id=%s", endpoint_id.c_str()); +} + +void EndpointChannelManager::ReplaceChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint != nullptr && endpoint->channel == nullptr) { + NEARBY_LOG(INFO, "Channel is missing while trying to update: id=%s", + endpoint_id.c_str()); + } + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); +} + +bool EndpointChannelManager::EncryptChannelForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + MutexLock lock(&mutex_); + + channel_state_.UpdateEncryptionContextForEndpoint(endpoint_id, + std::move(context)); + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + return channel_state_.EncryptChannel(endpoint); +} + +std::shared_ptr EndpointChannelManager::GetChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint == nullptr) { + NEARBY_LOG(INFO, "No channel info: id=%s", endpoint_id.c_str()); + return {}; + } + + return endpoint->channel; +} + +void EndpointChannelManager::SetActiveEndpointChannel( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + + // Update the channel first, then encrypt this new channel, if + // crypto context is present. + channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel)); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint); +} + +// endpoint - channel endpoint to encrypt +bool EndpointChannelManager::ChannelState::EncryptChannel( + EndpointChannelManager::ChannelState::EndpointData* endpoint) { + if (endpoint != nullptr && endpoint->channel != nullptr && + endpoint->context != nullptr) { + endpoint->channel->EnableEncryption(endpoint->context.get()); + return true; + } + return false; +} + +///////////////////////////////// ChannelState ///////////////////////////////// +EndpointChannelManager::ChannelState::EndpointData* +EndpointChannelManager::ChannelState::LookupEndpointData( + const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + return item != endpoints_.end() ? &item->second : nullptr; +} + +void EndpointChannelManager::ChannelState::UpdateChannelForEndpoint( + const std::string& endpoint_id, std::unique_ptr channel) { + // Create EndpointData instance, if necessary, and populate channel. + endpoints_[endpoint_id].channel = std::move(channel); +} + +void EndpointChannelManager::ChannelState::UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + // Create EndpointData instance, if necessary, and populate crypto context. + endpoints_[endpoint_id].context = std::move(context); +} + +bool EndpointChannelManager::ChannelState::RemoveEndpoint( + const std::string& endpoint_id, + proto::connections::DisconnectionReason reason) { + auto item = endpoints_.find(endpoint_id); + if (item == endpoints_.end()) return false; + item->second.disconnect_reason = reason; + endpoints_.erase(item); + return true; +} + +bool EndpointChannelManager::UnregisterChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!channel_state_.RemoveEndpoint( + endpoint_id, + proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) { + return false; + } + + NEARBY_LOG(INFO, "Unregistered channel: id=%s", endpoint_id.c_str()); + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel_manager.h b/cpp/core_v2/internal/endpoint_channel_manager.h new file mode 100644 index 00000000..c6e9e9c7 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.h @@ -0,0 +1,155 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +using EncryptionContext = ::securegcm::D2DConnectionContextV1; + +// NOTE(std::string): +// All the strings in internal class public interfaces should be exchanged as +// const std::string& if they are immutable, and as std::string +// it they are mutable. +// This is to keep all the internal classes compatible with each other, +// and minimize resources spent on the type conversion. +// Project-wide, strings are either passed around as reference (which has +// zero maintenance costs, and sizeof(void*) memory usage => passed around in a +// CPU register), and whenever lifetime etension is required, it must be copied +// to std::string instance (which will again propagate as a const reference +// within it's lifetime domain). + +// Manages the communication channels to all the remote endpoints with which we +// are interacting. +class EndpointChannelManager final { + public: + ~EndpointChannelManager(); + + // Registers the initial EndpointChannel to be associated with an endpoint; + // if there already exists a previously-associated EndpointChannel, that will + // be closed before continuing the registration. + void RegisterChannelForEndpoint(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Replaces the EndpointChannel to be associated with an endpoint from here on + // in, transferring the encryption context from the previous EndpointChannel + // to the newly-provided EndpointChannel. + void ReplaceChannelForEndpoint(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool EncryptChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr context) + ABSL_LOCKS_EXCLUDED(mutex_); + + // NOTE(shared_ptr<> usage): + // + // EndpointChannelManager is holding an EndpointChannel instance; + // GetChannelForEndpoint() is passing ownership over to a worker thread. + // It is not a pointer passing but an ownership passing, to guarantee that + // channel instance will not disappear underneath the feet of a worker thread + // inside EndpointManager [ EndpointManager::EndpointChannelLoopRunnable() ]. + // If it is just a pointer, Channel will get destroyed while in use by a + // worker thread. shared_ptr is a simple and reliable tool to avoid that. + // + // The reason why it can not be std::unique_ptr<> is: there are other code + // paths that expect to be able to read the pointer value multiple times, from + // multiple places (each of them needs "ownership" for the duration of their + // use). EndpointManager::SendTransferFrameBytes() is another such place. + // If EndpointChannelManager replaces the current channel, and any (or both) + // EndpointManager methods that use a channel are running, it is better to + // have a shared ownership. + std::shared_ptr GetChannelForEndpoint( + const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if 'endpoint_id' actually had a registered EndpointChannel. + // IOW, a return of false signifies a no-op. + bool UnregisterChannelForEndpoint(const std::string& endpoint_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Tracks channel state for all endpoints. This includes what EndpointChannel + // the endpoint is currently using and whether or not the EndpointChannel has + // been encrypted yet. + class ChannelState { + public: + struct EndpointData { + EndpointData() = default; + EndpointData(EndpointData&&) = default; + EndpointData& operator=(EndpointData&&) = default; + ~EndpointData() { + if (channel != nullptr) { + channel->Close(disconnect_reason); + } + } + + // True if we have a 'context' for the endpoint. + bool IsEncrypted() const { return context != nullptr; } + + std::shared_ptr channel; + std::unique_ptr context; + proto::connections::DisconnectionReason disconnect_reason = + proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON; + }; + + ChannelState() = default; + ~ChannelState() { DestroyAll(); } + ChannelState(ChannelState&&) = default; + ChannelState& operator=(ChannelState&&) = default; + + // Provides a way to destroy contents of a container, while holding a lock. + void DestroyAll() { endpoints_.clear(); } + // Return pointer to endpoint data, or nullptr, it not found. + EndpointData* LookupEndpointData(const std::string& endpoint_id); + + // Stores a new EndpointChannel for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr channel); + + // Stores a new EncryptionContext for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context); + + // Removes all knowledge of this endpoint, cleaning up as necessary. + // Returns false if the endpoint was not found. + bool RemoveEndpoint(const std::string& endpoint_id, + proto::connections::DisconnectionReason reason); + + bool EncryptChannel(EndpointData* endpoint); + + private: + // Endpoint ID -> EndpointData. Contains everything we know about the + // endpoint. + absl::flat_hash_map endpoints_; + }; + + void SetActiveEndpointChannel(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Mutex mutex_; + ChannelState channel_state_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager_test.cc b/cpp/core_v2/internal/endpoint_channel_manager_test.cc new file mode 100644 index 00000000..673ed7f1 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager_test.cc @@ -0,0 +1,17 @@ +#include "core_v2/internal/endpoint_channel_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(EndpointChannelManagerTest, ConstructorDestructorWorks) { + EndpointChannelManager mgr; + SUCCEED(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc new file mode 100644 index 00000000..501df7a3 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -0,0 +1,477 @@ +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +using ::location::nearby::proto::connections::Medium; + +// A Runnable that continuously grabs the most recent EndpointChannel available +// for an endpoint. +// +// handler - Called whenever an EndpointChannel is available for endpointId. +// Implementations are expected to read/write freely to the +// EndpointChannel until an Exception::IO is thrown. Once an +// Exception::IO occurs, a check will be performed to see if another +// EndpointChannel is available for the given endpoint and, if so, +// handler(EndpointChannel) will be called again. Return false to exit +// the loop. +void EndpointManager::EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler) { + // EndpointChannelManager will not let multiple channels exist simultaneously + // for the same endpoint_id; it will be closing "old" channels as new ones + // come. (There will be a short overlap). + // Closed channel will return Exception::kIo for any Read, and loop (below) + // will retry and attempt to pick another channel. + // If channel is deleted (no mapping), or it is still the same channel + // (same Medium) on which we got the Exception::kIo, we terminate the loop. + Medium last_failed_medium = Medium::UNKNOWN_MEDIUM; + while (true) { + // It's important to keep re-fetching the EndpointChannel for an endpoint + // because it can be changed out from under us (for example, when we + // upgrade from Bluetooth to Wifi). + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (channel == nullptr) { + // TODO(tracyzhou): Add logging. + break; + } + + // If we're looping back around after a failure, and there's not a new + // EndpointChannel for this endpoint, there's nothing more to do here. + if ((last_failed_medium != Medium::UNKNOWN_MEDIUM) && + (channel->GetMedium() == last_failed_medium)) { + // TODO(tracyzhou): Add logging. + break; + } + + ExceptionOr keep_using_channel = handler(channel.get()); + + if (!keep_using_channel.ok()) { + Exception exception = keep_using_channel.GetException(); + if (exception.Raised(Exception::kIo)) { + last_failed_medium = channel->GetMedium(); + // TODO(tracyzhou): Add logging. + continue; + } + if (exception.Raised(Exception::kInterrupted)) { + break; + } + } + + if (!keep_using_channel.result()) { + // TODO(tracyzhou): Add logging. + break; + } + } + // Indicate we're out of the loop and it is ok to schedule another instance + // if needed. + NEARBY_LOG(INFO, "Worker going down; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); + barrier->CountDown(); + + // Always clear out all state related to this endpoint before terminating + // this thread. + DiscardEndpoint(client, endpoint_id); + NEARBY_LOG(INFO, "Worker done; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); +} + +ExceptionOr EndpointManager::HandleData( + const std::string& endpoint_id, ClientProxy* client, + EndpointChannel* endpoint_channel) { + // Read as much as we can from the healthy EndpointChannel - when it is no + // longer in good shape (i.e. our read from it throws an Exception), our + // super class will loop back around and try our luck in case there's been + // a replacement for this endpoint since we last checked with the + // EndpointChannelManager. + while (true) { + ExceptionOr bytes = endpoint_channel->Read(); + if (!bytes.ok()) { + NEARBY_LOG(INFO, "Stop reading on read-time exception: %d", + bytes.exception()); + return ExceptionOr(bytes.exception()); + } + ExceptionOr wrapped_frame = parser::FromBytes(bytes.result()); + if (!wrapped_frame.ok()) { + if (wrapped_frame.GetException().Raised( + Exception::kInvalidProtocolBuffer)) { + NEARBY_LOG(INFO, "failed to decode; endpoint=%s; channel=%s; skip", + endpoint_id.c_str(), endpoint_channel->GetType().c_str()); + continue; + } else { + NEARBY_LOG(INFO, "Stop reading on parse-time exception: %d", + wrapped_frame.exception()); + return ExceptionOr(wrapped_frame.exception()); + } + } + OfflineFrame& frame = wrapped_frame.result(); + + // Route the incoming offlineFrame to its registered processor. + V1Frame::FrameType frame_type = parser::GetFrameType(frame); + EndpointManager::FrameProcessor* frame_processor = + GetFrameProcessor(frame_type); + if (frame_processor == nullptr) { + NEARBY_LOG(ERROR, "Unhandled message: type=%d", frame_type); + continue; + } + + frame_processor->OnIncomingFrame(frame, endpoint_id, client, + endpoint_channel->GetMedium()); + } +} + +ExceptionOr EndpointManager::HandleKeepAlive( + EndpointChannel* endpoint_channel) { + // Check if it has been too long since we received a frame from our + // endpoint. + if ((endpoint_channel->GetLastReadTimestamp() != kInvalidTimestamp) && + ((endpoint_channel->GetLastReadTimestamp() + + EndpointManager::kKeepAliveReadTimeout) < + SystemClock::ElapsedRealtime())) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(false); + } + + // Attempt to send the KeepAlive frame over the endpoint channel - if the + // write fails, our super class will loop back around and try our luck again + // in case there's been a replacement for this endpoint. + Exception write_exception = endpoint_channel->Write(parser::ForKeepAlive()); + if (!write_exception.Ok()) { + return ExceptionOr(write_exception); + } + + // We sleep as the very last step because we want to minimize the caching of + // the EndpointChannel. If we do hold on to the EndpointChannel, and it's + // switched out from under us in BandwidthUpgradeManager, our write will + // trigger an erroneous write to the encryption context that will cascade + // into all our remote endpoint's future reads failing. + Exception sleep_exception = + SystemClock::Sleep(EndpointManager::kKeepAliveWriteInterval); + if (!sleep_exception.Ok()) { + return ExceptionOr(sleep_exception); + } + + return ExceptionOr(true); +} + +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs == &rhs; +} + +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs < &rhs; +} + +EndpointManager::EndpointManager(EndpointChannelManager* manager) + : channel_manager_(manager) {} + +EndpointManager::~EndpointManager() { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, &latch]() { + NEARBY_LOG(INFO, "Bringing down endpoints"); + for (auto& item : endpoints_) { + const std::string& endpoint_id = item.first; + EndpointState& state = item.second; + // This will close the channel; all workers will sense that and + // terminate. + NEARBY_LOG(INFO, "Bringing down endpoint channels: id=%s", + endpoint_id.c_str()); + WaitForEndpointDisconnectionProcessing(state.client, endpoint_id); + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + } + latch.CountDown(); + }); + latch.Await(); + NEARBY_LOG(INFO, "Bringing down worker threads"); + + // Stop all the ongoing Runnables (as gracefully as possible). + // Order matters: bring worker pools down first; serial_executor_ thread + // should go last, since workers schedule jobs there even during shutdown. + handlers_executor_.Shutdown(); + keep_alive_executor_.Shutdown(); + NEARBY_LOG(INFO, "Bringing down control thread"); + serial_executor_.Shutdown(); + NEARBY_LOG(INFO, "EndpointManager is down"); +} + +const EndpointManager::FrameProcessor::Handle +EndpointManager::RegisterFrameProcessor( + V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) { + const FrameProcessor::Handle handle = processor; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &latch, processor]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + // TODO(tracyzhou): Add logging. + it->second = processor; + } else { + frame_processors_.emplace(frame_type, processor); + } + latch.CountDown(); + }); + latch.Await(); + return handle; +} + +void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle) { + RunOnEndpointManagerThread([this, frame_type, handle]() { + auto it = frame_processors_.find(frame_type); + if (it == frame_processors_.end()) return; + if (it->second != handle) { + NEARBY_LOG(INFO, + "Failed to unregister: type=%d; handle mismatch: passed=%p, " + "expected=%p", + frame_type, handle, it->second); + return; + } + + frame_processors_.erase(it); + NEARBY_LOG(INFO, "unregistered: type=%d", frame_type); + }); +} + +EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor( + V1Frame::FrameType frame_type) { + EndpointManager::FrameProcessor* processor = nullptr; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &processor, &latch]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + processor = it->second; + } + latch.CountDown(); + }); + latch.Await(); + return processor; +} + +void EndpointManager::EnsureWorkersTerminated(const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + if (item != endpoints_.end()) { + // If another instance of data and keep-alive handlers is running, it will + // terminate soon; we should block until it happens. + EndpointState& endpoint_state = item->second; + NEARBY_LOG(INFO, "Waiting for workers to terminate for endpoint_id='%s'", + endpoint_id.c_str()); + endpoint_state.barrier.Await(); + endpoints_.erase(item); + } +} + +void EndpointManager::RegisterEndpoint(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& listener) { + CountDownLatch latch(1); + + // NOTE (unique_ptr<> capture): + // std::unique_ptr<> is not copyable, so we can not pass it to + // lambda capture, because lambda eventually is converted to std::function<>. + // Instead, we release() a pointer, and pass a raw pointer, which is copyalbe. + // We ignore the risk of job not scheduled (and an associated risk of memory + // leak), because this may only happen during service shutdown. + RunOnEndpointManagerThread([this, client, channel = channel.release(), + &endpoint_id, &info, &listener, &latch]() { + // Pass ownership of channel to EndpointChannelManager + NEARBY_LOG(INFO, "Registering endpoint with channel manager: id=%s", + endpoint_id.c_str()); + channel_manager_->RegisterChannelForEndpoint( + client, endpoint_id, std::unique_ptr(channel)); + + EnsureWorkersTerminated(endpoint_id); + EndpointState& endpoint_state = + endpoints_.emplace(endpoint_id, EndpointState()).first->second; + endpoint_state.client = client; + + NEARBY_LOG(INFO, "Starting workers: id=%s", endpoint_id.c_str()); + // For every endpoint, there's normally only one Read handler instance + // running on the handlers_executor_ pool. This instance reads data from the + // endpoint and delegates incoming frames to various FrameProcessors. + // Once the frame has been properly handled, it starts reading again for + // the next frame. If the handler fails its read and no other + // EndpointChannels are available for this endpoint, a disconnection + // will be initiated. + StartEndpointReader( + [this, client, endpoint_id, barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable( + "Read", client, endpoint_id, barrier, + [this, client, endpoint_id](EndpointChannel* channel) { + return HandleData(endpoint_id, client, channel); + }); + }); + + // For every endpoint, there's only one KeepAliveManager instance + // running on the keep_alive_executor_ pool. This instance will + // periodically send out a ping* to the endpoint while listening for an + // incoming pong**. If it fails to send the ping, or if no pong is heard + // within kKeepAliveReadTimeoutMillis milliseconds, it initiates a + // disconnection. + // + // (*) Bluetooth requires a constant outgoing stream of messages. If + // there's silence, Android will break the socket. This is why we ping. + // (**) Wifi Hotspots can fail to notice a connection has been lost, and + // they will happily keep writing to /dev/null. This is why we listen + // for the pong. + StartEndpointKeepAliveManager([this, client, endpoint_id, + barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable("KeepAliveManager", client, endpoint_id, + barrier, [this](EndpointChannel* channel) { + return HandleKeepAlive(channel); + }); + }); + // TODO(tracyzhou): Add logging. + + // It's now time to let the client know of this new connection so that + // they can accept or reject it. + client->OnConnectionInitiated(endpoint_id, info, listener); + latch.CountDown(); + }); + latch.Await(); +} + +void EndpointManager::UnregisterEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, client, endpoint_id, &latch]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, /*notify=*/false); + latch.CountDown(); + }); + latch.Await(); +} + +// Designed to run asynchronously. It is called from IO thread pools, and +// jobs in these pools may be waited for from the EndpointManager thread. If we +// allow synchronous behavior here it will cause a live lock. +void EndpointManager::DiscardEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + RunOnEndpointManagerThread([this, client, endpoint_id]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, + /*notify=*/ + client->IsConnectedToEndpoint(endpoint_id)); + }); +} + +std::vector EndpointManager::SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids) { + ByteArray bytes = + parser::ForDataPayloadTransfer(payload_header, payload_chunk); + + return SendTransferFrameBytes(endpoint_ids, bytes, payload_header.id(), + /*offset=*/payload_chunk.offset(), + /*packet_type=*/"DATA"); +} + +std::vector EndpointManager::SendControlMessage( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control, + const std::vector& endpoint_ids) { + ByteArray bytes = parser::ForControlPayloadTransfer(header, control); + + return SendTransferFrameBytes(endpoint_ids, bytes, header.id(), + /*offset=*/control.offset(), + /*packet_type=*/"CONTROL"); +} + +// @EndpointManagerThread +void EndpointManager::RemoveEndpoint(ClientProxy* client, + const std::string& endpoint_id, + bool notify) { + // Unregistering from channel_manager_ will also serve to terminate + // the dedicated handler and KeepAlive threads we started when we registered + // this endpoint. + if (channel_manager_->UnregisterChannelForEndpoint(endpoint_id)) { + // Notify all frame processors of the disconnection immediately and wait + // for them to clean up state. Only once all processors are done cleaning + // up, we can remove the endpoint from ClientProxy after which there + // should be no further interactions with the endpoint. + // (See b/37352254 for history) + WaitForEndpointDisconnectionProcessing(client, endpoint_id); + EnsureWorkersTerminated(endpoint_id); + + client->OnDisconnected(endpoint_id, notify); + // TODO(tracyzhou): Add logging. + } +} + +// @EndpointManagerThread +void EndpointManager::WaitForEndpointDisconnectionProcessing( + ClientProxy* client, const std::string& endpoint_id) { + CountDownLatch barrier(frame_processors_.size()); + + for (auto& item : frame_processors_) { + auto& processor = item.second; + processor->OnEndpointDisconnect(client, endpoint_id, &barrier); + } + + barrier.Await(kProcessEndpointDisconnectionTimeout); +} + +std::vector EndpointManager::SendTransferFrameBytes( + const std::vector& endpoint_ids, const ByteArray& bytes, + std::int64_t payload_id, std::int64_t offset, + const std::string& packet_type) { + std::vector failed_endpoint_ids; + for (const std::string& endpoint_id : endpoint_ids) { + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + + if (channel == nullptr) { + // We no longer know about this endpoint (it was either explicitly + // unregistered, or a read/write error made us unregister it internally). + NEARBY_LOG(INFO, "Channel not available; id=%s", endpoint_id.c_str()); + failed_endpoint_ids.push_back(endpoint_id); + continue; + } + + Exception write_exception = channel->Write(bytes); + if (!write_exception.Ok()) { + failed_endpoint_ids.push_back(endpoint_id); + NEARBY_LOG(INFO, "Failed to send packet; endpoint_id=%s", + endpoint_id.c_str()); + continue; + } + } + + return failed_endpoint_ids; +} + +void EndpointManager::StartEndpointReader(Runnable runnable) { + handlers_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::StartEndpointKeepAliveManager(Runnable runnable) { + keep_alive_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::RunOnEndpointManagerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h new file mode 100644 index 00000000..b9ddd5b7 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -0,0 +1,218 @@ +#ifndef CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/listeners.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all operations related to the remote endpoints with which we are +// interacting. +// +// All processing of incoming and outgoing payloads is spread across this and +// the PayloadManager as described below. +// +// The sending of outgoing payloads originates in +// PayloadManager::SendPayload() before control is transferred over to +// EndpointManager::SendPayloadChunk(). This work happens on one of three +// dedicated writer threads belonging to the PayloadManager. The writer thread +// that is used depends on the Payload::Type. +// +// The EndpointManager has one dedicated reader thread for each registered +// endpoint, and the receiving of every incoming payload (and its subsequent +// chunks) originates on one of those threads before control is transferred over +// to PayloadManager::ProcessFrame() (still running on that +// same dedicated reader thread). + +class EndpointManager { + public: + class FrameProcessor { + public: + using Handle = void*; + + virtual ~FrameProcessor() = default; + + // @EndpointManagerReaderThread + virtual void OnIncomingFrame(const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, + ClientProxy* to_client, + proto::connections::Medium current_medium) = 0; + + // Implementations must call barrier.CountDown() once + // they're done. This parallelizes the disconnection event across all frame + // processors. + // + // @EndpointManagerThread + virtual void OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier) = 0; + }; + + explicit EndpointManager(EndpointChannelManager* manager); + ~EndpointManager(); + + // Invoked from the constructors of the various *Manager components that make + // up the OfflineServiceController implementation. + // FrameProcessor* instances are of dynamic duration and survive all sessions. + // returns unique handle to be used for unregistering. + // Blocks until registration is complete. + const FrameProcessor::Handle RegisterFrameProcessor( + V1Frame::FrameType frame_type, FrameProcessor* processor); + void UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle); + + // Invoked from the different PcpHandler implementations (of which there can + // be only one at a time). + // Blocks until registration is complete. + void RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& listener); + // Called when a client explicitly asks to disconnect from this endpoint. In + // this case, we do not notify the client of onDisconnected(). + void UnregisterEndpoint(ClientProxy* client, const std::string& endpoint_id); + + // Returns the list of endpoints to which sending this chunk failed. + // + // Invoked from the PayloadManager's sendPayload() method. + std::vector SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids); + std::vector SendControlMessage( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::ControlMessage& control_message, + const std::vector& endpoint_ids); + + // Called when we internally want to get rid of the endpoint, without the + // client directly telling us to. For example... + // a) We failed to read from the endpoint in its dedicated reader thread. + // b) We failed to write to the endpoint in PayloadManager. + // c) The connection was rejected in PCPHandler. + // d) The dedicated KeepAlive thread exceeded its period of inactivity. + // Or in the numerous other cases where a failure occurred and we no longer + // believe the endpoint is in a healthy state. + // + // Note: This must not block. Otherwise we can get into a deadlock where we + // ask everyone who's registered an FrameProcessor to + // processEndpointDisconnection() while the caller of DiscardEndpoint() is + // blocked here. + void DiscardEndpoint(ClientProxy* client, const std::string& endpoint_id); + + private: + struct EndpointState { + // ClientProxy object associated with this endpoint. + ClientProxy* client; + // Execution barrier, used to ensure that all workers associated with an + // endpoint on handlers_executor_ and keep_alive_executor_ are terminated. + CountDownLatch barrier{2}; + }; + + FrameProcessor* GetFrameProcessor(V1Frame::FrameType frame_type); + + ExceptionOr HandleData(const std::string& endpoint_id, + ClientProxy* client_proxy, + EndpointChannel* endpoint_channel); + + ExceptionOr HandleKeepAlive(EndpointChannel* endpoint_channel); + + // Waits for a given endpoint EndpointChannelLoopRunnable() workers to + // terminate. + // Is called from RegisterEndpoint to avoid races; also called from + // RemoveEndpoint as part of proper endpoint shutdown sequence. + // @EndpointManagerThread + void EnsureWorkersTerminated(const std::string& endpoint_id); + + void EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client_proxy, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler); + + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch); + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch, std::int32_t timeout_millis); + + static constexpr absl::Duration kKeepAliveWriteInterval = + absl::Milliseconds(5000); + static constexpr absl::Duration kKeepAliveReadTimeout = + absl::Milliseconds(30000); + static constexpr absl::Duration kProcessEndpointDisconnectionTimeout = + absl::Milliseconds(2000); + static constexpr std::int32_t kMaxConcurrentEndpoints = 50; + static constexpr absl::Time kInvalidTimestamp = absl::InfinitePast(); + + // It should be noted that this method may be called multiple times (because + // invoking this method closes the endpoint channel, which causes the + // dedicated reader and KeepAlive threads to terminate, which in turn leads to + // this method being called), but that's alright because the implementation of + // this method is idempotent. + // @EndpointManagerThread + void RemoveEndpoint(ClientProxy* client, const std::string& endpoint_id, + bool notify); + + void WaitForEndpointDisconnectionProcessing(ClientProxy* client, + const std::string& endpoint_id); + + std::vector SendTransferFrameBytes( + const std::vector& endpoint_ids, + const ByteArray& payload_transfer_frame_bytes, std::int64_t payload_id, + std::int64_t offset, const std::string& packet_type); + + // Executes data-handing jobs on a separate thread for each endpoint, on a + // handlers_executor_. + // If amount of concurrent connections is less the pool capacity, it is + // possible that while a channel is being replaced, two jobs are trying to + // run for the same endpoint (for a short time). + // TODO (apolyudov): do not let extra job start. + void StartEndpointReader(Runnable runnable); + + // Executes keep-alive jobs on a separate thread for each endpoint on a + // keep_alive_executor_. + void StartEndpointKeepAliveManager(Runnable runnable); + + // Executes all jobs sequentially, on a serial_executor_. + void RunOnEndpointManagerThread(Runnable runnable); + + EndpointChannelManager* channel_manager_; + + absl::flat_hash_map + frame_processors_; + + // We keep track of all registered channel endpoints here. + absl::flat_hash_map endpoints_; + + MultiThreadExecutor keep_alive_executor_{kMaxConcurrentEndpoints}; + MultiThreadExecutor handlers_executor_{kMaxConcurrentEndpoints}; + SingleThreadExecutor serial_executor_; +}; + +// Operator overloads when comparing FrameProcessor*. +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_manager_test.cc b/cpp/core_v2/internal/endpoint_manager_test.cc new file mode 100644 index 00000000..23c816c5 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager_test.cc @@ -0,0 +1,242 @@ +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/pipe.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; +using ::securegcm::D2DConnectionContextV1; +using ::testing::_; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public EndpointChannel { + public: + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, Close, (), (override)); + MOCK_METHOD(void, Close, (DisconnectionReason reason), (override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, EnableEncryption, + (D2DConnectionContextV1 * connection_context), + (override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); + + bool IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; + } + void DoClose() { + absl::MutexLock lock(&mutex_); + closed_ = true; + } + + private: + mutable absl::Mutex mutex_; + bool closed_ = false; +}; + +class MockFrameProcessor : public EndpointManager::FrameProcessor { + public: + MOCK_METHOD(void, OnIncomingFrame, + (const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, ClientProxy* to_client, + Medium current_medium), + (override)); + + MOCK_METHOD(void, OnEndpointDisconnect, + (ClientProxy * client, const std::string& endpoint_id, + CountDownLatch* barrier), + (override)); +}; + +class EndpointManagerTest : public ::testing::Test { + protected: + void RegisterEndpoint(std::unique_ptr channel, + bool should_close = true) { + CountDownLatch done(1); + if (should_close) { + ON_CALL(*channel, Close(_)) + .WillByDefault( + [&done](DisconnectionReason reason) { done.CountDown(); }); + } + EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel, GetLastReadTimestamp()) + .WillRepeatedly(Return(start_time_)); + EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1); + em_.RegisterEndpoint(&client_, endpoint_id_, info_, std::move(channel), + listener_); + if (should_close) { + EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); + } + } + + ClientProxy client_; + std::vector> processors_; + EndpointChannelManager ecm_; + EndpointManager em_{&ecm_}; + std::string endpoint_id_ = "endpoint_id"; + ConnectionResponseInfo info_ = { + .remote_endpoint_name = "name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + } mock_listener_; + ConnectionListener listener_{ + .initiated_cb = mock_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = mock_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + absl::Time start_time_{absl::Now()}; +}; + +TEST_F(EndpointManagerTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(EndpointManagerTest, RegisterEndpointCallsOnConnectionInitiated) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Close(_)).Times(1); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + RegisterEndpoint(std::make_unique()); + // NOTE: disconnect_cb is not called, because we did not reach fully connected + // state. On top of that, UnregisterEndpoint is suppressing this notification. + // (IMO, it should be called as long as any connection callback was called + // before. (in this case initiated_cb is called)). + // Test captures current protocol behavior. + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + auto connect_request = std::make_unique(); + auto read_data = parser::ForConnectionRequest("endpoint_id", "endpoint_name", + 1234, std::vector{Medium::BLE}); + EXPECT_CALL(*connect_request, OnIncomingFrame); + EXPECT_CALL(*connect_request, OnEndpointDisconnect); + EXPECT_CALL(*endpoint_channel, Read()) + .WillOnce(Return(ExceptionOr(read_data))) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + // Register frame processor, then register endpoint. + // Endpoint will read one frame, then fail to read more and terminate. + // On disconnection, it will notify frame processor and we verify that. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + // We should not receive any notifications to frame processor. + auto connect_request = std::make_unique>(); + + // Register frame processor and immediately unregister it. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + em_.UnregisterFrameProcessor(V1Frame::CONNECTION_REQUEST, handle); + // Endpoint will not send OnDisconnect notification to frame processor. + RegisterEndpoint(std::move(endpoint_channel), false); + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, SendControlMessageWorks) { + auto endpoint_channel = std::make_unique(); + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_offset(150); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + + ON_CALL(*endpoint_channel, Read()) + .WillByDefault([channel = endpoint_channel.get()]() { + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + NEARBY_LOG(INFO, "Simulate read delay: wait"); + absl::SleepFor(absl::Milliseconds(100)); + NEARBY_LOG(INFO, "Simulate read delay: done"); + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + return ExceptionOr(ByteArray{}); + }); + ON_CALL(*endpoint_channel, Close(_)) + .WillByDefault( + [channel = endpoint_channel.get()](DisconnectionReason reason) { + channel->DoClose(); + NEARBY_LOG(INFO, "Channel closed"); + }); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + RegisterEndpoint(std::move(endpoint_channel), false); + auto failed_ids = + em_.SendControlMessage(header, control, std::vector{endpoint_id_}); + EXPECT_EQ(failed_ids, std::vector{}); + NEARBY_LOG(INFO, "Will unregister endpoint now"); + em_.UnregisterEndpoint(&client_, endpoint_id_); + NEARBY_LOG(INFO, "Will call destructors now"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD new file mode 100644 index 00000000..5a33fe85 --- /dev/null +++ b/cpp/core_v2/internal/mediums/BUILD @@ -0,0 +1,70 @@ +cc_library( + name = "mediums", + srcs = [ + "advertisement_read_result.cc", + "ble_advertisement.cc", + "ble_advertisement_header.cc", + "ble_packet.cc", + "bluetooth_radio.cc", + "uuid.cc", + ], + hdrs = [ + "advertisement_read_result.h", + "ble_advertisement.h", + "ble_advertisement_header.h", + "ble_packet.h", + "ble_peripheral.h", + "bluetooth_radio.h", + "lost_entity_tracker.h", + "uuid.h", + ], + visibility = [ + "//core_v2/internal:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "utils", + srcs = ["utils.cc"], + hdrs = ["utils.h"], + visibility = [ + "//core_v2/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + ], +) + +cc_test( + name = "core_v2_internal_mediums_test", + srcs = [ + "advertisement_read_result_test.cc", + "ble_advertisement_header_test.cc", + "ble_advertisement_test.cc", + "ble_packet_test.cc", + "ble_peripheral_test.cc", + "bluetooth_radio_test.cc", + "lost_entity_tracker_test.cc", + "uuid_test.cc", + ], + shard_count = 16, + deps = [ + ":mediums", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.cc b/cpp/core_v2/internal/mediums/advertisement_read_result.cc new file mode 100644 index 00000000..fbd97e34 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.cc @@ -0,0 +1,125 @@ +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include +#include + +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{ + .backoff_multiplier = 2.0, + .base_backoff_duration = absl::Seconds(1), + .max_backoff_duration = absl::Minutes(5), +}; + +// Adds a successfully read advertisement for the specified slot to this read +// result. This is fundamentally different from RecordLastReadStatus() because +// we can report a read failure, but still manage to read some advertisements. +void AdvertisementReadResult::AddAdvertisement(std::int32_t slot, + const ByteArray& advertisement) { + MutexLock lock(&mutex_); + + // Blindly remove from the advertisements map to make sure any existing + // key-value pair is destroyed. + advertisements_.emplace(slot, advertisement); +} + +// Determines whether or not an advertisement was successfully read at the +// specified slot. +bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const { + MutexLock lock(&mutex_); + + return advertisements_.contains(slot); +} + +// Retrieves all raw advertisements that were successfully read. +std::vector AdvertisementReadResult::GetAdvertisements() + const { + MutexLock lock(&mutex_); + + std::vector all_advertisements; + all_advertisements.reserve(advertisements_.size()); + for (const auto& item : advertisements_) { + all_advertisements.emplace_back(&item.second); + } + + return all_advertisements; +} + +// Determines what stage we're in for retrying a read from an advertisement +// GATT server. +AdvertisementReadResult::RetryStatus +AdvertisementReadResult::EvaluateRetryStatus() const { + MutexLock lock(&mutex_); + + // Check if we have already succeeded reading this advertisement. + if (status_ == Status::kSuccess) { + return RetryStatus::kPreviouslySucceeded; + } + + // Check if we have recently failed to read this advertisement. + if (GetDurationSinceReadLocked() < backoff_duration_) { + return RetryStatus::kTooSoon; + } + + return RetryStatus::kRetry; +} + +// Records the status of the latest read, and updates the next backoff +// duration for subsequent reads. Be sure to also call +// AddAdvertisement() if any advertisements were read. +void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { + MutexLock lock(&mutex_); + + // Update the last read timestamp. + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + + // Update the backoff duration. + if (is_success) { + // Reset the backoff duration now that we had a successful read. + backoff_duration_ = config_.base_backoff_duration; + } else { + // Determine whether or not we were already failing before. If we were, we + // should increase the backoff duration. + if (status_ == Status::kFailure) { + // Use exponential backoff to determine the next backoff duration. This + // simply involves multiplying our current backoff duration by some + // multiplier. + absl::Duration next_backoff_duration = + config_.backoff_multiplier * backoff_duration_; + // Update the backoff duration, making sure not to blow past the + // ceiling. + backoff_duration_ = + std::min(next_backoff_duration, config_.max_backoff_duration); + } else { + // This is our first time failing, so we should only backoff for the + // initial duration. + backoff_duration_ = config_.base_backoff_duration; + } + } + + // Update the internal result. + status_ = is_success ? Status::kSuccess : Status::kFailure; +} + +// Returns how much time has passed since we last tried reading from an +// advertisement GATT server. +absl::Duration AdvertisementReadResult::GetDurationSinceRead() const { + MutexLock lock(&mutex_); + return GetDurationSinceReadLocked(); +} + +absl::Duration AdvertisementReadResult::GetDurationSinceReadLocked() const { + return SystemClock::ElapsedRealtime() - last_read_timestamp_; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.h b/cpp/core_v2/internal/mediums/advertisement_read_result.h new file mode 100644 index 00000000..c4d2c566 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.h @@ -0,0 +1,90 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Representation of a GATT advertisement read result. This object helps us +// determine whether or not we need to retry GATT reads. +class AdvertisementReadResult { + public: + // We need a long enough duration such that we always trigger a read + // retry AND we always connect to it without delay. The former case + // helps us initialize an AdvertisementReadResult so that we + // unconditionally try reading on the first sighting. And the latter + // case helps us connect immediately when we initialize a dummy read + // result for fast advertisements (which don't use the GATT server). + + struct Config { + // How much to multiply the backoff duration by with every failure to read + // from the advertisement GATT server. This should never be below 1! + float backoff_multiplier; + // The initial backoff duration when we fail to read from an advertisement + // GATT server. + absl::Duration base_backoff_duration; + // The maximum backoff duration allowed between advertisement GATT server + // reads. + absl::Duration max_backoff_duration; + }; + + static const Config kDefaultConfig; + explicit AdvertisementReadResult(const Config& config = kDefaultConfig) + : config_(config) {} + ~AdvertisementReadResult() = default; + + enum class RetryStatus { + kUnknown = 0, + kRetry = 1, + kPreviouslySucceeded = 2, + kTooSoon = 3, + }; + + void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement) + ABSL_LOCKS_EXCLUDED(mutex_); + bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetAdvertisements() const + ABSL_LOCKS_EXCLUDED(mutex_); + RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_); + void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_); + absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + enum class Status { + kUnknown = 0, + kSuccess = 1, + kFailure = 2, + }; + + absl::Duration GetDurationSinceReadLocked() const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + + // Maps slot numbers to the GATT advertisement found in that slot. + absl::flat_hash_map advertisements_ + ABSL_GUARDED_BY(mutex_); + + Config config_; + absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_); + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_); + Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc new file mode 100644 index 00000000..0d822274 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc @@ -0,0 +1,129 @@ +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +constexpr char kAdvertisementBytes[] = "\x0A\x0B\x0C"; + +// Default values may be too big and impractical to wait for in the test. +// For the test platform, we redefine them to some reasonable values. +const absl::Duration kAdvertisementBaseBackoffDuration = absl::Seconds(1); +const absl::Duration kAdvertisementMaxBackoffDuration = absl::Seconds(6); + +const AdvertisementReadResult::Config test_config{ + .backoff_multiplier = + AdvertisementReadResult::kDefaultConfig.backoff_multiplier, + .base_backoff_duration = kAdvertisementBaseBackoffDuration, + .max_backoff_duration = kAdvertisementMaxBackoffDuration, +}; + +TEST(AdvertisementReadResultTest, AdvertisementExists) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + advertisement_read_result.AddAdvertisement(slot, + ByteArray(kAdvertisementBytes)); + + EXPECT_TRUE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + + EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { + AdvertisementReadResult advertisement_read_result(test_config); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kPreviouslySucceeded); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep for some time, but not long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration / 2); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Record an additional failure so our backoff duration increases. + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep for the backoff duration. We shouldn't trigger a retry because the + // backoff should have increased from failing a second time. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Record an absurd amount of failures so we hit the maximum backoff duration. + for (std::int32_t i = 0; i < 1000; i++) { + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + } + + // Sleep for the maximum backoff duration. This should be enough to warrant a + // retry. + absl::SleepFor(kAdvertisementMaxBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, GetDurationSinceRead) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + absl::Duration sleepTime = absl::Milliseconds(420); + absl::SleepFor(sleepTime); + + EXPECT_GE(advertisement_read_result.GetDurationSinceRead(), sleepTime); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_advertisement.cc new file mode 100644 index 00000000..027a3a92 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.cc @@ -0,0 +1,201 @@ +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisement::BleAdvertisement(Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, + const ByteArray &data) { + // Check that the given input is valid. + if (!IsSupportedVersion(version) || + !IsSupportedSocketVersion(socket_version) || + service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + + version_ = version; + socket_version_ = socket_version; + service_id_hash_ = service_id_hash; + data_ = data; +} + +BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Now, time to read the bytes! + const auto *read_ptr = ble_advertisement_bytes.data(); + + // 1. Version. + version_ = static_cast((*read_ptr & kVersionBitmask) >> 5); + if (!IsSupportedVersion(version_)) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: unsupported Version %u", + version_); + return; + } + + // 2. Socket Version. + socket_version_ = + static_cast((*read_ptr & kSocketVersionBitmask) >> 2); + if (!IsSupportedSocketVersion(socket_version_)) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u", + socket_version_); + version_ = Version::kUndefined; + return; + } + read_ptr += kVersionLength; + + // 3. Service ID hash. + service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength); + read_ptr += kServiceIdHashLength; + + // 4.1. Data size. + size_t expected_data_size = DeserializeDataSize(read_ptr); + if (expected_data_size < 0) { + NEARBY_LOG( + INFO, + "Cannot deserialize BleAdvertisement: negative data size %" PRIu64, + expected_data_size); + version_ = Version::kUndefined; + return; + } + read_ptr += kDataSizeLength; + + // Check that the stated data size is the same as what we received. + size_t actual_data_size = ComputeDataSize(ble_advertisement_bytes); + if (actual_data_size < expected_data_size) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: expected data to be %zu " + "bytes, got %" PRIu64 " bytes", + expected_data_size, actual_data_size); + version_ = Version::kUndefined; + return; + } + + // 4.2. Data. + data_ = ByteArray(read_ptr, expected_data_size); + read_ptr += expected_data_size; +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray{}; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_socket_version_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 3 bits are the Socket version. 2 bits left are reserved. + version_and_socket_version_byte |= + (static_cast(socket_version_) << 2) & kSocketVersionBitmask; + // Serialize Data size bytes(4). + ByteArray data_size_bytes{kDataSizeLength}; + auto *data_size_bytes_write_ptr = data_size_bytes.data(); + SerializeDataSize(data_size_bytes_write_ptr, data_.size()); + + out.reserve(1 + service_id_hash_.size() + 1 + data_.size()); + out.append(1, version_and_socket_version_byte); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_size_bytes)); + out.append(std::string(data_)); + + return ByteArray{std::move(out)}; +} + +bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const { + return this->GetVersion() == rhs.GetVersion() && + this->GetSocketVersion() == rhs.GetSocketVersion() && + this->GetServiceIdHash() == rhs.GetServiceIdHash() && + this->GetData() == rhs.GetData(); +} + +bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const { + if (this->GetVersion() != rhs.GetVersion()) { + return this->GetVersion() < rhs.GetVersion(); + } + if (this->GetSocketVersion() != rhs.GetSocketVersion()) { + return this->GetSocketVersion() < rhs.GetSocketVersion(); + } + if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) { + return this->GetServiceIdHash() < rhs.GetServiceIdHash(); + } + return this->GetData() < rhs.GetData(); +} + +bool BleAdvertisement::IsSupportedVersion(Version version) const { + return version >= Version::kV1 && version <= Version::kV2; +} + +bool BleAdvertisement::IsSupportedSocketVersion( + SocketVersion socket_version) const { + return socket_version >= SocketVersion::kV1 && + socket_version <= SocketVersion::kV2; +} + +void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const { + // Get a raw representation of the data size bytes in memory. + char *data_size_bytes = reinterpret_cast(&data_size); + + // Append these raw bytes to advertisement bytes, keeping in mind that we need + // to convert from Little Endian to Big Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1]; + } +} + +size_t BleAdvertisement::DeserializeDataSize( + const char *data_size_bytes_read_ptr) const { + // Allocate a chunk of memory to store our deserialized size. + char data_size_bytes[kDataSizeLength]; + + // Assign the bits of our size from the given raw bytes, keeping in mind that + // we need to convert from Big Endian to Little Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1]; + } + + // Interpret the char array as a single int. + return static_cast( + *(reinterpret_cast(&data_size_bytes))); +} + +size_t BleAdvertisement::ComputeDataSize( + const ByteArray &ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +size_t BleAdvertisement::ComputeAdvertisementLength( + const ByteArray &data) const { + // The advertisement length is the minimum length + the length of the data. + return kMinAdvertisementLength + data.size(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_advertisement.h new file mode 100644 index 00000000..557b93b8 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.h @@ -0,0 +1,100 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums Ble Advertisement used in advertising +// and discovery. +// +// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA] +// +// See go/nearby-ble-design for more information. +class BleAdvertisement { + public: + // Versions of the BleAdvertisement. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // Version is only allocated 3 bits in the BleAdvertisement, so this can + // never go beyond V7. + }; + + // Versions of the BLESocket. + enum class SocketVersion { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // SocketVersion is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + + BleAdvertisement() = default; + BleAdvertisement(Version version, SocketVersion socket_version, + const ByteArray &service_id_hash, const ByteArray &data); + explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes); + BleAdvertisement(const BleAdvertisement &) = default; + BleAdvertisement &operator=(const BleAdvertisement &) = default; + BleAdvertisement(BleAdvertisement &&) = default; + BleAdvertisement &operator=(BleAdvertisement &&) = default; + ~BleAdvertisement() = default; + + explicit operator ByteArray() const; + // Operator overloads when comparing BleAdvertisement. + bool operator==(const BleAdvertisement &rhs) const; + bool operator<(const BleAdvertisement &rhs) const; + + bool IsValid() const { return IsSupportedVersion(version_); } + Version GetVersion() const { return version_; } + SocketVersion GetSocketVersion() const { return socket_version_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray &GetData() & { return data_; } + const ByteArray &GetData() const & { return data_; } + ByteArray &&GetData() && { return std::move(data_); } + const ByteArray &&GetData() const && { return std::move(data_); } + + private: + bool IsSupportedVersion(Version version) const; + bool IsSupportedSocketVersion(SocketVersion socket_version) const; + void SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const; + size_t DeserializeDataSize(const char *data_size_bytes_read_ptr) const; + size_t ComputeDataSize(const ByteArray &ble_advertisement_bytes) const; + size_t ComputeAdvertisementLength(const ByteArray &data) const; + + static constexpr int kVersionLength = 1; + // Length of one int. Be sure to re-evaluate how we compute data size in this + // class if this constant ever changes! + static constexpr int kDataSizeLength = 4; + static constexpr int kMinAdvertisementLength = + kVersionLength + kServiceIdHashLength + kDataSizeLength; + // The maximum length for a Gatt characteristic value is 512 bytes, so make + // sure the entire advertisement is less than that. The data can take up + // whatever space is remaining after the bytes preceding it. + static constexpr int kMaxGattCharacteristicValueSize = 512; + static constexpr int kMaxDataSize = + kMaxGattCharacteristicValueSize - kMinAdvertisementLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kSocketVersionBitmask = 0x01C; + + Version version_{Version::kUndefined}; + SocketVersion socket_version_{SocketVersion::kUndefined}; + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc new file mode 100644 index 00000000..e8910194 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc @@ -0,0 +1,118 @@ +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisementHeader::BleAdvertisementHeader( + Version version, int num_slots, const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash) { + // TODO(edwinwu): Checks if num_slots needs to be >= 0 + if (version != Version::kV2 || + service_id_bloom_filter.size() != kServiceIdBloomFilterLength || + advertisement_hash.size() != kAdvertisementHashLength) { + return; + } + + version_ = version; + num_slots_ = num_slots; + service_id_bloom_filter_ = service_id_bloom_filter; + advertisement_hash_ = advertisement_hash; +} + +BleAdvertisementHeader::BleAdvertisementHeader( + const std::string &ble_advertisement_header_string) { + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + + if (ble_advertisement_header_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding"); + return; + } + + if (ble_advertisement_header_bytes.size() < kMinAdvertisementHeaderLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisementHeader: expecting min %u " + "raw bytes, got %" PRIu64 " instead", + kMinAdvertisementHeaderLength, + ble_advertisement_header_bytes.size()); + return; + } + + // Start reading the bytes. + auto *ble_advertisement_header_read_ptr = + ble_advertisement_header_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_header_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV2) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisementHeader: unsupported Version %d", + version_); + return; + } + // The last 5 bits of the first byte represent the number of slots. + num_slots_ = static_cast(*ble_advertisement_header_read_ptr & + kNumSlotsBitmask); + ble_advertisement_header_read_ptr++; + + // Service ID bloom filter. + service_id_bloom_filter_ = + ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength); + ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength; + + // Advertisement hash. + advertisement_hash_ = + ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength); + ble_advertisement_header_read_ptr += kAdvertisementHashLength; +} + +BleAdvertisementHeader::operator std::string() const { + if (!IsValid()) { + return ""; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_num_slots_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the number of slots. + version_and_num_slots_byte |= + static_cast(num_slots_) & kNumSlotsBitmask; + out.reserve(1 + service_id_bloom_filter_.size() + advertisement_hash_.size()); + out.append(1, version_and_num_slots_byte); + out.append(std::string(service_id_bloom_filter_)); + out.append(std::string(advertisement_hash_)); + + return Base64Utils::Encode(ByteArray(std::move(out))); +} + +bool BleAdvertisementHeader::operator<( + const BleAdvertisementHeader &rhs) const { + if (this->GetVersion() != rhs.GetVersion()) { + return this->GetVersion() < rhs.GetVersion(); + } + if (this->GetNumSlots() != rhs.GetNumSlots()) { + return this->GetNumSlots() < rhs.GetNumSlots(); + } + if (this->GetServiceIdBloomFilter() != rhs.GetServiceIdBloomFilter()) { + return this->GetServiceIdBloomFilter() < rhs.GetServiceIdBloomFilter(); + } + return this->GetAdvertisementHash() < rhs.GetAdvertisementHash(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.h b/cpp/core_v2/internal/mediums/ble_advertisement_header.h new file mode 100644 index 00000000..aa8163df --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.h @@ -0,0 +1,84 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums BLE Advertisement Header used in +// Advertising + Discovery. +// +// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH] +// +// See go/nearby-ble-design for more information. +// +// Note. The object constructed by default constructor or the parameterized +// constructor with invalid value(s) is treated as invalid instance. Caller +// should be responsible to call IsValid() to check the instance is invalid in +// advance before continue on. +class BleAdvertisementHeader { + public: + // Versions of the BleAdvertisementHeader. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // Version is only allocated 3 bits in the BleAdvertisementHeader, so this + // can never go beyond V7. + // + // V1 is not present because it's an old format used in Nearby Connections + // before this logic was pushed down into Nearby Mediums. V1 put + // everything in the service data, while V2 puts the data inside a GATT + // characteristic so the two are not compatible. + }; + + BleAdvertisementHeader() = default; + BleAdvertisementHeader(Version version, int num_slots, + const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash); + explicit BleAdvertisementHeader( + const std::string &ble_advertisement_header_string); + ~BleAdvertisementHeader() = default; + + BleAdvertisementHeader(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader(BleAdvertisementHeader &&) = default; + BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default; + + // Produces an encoded binary string which can be decoded by the explicit + // constructor. The returned string is empty if BleAdvertisementHeader is not + // valid - false on IsValid(). + explicit operator std::string() const; + bool operator<(const BleAdvertisementHeader &rhs) const; + + bool IsValid() const { return version_ == Version::kV2; } + Version GetVersion() const { return version_; } + int GetNumSlots() const { return num_slots_; } + ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; } + ByteArray GetAdvertisementHash() const { return advertisement_hash_; } + + private: + static constexpr int kServiceIdBloomFilterLength = 10; + static constexpr int kAdvertisementHashLength = 4; + static constexpr int kMinAdvertisementHeaderLength = + 1 + kServiceIdBloomFilterLength + kAdvertisementHashLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kNumSlotsBitmask = 0x01F; + + Version version_ = Version::kUndefined; + int num_slots_; + ByteArray service_id_bloom_filter_; + ByteArray advertisement_hash_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc new file mode 100644 index 00000000..30bfe536 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc @@ -0,0 +1,176 @@ +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { +constexpr BleAdvertisementHeader::Version kVersion = + BleAdvertisementHeader::Version::kV2; +constexpr int kNumSlots = 2; +constexpr char kServiceIDBloomFilter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"; +constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d"; + +TEST(BleAdvertisementHeaderTest, ConstructionWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithShortServiceIdBloomFilter) { + char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09"; + + ByteArray short_service_id_bloom_filter_bytes(short_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, short_service_id_bloom_filter_bytes, + advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithLongServiceIdBloomFilter) { + char long_service_id_bloom_filter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"; + + ByteArray service_id_bloom_filter(long_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { + char short_advertisement_hash[] = "\x0a\x0b\x0c"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(short_advertisement_hash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { + char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\0x0e"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(long_advertisement_hash, + sizeof(long_advertisement_hash) / sizeof(char)); + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader org_ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = + std::string(org_ble_advertisement_header); + + auto ble_advertisement_header = + BleAdvertisementHeader(ble_advertisement_header_string); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, add a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray long_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() + 1); + long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes); + std::string long_ble_advertisement_header_string = + Base64Utils::Encode(long_ble_advertisement_header_bytes); + + auto long_ble_advertisement_header = + BleAdvertisementHeader(long_ble_advertisement_header_string); + + EXPECT_TRUE(long_ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, long_ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + long_ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + long_ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, remove a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray short_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() - 1); + short_ble_advertisement_header_bytes.CopyAt(0, + ble_advertisement_header_bytes); + std::string short_ble_advertisement_header_string = + Base64Utils::Encode(short_ble_advertisement_header_bytes); + + auto short_ble_advertisement_header = + BleAdvertisementHeader(short_ble_advertisement_header_string); + + EXPECT_FALSE(short_ble_advertisement_header.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc new file mode 100644 index 00000000..cefb7f7a --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc @@ -0,0 +1,223 @@ +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; +const BleAdvertisement::SocketVersion kSocketVersion = + BleAdvertisement::SocketVersion::kV2; +const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; +const char kData[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +// This corresponds to the length of a specific BleAdvertisement packed with the +// kData given above. Be sure to update this if kData ever changes. +const size_t kAdvertisementLength = 77; +const size_t kLongAdvertisementLength = kAdvertisementLength + 1000; + +TEST(BleAdvertisementTest, ConstructionWorksV1) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, + BleAdvertisement::SocketVersion::kV1, + service_id_hash, data}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); + EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, + ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + BleAdvertisement::Version bad_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{bad_version, kSocketVersion, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { + BleAdvertisement::SocketVersion bad_socket_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, bad_socket_version, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = "\x0a\x0b"; + + ByteArray bad_service_id_hash{short_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; + + ByteArray bad_service_id_hash{long_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { + // BleAdvertisement shouldn't be able to support data with the max GATT + // attribute length because it needs some room for the preceding fields. + char long_data[512]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray bad_data{long_data, 512}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash, + bad_data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { + char empty_data[0]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{empty_data}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Copy the bytes into a new array with extra bytes. We must explicitly + // define how long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kLongAdvertisementLength]{}; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + std::min(sizeof(raw_ble_advertisement_bytes), + org_ble_advertisement_bytes.size())); + + // Re-parse the Ble advertisement using our extra long advertisement bytes. + ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kLongAdvertisementLength}; + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + + EXPECT_TRUE(long_ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size()); + EXPECT_EQ(data, long_ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + BleAdvertisement ble_advertisement{ByteArray{}}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Cut off the advertisement so that it's too short. + ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), + 7}; + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithInvalidDataLengthFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble + // advertisement bytes so we can modify it. We must explicitly define how + // long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + kAdvertisementLength); + + // The data size field lives in indices 4-7. Corrupt it. + memset(raw_ble_advertisement_bytes + 4, 0xFF, 4); + + // Try to parse the Ble advertisement using our corrupted advertisement bytes. + ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kAdvertisementLength}; + BleAdvertisement corrupted_ble_advertisement{ + corrupted_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.cc b/cpp/core_v2/internal/mediums/ble_packet.cc new file mode 100644 index 00000000..0cfb14ff --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.cc @@ -0,0 +1,59 @@ +#include "core_v2/internal/mediums/ble_packet.h" + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) { + if (service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + service_id_hash_ = service_id_hash; + data_ = data; +} + +BlePacket::BlePacket(const ByteArray& ble_packet_bytes) { + if (ble_packet_bytes.Empty()) { + NEARBY_LOG(ERROR, "Cannot deserialize BlePacket: null bytes passed in"); + return; + } + + if (ble_packet_bytes.size() < kServiceIdHashLength) { + NEARBY_LOG( + INFO, + "Cannot deserialize BlePacket: expecting min %u raw bytes, got %zu", + kServiceIdHashLength, ble_packet_bytes.size()); + return; + } + + const char *ble_packet_bytes_read_ptr = ble_packet_bytes.data(); + service_id_hash_ = + ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength); + ble_packet_bytes_read_ptr += kServiceIdHashLength; + + data_ = ByteArray(ble_packet_bytes_read_ptr, + ble_packet_bytes.size() - kServiceIdHashLength); +} + +BlePacket::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + out.reserve(service_id_hash_.size() + data_.size()); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_)); + + return ByteArray(std::move(out)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.h b/cpp/core_v2/internal/mediums/ble_packet.h new file mode 100644 index 00000000..159f6349 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.h @@ -0,0 +1,51 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of data sent over Ble sockets. +// +// [SERVICE_ID_HASH][DATA] +// +// See go/nearby-ble-design for more information. +class BlePacket { + public: + static const std::uint32_t kServiceIdHashLength = 3; + + BlePacket() = default; + BlePacket(const ByteArray& service_id_hash, const ByteArray& data); + explicit BlePacket(const ByteArray& ble_packet_byte); + ~BlePacket() = default; + + BlePacket(const BlePacket&) = default; + BlePacket& operator=(const BlePacket&) = default; + BlePacket(BlePacket&&) = default; + BlePacket& operator=(BlePacket&&) = default; + + explicit operator ByteArray() const; + + bool IsValid() const { return !service_id_hash_.Empty(); } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray GetData() const { return data_; } + + private: + static const std::uint32_t kMaxDataSize = + std::numeric_limits::max() - kServiceIdHashLength; + + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_packet_test.cc new file mode 100644 index 00000000..b9a1c858 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet_test.cc @@ -0,0 +1,97 @@ +#include "core_v2/internal/mediums/ble_packet.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +constexpr char kServiceIDHash[] = "\x0a\x0b\x0c"; +constexpr char kData[] = "\x01\x02\x03\x04\x05"; + +TEST(BlePacketTest, ConstructionWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionWorksWithEmptyData) { + char empty_data[] = {}; + + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(empty_data); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash[] = "\x0a\x0b"; + + ByteArray service_id_hash(short_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash[] = "\x0a\x0b\x0c\x0d"; + + ByteArray service_id_hash(long_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray ble_packet_bytes(org_ble_packet); + + BlePacket ble_packet(ble_packet_bytes); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFromNullBytesFails) { + BlePacket ble_packet(ByteArray{}); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromShortLengthDataFails) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray org_ble_packet_bytes(org_ble_packet); + + // Cut off the packet so that it's too short + ByteArray short_ble_packet_bytes(ByteArray(org_ble_packet_bytes.data(), 2)); + + BlePacket short_ble_packet(short_ble_packet_bytes); + + EXPECT_FALSE(short_ble_packet.IsValid()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_peripheral.h b/cpp/core_v2/internal/mediums/ble_peripheral.h new file mode 100644 index 00000000..01d0b594 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral.h @@ -0,0 +1,36 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class BlePeripheral { + public: + BlePeripheral() = default; + explicit BlePeripheral(const ByteArray& id) : id_(id) {} + ~BlePeripheral() = default; + + BlePeripheral(const BlePeripheral&) = default; + BlePeripheral& operator=(const BlePeripheral&) = default; + BlePeripheral(BlePeripheral&&) = default; + BlePeripheral& operator=(BlePeripheral&&) = default; + + bool IsValid() const { return !id_.Empty(); } + ByteArray GetId() const { return id_; } + + private: + // A unique identifier for this peripheral. It can be the BLE advertisement it + // was found on, or even simply the BLE MAC address. + ByteArray id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc new file mode 100644 index 00000000..d43c375a --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc @@ -0,0 +1,33 @@ +#include "core_v2/internal/mediums/ble_peripheral.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const char kId[] = "AB12"; + +TEST(BlePeripheralTest, ConstructionWorks) { + ByteArray id(kId); + + BlePeripheral ble_peripheral(id); + + EXPECT_TRUE(ble_peripheral.IsValid()); + EXPECT_EQ(id, ble_peripheral.GetId()); +} + +TEST(BlePeripheralTest, ConstructionEmptyFails) { + BlePeripheral ble_peripheral; + + EXPECT_FALSE(ble_peripheral.IsValid()); + EXPECT_TRUE(ble_peripheral.GetId().Empty()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.cc b/cpp/core_v2/internal/mediums/bluetooth_radio.cc new file mode 100644 index 00000000..77a7ec00 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.cc @@ -0,0 +1,104 @@ +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "platform_v2/base/exception.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothRadio::BluetoothRadio() { + if (!IsAdapterValid()) { + NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported"); + } +} + +BluetoothRadio::~BluetoothRadio() { + // We never enabled Bluetooth, nothing to do. + if (!ever_saved_state_.Get()) { + NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW."); + return; + } + + // Toggle Bluetooth regardless of our original state. Some devices/chips can + // start to freak out after some time (e.g. b/37775337), and this helps to + // ensure BT resets properly. + NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter."); + Toggle(); + + NEARBY_LOG(INFO, "Bring BT adapter to original state"); + if (!SetBluetoothState(originally_enabled_.Get())) { + NEARBY_LOG(INFO, "Failed to restore BT adapter original state."); + } +} + +bool BluetoothRadio::Enable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(true); +} + +bool BluetoothRadio::Disable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(false); +} + +bool BluetoothRadio::IsEnabled() const { + return IsAdapterValid() && IsInDesiredState(true); +} + +bool BluetoothRadio::Toggle() { + if (!SaveOriginalState()) { + return false; + } + + if (!SetBluetoothState(false)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off."); + return false; + } + + if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) { + NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on."); + return false; + } + + if (!SetBluetoothState(true)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on."); + return false; + } + + return true; +} + +bool BluetoothRadio::SetBluetoothState(bool enable) { + return bluetooth_adapter_.SetStatus( + enable ? BluetoothAdapter::Status::kEnabled + : BluetoothAdapter::Status::kDisabled); +} + +bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const { + return bluetooth_adapter_.IsEnabled() == should_be_enabled; +} + +bool BluetoothRadio::SaveOriginalState() { + if (!IsAdapterValid()) { + return false; + } + + // If we haven't saved the original state of the radio, save it. + if (!ever_saved_state_.Set(true)) { + originally_enabled_.Set(bluetooth_adapter_.IsEnabled()); + } + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.h b/cpp/core_v2/internal/mediums/bluetooth_radio.h new file mode 100644 index 00000000..ebec1881 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.h @@ -0,0 +1,80 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ + +#include + +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +// Provides the operations that can be performed on the Bluetooth radio. +class BluetoothRadio { + public: + BluetoothRadio(); + BluetoothRadio(BluetoothRadio&&) = default; + BluetoothRadio& operator=(BluetoothRadio&&) = default; + + // Reverts the Bluetooth radio to its original state. + ~BluetoothRadio(); + + // Enables Bluetooth. + // + // This must be called before attempting to invoke any other methods of + // this class. + // + // Returns true if enabled successfully. + bool Enable(); + + // Disables Bluetooth. + // + // Returns true if disabled successfully. + bool Disable(); + + // Returns true if the Bluetooth radio is currently enabled. + bool IsEnabled() const; + + // Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On. + // This will block calling thread for at least kPauseBetweenToggle duration. + bool Toggle(); + + // Returns result of BluetoothAdapter::IsValid() for private adapter instance. + bool IsAdapterValid() const { + return bluetooth_adapter_.IsValid(); + } + + BluetoothAdapter& GetBluetoothAdapter() { + return bluetooth_adapter_; + } + + private: + static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3); + + bool SetBluetoothState(bool enable); + bool IsInDesiredState(bool should_be_enabled) const; + // To be called in enable(), disable(), and toggle(). This will remember the + // original state of the radio before any radio state has been modified. + // Returns false if Bluetooth doesn't exist on the device and the state cannot + // be obtained. + bool SaveOriginalState(); + + // BluetoothAdapter::IsValid() will return false if BT is not supported. + BluetoothAdapter bluetooth_adapter_; + + // The Bluetooth radio's original state, before we modified it. True if + // originally enabled, false if originally disabled. + // We restore the radio to its original state in the destructor. + + AtomicBoolean originally_enabled_{false}; + // false if we never modified the radio state, true otherwise. + AtomicBoolean ever_saved_state_{false}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc new file mode 100644 index 00000000..f02d19de --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc @@ -0,0 +1,45 @@ +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(BluetoothRadioTest, ConstructorDestructorWorks) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); +} + +TEST(BluetoothRadioTest, CanEnable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanDisable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); + EXPECT_TRUE(radio.Disable()); + EXPECT_FALSE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanToggle) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Toggle()); + EXPECT_TRUE(radio.IsEnabled()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker.h b/cpp/core_v2/internal/mediums/lost_entity_tracker.h new file mode 100644 index 00000000..e83b21f1 --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker.h @@ -0,0 +1,80 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ + +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Tracks "lost" entities based on a manual update/compute model. Used by +// mediums that only report found devices. Lost entities are computed based off +// of whether a specific entity was rediscovered since the last call to +// ComputeLostEntities. +// +// Note: Entity must overload the < and == operators. +template +class LostEntityTracker { + public: + using EntitySet = absl::flat_hash_set; + + LostEntityTracker(); + ~LostEntityTracker(); + + // Records the given entity as being recently found, whether or not this is + // our first time discovering the entity. + void RecordFoundEntity(const Entity& entity) ABSL_LOCKS_EXCLUDED(mutex_); + + // Computes and returns the set of entities considered lost since the last + // time this method was called. + EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Mutex mutex_; + EntitySet current_entities_ ABSL_GUARDED_BY(mutex_); + EntitySet previously_found_entities_ ABSL_GUARDED_BY(mutex_); +}; + +template +LostEntityTracker::LostEntityTracker() + : current_entities_{}, previously_found_entities_{} {} + +template +LostEntityTracker::~LostEntityTracker() { + previously_found_entities_.clear(); + current_entities_.clear(); +} + +template +void LostEntityTracker::RecordFoundEntity(const Entity& entity) { + MutexLock lock(&mutex_); + + current_entities_.insert(entity); +} + +template +typename LostEntityTracker::EntitySet +LostEntityTracker::ComputeLostEntities() { + MutexLock lock(&mutex_); + + // The set of lost entities is the previously found set MINUS the currently + // found set. + for (const auto& item : current_entities_) { + previously_found_entities_.erase(item); + } + auto lost_entities = std::move(previously_found_entities_); + previously_found_entities_ = std::move(current_entities_); + current_entities_ = {}; + + return lost_entities; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc new file mode 100644 index 00000000..829aaadf --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc @@ -0,0 +1,123 @@ +#include "core_v2/internal/mediums/lost_entity_tracker.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +struct TestEntity { + int id; + + template + friend H AbslHashValue(H h, const TestEntity& test_entity) { + return H::combine(std::move(h), test_entity.id); + } + + bool operator==(const TestEntity& other) const { return id == other.id; } + bool operator<(const TestEntity& other) const { return id < other.id; } +}; + +TEST(LostEntityTrackerTest, NoEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Rediscover the same entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure we still didn't lose any entities. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); +} + +TEST(LostEntityTrackerTest, AllEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through a round without rediscovering any entities. + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) != lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SomeEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through the next round only rediscovering one of our entities and + // discovering an additional entity as well. Then, verify that only one entity + // was lost after the check. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_3); + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) == lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) == lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SameEntityMultipleCopies) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_1_copy{1}; + + // Discover an entity. + lost_entity_tracker.RecordFoundEntity(entity_1); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Rediscover the same entity, but through a copy of it. + lost_entity_tracker.RecordFoundEntity(entity_1_copy); + + // Make sure none are lost on the second round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through a round without rediscovering any entities and verify that we + // lost an entity equivalent to both copies of it. + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_EQ(lost_entities.size(), 1); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_1_copy) != lost_entities.end()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc new file mode 100644 index 00000000..6785345a --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -0,0 +1,41 @@ +#include "core_v2/internal/mediums/utils.h" + +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { + +ByteArray Utils::GenerateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.NextUint32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return ByteArray(data); +} + +ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) { + ByteArray full_hash(length); + full_hash.CopyAt(0, Crypto::Sha256(std::string(source))); + return full_hash; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.h b/cpp/core_v2/internal/mediums/utils.h new file mode 100644 index 00000000..7234a897 --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.h @@ -0,0 +1,22 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +class Utils { + public: + static ByteArray GenerateRandomBytes(size_t length); + static ByteArray Sha256Hash(const ByteArray& source, size_t length); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ diff --git a/cpp/core_v2/internal/mediums/uuid.cc b/cpp/core_v2/internal/mediums/uuid.cc new file mode 100644 index 00000000..2bd8b947 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.cc @@ -0,0 +1,75 @@ +#include "core_v2/internal/mediums/uuid.h" + +#include +#include + +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { +std::ostream& write_hex(std::ostream& os, absl::string_view data) { + for (const auto b : data) { + os << std::setfill('0') + << std::setw(2) + << std::hex + << (static_cast(b) & 0x0ff); + } + return os; +} +} // namespace + +Uuid::Uuid(absl::string_view data) : data_(Crypto::Md5(data)) { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162. + data_[6] &= 0x0f; // Clear version. + data_[6] |= 0x30; // Set to version 3. + data_[8] &= 0x3f; // Clear variant. + data_[8] |= 0x80; // Set to IETF variant. +} + +Uuid::Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits) { + // Base on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104. + data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits)); + + data_[0] = static_cast((most_sig_bits >> 56) & 0x0ff); + data_[1] = static_cast((most_sig_bits >> 48) & 0x0ff); + data_[2] = static_cast((most_sig_bits >> 40) & 0x0ff); + data_[3] = static_cast((most_sig_bits >> 32) & 0x0ff); + data_[4] = static_cast((most_sig_bits >> 24) & 0x0ff); + data_[5] = static_cast((most_sig_bits >> 16) & 0x0ff); + data_[6] = static_cast((most_sig_bits >> 8) & 0x0ff); + data_[7] = static_cast((most_sig_bits >> 0) & 0x0ff); + + data_[8] = static_cast((least_sig_bits >> 56) & 0x0ff); + data_[9] = static_cast((least_sig_bits >> 48) & 0x0ff); + data_[10] = static_cast((least_sig_bits >> 40) & 0x0ff); + data_[11] = static_cast((least_sig_bits >> 32) & 0x0ff); + data_[12] = static_cast((least_sig_bits >> 24) & 0x0ff); + data_[13] = static_cast((least_sig_bits >> 16) & 0x0ff); + data_[14] = static_cast((least_sig_bits >> 8) & 0x0ff); + data_[15] = static_cast((least_sig_bits >> 0) & 0x0ff); +} + +Uuid::operator std::string() const { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375. + std::ostringstream md5_hex; + write_hex(md5_hex, absl::string_view(&data_[0], 4)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[4], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[6], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[8], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[10], 6)); + + return md5_hex.str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/uuid.h b/cpp/core_v2/internal/mediums/uuid.h new file mode 100644 index 00000000..e197ff69 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.h @@ -0,0 +1,45 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_UUID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UUID_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// A type 3 name-based +// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) +// UUID. +// +// https://developer.android.com/reference/java/util/UUID.html +class Uuid final { + public: + Uuid() : Uuid("uuid") {} + explicit Uuid(absl::string_view data); + Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits); + Uuid(const Uuid&) = default; + Uuid& operator=(const Uuid&) = default; + Uuid(Uuid&&) = default; + Uuid& operator=(Uuid&&) = default; + ~Uuid() = default; + + // Returns the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the + // UUID. + explicit operator std::string() const; + std::string data() const { + return data_; + } + + private: + std::string data_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UUID_H_ diff --git a/cpp/core_v2/internal/mediums/uuid_test.cc b/cpp/core_v2/internal/mediums/uuid_test.cc new file mode 100644 index 00000000..f5872dfa --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid_test.cc @@ -0,0 +1,56 @@ +#include "core_v2/internal/mediums/uuid.h" + +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr char kString[] = "some string"; +constexpr std::uint64_t kNum1 = 0x123456789abcdef0; +constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f; + +TEST(UuidTest, CreateFromStringWithMd5) { + Uuid uuid(kString); + std::string uuid_str(uuid); + std::string uuid_data(uuid.data()); + std::string md5_data(Crypto::Md5(kString)); + NEARBY_LOG(INFO, "MD5-based UUID: '%s'", uuid_str.c_str()); + uuid_data[6] = 0; + uuid_data[8] = 0; + md5_data[6] = 0; + md5_data[8] = 0; + EXPECT_EQ(md5_data, uuid_data); +} + +TEST(UuidTest, CreateFromBinary) { + Uuid uuid(kNum1, kNum2); + std::string uuid_data(uuid.data()); + std::string uuid_str(uuid); + NEARBY_LOG(INFO, "UUID: '%s'", uuid_str.c_str()); + EXPECT_EQ(uuid_data[0], (kNum1 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[1], (kNum1 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[2], (kNum1 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[3], (kNum1 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[4], (kNum1 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[5], (kNum1 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[6], (kNum1 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[7], (kNum1 >> 0) & 0xFF); + EXPECT_EQ(uuid_data[8], (kNum2 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[9], (kNum2 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[10], (kNum2 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[11], (kNum2 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[12], (kNum2 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[13], (kNum2 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[14], (kNum2 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[15], (kNum2 >> 0) & 0xFF); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..9e8cc9e8 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -0,0 +1,76 @@ +cc_library( + name = "webrtc", + srcs = [ + "webrtc_socket.cc", + ], + hdrs = [ + "webrtc_socket.h", + ], + deps = [ + "//core_v2:core_types", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform_v2/base", + "//platform_v2/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform_v2/base", + "//platform_v2/impl/g3", #buildcleaner: keep + "//platform_v2/public", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform_v2/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep + ], +) + +cc_library( + name = "peer_id", + srcs = ["peer_id.cc"], + hdrs = ["peer_id.h"], + deps = [ + "//core_v2/internal/mediums:utils", + "//platform_v2/base", + "//absl/strings", + ], +) + +cc_library( + name = "signaling_frames", + srcs = ["signaling_frames.cc"], + hdrs = ["signaling_frames.h"], + deps = [ + ":peer_id", + "//platform_v2/base", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + ], +) diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..71d2c5db --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,38 @@ +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core_v2/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(const ByteArray& bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes.data(), bytes.size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +PeerId PeerId::FromRandom() { + return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength)); +} + +PeerId PeerId::FromSeed(const ByteArray& seed) { + ByteArray full_hash(Utils::Sha256Hash(seed, kPeerIdLength)); + ByteArray hashed_seed(full_hash.data(), kPeerIdLength / 2); + return PeerId(BytesToStringUppercase(hashed_seed)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.h b/cpp/core_v2/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..e2bd1262 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,35 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static PeerId FromRandom(); + static PeerId FromSeed(const ByteArray& seed); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..37b54d04 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,42 @@ +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/crypto.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +TEST(PeerIdTest, GenerateRandomPeerId) { + PeerId peer_id = PeerId::FromRandom(); + EXPECT_EQ(64, peer_id.GetId().size()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "seed"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + ByteArray seed_bytes(seed); + PeerId peer_id = PeerId::FromSeed(seed_bytes); + + EXPECT_EQ(64, peer_id.GetId().size()); + EXPECT_EQ(expected_peer_id, peer_id.GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + EXPECT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..7bb7872b --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,120 @@ +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return ByteArray(message.c_str(), message.size()); +} + +void SetSenderId(const PeerId& sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id.GetId()); +} + +std::unique_ptr DecodeIceCandidate( + location::nearby::mediums::IceCandidate ice_candidate_proto) { + webrtc::SdpParseError error; + return std::unique_ptr( + webrtc::CreateIceCandidate(ice_candidate_proto.sdp_mid(), + ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.set_allocated_ready_for_signaling_poke( + new location::nearby::mediums::ReadyForSignalingPoke()); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +std::unique_ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()); +} + +std::unique_ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..63a92718 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,44 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "platform_v2/base/byte_array.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id); + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer); +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +std::unique_ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +std::unique_ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..54ecd527 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,182 @@ +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + PeerId sender_id("abc"); + ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + PeerId sender_id("abc"); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ByteArray encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer.data(), encoded_offer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignaingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + std::unique_ptr decoded_offer = + DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + PeerId sender_id("abc"); + std::unique_ptr answer( + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp)); + ByteArray encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer.data(), encoded_answer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + std::unique_ptr decoded_answer = + DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + PeerId sender_id("abc"); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate)); + } + ByteArray encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates.data(), encoded_candidates.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> + decoded_candidates = DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..a961ee0d --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,101 @@ +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) { + if (data.size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return {Exception::kIo}; + } + + socket_->BlockUntilSufficientSpaceInBuffer(data.size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return {Exception::kIo}; + } + + if (!socket_->SendMessage(data)) { + return {Exception::kIo}; + } + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Flush() { + // Java implementation is empty. + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Close() { + socket_->Close(); + return {Exception::kSuccess}; +} + +// WebRtcSocket +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), data_channel_(std::move(data_channel)) {} + +InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); } + +OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; } + +void WebRtcSocket::Close() { + if (IsClosed()) return; + + closed_.Set(true); + pipe_.GetInputStream().Close(); + pipe_.GetOutputStream().Close(); + data_channel_->Close(); + WakeUpWriter(); + socket_closed_listener_.socket_closed_cb(); +} + +void WebRtcSocket::NotifyDataChannelMsgReceived(const ByteArray& message) { + if (!pipe_.GetOutputStream().Write(message).Ok()) { + Close(); + return; + } + + if (!pipe_.GetOutputStream().Flush().Ok()) Close(); +} + +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { WakeUpWriter(); } + +bool WebRtcSocket::SendMessage(const ByteArray& data) { + return data_channel_->Send( + webrtc::DataBuffer(std::string(data.data(), data.size()))); +} + +bool WebRtcSocket::IsClosed() { return closed_.Get(); } + +void WebRtcSocket::WakeUpWriter() { + MutexLock lock(&backpressure_mutex_); + buffer_variable_.Notify(); +} + +void WebRtcSocket::SetOnSocketClosedListener(SocketClosedListener&& listener) { + socket_closed_listener_ = std::move(listener); +} + +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + MutexLock lock(&backpressure_mutex_); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_.Wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..e5d90939 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,101 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include + +#include "core_v2/listeners.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/base/socket.h" +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/pipe.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + InputStream& GetInputStream() override; + OutputStream& GetOutputStream() override; + void Close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(const ByteArray& message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + struct SocketClosedListener { + std::function socket_closed_cb = DefaultCallback<>(); + }; + + void SetOnSocketClosedListener(SocketClosedListener&& listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(const ByteArray& data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Pipe pipe_; + + OutputStreamImpl output_stream_{this}; + + AtomicBoolean closed_{false}; + + SocketClosedListener socket_closed_listener_; + + mutable Mutex backpressure_mutex_; + ConditionVariable buffer_variable_{&backpressure_mutex_}; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..89184569 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,154 @@ +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +// using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +TEST(WebRtcSocketTest, ReadFromSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"Me"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ssa"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ge"}); + + ExceptionOr result; + + // This behaviour is different from the Java code + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"Me"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ssa"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ge"}); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok()); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + const ByteArray kMessage{kMaxDataSize + 1}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Close()); + + int socket_closed_cb_called = 0; + + webrtc_socket.SetOnSocketClosedListener( + {.socket_closed_cb = [&]() { socket_closed_cb_called++; }}); + webrtc_socket.Close(); + + EXPECT_EQ(socket_closed_cb_called, 1); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.Close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.GetOutputStream().Write(kMessage); + webrtc_socket.Close(); + + EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mock_service_controller.h b/cpp/core_v2/internal/mock_service_controller.h new file mode 100644 index 00000000..f2668139 --- /dev/null +++ b/cpp/core_v2/internal/mock_service_controller.h @@ -0,0 +1,71 @@ +#ifndef CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ + +#include "core_v2/internal/service_controller.h" +#include "gmock/gmock.h" + +namespace location { +namespace nearby { +namespace connections { + +/* Mock implementation for ServiceController: + * All methods execute asynchronously (in a private executor thread). + * To synchronise, two approaches may be used: + * 1. For methods that have result callback, we use it to unblock main thread. + * 2. For methods that do not have callbacks, we provide a mock implementation + * that unblocks main thread. + */ +class MockServiceController : public ServiceController { + public: + MOCK_METHOD(Status, StartAdvertising, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(void, StopAdvertising, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, StartDiscovery, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener), + (override)); + + MOCK_METHOD(void, StopDiscovery, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, RequestConnection, + (ClientProxy * client, const std::string& endpoint_id, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(Status, AcceptConnection, + (ClientProxy * client, const std::string& endpoint_id, + const PayloadListener& listener), + (override)); + + MOCK_METHOD(Status, RejectConnection, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, InitiateBandwidthUpgrade, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, SendPayload, + (ClientProxy * client, + const std::vector& endpoint_ids, Payload payload), + (override)); + + MOCK_METHOD(Status, CancelPayload, + (ClientProxy * client, std::int64_t payload_id), (override)); + + MOCK_METHOD(void, DisconnectFromEndpoint, + (ClientProxy * client, const std::string& endpoint_id), + (override)); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc new file mode 100644 index 00000000..792922bb --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.cc @@ -0,0 +1,251 @@ +#include "core_v2/internal/offline_frames.h" + +#include +#include + +#include "core/internal/message_lite.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using ExceptionOrOfflineFrame = ExceptionOr; +using Medium = proto::connections::Medium; +using MessageLite = ::google3_proto_compat::MessageLite; + +ByteArray ToBytes(OfflineFrame&& frame) { + ByteArray bytes(frame.ByteSizeLong()); + frame.set_version(OfflineFrame::V1); + frame.SerializeToArray(bytes.data(), bytes.size()); + return bytes; +} + +} // namespace + +ExceptionOrOfflineFrame FromBytes(const ByteArray& bytes) { + OfflineFrame frame; + + if (frame.ParseFromString(std::string(bytes))) { + return ExceptionOrOfflineFrame(std::move(frame)); + } else { + return ExceptionOrOfflineFrame(Exception::kInvalidProtocolBuffer); + } +} + +V1Frame::FrameType GetFrameType(const OfflineFrame& frame) { + if ((frame.version() == OfflineFrame::V1) && frame.has_v1()) { + return frame.v1().type(); + } + + return V1Frame::UNKNOWN_FRAME_TYPE; +} + +ByteArray ForConnectionRequest(const std::string& endpoint_id, + const std::string& endpoint_name, + std::int32_t nonce, + const std::vector& mediums) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + auto* connection_request = v1_frame->mutable_connection_request(); + connection_request->set_endpoint_id(endpoint_id); + connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); + connection_request->set_nonce(nonce); + for (const auto& medium : mediums) { + connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); + } + + return ToBytes(std::move(frame)); +} + +ByteArray ForConnectionResponse(std::int32_t status) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_RESPONSE); + auto* sub_frame = v1_frame->mutable_connection_response(); + sub_frame->set_status(status); + + return ToBytes(std::move(frame)); +} + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::DATA); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_payload_chunk() = chunk; + + return ToBytes(std::move(frame)); +} + +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::CONTROL); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_control_message() = control; + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, + const std::string& password, + std::int32_t port) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium( + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); + auto* wifi_hotspot_credentials = + upgrade_path_info->mutable_wifi_hotspot_credentials(); + wifi_hotspot_credentials->set_ssid(ssid); + wifi_hotspot_credentials->set_password(password); + wifi_hotspot_credentials->set_port(port); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeLastWrite() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeSafeToClose() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION); + auto* client_introduction = sub_frame->mutable_client_introduction(); + client_introduction->set_endpoint_id(endpoint_id); + + return ToBytes(std::move(frame)); +} + +ByteArray ForKeepAlive() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::KEEP_ALIVE); + v1_frame->mutable_keep_alive(); + + return ToBytes(std::move(frame)); +} + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium) { + switch (medium) { + case Medium::MDNS: + return ConnectionRequestFrame::MDNS; + case Medium::BLUETOOTH: + return ConnectionRequestFrame::BLUETOOTH; + case Medium::WIFI_HOTSPOT: + return ConnectionRequestFrame::WIFI_HOTSPOT; + case Medium::BLE: + return ConnectionRequestFrame::BLE; + case Medium::WIFI_LAN: + return ConnectionRequestFrame::WIFI_LAN; + case Medium::WIFI_AWARE: + return ConnectionRequestFrame::WIFI_AWARE; + case Medium::NFC: + return ConnectionRequestFrame::NFC; + case Medium::WIFI_DIRECT: + return ConnectionRequestFrame::WIFI_DIRECT; + case Medium::WEB_RTC: + return ConnectionRequestFrame::WEB_RTC; + default: + return ConnectionRequestFrame::UNKNOWN_MEDIUM; + } +} + +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium) { + switch (medium) { + case ConnectionRequestFrame::MDNS: + return Medium::MDNS; + case ConnectionRequestFrame::BLUETOOTH: + return Medium::BLUETOOTH; + case ConnectionRequestFrame::WIFI_HOTSPOT: + return Medium::WIFI_HOTSPOT; + case ConnectionRequestFrame::BLE: + return Medium::BLE; + case ConnectionRequestFrame::WIFI_LAN: + return Medium::WIFI_LAN; + case ConnectionRequestFrame::WIFI_AWARE: + return Medium::WIFI_AWARE; + case ConnectionRequestFrame::NFC: + return Medium::NFC; + case ConnectionRequestFrame::WIFI_DIRECT: + return Medium::WIFI_DIRECT; + case ConnectionRequestFrame::WEB_RTC: + return Medium::WEB_RTC; + default: + return Medium::UNKNOWN_MEDIUM; + } +} + +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& frame) { + std::vector result; + for (const auto& int_medium : frame.mediums()) { + result.push_back(ConnectionRequestMediumToMedium( + static_cast(int_medium))); + } + return result; +} + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_frames.h b/cpp/core_v2/internal/offline_frames.h new file mode 100644 index 00000000..81bf8aca --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.h @@ -0,0 +1,61 @@ +#ifndef CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ +#define CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ + +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { + +// Serialize/Deserialize Nearby Connections Protocol messages. + +// Parses incoming message. +// Returns OfflineFrame if parser was able to understand it, or +// Exception::kInvalidProtocolBuffer, if parser failed. +ExceptionOr FromBytes(const ByteArray& offline_frame_bytes); + +// Returns FrameType of a parsed message, or +// V1Frame::UNKNOWN_FRAME_TYPE, if frame contents is not recognized. +V1Frame::FrameType GetFrameType(const OfflineFrame& offline_frame); + +// Build ConnectionRequest message. +ByteArray ForConnectionRequest( + const std::string& endpoint_id, const std::string& endpoint_name, + std::int32_t nonce, const std::vector& mediums); +ByteArray ForConnectionResponse(std::int32_t status); + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk); +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control); + +ByteArray ForBandwidthUpgradeWifiHotspot( + const std::string& ssid, const std::string& password, std::int32_t port); +ByteArray ForBandwidthUpgradeLastWrite(); +ByteArray ForBandwidthUpgradeSafeToClose(); +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id); + +ByteArray ForKeepAlive(); + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium); +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium); +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& connection_request_frame); + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc new file mode 100644 index 00000000..b0dedddd --- /dev/null +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -0,0 +1,252 @@ +#include "core_v2/internal/offline_frames.h" + +#include +#include +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using Medium = proto::connections::Medium; +using ::testing::EqualsProto; + +constexpr char kEndpointId[] = "ABC"; +constexpr char kEndpointName[] = "XYZ"; +constexpr int kNonce = 1234; +constexpr std::array kMediums = { + Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, + Medium::BLE, Medium::WIFI_LAN, Medium::WIFI_AWARE, + Medium::NFC, Medium::WIFI_DIRECT, Medium::WEB_RTC, +}; + +TEST(OfflineFramesTest, CanParseMessageFromBytes) { + OfflineFrame tx_message; + + { + tx_message.set_version(OfflineFrame::V1); + auto* v1_frame = tx_message.mutable_v1(); + auto* sub_frame = v1_frame->mutable_connection_request(); + + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + sub_frame->set_endpoint_id(kEndpointId); + sub_frame->set_endpoint_name(kEndpointName); + sub_frame->set_endpoint_info(kEndpointName); + sub_frame->set_nonce(kNonce); + for (auto& medium : kMediums) { + sub_frame->add_mediums(MediumToConnectionRequestMedium(medium)); + } + } + auto serialized_bytes = ByteArray(tx_message.SerializeAsString()); + auto ret_value = FromBytes(serialized_bytes); + ASSERT_TRUE(ret_value.ok()); + const auto& rx_message = ret_value.result(); + EXPECT_THAT(rx_message, EqualsProto(tx_message)); + EXPECT_EQ(GetFrameType(rx_message), V1Frame::CONNECTION_REQUEST); + EXPECT_EQ( + ConnectionRequestMediumsToMediums(rx_message.v1().connection_request()), + std::vector(kMediums.begin(), kMediums.end())); +} + +TEST(OfflineFramesTest, CanGenerateConnectionRequest) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_REQUEST + connection_request: < + endpoint_id: "ABC" + endpoint_name: "XYZ" + endpoint_info: "XYZ" + nonce: 1234 + mediums: MDNS + mediums: BLUETOOTH + mediums: WIFI_HOTSPOT + mediums: BLE + mediums: WIFI_LAN + mediums: WIFI_AWARE + mediums: NFC + mediums: WIFI_DIRECT + mediums: WEB_RTC + > + >)pb"; + ByteArray bytes = + ForConnectionRequest(kEndpointId, kEndpointName, kNonce, + std::vector(kMediums.begin(), kMediums.end())); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateConnectionResponse) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_RESPONSE + connection_response: < status: 1 > + >)pb"; + ByteArray bytes = ForConnectionResponse(1); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateControlPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(150); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: CONTROL, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + control_message: < event: PAYLOAD_CANCELED offset: 150 > + > + >)pb"; + ByteArray bytes = ForControlPayloadTransfer(header, control); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateDataPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: DATA, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + payload_chunk: < flags: 1 offset: 150 body: "payload data" > + > + >)pb"; + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeWifiHotspot) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_HOTSPOT + wifi_hotspot_credentials: < + ssid: "ssid" + password: "password" + port: 1234 + > + > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeWifiHotspot("ssid", "password", 1234); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeLastWrite) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: LAST_WRITE_TO_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeLastWrite(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeSafeToClose) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: SAFE_TO_CLOSE_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeSafeToClose(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: CLIENT_INTRODUCTION + client_introduction: < endpoint_id: "ABC" > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeIntroduction(kEndpointId); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateKeepAlive) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: KEEP_ALIVE + keep_alive: <> + >)pb"; + ByteArray bytes = ForKeepAlive(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +} // namespace +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/pcp.h b/cpp/core_v2/internal/pcp.h new file mode 100644 index 00000000..f2fec5ea --- /dev/null +++ b/cpp/core_v2/internal/pcp.h @@ -0,0 +1,26 @@ +#ifndef CORE_V2_INTERNAL_PCP_H_ +#define CORE_V2_INTERNAL_PCP_H_ + +namespace location { +namespace nearby { +namespace connections { + +// The PreConnectionProtocol (PCP) defines the combinations of interactions +// between the techniques (ultrasound audio, Bluetooth device names, BLE +// advertisements) used for offline Advertisement + Discovery, and identifies +// the steps to go through on each device. +// +// See go/nearby-offline-data-interchange-formats for more. +enum class Pcp { + kUnknown = 0, + kP2pStar = 1, + kP2pCluster = 2, + kP2pPointToPoint = 3, + // PCP is only allocated 5 bits in our data interchange formats, so there can + // never be more than 31 PCP values. +}; +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_H_ diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h new file mode 100644 index 00000000..3666360d --- /dev/null +++ b/cpp/core_v2/internal/pcp_handler.h @@ -0,0 +1,88 @@ +#ifndef CORE_V2_INTERNAL_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_PCP_HANDLER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the set of methods that need to be implemented to handle the +// per-PCP-specific operations in the OfflineServiceController. +// +// These methods are all meant to be synchronous, and should return only after +// knowing they've done what they were supposed to do (or unequivocally failed +// to do so). +// +// See details here: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +class PcpHandler { + public: + virtual ~PcpHandler() = default; + + // Return strategy supported by this protocol. + virtual Strategy GetStrategy() = 0; + + // Return concrete variant of protocol. + virtual Pcp GetPcp() = 0; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See + // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;bpv=1;bpt=1;l=71?gsn=ConnectionListener + virtual Status StartAdvertising(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopAdvertising(ClientProxy* client) = 0; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + virtual Status StartDiscovery(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopDiscovery(ClientProxy* client) = 0; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + virtual Status RequestConnection(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + virtual Status AcceptConnection(ClientProxy* clientProxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) = 0; + + // Either party may call this to reject connection on their part before + // connection reaches data phase. If either party does call it, connection + // will terminate. Update state in ClientProxy. + virtual Status RejectConnection(ClientProxy* client, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/service_controller.h b/cpp/core_v2/internal/service_controller.h new file mode 100644 index 00000000..119a633b --- /dev/null +++ b/cpp/core_v2/internal/service_controller.h @@ -0,0 +1,77 @@ +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" + +namespace location { +namespace nearby { +namespace connections { + +// Interface defines the core functionality of Nearby Connections Service. +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// ResultCallback arguments are not provided for this class, because all methods +// are called synchronously. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// See details here: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +class ServiceController { + public: + virtual ~ServiceController() = default; + ServiceController() = default; + ServiceController(const ServiceController&) = delete; + ServiceController& operator=(const ServiceController&) = delete; + + // Starts advertising an endpoint for a local app. + virtual Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + virtual void StopAdvertising(ClientProxy* client_proxy) = 0; + + virtual Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + virtual void StopDiscovery(ClientProxy* client_proxy) = 0; + + virtual Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + virtual Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& listener) = 0; + virtual Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void InitiateBandwidthUpgrade(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void SendPayload(ClientProxy* client_proxy, + const std::vector& endpoint_ids, + Payload payload) = 0; + + virtual Status CancelPayload(ClientProxy* client_proxy, + std::int64_t payload_id) = 0; + + virtual void DisconnectFromEndpoint(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc new file mode 100644 index 00000000..dd1c044b --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -0,0 +1,383 @@ +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +ServiceControllerRouter::~ServiceControllerRouter() { + // TODO(tracyzhou): Add logging. + + // And make sure that cleanup is the last thing we do. + serializer_.Shutdown(); +} + +void ServiceControllerRouter::StartAdvertising( + ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, const ConnectionRequestInfo& info, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, info, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsAdvertising()) { + callback.result_cb({Status::kAlreadyAdvertising}); + return; + } + + status = service_controller_->StartAdvertising(client, service_id, options, + info); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopAdvertising(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsAdvertising()) { + service_controller_->StopAdvertising(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::StartDiscovery(ClientProxy* client, + absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, listener, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsDiscovering()) { + callback.result_cb({Status::kAlreadyDiscovering}); + return; + } + + status = service_controller_->StartDiscovery(client, service_id, options, + listener); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopDiscovery(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsDiscovering()) { + service_controller_->StopDiscovery(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::RequestConnection( + ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), info, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->HasPendingConnectionToEndpoint(endpoint_id) || + client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + callback.result_cb( + service_controller_->RequestConnection(client, endpoint_id, info)); + }); +} + +void ServiceControllerRouter::AcceptConnection(ClientProxy* client, + absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, + endpoint_id = std::string(endpoint_id), listener, + callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->AcceptConnection(client, endpoint_id, listener)); + }); +} + +void ServiceControllerRouter::RejectConnection(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->RejectConnection(client, endpoint_id)); + }); +} + +void ServiceControllerRouter::InitiateBandwidthUpgrade( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client) || + !client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + service_controller_->InitiateBandwidthUpgrade(client, endpoint_id); + + // Operation is triggered; the caller can listen to + // ConnectionListener::OnBandwidthChanged() to determine its success. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::SendPayload( + ClientProxy* client, absl::Span endpoint_ids, + Payload payload, const ResultCallback& callback) { + // Payload is a move-only type. + // We have to capture it by value inside the lambda, and pass it over to + // the executor as an std::function instance. + // Lambda must be copyable, in order ot satisfy std::function<> requirements. + // To make it so, we need Payload wrapped by a copyable wrapper. + // std::shared_ptr<> is used, because it is copyable. + auto shared_payload = std::make_shared(std::move(payload)); + RouteToServiceController( + [this, client, shared_payload, + endpoint_ids = std::vector(endpoint_ids.begin(), endpoint_ids.end()), + &callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (!ClientHasConnectionToAtLeastOneEndpoint(client, endpoint_ids)) { + callback.result_cb({Status::kEndpointUnknown}); + return; + } + + service_controller_->SendPayload(client, endpoint_ids, + std::move(*shared_payload)); + + // At this point, we've queued up the send Payload request with the + // ServiceController; any further failures (e.g. one of the endpoints is + // unknown, goes away, or otherwise fails) will be returned to the + // client as a PayloadTransferUpdate. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::CancelPayload(ClientProxy* client, + std::uint64_t payload_id, + const ResultCallback& callback) { + RouteToServiceController([this, client, payload_id, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb(service_controller_->CancelPayload(client, payload_id)); + }); +} + +void ServiceControllerRouter::DisconnectFromEndpoint( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (ClientHasAcquiredServiceController(client)) { + if (!client->IsConnectedToEndpoint(endpoint_id) && + !client->HasPendingConnectionToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + callback.result_cb({Status::kSuccess}); + } + }); +} + +void ServiceControllerRouter::StopAllEndpoints(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::ClientDisconnecting( + ClientProxy* client, const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + // Log the completion of this client's connection. + // TODO(tracyzhou): Add logging. + } + callback.result_cb({Status::kSuccess}); + }); +} + +Status ServiceControllerRouter::AcquireServiceControllerForClient( + ClientProxy* client, Strategy strategy) { + if (current_strategy_.IsNone()) { + // Case 1: There is no existing Strategy at all. + + // Set everything up for the first time. + Status status = UpdateCurrentServiceControllerAndStrategy(strategy); + if (!status.Ok()) { + return status; + } + clients_.insert(client); + return {Status::kSuccess}; + } else if (strategy == current_strategy_) { + // Case 2: The existing Strategy matches. + + // The new client just needs to be added to the set of clients using the + // current ServiceController. + clients_.insert(client); + return {Status::kSuccess}; + } else { + // Case 3: The existing Strategy doesn't match. + + // It's only safe for a client to cause a switch if it's the only client + // using the current ServiceController. + bool is_the_only_client_of_service_controller = + clients_.size() == 1 && ClientHasAcquiredServiceController(client); + if (!is_the_only_client_of_service_controller) { + // TODO(tracyzhou): logging + return {Status::kAlreadyHaveActiveStrategy}; + } + + // If the client still has connected endpoints, they must disconnect before + // they can switch. + if (!client->GetConnectedEndpoints().empty()) { + // TODO(tracyzhou): logging + return {Status::kOutOfOrderApiCall}; + } + + // By this point, it's safe to switch the Strategy and ServiceController + // (and since it's the only client, there's no need to add it to the set of + // clients using the current ServiceController). + return UpdateCurrentServiceControllerAndStrategy(strategy); + } +} + +bool ServiceControllerRouter::ClientHasAcquiredServiceController( + ClientProxy* client) const { + return clients_.contains(client); +} + +void ServiceControllerRouter::ReleaseServiceControllerForClient( + ClientProxy* client) { + clients_.erase(client); + + if (clients_.empty()) { + service_controller_.reset(); + current_strategy_ = Strategy{}; + } +} + +/** Clean up all state for this client. The client is now free to switch + * strategies. */ +void ServiceControllerRouter::DoneWithStrategySessionForClient( + ClientProxy* client) { + // Disconnect from all the connected endpoints tied to this clientProxy. + for (auto& endpoint_id : client->GetPendingConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + for (auto& endpoint_id : client->GetConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + // Stop any advertising and discovery that may be underway due to this + // clientProxy. + service_controller_->StopAdvertising(client); + service_controller_->StopDiscovery(client); + + ReleaseServiceControllerForClient(client); +} + +void ServiceControllerRouter::RouteToServiceController(Runnable runnable) { + serializer_.Execute(std::move(runnable)); +} + +bool ServiceControllerRouter::ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids) { + for (auto& endpoint_id : remote_endpoint_ids) { + if (client->IsConnectedToEndpoint(endpoint_id)) { + return true; + } + } + return false; +} + +Status ServiceControllerRouter::UpdateCurrentServiceControllerAndStrategy( + Strategy strategy) { + if (!strategy.IsValid()) { + // TODO(tracyzhou): logging + return {Status::kError}; + } + + service_controller_.reset(service_controller_factory_()); + current_strategy_ = strategy; + + return {Status::kSuccess}; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/service_controller_router.h b/cpp/core_v2/internal/service_controller_router.h new file mode 100644 index 00000000..8ccfd057 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.h @@ -0,0 +1,111 @@ +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// ServiceControllerRouter: this class is an implementation detail of a +// location::nearby::Core class. The latter delegates all of its activities to +// the former. +// +// All the activities are documented in the public API class: +// https://source.corp.google.com/piper///depot/google3/core_v2/core.h +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// Every activity is handled the same way: +// 1) all the arguments to the call are captured by value; +// 2) the actual processing is scheduled on a private single-threaded executor, +// which makes locking unnecessary, when internal data is being manipulated. +// 3) activity handlers are delegating much of their work to an implementation +// of a ServiceController interface, which does the actual job. +class ServiceControllerRouter { + public: + explicit ServiceControllerRouter(std::function factory) + : service_controller_factory_(std::move(factory)) {} + ~ServiceControllerRouter(); + ServiceControllerRouter(ServiceControllerRouter&&) = default; + ServiceControllerRouter& operator=(ServiceControllerRouter&&) = default; + + void StartAdvertising(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void StopAdvertising(ClientProxy* client, const ResultCallback& callback); + + void StartDiscovery(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback); + void StopDiscovery(ClientProxy* client, const ResultCallback& callback); + + void RequestConnection(ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void AcceptConnection(ClientProxy* client, absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback); + void RejectConnection(ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback); + + void InitiateBandwidthUpgrade(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + + void SendPayload(ClientProxy* client, + absl::Span endpoint_ids, Payload payload, + const ResultCallback& callback); + void CancelPayload(ClientProxy* client, std::uint64_t payload_id, + const ResultCallback& callback); + + void DisconnectFromEndpoint(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + void StopAllEndpoints(ClientProxy* client, const ResultCallback& callback); + + void ClientDisconnecting(ClientProxy* client, const ResultCallback& callback); + + private: + friend class ServiceControllerRouterTest; + static bool ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids); + + void RouteToServiceController(Runnable runnable); + + Status AcquireServiceControllerForClient(ClientProxy* client, + Strategy strategy); + bool ClientHasAcquiredServiceController(ClientProxy* client) const; + void ReleaseServiceControllerForClient(ClientProxy* client); + void DoneWithStrategySessionForClient(ClientProxy* client); + Status UpdateCurrentServiceControllerAndStrategy(Strategy strategy); + + absl::flat_hash_set clients_; + std::function service_controller_factory_; + std::unique_ptr service_controller_; + Strategy current_strategy_; + SingleThreadExecutor serializer_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ diff --git a/cpp/core_v2/internal/service_controller_router_test.cc b/cpp/core_v2/internal/service_controller_router_test.cc new file mode 100644 index 00000000..2fc45d00 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router_test.cc @@ -0,0 +1,376 @@ +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { +using ::testing::Return; +} // namespace + +// This class must be in the same namespace as ServiceControllerRouter for +// friend class to work. +class ServiceControllerRouterTest : public testing::Test { + public: + ServiceControllerRouterTest() = default; + ~ServiceControllerRouterTest() override { + router_.service_controller_.release(); + } + + void StartAdvertising(ClientProxy* client, std::string service_id, + ConnectionOptions options, ConnectionRequestInfo info, + ResultCallback callback) { + EXPECT_CALL(mock_, StartAdvertising) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartAdvertising(client, service_id, options, info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedAdvertising(kServiceId, options.strategy, info.listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StopAdvertising(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopAdvertising).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopAdvertising(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedAdvertising(); + EXPECT_FALSE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, std::string service_id, + ConnectionOptions options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, StartDiscovery) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartDiscovery(client, kServiceId, options, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedDiscovery(service_id, options.strategy, listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsDiscovering()); + } + + void StopDiscovery(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopDiscovery).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopDiscovery(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedDiscovery(); + EXPECT_FALSE(client->IsDiscovering()); + } + + void RequestConnection(ClientProxy* client, const std::string& endpoint_id, + const ConnectionRequestInfo& request_info, + ResultCallback callback) { + EXPECT_CALL(mock_, RequestConnection) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RequestConnection(client, endpoint_id, request_info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + ConnectionResponseInfo response_info{ + .remote_endpoint_name = "endpoint_name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + client->OnConnectionInitiated(endpoint_id, response_info, + request_info.listener); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + } + + void AcceptConnection(ClientProxy* client, const std::string endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, AcceptConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.AcceptConnection(client, endpoint_id, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointAcceptedConnection(endpoint_id, listener); + client->RemoteEndpointAcceptedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint_id)); + client->OnConnectionAccepted(endpoint_id); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + } + + void RejectConnection(ClientProxy* client, const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, RejectConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RejectConnection(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointRejectedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionRejected(endpoint_id)); + } + + void InitiateBandwidthUpgrade(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, InitiateBandwidthUpgrade).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.InitiateBandwidthUpgrade(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void SendPayload(ClientProxy* client, + const std::vector& endpoint_ids, + Payload payload, ResultCallback callback) { + EXPECT_CALL(mock_, SendPayload).Times(1); + + bool connected = false; + for (const auto& endpoint_id : endpoint_ids) { + connected = connected || client->IsConnectedToEndpoint(endpoint_id); + } + EXPECT_TRUE(connected); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.SendPayload(client, absl::MakeSpan(endpoint_ids), + std::move(payload), callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void CancelPayload(ClientProxy* client, std::int64_t payload_id, + ResultCallback callback) { + EXPECT_CALL(mock_, CancelPayload) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.CancelPayload(client, payload_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void DisconnectFromEndpoint(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, DisconnectFromEndpoint).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.DisconnectFromEndpoint(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + } + client->OnDisconnected(endpoint_id, false); + EXPECT_FALSE(client->IsConnectedToEndpoint(endpoint_id)); + } + + protected: + const ResultCallback kCallback{ + .result_cb = + [this](Status status) { + MutexLock lock(&mutex_); + result_ = status; + complete_ = true; + cond_.Notify(); + }, + }; + const std::string kServiceId = "service id"; + const std::string kRequestorName = "requestor name"; + const std::string kRemoteEndpointId = "remote endpoint id"; + const std::int64_t kPayloadId = UINT64_C(0x123456789ABCDEF0); + const ConnectionOptions kConnectionOptions{ + .strategy = Strategy::kP2pPointToPoint, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH}; + const ConnectionRequestInfo kConnectionRequestInfo{ + .name = kRequestorName, + .listener = ConnectionListener(), + }; + + DiscoveryListener discovery_listener_; + PayloadListener payload_listener_; + + Mutex mutex_; + ConditionVariable cond_{&mutex_}; + Status result_ ABSL_GUARDED_BY(mutex_) = {Status::kError}; + bool complete_ ABSL_GUARDED_BY(mutex_) = false; + MockServiceController mock_; + ClientProxy client_; + + ServiceControllerRouter router_{ + [this]() -> ServiceController* { return &mock_; }}; +}; + +namespace { +TEST_F(ServiceControllerRouterTest, CostructorDestructorWorks) { SUCCEED(); } + +TEST_F(ServiceControllerRouterTest, StartAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); + StopAdvertising(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StartDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + StopDiscovery(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RequestConnectionCalled) { + // Either Advertising, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, AcceptConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RejectConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can reject connection. + RejectConnection(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, InitiateBandwidthUpgradeCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can change connection bandwidth. + InitiateBandwidthUpgrade(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, SendPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can send payload. + SendPayload(&client_, std::vector{kRemoteEndpointId}, + Payload{ByteArray("data")}, kCallback); +} + +TEST_F(ServiceControllerRouterTest, CancelPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We have to know payload id, before we can cancel payload transfer. + // It is either after a call to SendPayload, or after receiving + // PayloadProgress callback. Let's assume we have it, and proceed. + CancelPayload(&client_, kPayloadId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, DisconnectFromEndpointCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We can disconnect at any time after RequestConnection. + DisconnectFromEndpoint(&client_, kRemoteEndpointId, kCallback); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc new file mode 100644 index 00000000..f034eeea --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -0,0 +1,180 @@ +#include "core_v2/internal/wifi_lan_service_info.h" + +#include + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, + absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name) { + if (version != Version::kV1 || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + service_id_hash.size() != kServiceIdHashLength) { + return; + } + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; +} + +WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { + ByteArray service_info_bytes = Base64Utils::Decode(service_info_string); + + if (service_info_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: failed Base64 decoding of %s", + std::string(service_info_string).c_str()); + return; + } + + if (service_info_bytes.size() > kMaxLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " + "bytes, got %" PRIu64, + kMaxLanServiceNameLength, service_info_bytes.size()); + return; + } + + if (service_info_bytes.size() < kMinLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting min %d raw " + "bytes, got %" PRIu64, + kMinLanServiceNameLength, service_info_bytes.size()); + return; + } + + // The upper 3 bits are supposed to be the version. + version_ = static_cast( + (service_info_bytes.data()[0] & kVersionBitmask) >> kVersionShift); + const char* service_info_bytes_read_ptr = service_info_bytes.data(); + switch (version_) { + case Version::kV1: + // The lower 5 bits of the V1 payload are supposed to be the Pcp. + pcp_ = static_cast(*service_info_bytes_read_ptr & kPcpBitmask); + service_info_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(service_info_bytes_read_ptr, kEndpointIdLength); + service_info_bytes_read_ptr += kEndpointIdLength; + + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(service_info_bytes_read_ptr, kServiceIdHashLength); + service_info_bytes_read_ptr += kServiceIdHashLength; + + // The next bits are supposed to be endpoint_name. + // TODO(edwinwu): Implements it. Temp to set "found_device". + endpoint_name_ = "found_device"; + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + break; + } + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", + version_); + break; + } +} + +WifiLanServiceInfo::operator std::string() const { + if (!IsValid()) { + return ""; + } + + ByteArray wifi_lan_service_info_name_bytes(kMinLanServiceNameLength); + auto* wifi_lan_service_info_name_bytes_write_ptr = + wifi_lan_service_info_name_bytes.data(); + + // The upper 3 bits are the Version. + auto version_and_pcp_byte = static_cast( + (static_cast(Version::kV1) << 5) & kVersionBitmask); + // The lower 5 bits are the PCP. + version_and_pcp_byte |= + static_cast(static_cast(pcp_) & kPcpBitmask); + *wifi_lan_service_info_name_bytes_write_ptr = version_and_pcp_byte; + wifi_lan_service_info_name_bytes_write_ptr++; + + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are the endpoint_id. + if (endpoint_id_.size() != kEndpointIdLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 Endpoint ID %s (%" PRIu64 + " bytes) should be exactly %d bytes", + endpoint_id_.c_str(), endpoint_id_.size(), kEndpointIdLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, endpoint_id_.data(), + kEndpointIdLength); + wifi_lan_service_info_name_bytes_write_ptr += kEndpointIdLength; + + // The next 24 bits are the service_id_hash. + if (service_id_hash_.size() != kServiceIdHashLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 ServiceID hash (%" PRIu64 + " bytes) should be exactly %d bytes", + service_id_hash_.size(), kServiceIdHashLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, + service_id_hash_.data(), kServiceIdHashLength); + wifi_lan_service_info_name_bytes_write_ptr += kServiceIdHashLength; + + // The next bits are the endpoint_name. + // TODO(edwinwu): Implements to parse endpoint_name. + break; + default: + NEARBY_LOG(ERROR, + "Cannot serialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + return ""; + } + + return Base64Utils::Encode(wifi_lan_service_info_name_bytes); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.h b/cpp/core_v2/internal/wifi_lan_service_info.h new file mode 100644 index 00000000..21f1f1bb --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -0,0 +1,81 @@ +#ifndef CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ +#define CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ + +#include + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the WifiLan service info used in Advertising + +// Discovery. +// +// See go/nearby-offline-data-interchange-formats for the specification. +class WifiLanServiceInfo { + public: + // Versions of the WifiLanServiceInfo. + enum class Version { + kUndefined = 0, + kV1 = 1, + }; + + static constexpr std::uint32_t kServiceIdHashLength = 3; + + WifiLanServiceInfo() = default; + WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name); + explicit WifiLanServiceInfo(absl::string_view service_info_string); + ~WifiLanServiceInfo() = default; + + WifiLanServiceInfo(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo(WifiLanServiceInfo&&) = default; + WifiLanServiceInfo& operator=(WifiLanServiceInfo&&) = default; + + explicit operator std::string() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline ByteArray GetServiceIdHash() const { return service_id_hash_; } + + private: + // The maximum length of encrypted WifiLanServiceInfo string. + static constexpr int kMaxLanServiceNameLength = 47; + // The minimum length of encrypted WifiLanServiceInfo string. + static constexpr int kMinLanServiceNameLength = 9; + // The length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kEndpointIdLength = 4; + // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kMaxEndpointNameLength = 131; + + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kVersionShift = 5; + + // WifiLanServiceInfo version. + Version version_ = Version::kUndefined; + // Pre-Connection Protocols version. + Pcp pcp_ = Pcp::kUnknown; + // Connected endpoint id. + std::string endpoint_id_; + // Connected hash service id. + ByteArray service_id_hash_; + // TODO(edwinwu): Replaces endpointName as endPointInfo eventually; + // it is not in this version yet for endpointName. + // Connected endpoint name. + std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ diff --git a/cpp/core_v2/internal/wifi_lan_service_info_test.cc b/cpp/core_v2/internal/wifi_lan_service_info_test.cc new file mode 100644 index 00000000..b5aee9aa --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -0,0 +1,143 @@ +#include "core_v2/internal/wifi_lan_service_info.h" + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kEndPointID[] = "AB12"; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +// TODO(edwinwu): Temp to set empty string for endpoint_name. +const char kEndPointName[] = ""; + +TEST(WifiLanServiceInfoTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto wifi_lan_service_info_string = std::string(org_wifi_lan_service_info); + + auto wifi_lan_service_info = WifiLanServiceInfo(wifi_lan_service_info_string); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName); + + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + auto short_service_id_hash = + ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + auto long_service_id_hash = + ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortStringLength) { + char wifi_lan_service_info_string[] = {'X'}; + + auto wifi_lan_service_info_bytes = + ByteArray(wifi_lan_service_info_string, + sizeof(wifi_lan_service_info_string) / sizeof(char)); + auto wifi_lan_service_info = + WifiLanServiceInfo(Base64Utils::Encode(wifi_lan_service_info_bytes)); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h new file mode 100644 index 00000000..4f375344 --- /dev/null +++ b/cpp/core_v2/listeners.h @@ -0,0 +1,180 @@ +#ifndef CORE_V2_LISTENERS_H_ +#define CORE_V2_LISTENERS_H_ + +#include +#include +#include +#include + +// This file defines all the protocol listeners and their parameter structures. +// Listeners are defined as collections of std::function instances, which is +// more flexible than a virtual function: +// - a subset of listener callbacks may be overridden, while others may remain +// default-initialized. +// - callbacks may be initialized with lambdas; lambda definitions are concize. + +#include "core_v2/payload.h" +#include "core_v2/status.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Common callback for asynchronously invoked methods. +// Called after a job scheduled for execution is completed. +// This is not the same as completion of the associated process, +// which may have many states, and multiple async jobs, and be still ongoing. +// Progress on the overall process is reported by the associated listener. +struct ResultCallback { + // Callback to access the status of the operation when available. + // status - result of job execution; + // Status::kSuccess, if successful; anything else indicates failure. + std::function result_cb = DefaultCallback(); +}; + +struct ConnectionResponseInfo { + std::string remote_endpoint_name; + std::string authentication_token; + ByteArray raw_authentication_token; + ByteArray endpoint_info; + bool is_incoming_connection; + bool is_connection_verified; +}; + +struct PayloadProgressInfo { + std::int64_t payload_id; + enum class Status { + kSuccess, + kFailure, + kInProgress, + kCanceled, + } status; + std::int64_t total_bytes; + std::int64_t bytes_transferred; +}; + +enum class DistanceInfo { + kUnknown = 1, + kVeryClose = 2, + kClose = 3, + kFar = 4, +}; + +struct ConnectionListener { + // A basic encrypted channel has been created between you and the endpoint. + // Both sides are now asked if they wish to accept or reject the connection + // before any data can be sent over this channel. + // + // This is your chance, before you accept the connection, to confirm that you + // connected to the correct device. Both devices are given an identical token; + // it's up to you to decide how to verify it before proceeding. Typically this + // involves showing the token on both devices and having the users manually + // compare and confirm; however, this is only required if you desire a secure + // connection between the devices. + // + // Whichever route you decide to take (including not authenticating the other + // device), call Core::AcceptConnection() when you're ready to talk, or + // Core::RejectConnection() to close the connection. + // + // endpoint_id - The identifier for the remote endpoint. + // info - Other relevant information about the connection. + std::function + initiated_cb = + DefaultCallback(); + + // Called after both sides have accepted the connection. + // Both sides may now send Payloads to each other. + // Call Core::SendPayload() or wait for incoming PayloadListener::OnPayload(). + // + // endpoint_id - The identifier for the remote endpoint. + std::function accepted_cb = + DefaultCallback(); + + // Called when either side rejected the connection. + // Payloads can not be exchaged. Call Core::DisconnectFromEndpoint() + // to terminate connection. + // + // endpoint_id - The identifier for the remote endpoint. + std::function + rejected_cb = DefaultCallback(); + + // Called when a remote endpoint is disconnected or has become unreachable. + // At this point service (re-)discovery may start again. + // + // endpoint_id - The identifier for the remote endpoint. + std::function disconnected_cb = + DefaultCallback(); + + // Called when the connection's available bandwidth has changed. + // + // endpoint_id - The identifier for the remote endpoint. + // quality - TODO(apolyudov): document. + std::function + bandwidth_changed_cb = + DefaultCallback(); +}; + +struct DiscoveryListener { + // Called when a remote endpoint is discovered. + // + // endpoint_id - The ID of the remote endpoint that was discovered. + // endpoint_name - The human readable name of the remote endpoint. + // service_id - The ID of the service advertised by the remote endpoint. + std::function + endpoint_found_cb = + DefaultCallback(); + + // Called when a remote endpoint is no longer discoverable; only called for + // endpoints that previously had been passed to {@link + // #onEndpointFound(String, DiscoveredEndpointInfo)}. + // + // endpoint_id - The ID of the remote endpoint that was lost. + std::function endpoint_lost_cb = + DefaultCallback(); + + // Called when a remote endpoint is found with an updated distance. + // + // arguments: + // endpoint_id - The ID of the remote endpoint that was lost. + // info - The distance info, encoded as enum value. + std::function + endpoint_distance_changed_cb = + DefaultCallback(); +}; + +struct PayloadListener { + // Called when a Payload is received from a remote endpoint. Depending + // on the type of the Payload, all of the data may or may not have been + // received at the time of this call. Use OnPayloadProgress() to + // get updates on the status of the data received. + // + // endpoint_id - The identifier for the remote endpoint that sent the + // payload. + // payload - The Payload object received. + std::function + payload_cb = DefaultCallback(); + + // Called with progress information about an active Payload transfer, either + // incoming or outgoing. + // + // endpoint_id - The identifier for the remote endpoint that is sending or + // receiving this payload. + // info - The PayloadProgressInfo structure describing the status of + // the transfer. + std::function + payload_progress_cb = + DefaultCallback(); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_LISTENERS_H_ diff --git a/cpp/core_v2/listeners_test.cc b/cpp/core_v2/listeners_test.cc new file mode 100644 index 00000000..8f73b1c0 --- /dev/null +++ b/cpp/core_v2/listeners_test.cc @@ -0,0 +1,45 @@ +#include "core_v2/listeners.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(ListenersTest, EnsureDefaultInitializedIsCallable) { + ConnectionListener listener; + std::string endpoint_id("endpoint_id"); + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + SUCCEED(); +} + +TEST(ListenersTest, EnsurePartiallyInitializedIsCallable) { + std::string endpoint_id = {"endpoint_id"}; + bool initiated_cb_called = false; + ConnectionListener listener{ + .initiated_cb = + [&](std::string, ConnectionResponseInfo) { + initiated_cb_called = true; + }, + }; + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + EXPECT_TRUE(initiated_cb_called); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h new file mode 100644 index 00000000..d55e41d5 --- /dev/null +++ b/cpp/core_v2/options.h @@ -0,0 +1,30 @@ +#ifndef CORE_V2_OPTIONS_H_ +#define CORE_V2_OPTIONS_H_ + +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +// Connection Options: used for both Advertising and Discovery. +// All fields are mutable, to make the type copy-assignable. +struct ConnectionOptions { + Strategy strategy; + bool auto_upgrade_bandwidth; + bool enforce_topology_constraints; + // Verify if ConnectionOptions is in a not-initialized (Empty) state. + bool Empty() const { + return strategy.IsNone(); + } + // Bring ConnectionOptions to a not-initialized (Empty) state. + void Clear() { + strategy.Clear(); + } +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_OPTIONS_H_ diff --git a/cpp/core_v2/params.h b/cpp/core_v2/params.h new file mode 100644 index 00000000..b0ddde22 --- /dev/null +++ b/cpp/core_v2/params.h @@ -0,0 +1,27 @@ +#ifndef CORE_V2_PARAMS_H_ +#define CORE_V2_PARAMS_H_ + +#include + +#include "core_v2/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Used by Discovery in Core::RequestConnection(). +// Used by Advertising in Core::StartAdvertising(). +struct ConnectionRequestInfo { + // name - A human readable name for this endpoint, to appear on + // other devices. + // listener - A set of callbacks notified when remote endpoints request a + // connection to this endpoint. + std::string name; + ConnectionListener listener; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PARAMS_H_ diff --git a/cpp/core_v2/payload.h b/cpp/core_v2/payload.h new file mode 100644 index 00000000..c1e81633 --- /dev/null +++ b/cpp/core_v2/payload.h @@ -0,0 +1,85 @@ +#ifndef CORE_V2_PAYLOAD_H_ +#define CORE_V2_PAYLOAD_H_ + +#include +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/file.h" +#include "absl/types/variant.h" + +namespace location { +namespace nearby { +namespace connections { + +// Payload is default-constructible, and moveable, but not copyable container +// that holds at most one instance of one of: +// ByteArray, InputStream, or InputFile. +class Payload { + public: + // Order of types in variant, and values in Type enum is important. + // Enum values must match respective variant types. + using Content = + absl::variant, + std::unique_ptr>; + enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; + + Payload(Payload&& other) = default; + ~Payload() = default; + Payload& operator=(Payload&& other) = default; + + // Create Payload from bytes, steam, or file. Payload is immutable. + Payload() : content_(absl::monostate()) {} + explicit Payload(ByteArray&& bytes) : content_(std::move(bytes)) {} + explicit Payload(const ByteArray& bytes) : content_(bytes) {} + explicit Payload(std::unique_ptr stream) + : content_(std::move(stream)) {} + explicit Payload(std::unique_ptr file) + : content_(std::move(file)) {} + + // Returns ByteArray payload, if it has been defined, or empty ByteArray. + const ByteArray& AsBytes() const & { + static const ByteArray empty; // NOLINT: function-level static is OK. + auto* result = absl::get_if(&content_); + return result ? *result : empty; + } + ByteArray&& AsBytes() && { + auto* result = absl::get_if(&content_); + return result ? std::move(*result) : std::move(ByteArray()); + } + // Returns InputStream* payload, if it has been defined, or nullptr. + InputStream* AsStream() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + // Returns InputFile* payload, if it has been defined, or nullptr. + InputFile* AsFile() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + + // Returns Payload unique ID. + std::int64_t GetId() const { return id_; } + + // Returns Payload type. + Type GetType() const { return type_; } + + private: + static std::int64_t GenerateId() { return Prng().NextInt64(); } + Type FindType(const Content& content) const { + return static_cast(content_.index()); + } + + Content content_; + std::int64_t id_{GenerateId()}; + Type type_{FindType(content_)}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PAYLOAD_H_ diff --git a/cpp/core_v2/payload_test.cc b/cpp/core_v2/payload_test.cc new file mode 100644 index 00000000..498efb7b --- /dev/null +++ b/cpp/core_v2/payload_test.cc @@ -0,0 +1,76 @@ +#include "core_v2/payload.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/public/file.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(PayloadTest, DefaultPayloadHasUnknownType) { + Payload payload; + EXPECT_EQ(payload.GetType(), Payload::Type::kUnknown); +} + +TEST(PayloadTest, SupportsByteArrayType) { + const ByteArray bytes("bytes"); + Payload payload(bytes); + EXPECT_EQ(payload.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), bytes); +} + +TEST(PayloadTest, SupportsFileType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr file(raw_file); + Payload payload(std::move(file)); + EXPECT_EQ(payload.GetType(), Payload::Type::kFile); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), raw_file); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, SupportsStreamType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr stream(raw_file); + Payload payload(std::move(stream)); + EXPECT_EQ(payload.GetType(), Payload::Type::kStream); + EXPECT_EQ(payload.AsStream(), raw_file); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, PayloadIsMoveable) { + Payload payload1; + Payload payload2(ByteArray("bytes")); + auto id = payload2.GetId(); + ByteArray bytes = payload2.AsBytes(); + EXPECT_EQ(payload1.GetType(), Payload::Type::kUnknown); + EXPECT_EQ(payload2.GetType(), Payload::Type::kBytes); + payload1 = std::move(payload2); + EXPECT_EQ(payload1.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload1.AsBytes(), bytes); + EXPECT_EQ(payload1.GetId(), id); +} + +TEST(PayloadTest, PayloadHasUniqueId) { + Payload payload1; + Payload payload2; + EXPECT_NE(payload1.GetId(), payload2.GetId()); +} + +TEST(PayloadTest, PayloadIsNotCopyable) { + EXPECT_FALSE(std::is_copy_constructible_v); + EXPECT_FALSE(std::is_copy_assignable_v); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/status.h b/cpp/core_v2/status.h new file mode 100644 index 00000000..c4ff633c --- /dev/null +++ b/cpp/core_v2/status.h @@ -0,0 +1,45 @@ +#ifndef CORE_V2_STATUS_H_ +#define CORE_V2_STATUS_H_ + +namespace location { +namespace nearby { +namespace connections { + +// Protocol operation result: kSuccess, if operation was successful; +// descriptive error code otherwise. +struct Status { + // Status is a struct, so it is possible to pass some context about failure, + // by adding extra fields to it when necessary, and not change any of the + // method signatures. + enum Value { + kSuccess, + kError, + kOutOfOrderApiCall, + kAlreadyHaveActiveStrategy, + kAlreadyAdvertising, + kAlreadyDiscovering, + kEndpointIoError, + kEndpointUnknown, + kConnectionRejected, + kAlreadyConnectedToEndpoint, + kNotConnectedToEndpoint, + kBluetoothError, + kPayloadUnknown, + }; + Value value {kError}; + bool Ok() const { return value == kSuccess; } +}; + +inline bool operator==(const Status& a, const Status& b) { + return a.value == b.value; +} + +inline bool operator!=(const Status& a, const Status& b) { + return !(a == b); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STATUS_H_ diff --git a/cpp/core_v2/status_test.cc b/cpp/core_v2/status_test.cc new file mode 100644 index 00000000..86f37b4f --- /dev/null +++ b/cpp/core_v2/status_test.cc @@ -0,0 +1,44 @@ +#include "core_v2/status.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StatusTest, DefaultIsError) { + Status status; + EXPECT_FALSE(status.Ok()); + EXPECT_EQ(status, Status{Status::kError}); +} + +TEST(StatusTest, DefaultEquals) { + Status status1; + Status status2; + EXPECT_EQ(status1, status2); +} + +TEST(StatusTest, ExplicitInitEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kSuccess}; + EXPECT_EQ(status1, status2); + EXPECT_TRUE(status1.Ok()); +} + +TEST(StatusTest, ExplicitInitNotEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kAlreadyAdvertising}; + EXPECT_NE(status1, status2); +} + +TEST(StatusTest, CopyInitEquals) { + Status status1 = {Status::kAlreadyAdvertising}; + Status status2 = {status1}; + + EXPECT_EQ(status1, status2); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.cc b/cpp/core_v2/strategy.cc new file mode 100644 index 00000000..d17a9090 --- /dev/null +++ b/cpp/core_v2/strategy.cc @@ -0,0 +1,47 @@ +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +const Strategy Strategy::kNone = {Strategy::ConnectionType::kNone, + Strategy::TopologyType::kUnknown}; +const Strategy Strategy::kP2pCluster{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kManyToMany}; +const Strategy Strategy::kP2pStar{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kOneToMany}; +const Strategy Strategy::kP2pPointToPoint{ + Strategy::ConnectionType::kPointToPoint, Strategy::TopologyType::kOneToOne}; + +bool Strategy::IsNone() const { + return *this == kNone; +} + +bool Strategy::IsValid() const { + return *this == kP2pStar || *this == kP2pCluster || *this ==kP2pPointToPoint; +} + +std::string Strategy::GetName() const { + if (*this == Strategy::kP2pCluster) { + return "P2P_CLUSTER"; + } else if (*this == Strategy::kP2pStar) { + return "P2P_STAR"; + } else if (*this == Strategy::kP2pPointToPoint) { + return "P2P_POINT_TO_POINT"; + } else { + return "UNKNOWN"; + } +} + +bool operator==(const Strategy& lhs, const Strategy& rhs) { + return lhs.connection_type_ == rhs.connection_type_ && + lhs.topology_type_ == rhs.topology_type_; +} + +bool operator!=(const Strategy& lhs, const Strategy& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.h b/cpp/core_v2/strategy.h new file mode 100644 index 00000000..de134f78 --- /dev/null +++ b/cpp/core_v2/strategy.h @@ -0,0 +1,62 @@ +#ifndef CORE_V2_STRATEGY_H_ +#define CORE_V2_STRATEGY_H_ + +#include + +namespace location { +namespace nearby { +namespace connections { + +// Defines a copyable, comparable connection strategy type. +// It is one of: kP2pCluster, kP2pStar, kP2pPointToPoint. +class Strategy { + public: + static const Strategy kNone; + static const Strategy kP2pCluster; + static const Strategy kP2pStar; + static const Strategy kP2pPointToPoint; + + Strategy() : Strategy(kNone) {} + + constexpr Strategy(const Strategy& other) + : connection_type_(other.connection_type_), + topology_type_(other.topology_type_) {} + + // Returns true, if strategy is kNone, false otherwise. + bool IsNone() const; + // Returns true, if a strategy is one of the supported strategies, + // false otherwise. + bool IsValid() const; + // Returns a string representing given strategy, for every valid strategy. + std::string GetName() const; + // Undefine strategy. + void Clear() { + *this = kNone; + } + + friend bool operator==(const Strategy& lhs, const Strategy& rhs); + friend bool operator!=(const Strategy& lhs, const Strategy& rhs); + + private: + enum class ConnectionType { + kNone = 0, + kPointToPoint = 1, + }; + enum class TopologyType { + kUnknown = 0, + kOneToOne = 1, + kOneToMany = 2, + kManyToMany = 3, + }; + Strategy(ConnectionType connection_type, TopologyType topology_type) + : connection_type_(connection_type), topology_type_(topology_type) {} + + ConnectionType connection_type_; + TopologyType topology_type_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STRATEGY_H_ diff --git a/cpp/core_v2/strategy_test.cc b/cpp/core_v2/strategy_test.cc new file mode 100644 index 00000000..6b1565e3 --- /dev/null +++ b/cpp/core_v2/strategy_test.cc @@ -0,0 +1,41 @@ +#include "core_v2/strategy.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StrategyTest, IsValidWorks) { + EXPECT_FALSE(Strategy().IsValid()); + EXPECT_TRUE(Strategy::kP2pCluster.IsValid()); + EXPECT_TRUE(Strategy::kP2pStar.IsValid()); + EXPECT_TRUE(Strategy::kP2pPointToPoint.IsValid()); +} + +TEST(StrategyTest, IsNoneWorks) { + EXPECT_TRUE(Strategy().IsNone()); + EXPECT_FALSE(Strategy::kP2pCluster.IsNone()); + EXPECT_FALSE(Strategy::kP2pStar.IsNone()); + EXPECT_FALSE(Strategy::kP2pPointToPoint.IsNone()); +} + +TEST(StrategyTest, CompareWorks) { + EXPECT_EQ(Strategy::kP2pCluster, Strategy::kP2pCluster); + EXPECT_EQ(Strategy::kP2pStar, Strategy::kP2pStar); + EXPECT_EQ(Strategy::kP2pPointToPoint, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pStar); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pStar, Strategy::kP2pPointToPoint); +} + +TEST(StrategyTest, GetNameWorks) { + EXPECT_EQ(Strategy().GetName(), "UNKNOWN"); + EXPECT_EQ(Strategy::kP2pCluster.GetName(), "P2P_CLUSTER"); + EXPECT_EQ(Strategy::kP2pStar.GetName(), "P2P_STAR"); + EXPECT_EQ(Strategy::kP2pPointToPoint.GetName(), "P2P_POINT_TO_POINT"); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 72d19bc6..a0d279b6 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -2,16 +2,16 @@ cc_library( name = "utils", srcs = [ "base64_utils.cc", + "cancelable_alarm.cc", "file_impl.cc", + "pipe.cc", "prng.cc", "reliability_utils.cc", ], hdrs = [ "base64_utils.h", - "cancelable_alarm.cc", "cancelable_alarm.h", "file_impl.h", - "pipe.cc", "pipe.h", "prng.h", "reliability_utils.h", @@ -50,7 +50,6 @@ cc_library( ], deps = [ ":logging", - "//platform/impl/default:lock", "//platform/port:down_cast", "//platform/port:string", ], @@ -64,6 +63,7 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", + "//platform_v2/public:__pkg__", ], deps = [ "//absl/base", @@ -72,75 +72,24 @@ cc_library( ) cc_test( - name = "container_of_test", - srcs = ["container_of_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", + name = "platform_test", + timeout = "short", + srcs = [ + "atomic_reference_test.cc", + "byte_array_test.cc", + "container_of_test.cc", + "file_impl_test.cc", + "pipe_test.cc", + "prng_test.cc", + "ptr_test.cc", + "settable_future_test.cc", ], -) - -cc_test( - name = "ptr_test", - srcs = ["ptr_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "prng_test", - srcs = ["prng_test.cc"], - deps = [ - ":utils", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "file_test", - srcs = ["file_impl_test.cc"], deps = [ ":utils", "//file/util:temp_path", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "exception_test", - srcs = ["exception_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "pipe_test", - timeout = "short", - srcs = ["pipe_test.cc"], - deps = [ - ":utils", "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", - "//platform/port:string", - "//testing/base/public:gunit_main", - "//absl/time", - ], -) - -cc_test( - name = "byte_array_test", - timeout = "short", - srcs = ["byte_array_test.cc"], - deps = [ - ":utils", - "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", "//absl/time", diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index f1c769b7..1b155f0c 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -9,6 +9,7 @@ cc_library( hdrs = [ "atomic_boolean.h", "atomic_reference.h", + "atomic_reference_def.h", "ble.h", "ble_v2.h", "bluetooth_adapter.h", @@ -25,12 +26,15 @@ cc_library( "multi_thread_executor.h", "output_file.h", "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", + "settable_future_def.h", "single_thread_executor.h", "socket.h", "submittable_executor.h", + "submittable_executor_def.h", "system_clock.h", "thread_utils.h", "webrtc.h", @@ -41,6 +45,8 @@ cc_library( "//platform:types", "//platform/port:down_cast", "//platform/port:string", + "//absl/strings", + "//absl/types:any", "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/atomic_reference.h b/cpp/platform/api/atomic_reference.h index 52a8b14e..f06a5e06 100644 --- a/cpp/platform/api/atomic_reference.h +++ b/cpp/platform/api/atomic_reference.h @@ -1,21 +1,48 @@ #ifndef PLATFORM_API_ATOMIC_REFERENCE_H_ #define PLATFORM_API_ATOMIC_REFERENCE_H_ +#include "platform/api/atomic_reference_def.h" +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + namespace location { namespace nearby { -// An object reference that may be updated atomically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { template -class AtomicReference { +class AtomicReferenceImpl : public AtomicReference { public: - virtual ~AtomicReference() {} + explicit AtomicReferenceImpl(T initial_value) { + atomic_ = platform::ImplementationPlatform::createAtomicReferenceAny( + absl::any(initial_value)); + } - virtual T get() = 0; - virtual void set(T value) = 0; + ~AtomicReferenceImpl() override = default; + + void set(T new_value) override { atomic_->set(absl::any(new_value)); } + + T get() override { return absl::any_cast(atomic_->get()); } + + private: + Ptr> atomic_; }; +} // namespace impl + +template +Ptr> ImplementationPlatform::createAtomicReference( + T initial_value) { + return Ptr>( + new impl::AtomicReferenceImpl{initial_value}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/atomic_reference_def.h b/cpp/platform/api/atomic_reference_def.h new file mode 100644 index 00000000..7133caf8 --- /dev/null +++ b/cpp/platform/api/atomic_reference_def.h @@ -0,0 +1,27 @@ +#ifndef PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ +#define PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateAtomicReferenceSizeT() +// Ptr>> CreateAtomicReferencePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class AtomicReference { + public: + virtual ~AtomicReference() = default; + + virtual T get() = 0; + virtual void set(T value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h index 06a88288..b4353076 100644 --- a/cpp/platform/api/ble_v2.h +++ b/cpp/platform/api/ble_v2.h @@ -24,7 +24,7 @@ namespace nearby { struct BLEAdvertisementData { typedef std::int8_t TXPowerLevel; - static const TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = + static constexpr TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = std::numeric_limits::min(); bool is_connectable; diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h index 3770fda4..f9aa8b9c 100644 --- a/cpp/platform/api/multi_thread_executor.h +++ b/cpp/platform/api/multi_thread_executor.h @@ -10,11 +10,9 @@ namespace nearby { // unbounded queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { +class MultiThreadExecutor : public SubmittableExecutor { public: - ~MultiThreadExecutor() override {} + ~MultiThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/platform.h b/cpp/platform/api/platform.h new file mode 100644 index 00000000..70260c7f --- /dev/null +++ b/cpp/platform/api/platform.h @@ -0,0 +1,106 @@ +#ifndef PLATFORM_API_PLATFORM_H_ +#define PLATFORM_API_PLATFORM_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/api/wifi_lan.h" + +// Project-specific basic types, that are not part of API. +// TODO(apolyudov): replace with c++ standard types. +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // Class Templates in platform code. + // + // Platform interface does not support templates directly. + // This is a design decision. The purpose is to have type isolation + // between platform library (or simply platform) and core library. + // Another goal is to make a platform implementation a black box, + // which does not leak implementation details in any form, be that types, + // methods, or variables. + // + // Core library code does provide platform-specific class templates + // on top of (a non-templated) platform support. + // + // For every common library template that needs platform support, + // platform must provide an absl::any specialization of class template: + template + static Ptr> createAtomicReference(T initial_value = T{}); + template + static Ptr> createSettableFuture(); + + // AtomicReference + static Ptr> createAtomicReferenceAny( + absl::any initial_value); + + // SettableFuture + static Ptr> createSettableFutureAny(); + + // Non-template methods: general platform support. + static Ptr createAtomicBoolean(bool initial_value); + static Ptr createCountDownLatch(std::int32_t count); + static Ptr createLock(); + static Ptr createConditionVariable(Ptr lock); + static Ptr createHashUtils(); + static Ptr createThreadUtils(); + static Ptr createSystemClock(); + + // Java-like Executors + // Type aliases used to API 1.0 compatibility. + // They will be retired soon. + // TODO(apolyudov): cleanup. + using SingleThreadExecutorType = SubmittableExecutor; + using MultiThreadExecutorType = SubmittableExecutor; + using ScheduledExecutorType = ScheduledExecutor; + + static Ptr createSingleThreadExecutor(); + static Ptr createMultiThreadExecutor( + std::int32_t max_concurrency); + static Ptr createScheduledExecutor(); + + // Protocol implementations, domain-specific support + static Ptr createBluetoothAdapter(); + static Ptr createWifiMedium(); + static Ptr createBluetoothClassicMedium(); + static Ptr createBLEMedium(); + static Ptr createBLEMediumV2(); + static Ptr createServerSyncMedium(); + static Ptr createWifiLanMedium(); + static Ptr createWebRtcSignalingMessenger( + const std::string& self_id); + static std::string getDeviceId(); + static std::string getPayloadPath(int64_t payload_id); +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_PLATFORM_H_ diff --git a/cpp/platform/api/scheduled_executor.h b/cpp/platform/api/scheduled_executor.h index 2100058b..f4877450 100644 --- a/cpp/platform/api/scheduled_executor.h +++ b/cpp/platform/api/scheduled_executor.h @@ -3,7 +3,7 @@ #include -#include "platform/api/executor.h" +#include "platform/api/submittable_executor_def.h" #include "platform/cancelable.h" #include "platform/ptr.h" #include "platform/runnable.h" @@ -15,9 +15,9 @@ namespace nearby { // execute periodically. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html -class ScheduledExecutor : public Executor { +class ScheduledExecutor : public SubmittableExecutor { public: - virtual ~ScheduledExecutor() {} + ~ScheduledExecutor() override = default; virtual Ptr schedule(Ptr runnable, std::int64_t delay_millis) = 0; diff --git a/cpp/platform/api/server_sync.h b/cpp/platform/api/server_sync.h index e6b01aa9..1be12149 100644 --- a/cpp/platform/api/server_sync.h +++ b/cpp/platform/api/server_sync.h @@ -22,7 +22,7 @@ class ServerSyncDevice { virtual std::string getOwnGuid() = 0; }; -// Container of operations that can be performed over the Chrome Sync medium. +// Container of operations that can be performed over the Server Sync medium. class ServerSyncMedium { public: virtual ~ServerSyncMedium() {} diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h index f9a5e35c..4fd69616 100644 --- a/cpp/platform/api/settable_future.h +++ b/cpp/platform/api/settable_future.h @@ -1,24 +1,65 @@ #ifndef PLATFORM_API_SETTABLE_FUTURE_H_ #define PLATFORM_API_SETTABLE_FUTURE_H_ -#include "platform/api/listenable_future.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future_def.h" +#include "platform/exception.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" namespace location { namespace nearby { -// A SettableFuture is a type of Future whose result can be set. -// -// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { + template -class SettableFuture : public ListenableFuture { +class SettableFutureImpl : public SettableFuture { public: - ~SettableFuture() override {} + SettableFutureImpl() { + future_ = platform::ImplementationPlatform::createSettableFutureAny(); + } - virtual bool set(T value) = 0; + ~SettableFutureImpl() override = default; - virtual bool setException(Exception exception) = 0; + bool set(T value) override { return future_->set(absl::any(value)); } + + bool setException(Exception exception) override { + return future_->setException(exception); + } + + void addListener(Ptr runnable, Executor* executor) override { + future_->addListener(runnable, executor); + } + + ExceptionOr get() override { return CommonGet(future_->get()); } + ExceptionOr get(std::int64_t timeout_ms) override { + return CommonGet(future_->get(timeout_ms)); + } + + private: + ExceptionOr CommonGet(ExceptionOr ret_val) { + if (ret_val.exception() != Exception::kSuccess) { + return ExceptionOr{ret_val.exception()}; + } + return ExceptionOr{absl::any_cast(ret_val.result())}; + } + + Ptr> future_; }; +} // namespace impl +template +Ptr> ImplementationPlatform::createSettableFuture() { + return Ptr>(new impl::SettableFutureImpl{}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/settable_future_def.h b/cpp/platform/api/settable_future_def.h new file mode 100644 index 00000000..e1a27c20 --- /dev/null +++ b/cpp/platform/api/settable_future_def.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_API_SETTABLE_FUTURE_DEF_H_ +#define PLATFORM_API_SETTABLE_FUTURE_DEF_H_ + +#include "platform/api/listenable_future.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// A SettableFuture is a type of Future whose result can be set. +// +// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateSettableFutureSizeT() +// Ptr>> CreateSettableFuturePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class SettableFuture : public ListenableFuture { + public: + ~SettableFuture() override = default; + + virtual bool set(T value) = 0; + + virtual bool setException(Exception exception) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SETTABLE_FUTURE_DEF_H_ diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h index e3338648..51dd02a2 100644 --- a/cpp/platform/api/single_thread_executor.h +++ b/cpp/platform/api/single_thread_executor.h @@ -10,11 +10,9 @@ namespace nearby { // queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { +class SingleThreadExecutor : public SubmittableExecutor { public: - ~SingleThreadExecutor() override {} + ~SingleThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h index 3d7bd625..b84ae602 100644 --- a/cpp/platform/api/submittable_executor.h +++ b/cpp/platform/api/submittable_executor.h @@ -1,37 +1,40 @@ #ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ #define PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ +#include + #include "platform/api/executor.h" #include "platform/api/future.h" -#include "platform/callable.h" -#include "platform/port/down_cast.h" -#include "platform/ptr.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/exception.h" namespace location { namespace nearby { -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class IOSSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// Ptr > submit(Ptr > callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - Ptr> submit(Ptr> callable) { - return DOWN_CAST(this)->submit(callable); +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +template +Ptr> SubmittableExecutor::submit(Ptr> callable) { + using Platform = platform::ImplementationPlatform; + Ptr> future{Platform::createSettableFuture()}; + bool submitted = DoSubmit([callable, future]() { + ExceptionOr result = callable->call(); + if (result.ok()) { + future->set(std::move(result.result())); + } else { + future->setException({result.exception()}); + } + }); + if (!submitted) { + // Raise Exception::kExecution if we are shutting down. + future->setException({Exception::kExecution}); } -}; + return future; +} } // namespace nearby } // namespace location diff --git a/cpp/platform/api/submittable_executor_def.h b/cpp/platform/api/submittable_executor_def.h new file mode 100644 index 00000000..0f7cd99c --- /dev/null +++ b/cpp/platform/api/submittable_executor_def.h @@ -0,0 +1,35 @@ +#ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ +#define PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ + +#include + +#include "platform/api/executor.h" +#include "platform/api/future.h" +#include "platform/callable.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + template + Ptr> submit(Ptr> callable); + + protected: + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(std::function wrapped_callable) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index 35f53e60..c428c0cb 100644 --- a/cpp/platform/api/webrtc.h +++ b/cpp/platform/api/webrtc.h @@ -33,7 +33,7 @@ class WebRtcSignalingMessenger { virtual bool registerSignaling() = 0; virtual bool unregisterSignaling() = 0; - virtual bool sendMessage(const string& peer_id, + virtual bool sendMessage(const std::string& peer_id, ConstPtr message) = 0; virtual bool startReceivingMessages( Ptr listener) = 0; diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h index 1b13b393..f282ba45 100644 --- a/cpp/platform/api/wifi_lan.h +++ b/cpp/platform/api/wifi_lan.h @@ -7,6 +7,7 @@ #include "platform/exception.h" #include "platform/port/string.h" #include "platform/ptr.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -50,9 +51,10 @@ class WifiLanMedium { public: virtual ~WifiLanMedium() = default; - virtual bool StartAdvertising(const std::string& service_id, - const string& wifi_lan_service_info_name) = 0; - virtual void StopAdvertising(const std::string& service_id) = 0; + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; // Callback for WifiLan discover results. class DiscoveredServiceCallback { @@ -64,9 +66,9 @@ class WifiLanMedium { }; virtual bool StartDiscovery( - const std::string& service_id, + absl::string_view service_id, Ptr discovered_service_callback) = 0; - virtual void StopDiscovery(const std::string& service_id) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; class AcceptedConnectionCallback { public: @@ -76,16 +78,16 @@ class WifiLanMedium { // destroyed) by the recipient of the callback methods (i.e. the creator of // the concrete AcceptedConnectionCallback object). virtual void OnConnectionAccepted(Ptr socket, - const string& service_id) = 0; + absl::string_view service_id) = 0; }; virtual bool StartAcceptingConnections( - const std::string& service_id, + absl::string_view service_id, Ptr accepted_connection_callback) = 0; - virtual void StopAcceptingConnections(const std::string& service_id) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; virtual Ptr Connect(Ptr wifi_lan_service, - const std::string& service_id) = 0; + absl::string_view service_id) = 0; }; } // namespace nearby diff --git a/cpp/platform/api2/atomic_boolean.h b/cpp/platform/api2/atomic_boolean.h deleted file mode 100644 index b5e729fa..00000000 --- a/cpp/platform/api2/atomic_boolean.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef PLATFORM_API2_ATOMIC_BOOLEAN_H_ -#define PLATFORM_API2_ATOMIC_BOOLEAN_H_ - -namespace location { -namespace nearby { - -// A boolean value that may be updated atomically. -// -// https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html -class AtomicBoolean { - public: - virtual ~AtomicBoolean() {} - - virtual bool Get() = 0; - virtual void Set(bool value) = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/input_file.h b/cpp/platform/api2/input_file.h deleted file mode 100644 index 0191aff8..00000000 --- a/cpp/platform/api2/input_file.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PLATFORM_API2_INPUT_FILE_H_ -#define PLATFORM_API2_INPUT_FILE_H_ - -#include - -#include "platform/api2/input_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An InputFile represents a readable file on the system. -class InputFile : public InputStream { - public: - ~InputFile() override = default; - virtual std::string GetFilePath() const = 0; - virtual size_t GetTotalSize() const = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_INPUT_FILE_H_ diff --git a/cpp/platform/api2/input_stream.h b/cpp/platform/api2/input_stream.h deleted file mode 100644 index f91a5466..00000000 --- a/cpp/platform/api2/input_stream.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef PLATFORM_API2_INPUT_STREAM_H_ -#define PLATFORM_API2_INPUT_STREAM_H_ - -#include - -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An InputStream represents an input stream of bytes. -// -// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html -class InputStream { - public: - virtual ~InputStream() {} - - virtual ExceptionOr Read( - size_t size) = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_INPUT_STREAM_H_ diff --git a/cpp/platform/api2/multi_thread_executor.h b/cpp/platform/api2/multi_thread_executor.h deleted file mode 100644 index f910bbc4..00000000 --- a/cpp/platform/api2/multi_thread_executor.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ - -#include "platform/api2/submittable_executor.h" - -namespace location { -namespace nearby { - -// An Executor that reuses a fixed number of threads operating off a shared -// unbounded queue. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { - public: - ~MultiThreadExecutor() override {} -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/mutex.h b/cpp/platform/api2/mutex.h deleted file mode 100644 index d4dbaf61..00000000 --- a/cpp/platform/api2/mutex.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_MUTEX_H_ -#define PLATFORM_API2_MUTEX_H_ - -namespace location { -namespace nearby { - -// A lock is a tool for controlling access to a shared resource by multiple -// threads. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html -class Mutex { - public: - virtual ~Mutex() {} - - virtual void Lock() = 0; - virtual void Unlock() = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_MUTEX_H_ diff --git a/cpp/platform/api2/output_file.h b/cpp/platform/api2/output_file.h deleted file mode 100644 index 4ac962e8..00000000 --- a/cpp/platform/api2/output_file.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef PLATFORM_API2_OUTPUT_FILE_H_ -#define PLATFORM_API2_OUTPUT_FILE_H_ - -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An OutputFile represents a writable file on the system. -class OutputFile : public OutputStream { - public: - ~OutputFile() override = default; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_OUTPUT_FILE_H_ diff --git a/cpp/platform/api2/output_stream.h b/cpp/platform/api2/output_stream.h deleted file mode 100644 index b9336ad1..00000000 --- a/cpp/platform/api2/output_stream.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PLATFORM_API2_OUTPUT_STREAM_H_ -#define PLATFORM_API2_OUTPUT_STREAM_H_ - -#include "platform/byte_array.h" -#include "platform/exception.h" - -namespace location { -namespace nearby { - -// An OutputStream represents an output stream of bytes. -// -// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html -class OutputStream { - public: - virtual ~OutputStream() {} - - virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo - virtual Exception Flush() = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_OUTPUT_STREAM_H_ diff --git a/cpp/platform/api2/scheduled_executor.h b/cpp/platform/api2/scheduled_executor.h deleted file mode 100644 index ae773ee1..00000000 --- a/cpp/platform/api2/scheduled_executor.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef PLATFORM_API2_SCHEDULED_EXECUTOR_H_ -#define PLATFORM_API2_SCHEDULED_EXECUTOR_H_ - -#include -#include - -#include "platform/api2/executor.h" -#include "platform/cancelable.h" -#include "platform/runnable.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -// An Executor that can schedule commands to run after a given delay, or to -// execute periodically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html -class ScheduledExecutor : public Executor { - public: - ~ScheduledExecutor() override = default; - virtual std::unique_ptr Schedule( - std::unique_ptr runnable, absl::Duration duration) = 0; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/single_thread_executor.h b/cpp/platform/api2/single_thread_executor.h deleted file mode 100644 index 990f2fe7..00000000 --- a/cpp/platform/api2/single_thread_executor.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ - -#include "platform/api2/submittable_executor.h" - -namespace location { -namespace nearby { - -// An Executor that uses a single worker thread operating off an unbounded -// queue. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { - public: - ~SingleThreadExecutor() override {} -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/api2/submittable_executor.h b/cpp/platform/api2/submittable_executor.h deleted file mode 100644 index 43f16f56..00000000 --- a/cpp/platform/api2/submittable_executor.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ -#define PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ - -#include - -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/callable.h" - -namespace location { -namespace nearby { - -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class XyzSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// std::unique_ptr> submit(std::unique_ptr> callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - std::unique_ptr> Submit(std::unique_ptr> callable) { - static_assert( - std::is_base_of_v, - "Class template type is not derived from SubmittableExecutor"); - return static_cast(this)->submit(callable); - } -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api2/system_clock.h b/cpp/platform/api2/system_clock.h deleted file mode 100644 index 3b0b8090..00000000 --- a/cpp/platform/api2/system_clock.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_SYSTEM_CLOCK_H_ -#define PLATFORM_API2_SYSTEM_CLOCK_H_ - -#include - -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -class SystemClock final { - public: - // Returns the time (in milliseconds) since the system was booted, and - // includes deep sleep. This clock should be guaranteed to be monotonic, and - // should continue to tick even when the CPU is in power saving modes. - static absl::Time ElapsedRealtime(); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/thread_utils.h b/cpp/platform/api2/thread_utils.h deleted file mode 100644 index 990c0ec2..00000000 --- a/cpp/platform/api2/thread_utils.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef PLATFORM_API2_THREAD_UTILS_H_ -#define PLATFORM_API2_THREAD_UTILS_H_ - -#include - -#include "platform/exception.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { - -class ThreadUtils final { - public: - // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long) - // throws Exception::kInterrupted - static Exception Sleep(absl::Duration timeout); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_THREAD_UTILS_H_ diff --git a/cpp/platform/atomic_reference_test.cc b/cpp/platform/atomic_reference_test.cc new file mode 100644 index 00000000..58df5fc3 --- /dev/null +++ b/cpp/platform/atomic_reference_test.cc @@ -0,0 +1,80 @@ +#include "platform/api/atomic_reference.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(5); + ASSERT_EQ(p->get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createAtomicReference(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createAtomicReference< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = p->get(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + auto ref = + platform::ImplementationPlatform::createAtomicReference(s); + ASSERT_EQ(s, ref->get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h index a3ea830e..fba6ad08 100644 --- a/cpp/platform/byte_array.h +++ b/cpp/platform/byte_array.h @@ -37,7 +37,7 @@ class ByteArray { data_.assign(size, value); } - char* getData() { return data_.data(); } + char* getData() { return &data_[0]; } const char* getData() const { return data_.data(); } size_t size() const { return data_.size(); } diff --git a/cpp/platform/cancelable_alarm.cc b/cpp/platform/cancelable_alarm.cc index 326a893e..bb5fd90d 100644 --- a/cpp/platform/cancelable_alarm.cc +++ b/cpp/platform/cancelable_alarm.cc @@ -1,25 +1,27 @@ #include "platform/cancelable_alarm.h" +#include "platform/api/platform.h" +#include "platform/api/scheduled_executor.h" #include "platform/synchronized.h" namespace location { namespace nearby { -template -CancelableAlarm::CancelableAlarm( - const string &name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor) +namespace { +using Platform = platform::ImplementationPlatform; +} + +CancelableAlarm::CancelableAlarm(const std::string &name, + Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor) : name_(name), lock_(Platform::createLock()), cancelable_(scheduled_executor->schedule(runnable, delay_millis)) {} -template -CancelableAlarm::~CancelableAlarm() { - cancelable_.destroy(); -} +CancelableAlarm::~CancelableAlarm() { cancelable_.destroy(); } -template -bool CancelableAlarm::cancel() { +bool CancelableAlarm::cancel() { Synchronized s(lock_.get()); if (cancelable_.isNull()) { diff --git a/cpp/platform/cancelable_alarm.h b/cpp/platform/cancelable_alarm.h index d5549cf6..e8e317c1 100644 --- a/cpp/platform/cancelable_alarm.h +++ b/cpp/platform/cancelable_alarm.h @@ -4,6 +4,7 @@ #include #include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" #include "platform/cancelable.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -17,18 +18,17 @@ namespace nearby { * for posting a Runnable on a ScheduledExecutor and (possibly) later * canceling it. */ -template class CancelableAlarm { public: - CancelableAlarm( - const string& name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor); + CancelableAlarm(const std::string& name, Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor); ~CancelableAlarm(); bool cancel(); private: - string name_; + std::string name_; ScopedPtr > lock_; Ptr cancelable_; }; @@ -36,6 +36,4 @@ class CancelableAlarm { } // namespace nearby } // namespace location -#include "platform/cancelable_alarm.cc" - #endif // PLATFORM_CANCELABLE_ALARM_H_ diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h index 485f03a3..01b333e8 100644 --- a/cpp/platform/exception.h +++ b/cpp/platform/exception.h @@ -15,13 +15,13 @@ struct Exception { EXECUTION, // New code should use the kConstants. // Old CONSTANTS are deprecated, and should not be used. - kFailed = -1, // Initial value of Exception; any unknown error. + kFailed = -1, // Initial value of Exception; any unknown error. kSuccess = NONE, // No exception. - kIo = IO, // IO Error happened. + kIo = IO, // IO Error happened. kInterrupted = INTERRUPTED, // Operation was interrupted. kInvalidProtocolBuffer = INVALID_PROTOCOL_BUFFER, // Couldn't parse. - kExecution = EXECUTION, // Couldn't execute. - kTimeout, // Operarion did not finish within specified time. + kExecution = EXECUTION, // Couldn't execute. + kTimeout, // Operation did not finish within specified time. }; Value value {kFailed}; }; diff --git a/cpp/platform/file_impl.h b/cpp/platform/file_impl.h index db522c23..702cf7d0 100644 --- a/cpp/platform/file_impl.h +++ b/cpp/platform/file_impl.h @@ -14,7 +14,7 @@ namespace nearby { class InputFileImpl final : public InputFile { public: - explicit InputFileImpl(const std::string& path, std::int64_t size); + InputFileImpl(const std::string& path, std::int64_t size); ~InputFileImpl() override {} ExceptionOr> read(std::int64_t size) override; diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc index f1397d5c..d4a5b339 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/file_impl_test.cc @@ -41,7 +41,7 @@ class FileImplTest : public ::testing::Test { ASSERT_TRUE(bytes.result().isNull()); } - static const int64_t kMaxSize = 3; + static constexpr int64_t kMaxSize = 3; std::unique_ptr temp_path_; std::string path_; diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/default/BUILD deleted file mode 100644 index 87f28f9c..00000000 --- a/cpp/platform/impl/default/BUILD +++ /dev/null @@ -1,45 +0,0 @@ -cc_library( - name = "default", - srcs = [ - "default_platform.cc", - ], - hdrs = [ - "default_condition_variable.h", - "default_lock.h", - "default_platform.h", - ], - visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//core:__subpackages__", - ], - deps = [ - ":condition_variable", - ":lock", - "//platform:types", - "//platform/api", - ], -) - -cc_library( - name = "lock", - srcs = ["default_lock.cc"], - hdrs = ["default_lock.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = ["//platform/api:lock"], -) - -cc_library( - name = "condition_variable", - srcs = ["default_condition_variable.cc"], - hdrs = ["default_condition_variable.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = [ - ":lock", - "//platform:types", - "//platform/api:condition_variable", - ], -) diff --git a/cpp/platform/impl/default/default_condition_variable.cc b/cpp/platform/impl/default/default_condition_variable.cc deleted file mode 100644 index d7e3811f..00000000 --- a/cpp/platform/impl/default/default_condition_variable.cc +++ /dev/null @@ -1,28 +0,0 @@ -#include "platform/impl/default/default_condition_variable.h" - -namespace location { -namespace nearby { - -DefaultConditionVariable::DefaultConditionVariable(Ptr lock) - : lock_(lock), attr_(), cond_() { - pthread_condattr_init(&attr_); - - pthread_cond_init(&cond_, &attr_); -} - -DefaultConditionVariable::~DefaultConditionVariable() { - pthread_cond_destroy(&cond_); - - pthread_condattr_destroy(&attr_); -} - -void DefaultConditionVariable::notify() { pthread_cond_broadcast(&cond_); } - -Exception::Value DefaultConditionVariable::wait() { - pthread_cond_wait(&cond_, &(lock_->mutex_)); - - return Exception::NONE; -} - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/impl/default/default_condition_variable.h b/cpp/platform/impl/default/default_condition_variable.h deleted file mode 100644 index 4aa1343f..00000000 --- a/cpp/platform/impl/default/default_condition_variable.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ - -#include - -#include "platform/api/condition_variable.h" -#include "platform/impl/default/default_lock.h" -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -class DefaultConditionVariable : public ConditionVariable { - public: - explicit DefaultConditionVariable(Ptr lock); - ~DefaultConditionVariable() override; - - void notify() override; - Exception::Value wait() override; - - private: - Ptr lock_; - pthread_condattr_t attr_; - pthread_cond_t cond_; -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_platform.cc b/cpp/platform/impl/default/default_platform.cc deleted file mode 100644 index 3d41d42b..00000000 --- a/cpp/platform/impl/default/default_platform.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "platform/impl/default/default_platform.h" - -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" - -namespace location { -namespace nearby { - -Ptr DefaultPlatform::createLock() { return MakePtr(new DefaultLock()); } - -Ptr DefaultPlatform::createConditionVariable( - Ptr lock) { - return MakePtr(new DefaultConditionVariable(DowncastPtr(lock))); -} - -} // namespace nearby -} // namespace location diff --git a/cpp/platform/impl/default/default_platform.h b/cpp/platform/impl/default/default_platform.h deleted file mode 100644 index 0d001825..00000000 --- a/cpp/platform/impl/default/default_platform.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ - -#include "platform/api/condition_variable.h" -#include "platform/api/lock.h" -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -// Provides obvious portable implementations of a subset of the hooks specified -// within //platform/api/. -// -// It's highly recommended that custom Platform implementations delegate to -// these methods unless there's a very good reason not to. -class DefaultPlatform { - public: - static Ptr createLock(); - - static Ptr createConditionVariable(Ptr lock); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ diff --git a/cpp/platform/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD index e69de29b..e043b58e 100644 --- a/cpp/platform/impl/g3/BUILD +++ b/cpp/platform/impl/g3/BUILD @@ -0,0 +1,26 @@ +cc_library( + name = "g3", + srcs = [ + "atomic_reference_impl.h", + "platform.cc", + "settable_future_impl.h", + "system_clock_impl.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform/api", + "//platform/impl/shared:atomic_boolean", + "//platform/impl/shared:posix_condition_variable", + "//platform/impl/shared:posix_lock", + "//platform/port:string", + "//absl/base:core_headers", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + ], +) diff --git a/cpp/platform/impl/g3/atomic_reference_impl.h b/cpp/platform/impl/g3/atomic_reference_impl.h new file mode 100644 index 00000000..b5f94c60 --- /dev/null +++ b/cpp/platform/impl/g3/atomic_reference_impl.h @@ -0,0 +1,39 @@ +#ifndef PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference.h" +#include "platform/ptr.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { + absl::MutexLock lock(&mutex_); + return value_; + } + void set(absl::any value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/g3/platform.cc b/cpp/platform/impl/g3/platform.cc new file mode 100644 index 00000000..b261cbe0 --- /dev/null +++ b/cpp/platform/impl/g3/platform.cc @@ -0,0 +1,137 @@ +#include "platform/api/platform.h" + +#include +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/impl/g3/atomic_reference_impl.h" +#include "platform/impl/g3/settable_future_impl.h" +#include "platform/impl/g3/system_clock_impl.h" +#include "platform/impl/shared/atomic_boolean_impl.h" +#include "platform/impl/shared/posix_condition_variable.h" +#include "platform/impl/shared/posix_lock.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr(/*new SingleThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr(/*new MultiThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr(/*new ScheduledExecutorImpl()*/); +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr(/*new CountDownLatchImpl(count)*/); +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr(/*new ThreadUtilsImpl()*/); +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr(new SystemClockImpl()); +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr(new AtomicBooleanImpl(initial_value)); +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr(/*new ServerSyncMediumImpl()*/); +} + +Ptr +ImplementationPlatform::createWebRtcSignalingMessenger( + const std::string& self_id) { + return Ptr(/*new FCMSignalingMessenger()*/); +} + +Ptr ImplementationPlatform::createLock() { + return Ptr(new PosixLock()); +} + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr(new PosixConditionVariable(lock)); +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr(/*new HashUtilsImpl()*/); +} + +std::string ImplementationPlatform::getDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/g3/settable_future_impl.h b/cpp/platform/impl/g3/settable_future_impl.h new file mode 100644 index 00000000..36e5aebf --- /dev/null +++ b/cpp/platform/impl/g3/settable_future_impl.h @@ -0,0 +1,94 @@ +#ifndef PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ + +#include + +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool setException(Exception exception) override { + absl::MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + absl::MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(&mutex_); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + absl::MutexLock lock(&mutex_); + absl::Duration timeout = absl::Milliseconds(timeout_ms); + while (!done_) { + absl::Time start_time = absl::Now(); + if (completed_.WaitWithTimeout(&mutex_, timeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = absl::Now() - start_time; + if (spent < timeout) { + timeout -= spent; + } else if (!done_) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + private: + bool SetExceptionLocked(Exception exception) { + if (!done_) { + exception_ = exception.value != Exception::kSuccess + ? exception + : Exception{Exception::kFailed}; + done_ = true; + completed_.SignalAll(); + } + return true; + } + + absl::Mutex mutex_; + absl::CondVar completed_; + bool done_{false}; + absl::any value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/impl/g3/system_clock_impl.h b/cpp/platform/impl/g3/system_clock_impl.h new file mode 100644 index 00000000..5f7d22ee --- /dev/null +++ b/cpp/platform/impl/g3/system_clock_impl.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ +#define PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ + +#include + +#include "platform/api/system_clock.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +class SystemClockImpl : public SystemClock { + public: + std::int64_t elapsedRealtime() override { + return absl::ToUnixMillis(absl::Now()); + } +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index 892ccd51..1ace2e42 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -1,10 +1,10 @@ cc_library( - name = "sample", + name = "sample_platform", srcs = [ - "sample_wifi_medium.cc", - "sample_wifi_medium.h", + "atomic_reference_impl.h", + "sample_platform.cc", + "settable_future_impl.h", ], - hdrs = ["sample_platform.h"], visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", @@ -14,7 +14,9 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", "//absl/time", + "//absl/types:any", ], ) diff --git a/cpp/platform/impl/sample/atomic_reference_impl.h b/cpp/platform/impl/sample/atomic_reference_impl.h new file mode 100644 index 00000000..8479cc52 --- /dev/null +++ b/cpp/platform/impl/sample/atomic_reference_impl.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { return {}; } + void set(absl::any value) override {} +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/sample/sample_platform.cc b/cpp/platform/impl/sample/sample_platform.cc new file mode 100644 index 00000000..dc660c46 --- /dev/null +++ b/cpp/platform/impl/sample/sample_platform.cc @@ -0,0 +1,125 @@ +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/wifi.h" +#include "platform/cancelable.h" +#include "platform/impl/sample/atomic_reference_impl.h" +#include "platform/impl/sample/settable_future_impl.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr{}; +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createWebRtcSignalingMessenger( + const std::string& self_id) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createLock() { return Ptr{}; } + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr{}; +} + +std::string ImplementationPlatform::getDeviceId() { return "sample"; } + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/sample-" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h deleted file mode 100644 index 113f4636..00000000 --- a/cpp/platform/impl/sample/sample_platform.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ - -#include - -#include "platform/api/atomic_boolean.h" -#include "platform/api/atomic_reference.h" -#include "platform/api/ble.h" -#include "platform/api/ble_v2.h" -#include "platform/api/bluetooth_adapter.h" -#include "platform/api/bluetooth_classic.h" -#include "platform/api/condition_variable.h" -#include "platform/api/count_down_latch.h" -#include "platform/api/hash_utils.h" -#include "platform/api/lock.h" -#include "platform/api/multi_thread_executor.h" -#include "platform/api/settable_future.h" -#include "platform/api/single_thread_executor.h" -#include "platform/api/system_clock.h" -#include "platform/api/thread_utils.h" -#include "platform/api/wifi.h" -#include "platform/cancelable.h" -#include "platform/impl/sample/sample_wifi_medium.h" -#include "platform/port/string.h" -#include "platform/ptr.h" -#include "platform/runnable.h" - -namespace location { -namespace nearby { -namespace sample { - -// The SamplePlatform class below shows an example of the factory functions -// and typedefs. -class SamplePlatform { - public: - class SampleSubmittableExecutor - : public SubmittableExecutor { - public: - template - Ptr > submit(Ptr > callable) { - return Ptr >(); - } - }; - - class SampleSingleThreadExecutor - : public SingleThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleMultiThreadExecutor - : public MultiThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleScheduledExecutor { - public: - Ptr schedule(Ptr runnable, - std::int64_t delay_millis) { - return Ptr(); - } - void shutdown() {} - }; - - typedef SampleSingleThreadExecutor SingleThreadExecutorType; - static Ptr createSingleThreadExecutor() { - return MakePtr(new SingleThreadExecutorType()); - } - - typedef SampleMultiThreadExecutor MultiThreadExecutorType; - static Ptr createMultiThreadExecutor( - std::int32_t max_concurrency) { - return MakePtr(new MultiThreadExecutorType()); - } - - typedef SampleScheduledExecutor ScheduledExecutorType; - static Ptr createScheduledExecutor() { - return MakePtr(new ScheduledExecutorType()); - } - - static Ptr createBluetoothAdapter() { - return Ptr(); - } - - static Ptr createWifiMedium() { - return MakePtr(new SampleWifiMedium()); - } - - static Ptr createCountDownLatch(std::int32_t count) { - return Ptr(); - } - - template - static Ptr > createSettableFuture() { - return Ptr >(); - } - - static Ptr createThreadUtils() { return Ptr(); } - - static Ptr createSystemClock() { return Ptr(); } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr > createAtomicReference(T initial_value) { - return Ptr >(); - } - - static Ptr createBluetoothClassicMedium() { - return Ptr(); - } - - static Ptr createBLEMedium() { return Ptr(); } - - static Ptr createBLEMediumV2() { return Ptr(); } - - static Ptr createLock() { return Ptr(); } - - static Ptr createConditionVariable(Ptr lock) { - return Ptr(); - } - - static Ptr createHashUtils() { return Ptr(); } - - static std::string getDeviceId() { return ""; } - - static std::string getPayloadPath(int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); - } -}; - -} // namespace sample -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ diff --git a/cpp/platform/impl/sample/settable_future_impl.h b/cpp/platform/impl/sample/settable_future_impl.h new file mode 100644 index 00000000..16f82672 --- /dev/null +++ b/cpp/platform/impl/sample/settable_future_impl.h @@ -0,0 +1,35 @@ +#ifndef PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ + +#include "platform/api/settable_future_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { return true; } + + bool setException(Exception exception) override { return true; } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + return ExceptionOr{Exception{Exception::kFailed}}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + return ExceptionOr{Exception{Exception::kFailed}}; + } +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/impl/shared/BUILD b/cpp/platform/impl/shared/BUILD new file mode 100644 index 00000000..fb1850b2 --- /dev/null +++ b/cpp/platform/impl/shared/BUILD @@ -0,0 +1,45 @@ +cc_library( + name = "posix_lock", + srcs = [ + "posix_lock.cc", + ], + hdrs = [ + "posix_lock.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + "//platform/api", + ], +) + +cc_library( + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + ":posix_lock", + "//platform:types", + "//platform/api:condition_variable", + ], +) + +cc_library( + name = "atomic_boolean", + hdrs = ["atomic_boolean_impl.h"], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = ["//platform/api"], +) diff --git a/cpp/platform/impl/shared/atomic_boolean_impl.h b/cpp/platform/impl/shared/atomic_boolean_impl.h new file mode 100644 index 00000000..8f28e64a --- /dev/null +++ b/cpp/platform/impl/shared/atomic_boolean_impl.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ +#define PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ + +#include + +#include "platform/api/atomic_boolean.h" + +namespace location { +namespace nearby { + +class AtomicBooleanImpl : public AtomicBoolean { + public: + explicit AtomicBooleanImpl(bool initial_value) : value_(initial_value) {} + ~AtomicBooleanImpl() override = default; + + // AtomicBoolean: + bool get() override { + return value_.load(); + } + + // AtomicBoolean: + void set(bool value) override { + value_.store(value); + } + + private: + std::atomic_bool value_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ diff --git a/cpp/platform/impl/shared/posix_condition_variable.cc b/cpp/platform/impl/shared/posix_condition_variable.cc new file mode 100644 index 00000000..72b4450e --- /dev/null +++ b/cpp/platform/impl/shared/posix_condition_variable.cc @@ -0,0 +1,28 @@ +#include "platform/impl/shared/posix_condition_variable.h" + +namespace location { +namespace nearby { + +PosixConditionVariable::PosixConditionVariable(Ptr lock) + : lock_(lock), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +PosixConditionVariable::~PosixConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void PosixConditionVariable::notify() { pthread_cond_broadcast(&cond_); } + +Exception::Value PosixConditionVariable::wait() { + pthread_cond_wait(&cond_, &(lock_->mutex_)); + + return Exception::kSuccess; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/shared/posix_condition_variable.h b/cpp/platform/impl/shared/posix_condition_variable.h new file mode 100644 index 00000000..ea558558 --- /dev/null +++ b/cpp/platform/impl/shared/posix_condition_variable.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ + +#include + +#include "platform/api/condition_variable.h" +#include "platform/impl/shared/posix_lock.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +class PosixConditionVariable : public ConditionVariable { + public: + explicit PosixConditionVariable(Ptr lock); + ~PosixConditionVariable() override; + + void notify() override; + Exception::Value wait() override; + + private: + Ptr lock_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_lock.cc b/cpp/platform/impl/shared/posix_lock.cc similarity index 55% rename from cpp/platform/impl/default/default_lock.cc rename to cpp/platform/impl/shared/posix_lock.cc index bfd3cf0b..3bb154b6 100644 --- a/cpp/platform/impl/default/default_lock.cc +++ b/cpp/platform/impl/shared/posix_lock.cc @@ -1,24 +1,24 @@ -#include "platform/impl/default/default_lock.h" +#include "platform/impl/shared/posix_lock.h" namespace location { namespace nearby { -DefaultLock::DefaultLock() : attr_(), mutex_() { +PosixLock::PosixLock() : attr_(), mutex_() { pthread_mutexattr_init(&attr_); pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex_, &attr_); } -DefaultLock::~DefaultLock() { +PosixLock::~PosixLock() { pthread_mutex_destroy(&mutex_); pthread_mutexattr_destroy(&attr_); } -void DefaultLock::lock() { pthread_mutex_lock(&mutex_); } +void PosixLock::lock() { pthread_mutex_lock(&mutex_); } -void DefaultLock::unlock() { pthread_mutex_unlock(&mutex_); } +void PosixLock::unlock() { pthread_mutex_unlock(&mutex_); } } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/default/default_lock.h b/cpp/platform/impl/shared/posix_lock.h similarity index 51% rename from cpp/platform/impl/default/default_lock.h rename to cpp/platform/impl/shared/posix_lock.h index 18d50e44..b972e7e4 100644 --- a/cpp/platform/impl/default/default_lock.h +++ b/cpp/platform/impl/shared/posix_lock.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#ifndef PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ +#define PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ #include @@ -8,16 +8,16 @@ namespace location { namespace nearby { -class DefaultLock : public Lock { +class PosixLock : public Lock { public: - DefaultLock(); - ~DefaultLock() override; + PosixLock(); + ~PosixLock() override; void lock() override; void unlock() override; private: - friend class DefaultConditionVariable; + friend class PosixConditionVariable; pthread_mutexattr_t attr_; pthread_mutex_t mutex_; @@ -26,4 +26,4 @@ class DefaultLock : public Lock { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#endif // PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ diff --git a/cpp/platform/impl/shared/sample/BUILD b/cpp/platform/impl/shared/sample/BUILD new file mode 100644 index 00000000..a1d0605f --- /dev/null +++ b/cpp/platform/impl/shared/sample/BUILD @@ -0,0 +1,22 @@ +cc_library( + name = "sample_wifi_medium", + srcs = [ + "sample_wifi_medium.cc", + ], + hdrs = [ + "sample_wifi_medium.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform/impl:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/time", + ], +) diff --git a/cpp/platform/impl/sample/sample_wifi_medium.cc b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc similarity index 98% rename from cpp/platform/impl/sample/sample_wifi_medium.cc rename to cpp/platform/impl/shared/sample/sample_wifi_medium.cc index 89b68391..fed3d1fc 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.cc +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc @@ -1,4 +1,4 @@ -#include "platform/impl/sample/sample_wifi_medium.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include diff --git a/cpp/platform/impl/sample/sample_wifi_medium.h b/cpp/platform/impl/shared/sample/sample_wifi_medium.h similarity index 90% rename from cpp/platform/impl/sample/sample_wifi_medium.h rename to cpp/platform/impl/shared/sample/sample_wifi_medium.h index e64f1b8e..688ea2d2 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.h +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#ifndef PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#define PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ #include "platform/api/wifi.h" @@ -56,4 +56,4 @@ class SampleWifiMedium : public WifiMedium { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#endif // PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ diff --git a/cpp/platform/pipe.cc b/cpp/platform/pipe.cc index e5572127..0cb6818c 100644 --- a/cpp/platform/pipe.cc +++ b/cpp/platform/pipe.cc @@ -1,19 +1,21 @@ #include "platform/pipe.h" +#include "platform/api/platform.h" #include "platform/synchronized.h" namespace location { namespace nearby { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace pipe { -template class PipeInputStream : public InputStream { public: - explicit PipeInputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeInputStream() override { - close(); - } + explicit PipeInputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeInputStream() override { close(); } ExceptionOr> read() override { return read(kChunkSize); } ExceptionOr> read(std::int64_t size) override { @@ -27,18 +29,15 @@ class PipeInputStream : public InputStream { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; - Ptr> pipe_; + Ptr pipe_; }; -template class PipeOutputStream : public OutputStream { public: - explicit PipeOutputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeOutputStream() override { - close(); - } + explicit PipeOutputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeOutputStream() override { close(); } Exception::Value write(ConstPtr data) override { // Avoid leaks. @@ -59,13 +58,12 @@ class PipeOutputStream : public OutputStream { } private: - Ptr> pipe_; + Ptr pipe_; }; } // namespace pipe -template -Pipe::Pipe() +Pipe::Pipe() : lock_(Platform::createLock()), cond_(Platform::createConditionVariable(lock_.get())), buffer_(), @@ -73,8 +71,7 @@ Pipe::Pipe() output_stream_closed_(false), read_all_chunks_(false) {} -template -Pipe::~Pipe() { +Pipe::~Pipe() { // Deallocate all the chunks still left in buffer_. for (BufferType::iterator chunk_iter = buffer_.begin(); chunk_iter != buffer_.end(); ++chunk_iter) { @@ -82,20 +79,17 @@ Pipe::~Pipe() { } } -template -Ptr Pipe::createInputStream(Ptr self) { +Ptr Pipe::createInputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeInputStream(self)); + return MakeRefCountedPtr(new pipe::PipeInputStream(self)); } -template -Ptr Pipe::createOutputStream(Ptr self) { +Ptr Pipe::createOutputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); + return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); } -template -ExceptionOr> Pipe::read(std::int64_t size) { +ExceptionOr> Pipe::read(std::int64_t size) { Synchronized s(lock_.get()); // We're done reading all the chunks that were written before the OutputStream @@ -148,15 +142,13 @@ ExceptionOr> Pipe::read(std::int64_t size) { } } -template -Exception::Value Pipe::write(ConstPtr data) { +Exception::Value Pipe::write(ConstPtr data) { Synchronized s(lock_.get()); return writeLocked(data); } -template -void Pipe::markInputStreamClosed() { +void Pipe::markInputStreamClosed() { Synchronized s(lock_.get()); input_stream_closed_ = true; @@ -165,8 +157,7 @@ void Pipe::markInputStreamClosed() { cond_->notify(); } -template -void Pipe::markOutputStreamClosed() { +void Pipe::markOutputStreamClosed() { Synchronized s(lock_.get()); // Write a sentinel null chunk before marking output_stream_closed as true. @@ -174,8 +165,7 @@ void Pipe::markOutputStreamClosed() { output_stream_closed_ = true; } -template -Exception::Value Pipe::writeLocked(ConstPtr data) { +Exception::Value Pipe::writeLocked(ConstPtr data) { // Avoid leaks. ScopedPtr> scoped_data(data); @@ -190,8 +180,7 @@ Exception::Value Pipe::writeLocked(ConstPtr data) { return Exception::NONE; } -template -bool Pipe::eitherStreamClosed() const { +bool Pipe::eitherStreamClosed() const { return input_stream_closed_ || output_stream_closed_; } diff --git a/cpp/platform/pipe.h b/cpp/platform/pipe.h index 29e06d11..4242f845 100644 --- a/cpp/platform/pipe.h +++ b/cpp/platform/pipe.h @@ -17,14 +17,11 @@ namespace nearby { namespace pipe { -template class PipeInputStream; -template class PipeOutputStream; } // namespace pipe -template class Pipe { public: Pipe(); @@ -42,9 +39,7 @@ class Pipe { // classes. ////////////////////////////////////////////////////////////////////////////// - template friend class pipe::PipeInputStream; - template friend class pipe::PipeOutputStream; ExceptionOr > read(std::int64_t size); @@ -70,6 +65,4 @@ class Pipe { } // namespace nearby } // namespace location -#include "platform/pipe.cc" - #endif // PLATFORM_PIPE_H_ diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc index 35b97a2d..8657b1c5 100644 --- a/cpp/platform/pipe_test.cc +++ b/cpp/platform/pipe_test.cc @@ -4,8 +4,7 @@ #include -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/prng.h" #include "platform/ptr.h" @@ -17,16 +16,7 @@ namespace location { namespace nearby { namespace { -class SamplePlatform { - public: - static Ptr createLock() { return MakePtr(new DefaultLock()); } - static Ptr createConditionVariable(Ptr lock) { - return MakePtr( - new DefaultConditionVariable(DowncastPtr(lock))); - } -}; - -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(PipeTest, SimpleWriteRead) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h index 6527db19..e675cac6 100644 --- a/cpp/platform/ptr.h +++ b/cpp/platform/ptr.h @@ -46,6 +46,7 @@ class Ptr { Ptr() = default; explicit Ptr(T* pointee) : ptr_(pointee) {} Ptr(const Ptr& that) = default; + Ptr(Ptr&& that) = default; Ptr(std::shared_ptr ptr) : ptr_(ptr) {} // NOLINT @@ -81,18 +82,23 @@ class Ptr { return *(this->ptr_) < *(other.ptr_); } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void destroy(bool = true) {} + void destroy(bool = true) { + // Legacy code expects isNull() to return true after destroy(). + ptr_.reset(); + } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void clear() {} + void clear() { + // Legacy code expects isNull() to return true after clear(). + ptr_.reset(); + } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_.get(); } T* get() { return ptr_.get(); } + T* get() const { return ptr_.get(); } void reset() { return ptr_.reset(); } ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") @@ -180,11 +186,12 @@ class ScopedPtr { // Accessor for the underlying Ptr. PtrType get() const { return this->ptr_; } - // Does nothing; - // this is to avoid unintended destruction of a managed pointer. // TODO(b/149938110): remove this completely. PtrType release() { - return ptr_; + // Legacy code expects isNull() to return true after release(). + PtrType ptr = std::move(ptr_); + ptr_.clear(); + return ptr; } private: @@ -252,14 +259,16 @@ ConstPtr ConstifyPtr(Ptr ptr) { // Ptr my_child_ptr = DowncastPtr(my_base_ptr); template Ptr DowncastPtr(Ptr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return Ptr(std::static_pointer_cast(base_ptr.ptr_)); } // ConstPtr counterpart to DowncastPtr(). template ConstPtr DowncastConstPtr(ConstPtr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return ConstPtr( std::static_pointer_cast(base_ptr.ptr_)); } diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc index adc73c08..622c4a60 100644 --- a/cpp/platform/ptr_test.cc +++ b/cpp/platform/ptr_test.cc @@ -116,7 +116,8 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted) { Ptr ref_counted_2 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_2); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); + ASSERT_EQ(1234, *ref_counted_1); ASSERT_EQ(1234, *ref_counted_2); } @@ -127,7 +128,7 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted_Stay_Valid) { Ptr ref_counted_3 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_3); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); ASSERT_EQ(1234, *ref_counted_2); ASSERT_EQ(1234, *ref_counted_3); } diff --git a/cpp/platform/settable_future_test.cc b/cpp/platform/settable_future_test.cc new file mode 100644 index 00000000..2de63970 --- /dev/null +++ b/cpp/platform/settable_future_test.cc @@ -0,0 +1,84 @@ +#include "platform/api/settable_future.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(SettableFutureTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(5); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), 5); +} + +TEST(SettableFutureTest, SetExceptionIsPropagated) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->setException({Exception::kIo}); + ASSERT_EQ(p->get().exception(), Exception::kIo); +} + +TEST(SettableFutureTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), TestEnum::kValue1); +} + +TEST(SettableFutureTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createSettableFuture(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), ScopedTestEnum::kValue1); +} + +TEST(SettableFutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createSettableFuture< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + BigSizedStruct v2 = p->get().result(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/api2/BUILD b/cpp/platform_v2/api/BUILD similarity index 51% rename from cpp/platform/api2/BUILD rename to cpp/platform_v2/api/BUILD index 5313b366..c9b0e5a4 100644 --- a/cpp/platform/api2/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -1,11 +1,5 @@ -package(default_visibility = [ - "//core:__subpackages__", - "//platform:__subpackages__", - "//location/nearby/setup/core:__subpackages__", -]) - cc_library( - name = "api2", + name = "api", hdrs = [ "atomic_boolean.h", "atomic_reference.h", @@ -13,53 +7,37 @@ cc_library( "ble_v2.h", "bluetooth_adapter.h", "bluetooth_classic.h", + "cancelable.h", "condition_variable.h", "count_down_latch.h", + "crypto.h", "executor.h", "future.h", - "hash_utils.h", "input_file.h", - "input_stream.h", "listenable_future.h", - "multi_thread_executor.h", "mutex.h", "output_file.h", - "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", - "single_thread_executor.h", - "socket.h", "submittable_executor.h", "system_clock.h", - "thread_utils.h", "webrtc.h", "wifi.h", + "wifi_lan.h", + ], + visibility = [ + "//platform_v2/base:__pkg__", + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__subpackages__", ], deps = [ - "//platform:types", + "//platform_v2/base", + "//absl/base:core_headers", "//absl/strings", "//absl/time", + "//absl/types:any", "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) - -cc_library( - name = "mutex", - hdrs = ["mutex.h"], - visibility = [ - "//platform:__subpackages__", - ], -) - -cc_library( - name = "condition_variable", - hdrs = ["condition_variable.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = [ - "//platform:types", - "//absl/time", - ], -) diff --git a/cpp/platform_v2/api/atomic_boolean.h b/cpp/platform_v2/api/atomic_boolean.h new file mode 100644 index 00000000..fff1bfa8 --- /dev/null +++ b/cpp/platform_v2/api/atomic_boolean.h @@ -0,0 +1,24 @@ +#ifndef PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ + +namespace location { +namespace nearby { +namespace api { + +// A boolean value that may be updated atomically. +class AtomicBoolean { + public: + virtual ~AtomicBoolean() = default; + + // Atomically read and return current value. + virtual bool Get() const = 0; + + // Atomically exchange original value with a new one. Return previous value. + virtual bool Set(bool value) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/atomic_reference.h b/cpp/platform_v2/api/atomic_reference.h similarity index 53% rename from cpp/platform/api2/atomic_reference.h rename to cpp/platform_v2/api/atomic_reference.h index 8740be0d..c6e6a3e4 100644 --- a/cpp/platform/api2/atomic_reference.h +++ b/cpp/platform_v2/api/atomic_reference.h @@ -1,8 +1,9 @@ -#ifndef PLATFORM_API2_ATOMIC_REFERENCE_H_ -#define PLATFORM_API2_ATOMIC_REFERENCE_H_ +#ifndef PLATFORM_V2_API_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_API_ATOMIC_REFERENCE_H_ namespace location { namespace nearby { +namespace api { // An object reference that may be updated atomically. // @@ -10,13 +11,16 @@ namespace nearby { template class AtomicReference { public: - virtual ~AtomicReference() {} + virtual ~AtomicReference() = default; - virtual T Get() = 0; + virtual T Get() const & = 0; + virtual T Get() && = 0; virtual void Set(const T& value) = 0; + virtual void Set(T&& value) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_ATOMIC_REFERENCE_H_ +#endif // PLATFORM_V2_API_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api2/ble.h b/cpp/platform_v2/api/ble.h similarity index 90% rename from cpp/platform/api2/ble.h rename to cpp/platform_v2/api/ble.h index 337f0717..26883dec 100644 --- a/cpp/platform/api2/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -1,14 +1,15 @@ -#ifndef PLATFORM_API2_BLE_H_ -#define PLATFORM_API2_BLE_H_ +#ifndef PLATFORM_V2_API_BLE_H_ +#define PLATFORM_V2_API_BLE_H_ -#include "platform/api2/bluetooth_classic.h" -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Opaque wrapper over a BLE peripheral. Must contain enough data about a // particular BLE device to connect to its GATT server. @@ -16,8 +17,7 @@ class BlePeripheral { public: virtual ~BlePeripheral() {} - // The returned Ptr is not owned by the caller, and can be invalidated once - // the corresponding BLEPeripheral object is destroyed. + // The returned reference lifetime matches BlePeripheral object. virtual BluetoothDevice& GetBluetoothDevice() = 0; }; @@ -105,7 +105,8 @@ class BleMedium { absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_H_ +#endif // PLATFORM_V2_API_BLE_H_ diff --git a/cpp/platform/api2/ble_v2.h b/cpp/platform_v2/api/ble_v2.h similarity index 98% rename from cpp/platform/api2/ble_v2.h rename to cpp/platform_v2/api/ble_v2.h index e0573c55..8858037e 100644 --- a/cpp/platform/api2/ble_v2.h +++ b/cpp/platform_v2/api/ble_v2.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_API2_BLE_V2_H_ -#define PLATFORM_API2_BLE_V2_H_ +#ifndef PLATFORM_V2_API_BLE_V2_H_ +#define PLATFORM_V2_API_BLE_V2_H_ #include #include @@ -9,13 +9,14 @@ #include #include -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { -namespace v2 { +namespace api { +namespace ble_v2 { // https://developer.android.com/reference/android/bluetooth/le/AdvertiseData // @@ -383,8 +384,9 @@ class BleMedium { const BleSocketLifeCycleCallback& callback) = 0; }; -} // namespace v2 +} // namespace ble_v2 +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_V2_H_ +#endif // PLATFORM_V2_API_BLE_V2_H_ diff --git a/cpp/platform/api2/bluetooth_adapter.h b/cpp/platform_v2/api/bluetooth_adapter.h similarity index 82% rename from cpp/platform/api2/bluetooth_adapter.h rename to cpp/platform_v2/api/bluetooth_adapter.h index 21171a01..a18bbef3 100644 --- a/cpp/platform/api2/bluetooth_adapter.h +++ b/cpp/platform_v2/api/bluetooth_adapter.h @@ -1,18 +1,18 @@ -#ifndef PLATFORM_API2_BLUETOOTH_ADAPTER_H_ -#define PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ -#include #include #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html class BluetoothAdapter { public: - virtual ~BluetoothAdapter() {} + virtual ~BluetoothAdapter() = default; // Eligible statuses of the BluetoothAdapter. enum class Status { @@ -25,19 +25,21 @@ class BluetoothAdapter { virtual bool SetStatus(Status status) = 0; // Returns true if the BluetoothAdapter's current status is // Status::Value::kEnabled. - virtual bool IsEnabled() = 0; + virtual bool IsEnabled() const = 0; // Scan modes of a BluetoothAdapter, as described at // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). enum class ScanMode { kUnknown, + kNone, + kConnectable, kConnectableDiscoverable, }; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() // // Returns ScanMode::kUnknown on error. - virtual ScanMode GetScanMode() = 0; + virtual ScanMode GetScanMode() const = 0; // Synchronously sets the scan mode of the adapter, and returns true if the // operation was a success. virtual bool SetScanMode(ScanMode scan_mode) = 0; @@ -49,7 +51,8 @@ class BluetoothAdapter { virtual bool SetName(absl::string_view name) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api2/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h similarity index 91% rename from cpp/platform/api2/bluetooth_classic.h rename to cpp/platform_v2/api/bluetooth_classic.h index 57de4ddc..8919dc8b 100644 --- a/cpp/platform/api2/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -1,17 +1,18 @@ -#ifndef PLATFORM_API2_BLUETOOTH_CLASSIC_H_ -#define PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ #include #include -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice { @@ -19,7 +20,7 @@ class BluetoothDevice { virtual ~BluetoothDevice() {} // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() - virtual std::string GetName() = 0; + virtual std::string GetName() const = 0; }; // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. @@ -118,7 +119,8 @@ class BluetoothClassicMedium { absl::string_view service_name, absl::string_view service_uuid) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/api/cancelable.h b/cpp/platform_v2/api/cancelable.h new file mode 100644 index 00000000..56eb5699 --- /dev/null +++ b/cpp/platform_v2/api/cancelable.h @@ -0,0 +1,21 @@ +#ifndef PLATFORM_V2_API_CANCELABLE_H_ +#define PLATFORM_V2_API_CANCELABLE_H_ + +namespace location { +namespace nearby { +namespace api { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable { + public: + virtual ~Cancelable() = default; + + virtual bool Cancel() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_CANCELABLE_H_ diff --git a/cpp/platform/api2/condition_variable.h b/cpp/platform_v2/api/condition_variable.h similarity index 75% rename from cpp/platform/api2/condition_variable.h rename to cpp/platform_v2/api/condition_variable.h index 936a3c36..d1d34c98 100644 --- a/cpp/platform/api2/condition_variable.h +++ b/cpp/platform_v2/api/condition_variable.h @@ -1,10 +1,11 @@ -#ifndef PLATFORM_API2_CONDITION_VARIABLE_H_ -#define PLATFORM_API2_CONDITION_VARIABLE_H_ +#ifndef PLATFORM_V2_API_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_API_CONDITION_VARIABLE_H_ -#include "platform/exception.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // The ConditionVariable class is a synchronization primitive that can be used // to block a thread, or multiple threads at the same time, until another thread @@ -20,7 +21,8 @@ class ConditionVariable { virtual Exception Wait() = 0; // throws Exception::kInterrupted }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_CONDITION_VARIABLE_H_ +#endif // PLATFORM_V2_API_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api2/count_down_latch.h b/cpp/platform_v2/api/count_down_latch.h similarity index 70% rename from cpp/platform/api2/count_down_latch.h rename to cpp/platform_v2/api/count_down_latch.h index ae0dfc86..7e0d407f 100644 --- a/cpp/platform/api2/count_down_latch.h +++ b/cpp/platform_v2/api/count_down_latch.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_COUNT_DOWN_LATCH_H_ -#define PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#ifndef PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ #include -#include "platform/exception.h" +#include "platform_v2/base/exception.h" #include "absl/time/time.h" namespace location { namespace nearby { +namespace api { // A synchronization aid that allows one or more threads to wait until a set of // operations being performed in other threads completes. @@ -15,7 +16,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html class CountDownLatch { public: - virtual ~CountDownLatch() {} + virtual ~CountDownLatch() = default; virtual Exception Await() = 0; // throws Exception::kInterrupted virtual ExceptionOr Await( @@ -23,7 +24,8 @@ class CountDownLatch { virtual void CountDown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#endif // PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api2/hash_utils.h b/cpp/platform_v2/api/crypto.h similarity index 50% rename from cpp/platform/api2/hash_utils.h rename to cpp/platform_v2/api/crypto.h index fab68f32..c43279b3 100644 --- a/cpp/platform/api2/hash_utils.h +++ b/cpp/platform_v2/api/crypto.h @@ -1,20 +1,24 @@ -#ifndef PLATFORM_API2_HASH_UTILS_H_ -#define PLATFORM_API2_HASH_UTILS_H_ +#ifndef PLATFORM_V2_API_CRYPTO_H_ +#define PLATFORM_V2_API_CRYPTO_H_ -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { // A provider of standard hashing algorithms. -class HashUtils { +class Crypto { public: + // Initialize global crypto state. + static void Init(); + // Return MD5 hash of input. static ByteArray Md5(absl::string_view input); + // Return SHA256 hash of input. static ByteArray Sha256(absl::string_view input); }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_HASH_UTILS_H_ +#endif // PLATFORM_V2_API_CRYPTO_H_ diff --git a/cpp/platform/api2/executor.h b/cpp/platform_v2/api/executor.h similarity index 59% rename from cpp/platform/api2/executor.h rename to cpp/platform_v2/api/executor.h index ee561894..1b390124 100644 --- a/cpp/platform/api2/executor.h +++ b/cpp/platform_v2/api/executor.h @@ -1,26 +1,28 @@ -#ifndef PLATFORM_API2_EXECUTOR_H_ -#define PLATFORM_API2_EXECUTOR_H_ +#ifndef PLATFORM_V2_API_EXECUTOR_H_ +#define PLATFORM_V2_API_EXECUTOR_H_ -#include - -#include "platform/runnable.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // This abstract class is the superclass of all classes representing an // Executor. class Executor { public: + // Before returning from destructor, executor must wait for all pending + // jobs to finish. virtual ~Executor() = default; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- - virtual void Execute(std::unique_ptr runnable) = 0; + virtual void Execute(Runnable&& runnable) = 0; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- virtual void Shutdown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_EXECUTOR_H_ +#endif // PLATFORM_V2_API_EXECUTOR_H_ diff --git a/cpp/platform/api2/future.h b/cpp/platform_v2/api/future.h similarity index 74% rename from cpp/platform/api2/future.h rename to cpp/platform_v2/api/future.h index 7f46c484..b3ec2f0f 100644 --- a/cpp/platform/api2/future.h +++ b/cpp/platform_v2/api/future.h @@ -1,11 +1,12 @@ -#ifndef PLATFORM_API2_FUTURE_H_ -#define PLATFORM_API2_FUTURE_H_ +#ifndef PLATFORM_V2_API_FUTURE_H_ +#define PLATFORM_V2_API_FUTURE_H_ -#include "platform/exception.h" -#include "absl/time/time.h" +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" namespace location { namespace nearby { +namespace api { // A Future represents the result of an asynchronous computation. // @@ -24,7 +25,8 @@ class Future { virtual ExceptionOr Get(absl::Duration timeout) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_FUTURE_H_ +#endif // PLATFORM_V2_API_FUTURE_H_ diff --git a/cpp/platform_v2/api/input_file.h b/cpp/platform_v2/api/input_file.h new file mode 100644 index 00000000..cc8730ee --- /dev/null +++ b/cpp/platform_v2/api/input_file.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_API_INPUT_FILE_H_ +#define PLATFORM_V2_API_INPUT_FILE_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" + +namespace location { +namespace nearby { +namespace api { + +// An InputFile represents a readable file on the system. +class InputFile : public InputStream { + public: + ~InputFile() override = default; + virtual std::string GetFilePath() const = 0; + virtual std::int64_t GetTotalSize() const = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_INPUT_FILE_H_ diff --git a/cpp/platform/api2/listenable_future.h b/cpp/platform_v2/api/listenable_future.h similarity index 52% rename from cpp/platform/api2/listenable_future.h rename to cpp/platform_v2/api/listenable_future.h index 2993bc88..af38e8a5 100644 --- a/cpp/platform/api2/listenable_future.h +++ b/cpp/platform_v2/api/listenable_future.h @@ -1,15 +1,17 @@ -#ifndef PLATFORM_API2_LISTENABLE_FUTURE_H_ -#define PLATFORM_API2_LISTENABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#define PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#include #include -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/exception.h" -#include "platform/runnable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // A Future that accepts completion listeners. // @@ -19,11 +21,12 @@ class ListenableFuture : public Future { public: ~ListenableFuture() override = default; - virtual void AddListener(std::unique_ptr runnable, + virtual void AddListener(Runnable runnable, Executor* executor) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_LISTENABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/mutex.h b/cpp/platform_v2/api/mutex.h new file mode 100644 index 00000000..b7ed29d6 --- /dev/null +++ b/cpp/platform_v2/api/mutex.h @@ -0,0 +1,41 @@ +#ifndef PLATFORM_V2_API_MUTEX_H_ +#define PLATFORM_V2_API_MUTEX_H_ + +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace api { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class ABSL_LOCKABLE Mutex { + public: + // Mode to pass to implementation constructor. + // kRegular - produces a regular mutex: disallows multiple locks from + // the same thread; optionally, detects double locks in + // debug mode. + // This is the default option. + // kRecursive - produces recursive mutex: allows multiple locks from the + // same thread. + // kRegularNoCheck - produces a regular mutex: disallows double locks, + // but does not check for deadlocks. + enum class Mode { + kRegular = 0, + kRecursive = 1, + kRegularNoCheck = 2, + }; + + virtual ~Mutex() {} + + virtual void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() = 0; + virtual void Unlock() ABSL_UNLOCK_FUNCTION() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_MUTEX_H_ diff --git a/cpp/platform_v2/api/output_file.h b/cpp/platform_v2/api/output_file.h new file mode 100644 index 00000000..2e694b05 --- /dev/null +++ b/cpp/platform_v2/api/output_file.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_API_OUTPUT_FILE_H_ +#define PLATFORM_V2_API_OUTPUT_FILE_H_ + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/output_stream.h" + +namespace location { +namespace nearby { +namespace api { + +// An OutputFile represents a writable file on the system. +class OutputFile : public OutputStream { + public: + ~OutputFile() override = default; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_OUTPUT_FILE_H_ diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h new file mode 100644 index 00000000..ef217692 --- /dev/null +++ b/cpp/platform_v2/api/platform.h @@ -0,0 +1,78 @@ +#ifndef PLATFORM_V2_API_PLATFORM_H_ +#define PLATFORM_V2_API_PLATFORM_H_ + +#include +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/crypto.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/system_clock.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/api/wifi_lan.h" +#include "absl/strings/string_view.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace api { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // General platform support: + // - atomic variables (boolean, and any other copyable type) + // - synchronization primitives: + // - mutex (regular, and recursive) + // - condition variable (must work with regular mutex only) + // - Future : to synchronize on Callable schduled to execute. + // - CountDownLatch : to ensure at least N threads are waiting. + static std::unique_ptr> CreateAtomicReferenceAny( + absl::any initial_value); + static std::unique_ptr> CreateSettableFutureAny(); + static std::unique_ptr CreateAtomicBoolean(bool initial_value); + static std::unique_ptr CreateCountDownLatch( + std::int32_t count); + static std::unique_ptr CreateMutex(Mutex::Mode mode); + static std::unique_ptr CreateConditionVariable( + Mutex* mutex); + + // Java-like Executors + static std::unique_ptr CreateSingleThreadExecutor(); + static std::unique_ptr CreateMultiThreadExecutor( + std::int32_t max_concurrency); + static std::unique_ptr CreateScheduledExecutor(); + + // Protocol implementations, domain-specific support + static std::unique_ptr CreateBluetoothAdapter(); + static std::unique_ptr CreateBluetoothClassicMedium(); + static std::unique_ptr CreateBleMedium(); + static std::unique_ptr CreateBleV2Medium(); + static std::unique_ptr CreateServerSyncMedium(); + static std::unique_ptr CreateWifiMedium(); + static std::unique_ptr CreateWifiLanMedium(); + static std::unique_ptr + CreateWebRtcSignalingMessenger(absl::string_view self_id); + static std::string GetDeviceId(); + static std::string GetPayloadPath(std::int64_t payload_id); +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_PLATFORM_H_ diff --git a/cpp/platform_v2/api/scheduled_executor.h b/cpp/platform_v2/api/scheduled_executor.h new file mode 100644 index 00000000..a19369e4 --- /dev/null +++ b/cpp/platform_v2/api/scheduled_executor.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace api { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor : public Executor { + public: + ~ScheduledExecutor() override = default; + // Cancelable is kept both in the executor context, and in the caller context. + // We want Cancelable to live until both caller and executor are done with it. + // Exclusive ownership model does not work for this case; + // using std:shared_ptr<> instead if std::unique_ptr<>. + virtual std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration duration) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/server_sync.h b/cpp/platform_v2/api/server_sync.h similarity index 90% rename from cpp/platform/api2/server_sync.h rename to cpp/platform_v2/api/server_sync.h index 47bc3aa5..4e9f1b90 100644 --- a/cpp/platform/api2/server_sync.h +++ b/cpp/platform_v2/api/server_sync.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_SERVER_SYNC_H_ -#define PLATFORM_API2_SERVER_SYNC_H_ +#ifndef PLATFORM_V2_API_SERVER_SYNC_H_ +#define PLATFORM_V2_API_SERVER_SYNC_H_ #include -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Abstraction that represents a Nearby endpoint exchanging data through // ServerSync Medium. @@ -54,7 +55,8 @@ class ServerSyncMedium { virtual void StopDiscovery(absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SERVER_SYNC_H_ +#endif // PLATFORM_V2_API_SERVER_SYNC_H_ diff --git a/cpp/platform/api2/settable_future.h b/cpp/platform_v2/api/settable_future.h similarity index 62% rename from cpp/platform/api2/settable_future.h rename to cpp/platform_v2/api/settable_future.h index 2089173c..8298bbfd 100644 --- a/cpp/platform/api2/settable_future.h +++ b/cpp/platform_v2/api/settable_future.h @@ -1,10 +1,12 @@ -#ifndef PLATFORM_API2_SETTABLE_FUTURE_H_ -#define PLATFORM_API2_SETTABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_SETTABLE_FUTURE_H_ +#define PLATFORM_V2_API_SETTABLE_FUTURE_H_ -#include "platform/api2/listenable_future.h" +#include "platform_v2/api/listenable_future.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // A SettableFuture is a type of Future whose result can be set. // @@ -15,10 +17,12 @@ class SettableFuture : public ListenableFuture { ~SettableFuture() override = default; virtual bool Set(const T& value) = 0; + virtual bool Set(T&& value) = 0; virtual bool SetException(Exception exception) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SETTABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_SETTABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/submittable_executor.h b/cpp/platform_v2/api/submittable_executor.h new file mode 100644 index 00000000..542e7fd1 --- /dev/null +++ b/cpp/platform_v2/api/submittable_executor.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/runnable.h" + +namespace location { +namespace nearby { +namespace api { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(Runnable&& wrapped_callable) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform_v2/api/system_clock.h b/cpp/platform_v2/api/system_clock.h new file mode 100644 index 00000000..c805a915 --- /dev/null +++ b/cpp/platform_v2/api/system_clock.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_API_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_API_SYSTEM_CLOCK_H_ + +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +class SystemClock final { + public: + // Initialize global system state. + static void Init(); + // Returns current absolute time. It is guaranteed to be monotonic. + static absl::Time ElapsedRealtime(); + // Pauses current thread for the specified duration. + static Exception Sleep(absl::Duration duration); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/webrtc.h b/cpp/platform_v2/api/webrtc.h similarity index 85% rename from cpp/platform/api2/webrtc.h rename to cpp/platform_v2/api/webrtc.h index e1dbde9e..ee507e9d 100644 --- a/cpp/platform/api2/webrtc.h +++ b/cpp/platform_v2/api/webrtc.h @@ -1,13 +1,14 @@ -#ifndef PLATFORM_API2_WEBRTC_H_ -#define PLATFORM_API2_WEBRTC_H_ +#ifndef PLATFORM_V2_API_WEBRTC_H_ +#define PLATFORM_V2_API_WEBRTC_H_ #include -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { +namespace api { class WebRtcSignalingMessenger { public: @@ -40,7 +41,8 @@ class WebRtcSignalingMessenger { const IceServersListener& ice_servers_listener) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WEBRTC_H_ +#endif // PLATFORM_V2_API_WEBRTC_H_ diff --git a/cpp/platform/api2/wifi.h b/cpp/platform_v2/api/wifi.h similarity index 92% rename from cpp/platform/api2/wifi.h rename to cpp/platform_v2/api/wifi.h index 74f0e5c9..74ddb6f9 100644 --- a/cpp/platform/api2/wifi.h +++ b/cpp/platform_v2/api/wifi.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_API2_WIFI_H_ -#define PLATFORM_API2_WIFI_H_ +#ifndef PLATFORM_V2_API_WIFI_H_ +#define PLATFORM_V2_API_WIFI_H_ #include #include @@ -9,6 +9,7 @@ namespace location { namespace nearby { +namespace api { // Possible authentication types for a WiFi network. enum class WifiAuthType { @@ -34,7 +35,7 @@ enum class WifiConnectionStatus { // Represents a WiFi network found during a call to WifiMedium#scan(). class WifiScanResult { public: - virtual ~WifiScanResult() {} + virtual ~WifiScanResult() = default; // Gets the SSID of this WiFi network. virtual std::string GetSsid() const = 0; @@ -53,7 +54,7 @@ class WifiMedium { class ScanResultCallback { public: - virtual ~ScanResultCallback() {} + virtual ~ScanResultCallback() = default; virtual void OnScanResults( const std::vector& scan_results) = 0; @@ -82,7 +83,8 @@ class WifiMedium { virtual std::string GetIpAddress() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WIFI_H_ +#endif // PLATFORM_V2_API_WIFI_H_ diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h new file mode 100644 index 00000000..3b95420b --- /dev/null +++ b/cpp/platform_v2/api/wifi_lan.h @@ -0,0 +1,87 @@ +#ifndef PLATFORM_V2_API_WIFI_LAN_H_ +#define PLATFORM_V2_API_WIFI_LAN_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace api { + +// Opaque wrapper over a WifiLan service which contains encoded service name. +class WifiLanService { + public: + virtual ~WifiLanService() = default; + + virtual std::string GetName() = 0; +}; + +class WifiLanSocket { + public: + virtual ~WifiLanSocket() = default; + + // Returns the InputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetInputStream() = 0; + + // Returns the OutputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetOutputStream() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception::Value Close() = 0; + + virtual WifiLanService& GetRemoteWifiLanService() = 0; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium { + public: + virtual ~WifiLanMedium() = default; + + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; + + // Callback for WifiLan discover results. + class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(WifiLanService* wifi_lan_service) = 0; + virtual void OnServiceLost(WifiLanService* wifi_lan_service) = 0; + }; + + virtual bool StartDiscovery( + absl::string_view service_id, + DiscoveredServiceCallback* discovered_service_callback) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(WifiLanSocket* socket, + absl::string_view service_id) = 0; + }; + + virtual bool StartAcceptingConnections( + absl::string_view service_id, + AcceptedConnectionCallback* accepted_connection_callback) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; + + virtual WifiLanSocket* Connect(WifiLanService* wifi_lan_service, + absl::string_view service_id) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_WIFI_LAN_H_ diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD new file mode 100644 index 00000000..d11245eb --- /dev/null +++ b/cpp/platform_v2/base/BUILD @@ -0,0 +1,73 @@ +load("//ads/util/non_compile:non_compile.bzl", "cc_with_non_compile_test") + +cc_library( + name = "base", + srcs = [ + "base64_utils.cc", + "prng.cc", + ], + hdrs = [ + "base64_utils.h", + "byte_array.h", + "callable.h", + "exception.h", + "input_stream.h", + "listeners.h", + "output_stream.h", + "prng.h", + "runnable.h", + "socket.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + "//platform_v2/api:__subpackages__", + ], + deps = [ + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "util", + srcs = [ + "base_pipe.cc", + ], + hdrs = [ + "base_mutex_lock.h", + "base_pipe.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + ":base", + "//platform_v2/api", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "platform_base_test", + srcs = [ + "byte_array_test.cc", + "prng_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) + +cc_with_non_compile_test( + name = "exception_test", + srcs = [ + "exception_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/platform_v2/base/base64_utils.cc b/cpp/platform_v2/base/base64_utils.cc new file mode 100644 index 00000000..dfedf417 --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.cc @@ -0,0 +1,27 @@ +#include "platform_v2/base/base64_utils.h" + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { + +std::string Base64Utils::Encode(const ByteArray& bytes) { + std::string base64_string; + + absl::WebSafeBase64Escape(std::string(bytes), &base64_string); + + return base64_string; +} + +ByteArray Base64Utils::Decode(absl::string_view base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return ByteArray(); + } + + return ByteArray(decoded_string.data(), decoded_string.size()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base64_utils.h b/cpp/platform_v2/base/base64_utils.h new file mode 100644 index 00000000..a5398c4d --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_V2_BASE_BASE64_UTILS_H_ +#define PLATFORM_V2_BASE_BASE64_UTILS_H_ + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class Base64Utils { + public: + static std::string Encode(const ByteArray& bytes); + static ByteArray Decode(absl::string_view base64_string); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE64_UTILS_H_ diff --git a/cpp/platform_v2/base/base_mutex_lock.h b/cpp/platform_v2/base/base_mutex_lock.h new file mode 100644 index 00000000..e48c45cc --- /dev/null +++ b/cpp/platform_v2/base/base_mutex_lock.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ +#define PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE BaseMutexLock final { + public: + explicit BaseMutexLock(api::Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex) { + mutex_->Lock(); + } + ~BaseMutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/base/base_pipe.cc b/cpp/platform_v2/base/base_pipe.cc new file mode 100644 index 00000000..e97ace56 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.cc @@ -0,0 +1,96 @@ +#include "platform_v2/base/base_pipe.h" + +#include "platform_v2/api/platform.h" +#include "platform_v2/base/base_mutex_lock.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" + +namespace location { +namespace nearby { + +ExceptionOr BasePipe::Read(size_t size) { + BaseMutexLock lock(mutex_.get()); + + // We're done reading all the chunks that were written before the OutputStream + // was closed, so there's nothing to do here other than return an empty chunk + // to serve as an EOF indication to callers. + if (read_all_chunks_) { + return ExceptionOr{ByteArray{}}; + } + + while (buffer_.empty() && !input_stream_closed_) { + Exception wait_exception = cond_->Wait(); + + if (wait_exception.Raised()) { + return ExceptionOr{wait_exception}; + } + } + + if (input_stream_closed_) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray first_chunk{buffer_.front()}; + buffer_.pop_front(); + + // If we received our sentinel chunk, mark the fact that there cannot + // possibly be any more chunks to read here on in, and return an empty chunk + // to serve as an EOF indication to callers. + if (first_chunk.Empty()) { + read_all_chunks_ = true; + return ExceptionOr{ByteArray{}}; + } + + // If first_chunk is small enough to not overshoot the requested 'size', just + // return that. + if (first_chunk.size() <= size) { + return ExceptionOr{first_chunk}; + } else { + // Break first_chunk into 2 parts -- the first one of which (next_chunk) + // will be 'size' bytes long, and will be returned, and the second one of + // which (overflow_chunk) will be re-inserted into buffer_, at the head of + // the queue, to be served up in the next call to read(). + ByteArray next_chunk(first_chunk.data(), size); + buffer_.push_front( + ByteArray(first_chunk.data() + size, first_chunk.size() - size)); + return ExceptionOr{next_chunk}; + } +} + +Exception BasePipe::Write(const ByteArray& data) { + BaseMutexLock lock(mutex_.get()); + + return WriteLocked(data); +} + +void BasePipe::MarkInputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + input_stream_closed_ = true; + // Trigger cond_ to unblock a potentially-blocked call to read(), and to let + // it know to return Exception::IO. + cond_->Notify(); +} + +void BasePipe::MarkOutputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + // Write a sentinel null chunk before marking output_stream_closed as true. + WriteLocked(ByteArray{}); + output_stream_closed_ = true; +} + +Exception BasePipe::WriteLocked(const ByteArray& data) { + if (input_stream_closed_ || output_stream_closed_) { + return {Exception::kIo}; + } + + buffer_.push_back(data); + // Trigger cond_ to unblock a potentially-blocked call to read(), now that + // there's more data for it to consume. + cond_->Notify(); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base_pipe.h b/cpp/platform_v2/base/base_pipe.h new file mode 100644 index 00000000..f74b3646 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.h @@ -0,0 +1,128 @@ +#ifndef PLATFORM_V2_BASE_BASE_PIPE_H_ +#define PLATFORM_V2_BASE_BASE_PIPE_H_ + +#include +#include +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// Common Pipe implenentation. +// It does not depend on platform implementation, and this allows it to +// be used in the platform implementation itself. +// Concrete class must be derived from it, as follows: +// +// class DerivedPipe : public BasePipe { +// public: +// DerivedPipe() { +// auto mutex = /* construct platform-dependent mutex */; +// auto cond = /* construct platform-dependent condition variable */; +// Setup(std::move(mutex), std::move(cond)); +// } +// ~DerivedPipe() override = default; +// DerivedPipe(DerivedPipe&&) = default; +// DerivedPipe& operator=(DerivedPipe&&) = default; +// }; +class BasePipe { + public: + static constexpr const size_t kChunkSize = 64 * 1024; + virtual ~BasePipe() = default; + + // Pipe is not copyable or movable, because copy/move will invalidate + // references to input and output streams. + // If move is required, Pipe could be wrapped with std::unique_ptr<>. + BasePipe(BasePipe&&) = delete; + BasePipe& operator=(BasePipe&&) = delete; + + // Get...() methods return references to input and output steam facades. + // It is safe to call Get...() methods multiple times. + InputStream& GetInputStream() { return input_stream_; } + OutputStream& GetOutputStream() { return output_stream_; } + + protected: + BasePipe() = default; + + void Setup(std::unique_ptr mutex, + std::unique_ptr cond) { + mutex_ = std::move(mutex); + cond_ = std::move(cond); + } + + private: + class BasePipeInputStream : public InputStream { + public: + explicit BasePipeInputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeInputStream() override { DoClose(); } + + ExceptionOr Read(std::int64_t size) override { + return pipe_->Read(size); + } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkInputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + class BasePipeOutputStream : public OutputStream { + public: + explicit BasePipeOutputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeOutputStream() override { DoClose(); } + + Exception Write(const ByteArray& data) override { + return pipe_->Write(data); + } + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkOutputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + + ExceptionOr Read(size_t size) ABSL_LOCKS_EXCLUDED(mutex_); + Exception Write(const ByteArray& data) ABSL_LOCKS_EXCLUDED(mutex_); + + void MarkInputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + void MarkOutputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + + Exception WriteLocked(const ByteArray& data) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Order of declaration matters: + // - mutex must be defined before condvar; + // - input & output streams must be after both mutex and condvar. + bool input_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool output_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool read_all_chunks_ ABSL_GUARDED_BY(mutex_) = false; + + std::deque ABSL_GUARDED_BY(mutex_) buffer_; + std::unique_ptr mutex_; + std::unique_ptr cond_; + + BasePipeInputStream input_stream_{this}; + BasePipeOutputStream output_stream_{this}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_PIPE_H_ diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h new file mode 100644 index 00000000..81036f24 --- /dev/null +++ b/cpp/platform_v2/base/byte_array.h @@ -0,0 +1,81 @@ +#ifndef PLATFORM_V2_BASE_BYTE_ARRAY_H_ +#define PLATFORM_V2_BASE_BYTE_ARRAY_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class ByteArray { + public: + // Create an empty ByteArray + ByteArray() = default; + ByteArray(const ByteArray&) = default; + ByteArray& operator=(const ByteArray&) = default; + ByteArray(ByteArray&&) = default; + ByteArray& operator=(ByteArray&&) = default; + + // Create ByteArray from string. + explicit ByteArray(absl::string_view source) { data_ = source; } + + // Create default-initialized ByteArray of a given size. + explicit ByteArray(size_t size) { SetData(size); } + + // Create value-initialized ByteArray of a given size. + ByteArray(const char* data, size_t size) { SetData(data, size); } + + // Assign a new value to this ByteArray, as a copy of data, with a given size. + void SetData(const char* data, size_t size) { + if (data == nullptr) { + size = 0; + } + data_.assign(data, size); + } + + // Assign a new value of a given size to this ByteArray + // (as a repeated char value). + void SetData(size_t size, char value = 0) { data_.assign(size, value); } + + // Returns true, if changes were performed to container, false otherwise. + bool CopyAt(size_t offset, const ByteArray& from, size_t source_offset = 0) { + if (offset >= size()) return false; + if (source_offset >= from.size()) return false; + memcpy(data() + offset, from.data() + source_offset, + std::min(size() - offset, from.size() - source_offset)); + return true; + } + + char* data() { return &data_[0]; } + const char* data() const { return data_.data(); } + size_t size() const { return data_.size(); } + bool Empty() const { return data_.empty(); } + + friend bool operator==(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator!=(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator<(const ByteArray& lhs, const ByteArray& rhs); + + explicit operator std::string() const { return data_; } + + private: + std::string data_; +}; + +inline bool operator==(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ == rhs.data_; +} + +inline bool operator!=(const ByteArray& lhs, const ByteArray& rhs) { + return !(lhs == rhs); +} + +inline bool operator<(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ < rhs.data_; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BYTE_ARRAY_H_ diff --git a/cpp/platform_v2/base/byte_array_test.cc b/cpp/platform_v2/base/byte_array_test.cc new file mode 100644 index 00000000..1cc7bb37 --- /dev/null +++ b/cpp/platform_v2/base/byte_array_test.cc @@ -0,0 +1,68 @@ +#include "platform_v2/base/byte_array.h" + +#include + +#include "gtest/gtest.h" + +namespace { + +using location::nearby::ByteArray; + +TEST(ByteArrayTest, DefaultSizeIsZero) { + ByteArray bytes; + EXPECT_EQ(0, bytes.size()); +} + +TEST(ByteArrayTest, DefaultIsEmpty) { + ByteArray bytes; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, NullArrayIsEmpty) { + ByteArray bytes{nullptr, 5}; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, CopyAtDoesNotExtendArray) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + EXPECT_TRUE(v2.CopyAt(/*offset=*/5, v1)); + EXPECT_TRUE(v2.CopyAt(/*offset=*/1, v1, /*source_offset=*/3)); + EXPECT_EQ(v2, ByteArray("A45DE123")); +} + +TEST(ByteArrayTest, CopyAtOutOfBoundsIsIgnored) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + // Try to do an out-of-bounds read. + EXPECT_FALSE(v2.CopyAt(/* offset=*/5, v1, /*source_offset=*/10)); + // Try to do an out-of-bounds write. + EXPECT_FALSE(v2.CopyAt(/* offset=*/9, v1)); + EXPECT_EQ(v2, ByteArray("ABCDEFGH")); +} + +TEST(ByteArrayTest, SetFromString) { + std::string setup("setup_test"); + ByteArray bytes{setup}; // array initialized with a copy of string. + EXPECT_EQ(setup.size(), bytes.size()); + EXPECT_EQ(std::string(bytes), setup); +} + +TEST(ByteArrayTest, SetExplicitSize) { + constexpr size_t kArraySize = 10; + char reference[kArraySize]{}; + ByteArray bytes{kArraySize}; // array of size 10, zero-initialized. + EXPECT_EQ(kArraySize, bytes.size()); + EXPECT_EQ(0, memcmp(bytes.data(), reference, kArraySize)); +} + +TEST(ByteArrayTest, SetExplicitData) { + constexpr static const char message[]{"test_message"}; + constexpr size_t kMessageSize = sizeof(message); + ByteArray bytes{message, kMessageSize}; + EXPECT_EQ(kMessageSize, bytes.size()); + EXPECT_NE(message, bytes.data()); + EXPECT_EQ(0, memcmp(message, bytes.data(), kMessageSize)); +} + +} // namespace diff --git a/cpp/platform_v2/base/callable.h b/cpp/platform_v2/base/callable.h new file mode 100644 index 00000000..294c7244 --- /dev/null +++ b/cpp/platform_v2/base/callable.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_BASE_CALLABLE_H_ +#define PLATFORM_V2_BASE_CALLABLE_H_ + +#include + +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// The Callable is and object intended to be executed by a thread, that is able +// to return a value of specified type T. +// It must be invokable without arguments. It must return a value implicitly +// convertible to ExceptionOr. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html +template +using Callable = std::function()>; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_CALLABLE_H_ diff --git a/cpp/platform_v2/base/exception.h b/cpp/platform_v2/base/exception.h new file mode 100644 index 00000000..c9e73425 --- /dev/null +++ b/cpp/platform_v2/base/exception.h @@ -0,0 +1,97 @@ +#ifndef PLATFORM_V2_BASE_EXCEPTION_H_ +#define PLATFORM_V2_BASE_EXCEPTION_H_ + +#include +#include + +namespace location { +namespace nearby { + +struct Exception { + enum Value : int { + kFailed = -1, // Initial value of Exception; any unknown error. + kSuccess = 0, // No exception. + kIo = 1, // IO Error happened. + kInterrupted = 2, // Operation was interrupted. + kInvalidProtocolBuffer = 3, // Couldn't parse. + kExecution = 4, // Couldn't execute. + kTimeout = 5, // Operarion did not finish within specified time. + }; + bool Ok() const { return value == kSuccess; } + bool Raised() const { return !Ok(); } + bool Raised(Value value) const { return this->value == value; } + Value value{kFailed}; +}; + +constexpr inline bool operator==(const Exception& a, const Exception& b) { + return a.value == b.value; +} + +constexpr inline bool operator!=(const Exception& a, const Exception& b) { + return !(a == b); +} + +// ExceptionOr provides experience similar to StatusOr used in +// Google Cloud API, see: +// https://googleapis.github.io/google-cloud-cpp/0.7.0/common/status__or_8h_source.html +// +// If ok() returns true, result() is a usable return value. Otherwise, +// exception() explains why such a value is not present. +// +// A typical pattern of usage is as follows: +// +// if (!e.ok()) { +// if (Exception::EXCEPTION_TYPE_1 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_1. +// } else if (Exception::EXCEPTION_TYPE_2 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_2. +// } +// +// return; +// } +// +// // Use e.result(). +template +class ExceptionOr { + public: + ExceptionOr() = default; + explicit ExceptionOr(T&& result) + : result_{std::move(result)}, + exception_{Exception::kSuccess} {} // NOLINT + explicit ExceptionOr(const T& result) + : result_{result}, exception_{Exception::kSuccess} {} // NOLINT + ExceptionOr(Exception::Value exception) : exception_{exception} {} // NOLINT + ExceptionOr(Exception exception) : exception_{exception} {} // NOLINT + // If there exists explicit conversion from from U to T, + // then allow explicit conversion from ExceptionOr to ExceptionOr. + template ()})>> + explicit ExceptionOr(ExceptionOr value) { + if (!value.ok()) { + exception_ = value.GetException(); + } else { + result_ = T{std::move(value.result())}; + exception_ = Exception{Exception::kSuccess}; + } + } + + bool ok() const { return exception_.value == Exception::kSuccess; } + + T& result() & { return result_; } + const T& result() const& { return result_; } + T&& result() && { return std::move(result_); } + const T&& result() const&& { return std::move(result_); } + + Exception::Value exception() const { return exception_.value; } + + T GetResult() const { return result_; } + Exception GetException() const { return exception_; } + + private: + T result_{}; + Exception exception_{Exception::kFailed}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_EXCEPTION_H_ diff --git a/cpp/platform_v2/base/exception_test.cc b/cpp/platform_v2/base/exception_test.cc new file mode 100644 index 00000000..92a6cdea --- /dev/null +++ b/cpp/platform_v2/base/exception_test.cc @@ -0,0 +1,106 @@ +#include "platform_v2/base/exception.h" + +#include + +#include "platform_v2/base/exception_test.nc.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby { + +TEST(ExceptionOr, Result_Copy_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should not affect the copy. + exception_or_vector.result().clear(); + EXPECT_FALSE(copy.empty()); +} + +TEST(ExceptionOr, Result_Copy_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Reference_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should reflect in the reference. + exception_or_vector.result().clear(); + EXPECT_TRUE(reference.empty()); +} + +TEST(ExceptionOr, Result_Reference_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + const std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Move_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving the result should clear the source. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, Result_Move_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving const rvalue reference will result in a copy. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, ExplicitConversionWorks) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + explicit B(A) {} + }; + ExceptionOr a(A{}); + ExceptionOr b(a); + EXPECT_TRUE(a.ok()); + EXPECT_TRUE(b.ok()); +} + +TEST(ExceptionOr, ExplicitConversionFailsToCompile) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + }; + ExceptionOr a(A{}); + EXPECT_NON_COMPILE("no matching constructor", { ExceptionOr b(a); }); +} + +} // namespace location::nearby diff --git a/cpp/platform_v2/base/input_stream.h b/cpp/platform_v2/base/input_stream.h new file mode 100644 index 00000000..a29786f1 --- /dev/null +++ b/cpp/platform_v2/base/input_stream.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_BASE_INPUT_STREAM_H_ +#define PLATFORM_V2_BASE_INPUT_STREAM_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// An InputStream represents an input stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html +class InputStream { + public: + virtual ~InputStream() = default; + + // throws Exception::kIo + virtual ExceptionOr Read(std::int64_t size) = 0; + // throws Exception::kIo + virtual Exception Close() = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_INPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/listeners.h b/cpp/platform_v2/base/listeners.h new file mode 100644 index 00000000..8be7193e --- /dev/null +++ b/cpp/platform_v2/base/listeners.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_V2_BASE_LISTENERS_H_ +#define PLATFORM_V2_BASE_LISTENERS_H_ + +#include + +namespace location { +namespace nearby { + +// Provides default-initialization with a valid empty method, +// instead of nullptr. This allows partial initialization +// of a set of listeners. +template +constexpr std::function DefaultCallback() { + return std::function{[](Args...) {}}; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_LISTENERS_H_ diff --git a/cpp/platform_v2/base/output_stream.h b/cpp/platform_v2/base/output_stream.h new file mode 100644 index 00000000..f126e444 --- /dev/null +++ b/cpp/platform_v2/base/output_stream.h @@ -0,0 +1,25 @@ +#ifndef PLATFORM_V2_BASE_OUTPUT_STREAM_H_ +#define PLATFORM_V2_BASE_OUTPUT_STREAM_H_ + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" + +namespace location { +namespace nearby { + +// An OutputStream represents an output stream of bytes. +// +// https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html +class OutputStream { + public: + virtual ~OutputStream() = default; + + virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo + virtual Exception Flush() = 0; // throws Exception::kIo + virtual Exception Close() = 0; // throws Exception::kIo +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_OUTPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/prng.cc b/cpp/platform_v2/base/prng.cc new file mode 100644 index 00000000..ab5c1f75 --- /dev/null +++ b/cpp/platform_v2/base/prng.cc @@ -0,0 +1,45 @@ +#include "platform_v2/base/prng.h" + +#include + +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +#define UNSIGNED_INT_BITMASK (std::numeric_limits::max()) + +Prng::Prng() { + // absl::GetCurrentTimeNanos() returns 64 bits, but srand() wants an unsigned + // int, so we may have to lose some of those 64 bits. + // + // The lower bits of the current-time-in-nanos are likely to have more entropy + // than the upper bits, so choose the former. + srand(static_cast(absl::GetCurrentTimeNanos() & + UNSIGNED_INT_BITMASK)); +} + +Prng::~Prng() { + // Nothing to do. +} + +#define RANDOM_BYTE (rand() & 0x0FF) // NOLINT + +std::int32_t Prng::NextInt32() { + return (static_cast(RANDOM_BYTE) << 24) | + (static_cast(RANDOM_BYTE) << 16) | + (static_cast(RANDOM_BYTE) << 8) | + (static_cast(RANDOM_BYTE)); +} + +std::uint32_t Prng::NextUint32() { + return static_cast(NextInt32()); +} + +std::int64_t Prng::NextInt64() { + return (static_cast(NextInt32()) << 32) | + (static_cast(NextInt32())); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/prng.h b/cpp/platform_v2/base/prng.h new file mode 100644 index 00000000..8c915b89 --- /dev/null +++ b/cpp/platform_v2/base/prng.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_BASE_PRNG_H_ +#define PLATFORM_V2_BASE_PRNG_H_ + +#include + +namespace location { +namespace nearby { + +// A (non-cryptographic) pseudo-random number generator. +class Prng { + public: + Prng(); + ~Prng(); + + std::int32_t NextInt32(); + std::uint32_t NextUint32(); + std::int64_t NextInt64(); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_PRNG_H_ diff --git a/cpp/platform_v2/base/prng_test.cc b/cpp/platform_v2/base/prng_test.cc new file mode 100644 index 00000000..c8a52065 --- /dev/null +++ b/cpp/platform_v2/base/prng_test.cc @@ -0,0 +1,27 @@ +#include "platform_v2/base/prng.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PrngTest, NextInt32) { + std::int32_t i = Prng().NextInt32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextUInt32) { + std::uint32_t i = Prng().NextUint32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextInt64) { + std::int64_t i = Prng().NextInt64(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/runnable.h b/cpp/platform_v2/base/runnable.h new file mode 100644 index 00000000..4b7a6898 --- /dev/null +++ b/cpp/platform_v2/base/runnable.h @@ -0,0 +1,19 @@ +#ifndef PLATFORM_V2_BASE_RUNNABLE_H_ +#define PLATFORM_V2_BASE_RUNNABLE_H_ + +#include + +namespace location { +namespace nearby { + +// The Runnable is an object intended to be executed by a thread. +// It must be invokable without arguments. It must return void. +// +// https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html + +using Runnable = std::function; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_RUNNABLE_H_ diff --git a/cpp/platform/api2/socket.h b/cpp/platform_v2/base/socket.h similarity index 62% rename from cpp/platform/api2/socket.h rename to cpp/platform_v2/base/socket.h index 0f855609..41415083 100644 --- a/cpp/platform/api2/socket.h +++ b/cpp/platform_v2/base/socket.h @@ -1,8 +1,8 @@ -#ifndef PLATFORM_API2_SOCKET_H_ -#define PLATFORM_API2_SOCKET_H_ +#ifndef PLATFORM_V2_BASE_SOCKET_H_ +#define PLATFORM_V2_BASE_SOCKET_H_ -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" namespace location { namespace nearby { @@ -12,7 +12,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html class Socket { public: - virtual ~Socket() {} + virtual ~Socket() = default; virtual InputStream& GetInputStream() = 0; virtual OutputStream& GetOutputStream() = 0; @@ -22,4 +22,4 @@ class Socket { } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SOCKET_H_ +#endif // PLATFORM_V2_BASE_SOCKET_H_ diff --git a/cpp/platform_v2/config/BUILD b/cpp/platform_v2/config/BUILD new file mode 100644 index 00000000..1963f538 --- /dev/null +++ b/cpp/platform_v2/config/BUILD @@ -0,0 +1,21 @@ +cc_library( + name = "config", + hdrs = [ + "config.h", + ], + visibility = [ + "//visibility:private", + ], +) + +cc_library( + name = "string", + hdrs = [ + "string.h", + ], + visibility = [ + ], + deps = [ + ":config", + ], +) diff --git a/cpp/platform_v2/config/config.h b/cpp/platform_v2/config/config.h new file mode 100644 index 00000000..2efef96b --- /dev/null +++ b/cpp/platform_v2/config/config.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_CONFIG_CONFIG_H_ +#define PLATFORM_V2_CONFIG_CONFIG_H_ + +// Clients can modify this file to customize the Nearby C++ codebase as per +// their particular constraints and environments. + +// Note: Every entry in this file should conform to the following format, to +// give precedence to command-line options (-D) that set these symbols: +// +// #ifndef XXX +// #define XXX 0/1 +// #endif + +#ifndef NEARBY_USE_STD_STRING +#define NEARBY_USE_STD_STRING 0 +#endif + +#ifndef NEARBY_USE_RTTI +#define NEARBY_USE_RTTI 1 +#endif + +#endif // PLATFORM_V2_CONFIG_CONFIG_H_ diff --git a/cpp/platform_v2/config/string.h b/cpp/platform_v2/config/string.h new file mode 100644 index 00000000..10db45ef --- /dev/null +++ b/cpp/platform_v2/config/string.h @@ -0,0 +1,12 @@ +#ifndef PLATFORM_V2_CONFIG_STRING_H_ +#define PLATFORM_V2_CONFIG_STRING_H_ + +#include + +#include "platform_v2/config/config.h" + +#if NEARBY_USE_STD_STRING +using std::string; +#endif + +#endif // PLATFORM_V2_CONFIG_STRING_H_ diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD new file mode 100644 index 00000000..8f99b81f --- /dev/null +++ b/cpp/platform_v2/impl/g3/BUILD @@ -0,0 +1,57 @@ +cc_library( + name = "g3", + srcs = [ + "atomic_boolean.h", + "atomic_reference_any.h", + "bluetooth_adapter.cc", + "bluetooth_adapter.h", + "condition_variable.h", + "count_down_latch.h", + "medium_environment.cc", + "medium_environment.h", + "multi_thread_executor.h", + "mutex.h", + "platform.cc", + "scheduled_executor.cc", + "scheduled_executor.h", + "settable_future_any.h", + "single_thread_executor.h", + "system_clock.cc", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":crypto", # build_cleaner: keep + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/impl/shared:posix_mutex", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + "//thread", + ], +) + +cc_library( + name = "crypto", + srcs = [ + "crypto.cc", + ], + visibility = [ + "//platform_v2/g3:__pkg__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//absl/strings", + "//openssl:crypto", + ], +) diff --git a/cpp/platform_v2/impl/g3/atomic_boolean.h b/cpp/platform_v2/impl/g3/atomic_boolean.h new file mode 100644 index 00000000..f43a2bcf --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_boolean.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace g3 { + +// See documentation in +// https://source.corp.google.com/piper///depot/google3/platform_v2/api/atomic_boolean.h +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/g3/atomic_reference_any.h b/cpp/platform_v2/impl/g3/atomic_reference_any.h new file mode 100644 index 00000000..c59e23c3 --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_reference_any.h @@ -0,0 +1,46 @@ +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ + +#include "platform_v2/api/atomic_reference.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace g3 { + +// Provide implementation for absl::any. +class AtomicReferenceAny : public api::AtomicReference { + public: + explicit AtomicReferenceAny(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceAny() override = default; + + absl::any Get() const & override { + absl::MutexLock lock(&mutex_); + return value_; + } + absl::any Get() && override { + absl::MutexLock lock(&mutex_); + return std::move(value_); + } + void Set(const absl::any& value) override { + absl::MutexLock lock(&mutex_); + value_ = value; + } + void Set(absl::any&& value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + mutable absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc new file mode 100644 index 00000000..16059f53 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -0,0 +1,65 @@ +#include "platform_v2/impl/g3/bluetooth_adapter.h" + +#include + +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) + : adapter_(*adapter) {} + +std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } + +bool BluetoothAdapter::SetStatus(Status status) ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + enabled_ = (status == Status::kEnabled); + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +bool BluetoothAdapter::IsEnabled() const { + absl::MutexLock lock(&mutex_); + return enabled_; +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + absl::MutexLock lock(&mutex_); + return mode_; +} + +bool BluetoothAdapter::SetScanMode(BluetoothAdapter::ScanMode mode) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + mode_ = mode; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +std::string BluetoothAdapter::GetName() const { + absl::MutexLock lock(&mutex_); + return name_; +} + +bool BluetoothAdapter::SetName(absl::string_view name) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + name_ = name; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h new file mode 100644 index 00000000..2654df4b --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -0,0 +1,90 @@ +#ifndef PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// BluetoothDevice and BluetoothAdapter have a mutual dependency. +class BluetoothAdapter; + +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice : public api::BluetoothDevice { + public: + ~BluetoothDevice() override = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const override; + BluetoothAdapter& GetAdapter(); + + private: + // Only BluetoothAdapter may instantiate BluetoothDevice. + friend class BluetoothAdapter; + + explicit BluetoothDevice(BluetoothAdapter* adapter); + + BluetoothAdapter& adapter_; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() = default; + ~BluetoothAdapter() override = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode mode) override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothDevice& GetDevice() { return device_; } + + private: + void RunOnCallbackThread(std::function runnable) { + serial_executor_.Execute(std::move(runnable)); + } + + mutable absl::Mutex mutex_; + BluetoothDevice device_{this}; + ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; + std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; + bool enabled_ ABSL_GUARDED_BY(mutex_) = false; + SingleThreadExecutor serial_executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/impl/g3/condition_variable.h b/cpp/platform_v2/impl/g3/condition_variable.h new file mode 100644 index 00000000..74ef47ed --- /dev/null +++ b/cpp/platform_v2/impl/g3/condition_variable.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/impl/g3/mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(g3::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/g3/count_down_latch.h b/cpp/platform_v2/impl/g3/count_down_latch.h new file mode 100644 index 00000000..d5b423ab --- /dev/null +++ b/cpp/platform_v2/impl/g3/count_down_latch.h @@ -0,0 +1,59 @@ +#ifndef PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ + +#include "platform_v2/api/count_down_latch.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + CountDownLatch(CountDownLatch&&) = delete; + CountDownLatch& operator=(CountDownLatch&&) = delete; + ExceptionOr Await(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + absl::Time deadline = absl::Now() + timeout; + while (count_ > 0) { + if (cond_.WaitWithDeadline(&mutex_, deadline)) { + return ExceptionOr(false); + } + } + return ExceptionOr(true); + } + Exception Await() override { + absl::MutexLock lock(&mutex_); + while (count_ > 0) { + cond_.Wait(&mutex_); + } + return {Exception::kSuccess}; + } + void CountDown() override { + absl::MutexLock lock(&mutex_); + if (count_ > 0 && --count_ == 0) { + cond_.SignalAll(); + } + } + + private: + absl::Mutex mutex_; // Mutex to be used with cond_.Wait...() method family. + absl::CondVar cond_; // Condition to synchronize up to N waiting threads. + int count_ + ABSL_GUARDED_BY(mutex_); // When zero, latch should release all waiters. +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/g3/crypto.cc b/cpp/platform_v2/impl/g3/crypto.cc new file mode 100644 index 00000000..52912f13 --- /dev/null +++ b/cpp/platform_v2/impl/g3/crypto.cc @@ -0,0 +1,39 @@ +#include "platform_v2/api/crypto.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" +#include "openssl/digest.h" + +namespace location { +namespace nearby { + +// Initialize global crypto state. +void Crypto::Init() {} + +static ByteArray Hash(absl::string_view input, const EVP_MD* algo) { + unsigned int md_out_size = EVP_MAX_MD_SIZE; + uint8_t digest_buffer[EVP_MAX_MD_SIZE]; + if (input.empty()) return {}; + + if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo, + nullptr)) + return {}; + + return ByteArray{reinterpret_cast(digest_buffer), md_out_size}; +} + +// Return MD5 hash of input. +ByteArray Crypto::Md5(absl::string_view input) { + return Hash(input, EVP_md5()); +} + +// Return SHA256 hash of input. +ByteArray Crypto::Sha256(absl::string_view input) { + return Hash(input, EVP_sha256()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.cc b/cpp/platform_v2/impl/g3/medium_environment.cc new file mode 100644 index 00000000..5512a4fc --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.cc @@ -0,0 +1,32 @@ +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +MediumEnvironment& MediumEnvironment::Instance() { + static std::aligned_storage_t + storage; + static MediumEnvironment* env = new (&storage) MediumEnvironment(); + return *env; +} + +void MediumEnvironment::Reset() { + absl::MutexLock lock(&mutex_); + bluetooth_adapters_.clear(); +} + +void MediumEnvironment::OnBluetoothAdapterChangedState( + BluetoothAdapter& adapter) { + absl::MutexLock lock(&mutex_); + // We don't care if there is an adapter already since all we store is a + // pointer. + bluetooth_adapters_.emplace(&adapter); + // TODO(apolyudov): Add event propagation code when Medium registration is + // implemented. +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.h b/cpp/platform_v2/impl/g3/medium_environment.h new file mode 100644 index 00000000..3f3f73c6 --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ +#define PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ + +#include +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// MediumEnvironment is a simulated environment which allowes multiple instances +// of simulated HW devices to "work" together as if they are physical. +// For each medium type it provides necessary methods to implement +// advertising, discovery and establishment of a data link. +class MediumEnvironment { + public: + ~MediumEnvironment() = default; + // Singleton constructor/accessor. + static MediumEnvironment& Instance(); + + // Clear state. No notifications are sent. + void Reset() ABSL_LOCKS_EXCLUDED(mutex_); + + // Add an adapter to internal container. + // Notify BluetoothClassicMediums if any that adapter state has changed. + void OnBluetoothAdapterChangedState(BluetoothAdapter& adapter) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + MediumEnvironment() = default; + absl::Mutex mutex_; + absl::flat_hash_set bluetooth_adapters_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ diff --git a/cpp/platform_v2/impl/g3/multi_thread_executor.h b/cpp/platform_v2/impl/g3/multi_thread_executor.h new file mode 100644 index 00000000..c8a32233 --- /dev/null +++ b/cpp/platform_v2/impl/g3/multi_thread_executor.h @@ -0,0 +1,54 @@ +#ifndef PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ + +#include + +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_parallelism) + : thread_pool_(max_parallelism) { + thread_pool_.StartWorkers(); + } + void Execute(Runnable&& runnable) override { + if (!shutdown_) { + thread_pool_.Schedule(std::move(runnable)); + } + } + bool DoSubmit(Runnable&& runnable) override { + if (shutdown_) return false; + thread_pool_.Schedule(std::move(runnable)); + return true; + } + void Shutdown() override { DoShutdown(); } + ~MultiThreadExecutor() override { DoShutdown(); } + + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { + if (shutdown_) return; + thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); + } + bool InShutdown() const { return shutdown_; } + + private: + void DoShutdown() { + shutdown_ = true; + } + std::atomic_bool shutdown_ = false; + ThreadPool thread_pool_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/mutex.h b/cpp/platform_v2/impl/g3/mutex.h new file mode 100644 index 00000000..a70a2f2a --- /dev/null +++ b/cpp/platform_v2/impl/g3/mutex.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_G3_MUTEX_H_ +#define PLATFORM_V2_IMPL_G3_MUTEX_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/impl/shared/posix_mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(bool check) : check_(check) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + if (!check_) mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + bool check_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex { + public: + ~RecursiveMutex() override = default; + RecursiveMutex() = default; + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MUTEX_H_ diff --git a/cpp/platform_v2/impl/g3/pipe.h b/cpp/platform_v2/impl/g3/pipe.h new file mode 100644 index 00000000..9c1c1a8b --- /dev/null +++ b/cpp/platform_v2/impl/g3/pipe.h @@ -0,0 +1,30 @@ +#ifndef PLATFORM_V2_IMPL_G3_PIPE_H_ +#define PLATFORM_V2_IMPL_G3_PIPE_H_ + +#include + +#include "platform_v2/base/base_pipe.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class Pipe : public BasePipe { + public: + Pipe() { + auto mutex = std::make_unique(/*check=*/true); + auto cond = std::make_unique(mutex.get()); + Setup(std::move(mutex), std::move(cond)); + } + ~Pipe() override = default; + Pipe(Pipe &&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_PIPE_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc new file mode 100644 index 00000000..412dbacb --- /dev/null +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -0,0 +1,136 @@ +#include "platform_v2/api/platform.h" + +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/impl/g3/atomic_boolean.h" +#include "platform_v2/impl/g3/atomic_reference_any.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "platform_v2/impl/g3/multi_thread_executor.h" +#include "platform_v2/impl/g3/mutex.h" +#include "platform_v2/impl/g3/scheduled_executor.h" +#include "platform_v2/impl/g3/settable_future_any.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/integral_types.h" +#include "absl/memory/memory.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace api { + +std::unique_ptr +ImplementationPlatform::CreateSingleThreadExecutor() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateMultiThreadExecutor(int max_concurrency) { + return absl::make_unique(max_concurrency); +} + +std::unique_ptr +ImplementationPlatform::CreateScheduledExecutor() { + return absl::make_unique(); +} + +std::unique_ptr> +ImplementationPlatform::CreateAtomicReferenceAny(absl::any initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr> +ImplementationPlatform::CreateSettableFutureAny() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothAdapter() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch( + std::int32_t count) { + return absl::make_unique(count); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( + bool initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothClassicMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateServerSyncMedium() { + return std::unique_ptr(/*new ServerSyncMediumImpl()*/); +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateWebRtcSignalingMessenger( + absl::string_view self_id) { + return std::unique_ptr( + /*new FCMSignalingMessenger()*/); +} + +std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + if (mode == Mutex::Mode::kRecursive) + return absl::make_unique(); + else + return absl::make_unique(mode == Mutex::Mode::kRegular); +} + +std::unique_ptr +ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::unique_ptr( + new g3::ConditionVariable(static_cast(mutex))); +} + +std::string ImplementationPlatform::GetDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::GetPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.cc b/cpp/platform_v2/impl/g3/scheduled_executor.cc new file mode 100644 index 00000000..1f8a3290 --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.cc @@ -0,0 +1,65 @@ +#include "platform_v2/impl/g3/scheduled_executor.h" + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +namespace { + +class ScheduledCancelable : public api::Cancelable { + public: + bool Cancel() override { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kCanceled)) { + return true; + } + } + return false; + } + bool MarkExecuted() { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kExecuted)) { + return true; + } + } + return false; + } + + private: + enum Status { + kNotRun, + kExecuted, + kCanceled, + }; + std::atomic status_ = kNotRun; +}; + +} // namespace + +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration delay) { + auto scheduled_cancelable = std::make_shared(); + if (executor_.InShutdown()) { + return scheduled_cancelable; + } + executor_.ScheduleAfter( + delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() { + if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) { + runnable(); + } + }); + return scheduled_cancelable; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.h b/cpp/platform_v2/impl/g3/scheduled_executor.h new file mode 100644 index 00000000..6c65b009 --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.h @@ -0,0 +1,42 @@ +#ifndef PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class ScheduledExecutor final : public api::ScheduledExecutor { + public: + ScheduledExecutor() = default; + ~ScheduledExecutor() override { + executor_.Shutdown(); + } + + void Execute(Runnable&& runnable) override { + executor_.Execute(std::move(runnable)); + } + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration delay) override; + void Shutdown() override { executor_.Shutdown(); } + + private: + SingleThreadExecutor executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/settable_future_any.h b/cpp/platform_v2/impl/g3/settable_future_any.h new file mode 100644 index 00000000..acb1810d --- /dev/null +++ b/cpp/platform_v2/impl/g3/settable_future_any.h @@ -0,0 +1,104 @@ +#ifndef PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ +#define PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ + +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/settable_future.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace g3 { + +class SettableFutureAny : public api::SettableFuture { + public: + SettableFutureAny() = default; + ~SettableFutureAny() override = default; + + bool Set(const absl::any& value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = value; + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool Set(absl::any&& value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool SetException(Exception exception) override { + absl::MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + void AddListener(Runnable runnable, api::Executor* executor) override {} + + ExceptionOr Get() override { + absl::MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(&mutex_); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr Get(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + while (!done_) { + absl::Time start_time = absl::Now(); + if (completed_.WaitWithTimeout(&mutex_, timeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = absl::Now() - start_time; + if (spent < timeout) { + timeout -= spent; + } else if (!done_) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + private: + bool SetExceptionLocked(Exception exception) { + if (!done_) { + exception_ = exception.value != Exception::kSuccess + ? exception + : Exception{Exception::kFailed}; + done_ = true; + completed_.SignalAll(); + } + return true; + } + + absl::Mutex mutex_; + absl::CondVar completed_; + bool done_{false}; + absl::any value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SETTABLE_FUTURE_ANY_H_ diff --git a/cpp/platform_v2/impl/g3/single_thread_executor.h b/cpp/platform_v2/impl/g3/single_thread_executor.h new file mode 100644 index 00000000..384206d7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/single_thread_executor.h @@ -0,0 +1,22 @@ +#ifndef PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/impl/g3/multi_thread_executor.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +class SingleThreadExecutor final : public MultiThreadExecutor { + public: + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/system_clock.cc b/cpp/platform_v2/impl/g3/system_clock.cc new file mode 100644 index 00000000..2f613dd7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/system_clock.cc @@ -0,0 +1,16 @@ +#include "platform_v2/api/system_clock.h" + +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +absl::Time SystemClock::ElapsedRealtime() { return absl::Now(); } +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD new file mode 100644 index 00000000..013a0192 --- /dev/null +++ b/cpp/platform_v2/impl/shared/BUILD @@ -0,0 +1,34 @@ +cc_library( + name = "posix_mutex", + srcs = [ + "posix_mutex.cc", + ], + hdrs = [ + "posix_mutex.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + ], +) + +cc_library( + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + ":posix_mutex", + "//platform_v2/api", + "//platform_v2/base", + ], +) diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.cc b/cpp/platform_v2/impl/shared/posix_condition_variable.cc new file mode 100644 index 00000000..6d734b0f --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.cc @@ -0,0 +1,30 @@ +#include "platform_v2/impl/shared/posix_condition_variable.h" + +namespace location { +namespace nearby { +namespace posix { + +ConditionVariable::ConditionVariable(Mutex* mutex) + : mutex_(mutex), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +ConditionVariable::~ConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void ConditionVariable::Notify() { pthread_cond_broadcast(&cond_); } + +Exception ConditionVariable::Wait() { + pthread_cond_wait(&cond_, &(mutex_->mutex_)); + + return {Exception::kSuccess}; +} + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.h b/cpp/platform_v2/impl/shared/posix_condition_variable.h new file mode 100644 index 00000000..25e3e756 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ + +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(Mutex* mutex); + ~ConditionVariable() override; + + void Notify() override; + Exception Wait() override; + + private: + Mutex* mutex_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/shared/posix_mutex.cc b/cpp/platform_v2/impl/shared/posix_mutex.cc new file mode 100644 index 00000000..65cdc917 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.cc @@ -0,0 +1,26 @@ +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +Mutex::Mutex() : attr_(), mutex_() { + pthread_mutexattr_init(&attr_); + pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&mutex_, &attr_); +} + +Mutex::~Mutex() { + pthread_mutex_destroy(&mutex_); + + pthread_mutexattr_destroy(&attr_); +} + +void Mutex::Lock() { pthread_mutex_lock(&mutex_); } + +void Mutex::Unlock() { pthread_mutex_unlock(&mutex_); } + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_mutex.h b/cpp/platform_v2/impl/shared/posix_mutex.h new file mode 100644 index 00000000..01b2e1f2 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + Mutex(); + ~Mutex() override; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override; + void Unlock() ABSL_UNLOCK_FUNCTION() override; + + private: + friend class ConditionVariable; + + pthread_mutexattr_t attr_; + pthread_mutex_t mutex_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD new file mode 100644 index 00000000..5e260224 --- /dev/null +++ b/cpp/platform_v2/public/BUILD @@ -0,0 +1,86 @@ +cc_library( + name = "public", + srcs = [ + "file.cc", + "pipe.cc", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "bluetooth_adapter.h", + "cancelable.h", + "cancelable_alarm.h", + "condition_variable.h", + "count_down_latch.h", + "crypto.h", + "file.h", + "future.h", + "multi_thread_executor.h", + "mutex.h", + "mutex_lock.h", + "pipe.h", + "scheduled_executor.h", + "single_thread_executor.h", + "submittable_executor.h", + "system_clock.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/base:util", + "//absl/base:core_headers", + "//absl/strings", + "//absl/time", + "//absl/types:any", + ], +) + +cc_library( + name = "logging", + hdrs = [ + "logging.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + "//platform:logging", + ], +) + +cc_test( + name = "public_test", + srcs = [ + "atomic_boolean_test.cc", + "atomic_reference_test.cc", + "bluetooth_adapter_test.cc", + "count_down_latch_test.cc", + "crypto_test.cc", + "file_test.cc", + "future_test.cc", + "logging_test.cc", + "multi_thread_executor_test.cc", + "mutex_test.cc", + "pipe_test.cc", + "scheduled_executor_test.cc", + "single_thread_executor_test.cc", + ], + shard_count = 16, + deps = [ + ":logging", + ":public", + "//file/util:temp_path", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/public/atomic_boolean.h b/cpp/platform_v2/public/atomic_boolean.h new file mode 100644 index 00000000..08c5e833 --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean.h @@ -0,0 +1,34 @@ +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +// A boolean value that may be updated atomically. +// See documentation in +// https://source.corp.google.com/piper///depot/google3/platform_v2/api/atomic_boolean.h +class AtomicBoolean final : public api::AtomicBoolean { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicBoolean(bool value = false) + : impl_(Platform::CreateAtomicBoolean(value)) {} + ~AtomicBoolean() override = default; + AtomicBoolean(AtomicBoolean&&) = default; + AtomicBoolean& operator=(AtomicBoolean&&) = default; + + bool Get() const override { return impl_->Get(); } + bool Set(bool value) override { return impl_->Set(value); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/public/atomic_boolean_test.cc b/cpp/platform_v2/public/atomic_boolean_test.cc new file mode 100644 index 00000000..00d92d0d --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean_test.cc @@ -0,0 +1,24 @@ +#include "platform_v2/public/atomic_boolean.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(AtomicBooleanTest, SetReturnsPrevoiusValue) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Set(true)); +} + +TEST(AtomicBooleanTest, GetReturnsWhatWasSet) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Get()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/atomic_reference.h b/cpp/platform_v2/public/atomic_reference.h new file mode 100644 index 00000000..1fc02fac --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference.h @@ -0,0 +1,40 @@ +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ + +#include + +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/platform.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +template +class AtomicReference final : public api::AtomicReference { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicReference(const T& value) + : impl_(Platform::CreateAtomicReferenceAny(value)) {} + explicit AtomicReference(T&& value) + : impl_(Platform::CreateAtomicReferenceAny(std::move(value))) {} + ~AtomicReference() override = default; + AtomicReference(AtomicReference&&) = default; + AtomicReference& operator=(AtomicReference&&) = default; + + T Get() const& override { return absl::any_cast(impl_->Get()); } + T Get() && override { return absl::any_cast(std::move(impl_->Get())); } + void Set(const T& value) override { impl_->Set(absl::any(value)); } + void Set(T&& value) override { impl_->Set(absl::any(value)); } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/public/atomic_reference_test.cc b/cpp/platform_v2/public/atomic_reference_test.cc new file mode 100644 index 00000000..bd79b198 --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference_test.cc @@ -0,0 +1,75 @@ +#include "platform_v2/public/atomic_reference.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + AtomicReference atomic_ref({}); + atomic_ref.Set(5); + EXPECT_EQ(atomic_ref.Get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(TestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + AtomicReference atomic_ref({}); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + atomic_ref.Set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = atomic_ref.Get(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + AtomicReference atomic_ref({}); + atomic_ref.Set(s); + EXPECT_EQ(s, atomic_ref.Get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h new file mode 100644 index 00000000..f3b9df4e --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -0,0 +1,63 @@ +#ifndef PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/platform.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() + : impl_(api::ImplementationPlatform::CreateBluetoothAdapter()) {} + ~BluetoothAdapter() override = default; + BluetoothAdapter(BluetoothAdapter&&) = default; + BluetoothAdapter& operator=(BluetoothAdapter&&) = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override { return impl_->SetStatus(status); } + Status GetStatus() const { + return IsEnabled() ? Status::kEnabled : Status::kDisabled; + } + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override { return impl_->IsEnabled(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override { return impl_->GetScanMode(); } + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode scan_mode) override { + return impl_->SetScanMode(scan_mode); + } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override { return impl_->GetName(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override { return impl_->SetName(name); } + + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/public/bluetooth_adapter_test.cc b/cpp/platform_v2/public/bluetooth_adapter_test.cc new file mode 100644 index 00000000..3914b624 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter_test.cc @@ -0,0 +1,44 @@ +#include "platform_v2/public/bluetooth_adapter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(BluetoothAdapterTest, ConstructorDestructorWorks) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.IsValid()); +} + +TEST(BluetoothAdapterTest, CanSetName) { + constexpr char kAdapterName[] = "MyBtAdapter"; + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetName(kAdapterName)); + EXPECT_EQ(adapter.GetName(), std::string(kAdapterName)); +} + +TEST(BluetoothAdapterTest, CanSetStatus) { + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetStatus(BluetoothAdapter::Status::kEnabled)); + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kEnabled); +} + +TEST(BluetoothAdapterTest, CanSetMode) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kConnectable)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kConnectable); + EXPECT_TRUE(adapter.SetScanMode( + BluetoothAdapter::ScanMode::kConnectableDiscoverable)); + EXPECT_EQ(adapter.GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kNone)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kNone); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/cancelable.h b/cpp/platform_v2/public/cancelable.h new file mode 100644 index 00000000..3105648b --- /dev/null +++ b/cpp/platform_v2/public/cancelable.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" + +namespace location { +namespace nearby { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable final { + public: + Cancelable() = default; + Cancelable(const Cancelable&) = default; + Cancelable& operator=(const Cancelable& other) = default; + + ~Cancelable() = default; + + // This constructor is used internally only, + // by other classes in "//platform_v2/public/". + explicit Cancelable(std::shared_ptr impl) + : impl_(std::move(impl)) {} + + bool Cancel() { return impl_->Cancel(); } + + private: + std::shared_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_H_ diff --git a/cpp/platform_v2/public/cancelable_alarm.h b/cpp/platform_v2/public/cancelable_alarm.h new file mode 100644 index 00000000..1fc26788 --- /dev/null +++ b/cpp/platform_v2/public/cancelable_alarm.h @@ -0,0 +1,56 @@ +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ + +#include +#include +#include +#include + +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/scheduled_executor.h" + +namespace location { +namespace nearby { + +/** + * A cancelable alarm with a name. This is a simple wrapper around the logic + * for posting a Runnable on a ScheduledExecutor and (possibly) later + * canceling it. + */ +class CancelableAlarm { + public: + CancelableAlarm(absl::string_view name, std::function&& runnable, + absl::Duration delay, ScheduledExecutor* scheduled_executor) + : name_(name), + cancelable_(scheduled_executor->Schedule(std::move(runnable), delay)) {} + ~CancelableAlarm() = default; + CancelableAlarm(CancelableAlarm&& other) { + *this = std::move(other); + } + CancelableAlarm& operator=(CancelableAlarm&& other) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + name_ = std::move(other.name_); + cancelable_ = std::move(other.cancelable_); + } + return *this; + } + + bool Cancel() { + MutexLock lock(&mutex_); + return cancelable_.Cancel(); + } + + private: + Mutex mutex_; + std::string name_; + Cancelable cancelable_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ diff --git a/cpp/platform_v2/public/condition_variable.h b/cpp/platform_v2/public/condition_variable.h new file mode 100644 index 00000000..54f83cfe --- /dev/null +++ b/cpp/platform_v2/public/condition_variable.h @@ -0,0 +1,36 @@ +#ifndef PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" + +namespace location { +namespace nearby { + +// The ConditionVariable class is a synchronization primitive that can be used +// to block a thread, or multiple threads at the same time, until another thread +// both modifies a shared variable (the condition), and notifies the +// ConditionVariable. +class ConditionVariable final { + public: + using Platform = api::ImplementationPlatform; + explicit ConditionVariable(Mutex* mutex) + : impl_(Platform::CreateConditionVariable(mutex->impl_.get())) {} + ConditionVariable(ConditionVariable&&) = default; + ConditionVariable& operator=(ConditionVariable&&) = default; + + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + void Notify() { impl_->Notify(); } + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- + Exception Wait() { return impl_->Wait(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/public/count_down_latch.h b/cpp/platform_v2/public/count_down_latch.h new file mode 100644 index 00000000..37a76901 --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch.h @@ -0,0 +1,40 @@ +#ifndef PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ + +#include + +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final { + public: + using Platform = api::ImplementationPlatform; + explicit CountDownLatch(int count) + : impl_(Platform::CreateCountDownLatch(count)) {} + CountDownLatch(CountDownLatch&&) = default; + CountDownLatch& operator=(CountDownLatch&&) = default; + ~CountDownLatch() = default; + + Exception Await() { return impl_->Await(); } + ExceptionOr Await(absl::Duration timeout) { + return impl_->Await(timeout); + } + void CountDown() { impl_->CountDown(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/public/count_down_latch_test.cc b/cpp/platform_v2/public/count_down_latch_test.cc new file mode 100644 index 00000000..52aed3fd --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch_test.cc @@ -0,0 +1,48 @@ +#include "platform_v2/public/count_down_latch.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(CountDownLatch, ConstructorDestructorWorks) { CountDownLatch latch(1); } + +TEST(CountDownLatch, LatchAwaitCanWait) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchExtraCountDownIgnored) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + latch.CountDown(); + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchAwaitWithTimeoutCanExpire) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + auto response = latch.Await(absl::Milliseconds(100)); + EXPECT_TRUE(response.ok()); + EXPECT_FALSE(response.result()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/crypto.h b/cpp/platform_v2/public/crypto.h new file mode 100644 index 00000000..f12dc177 --- /dev/null +++ b/cpp/platform_v2/public/crypto.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_CRYPTO_H_ +#define PLATFORM_V2_PUBLIC_CRYPTO_H_ + +#include "platform_v2/api/crypto.h" + +#endif // PLATFORM_V2_PUBLIC_CRYPTO_H_ diff --git a/cpp/platform_v2/public/crypto_test.cc b/cpp/platform_v2/public/crypto_test.cc new file mode 100644 index 00000000..3499831b --- /dev/null +++ b/cpp/platform_v2/public/crypto_test.cc @@ -0,0 +1,34 @@ +#include "platform_v2/public/crypto.h" + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(CryptoTest, Md5GeneratesHash) { + const ByteArray expected_md5( + "\xb4\x5c\xff\xe0\x84\xdd\x3d\x20\xd9\x28\xbe\xe8\x5e\x7b\x0f\x21"); + ByteArray md5_hash = Crypto::Md5("string"); + EXPECT_EQ(md5_hash, expected_md5); +} + +TEST(CryptoTest, Md5ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Md5(""), ByteArray{}); +} + +TEST(CryptoTest, Sha256GeneratesHash) { + const ByteArray expected_sha256( + "\x47\x32\x87\xf8\x29\x8d\xba\x71\x63\xa8\x97\x90\x89\x58\xf7\xc0" + "\xea\xe7\x33\xe2\x5d\x2e\x02\x79\x92\xea\x2e\xdc\x9b\xed\x2f\xa8"); + ByteArray sha256_hash = Crypto::Sha256("string"); + EXPECT_EQ(sha256_hash, expected_sha256); +} + +TEST(CryptoTest, Sha256ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Sha256(""), ByteArray{}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.cc b/cpp/platform_v2/public/file.cc new file mode 100644 index 00000000..63e5bc8c --- /dev/null +++ b/cpp/platform_v2/public/file.cc @@ -0,0 +1,79 @@ +#include "platform_v2/public/file.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// InputFile + +InputFile::InputFile(const std::string& path, std::int64_t size) + : file_(path), path_(path), total_size_(size) {} + +ExceptionOr InputFile::Read(std::int64_t size) { + if (!file_.is_open()) { + return ExceptionOr{Exception::kIo}; + } + + if (file_.peek() == EOF) { + return ExceptionOr{ByteArray{}}; + } + + if (!file_.good()) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray bytes(size); + std::unique_ptr read_bytes{new char[size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr{Exception::kIo}; + } + + return ExceptionOr(ByteArray(read_bytes.get(), num_bytes_read)); +} + +Exception InputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +// OutputFile + +OutputFile::OutputFile(absl::string_view path) : file_(path) {} + +Exception OutputFile::Write(const ByteArray& data) { + if (!file_.is_open()) { + return {Exception::kIo}; + } + + if (!file_.good()) { + return {Exception::kIo}; + } + + file_.write(data.data(), data.size()); + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Flush() { + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.h b/cpp/platform_v2/public/file.h new file mode 100644 index 00000000..1f8dbce3 --- /dev/null +++ b/cpp/platform_v2/public/file.h @@ -0,0 +1,51 @@ +#ifndef PLATFORM_V2_PUBLIC_FILE_H_ +#define PLATFORM_V2_PUBLIC_FILE_H_ + +#include +#include + +#include "platform_v2/api/input_file.h" +#include "platform_v2/api/output_file.h" +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class InputFile final : public api::InputFile { + public: + explicit InputFile(const std::string& path, std::int64_t size); + ~InputFile() override = default; + InputFile(InputFile&&) = default; + InputFile& operator=(InputFile&&) = default; + + ExceptionOr Read(std::int64_t size) override; + std::string GetFilePath() const override { return path_; } + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + private: + std::ifstream file_; + std::string path_; + std::int64_t total_size_; +}; + +class OutputFile final : public api::OutputFile { + public: + explicit OutputFile(absl::string_view path); + ~OutputFile() override = default; + OutputFile(OutputFile&&) = default; + OutputFile& operator=(OutputFile&&) = default; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + std::ofstream file_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FILE_H_ diff --git a/cpp/platform_v2/public/file_test.cc b/cpp/platform_v2/public/file_test.cc new file mode 100644 index 00000000..d7d0a77d --- /dev/null +++ b/cpp/platform_v2/public/file_test.cc @@ -0,0 +1,131 @@ +#include "platform_v2/public/file.h" + +#include +#include +#include +#include + +#include "file/util/temp_path.h" +#include "platform_v2/base/byte_array.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +class FileTest : public ::testing::Test { + protected: + void SetUp() override { + temp_path_ = std::make_unique(TempPath::Local); + path_ = temp_path_->path() + "/file.txt"; + std::ofstream output_file(path_); + file_ = std::fstream(path_, std::fstream::in | std::fstream::out); + } + + void WriteToFile(const std::string& text) { + file_ << text; + file_.flush(); + size_ += text.size(); + } + + size_t GetSize() const { return size_; } + + void AssertEquals(const ExceptionOr& bytes, + const std::string& expected) { + EXPECT_TRUE(bytes.ok()); + EXPECT_EQ(std::string(bytes.result()), expected); + } + + void AssertEmpty(const ExceptionOr& bytes) { + EXPECT_TRUE(bytes.ok()); + EXPECT_TRUE(bytes.result().Empty()); + } + + static constexpr int64_t kMaxSize = 3; + + std::unique_ptr temp_path_; + std::string path_; + std::fstream file_; + size_t size_ = 0; +}; + +TEST_F(FileTest, InputFile_NonExistentPath) { + InputFile input_file("/not/a/valid/path.txt", GetSize()); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, InputFile_GetFilePath) { + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetFilePath(), path_); +} + +TEST_F(FileTest, InputFile_EmptyFileEOF) { + InputFile input_file(path_, GetSize()); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWorks) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Read(kMaxSize); + SUCCEED(); +} + +TEST_F(FileTest, InputFile_ReadUntilEOF) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWithSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(2), "ab"); + AssertEquals(input_file.Read(1), "c"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_GetTotalSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetTotalSize(), 3); + AssertEquals(input_file.Read(1), "a"); + EXPECT_EQ(input_file.GetTotalSize(), 3); +} + +TEST_F(FileTest, InputFile_Close) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Close(); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_NonExistentPath) { + OutputFile output_file("/not/a/valid/path.txt"); + ByteArray bytes("a", 1); + EXPECT_TRUE(output_file.Write(bytes).Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_Write) { + OutputFile output_file(path_); + ByteArray bytes1("a"); + ByteArray bytes2("bc"); + EXPECT_EQ(output_file.Write(bytes1), Exception{Exception::kSuccess}); + EXPECT_EQ(output_file.Write(bytes2), Exception{Exception::kSuccess}); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); +} + +TEST_F(FileTest, OutputFile_Close) { + OutputFile output_file(path_); + output_file.Close(); + ByteArray bytes("a"); + EXPECT_EQ(output_file.Write(bytes), Exception{Exception::kIo}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h new file mode 100644 index 00000000..aca9975f --- /dev/null +++ b/cpp/platform_v2/public/future.h @@ -0,0 +1,63 @@ +#ifndef PLATFORM_V2_PUBLIC_FUTURE_H_ +#define PLATFORM_V2_PUBLIC_FUTURE_H_ + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/time.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +template +class Future final : public api::SettableFuture { + public: + using Platform = api::ImplementationPlatform; + ~Future() override = default; + Future() : impl_(Platform::CreateSettableFutureAny().release()) {} + Future(Future&& other) = default; + Future& operator=(Future&& other) = default; + + void AddListener(Runnable runnable, api::Executor* executor) override { + impl_->AddListener(runnable, executor); + } + bool Set(const T& value) override { return impl_->Set(absl::any(value)); } + bool Set(T&& value) override { return impl_->Set(absl::any(value)); } + bool SetException(Exception exception) override { + return impl_->SetException(exception); + } + // throws Exception::kInterrupted, Exception::kExecution + ExceptionOr Get() override { + auto ret_val = impl_->Get(); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + ExceptionOr Get(absl::Duration timeout) override { + auto ret_val = impl_->Get(timeout); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FUTURE_H_ diff --git a/cpp/platform_v2/public/future_test.cc b/cpp/platform_v2/public/future_test.cc new file mode 100644 index 00000000..60515e36 --- /dev/null +++ b/cpp/platform_v2/public/future_test.cc @@ -0,0 +1,102 @@ +#include "platform_v2/public/future.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(FutureTest, SupportIntegralTypes) { + Future future; + future.Set(5); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), 5); +} + +TEST(FutureTest, SetExceptionIsPropagated) { + Future future; + future.SetException({Exception::kIo}); + EXPECT_EQ(future.Get().exception(), Exception::kIo); +} + +TEST(FutureTest, SupportEnum) { + Future future; + future.Set(TestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), TestEnum::kValue1); +} + +TEST(FutureTest, SupportScopedEnum) { + Future future; + future.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), ScopedTestEnum::kValue1); +} + +TEST(FutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + Future future; + v1.data[0] = 5; // Changing value before calling Set() will affect stored + v1.data[7] = 3; // value. + future.Set(v1); + v1.data[1] = 6; // Changing value after calling Set() will not affect stored + v1.data[5] = 4; // value. + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + BigSizedStruct v2 = future.Get().result(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(FutureTest, SetsExceptionOnTimeout) { + Future future; + EXPECT_EQ(future.Get(absl::Milliseconds(100)).exception(), + Exception::kTimeout); +} + +TEST(FutureTest, GetBlocksWhenNotReady) { + Future future; + SingleThreadExecutor executor; + absl::Time start = absl::Now(); + executor.Execute([&future](){ + absl::SleepFor(absl::Milliseconds(500)); + future.Set(10); + }); + auto response = future.Get(); + absl::Duration blocked_duration = absl::Now() - start; + EXPECT_EQ(response.result(), 10); + EXPECT_GE(blocked_duration, absl::Milliseconds(500)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/logging.h b/cpp/platform_v2/public/logging.h new file mode 100644 index 00000000..5a9b4767 --- /dev/null +++ b/cpp/platform_v2/public/logging.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_LOGGING_H_ +#define PLATFORM_V2_PUBLIC_LOGGING_H_ + +#include "platform/logging.h" + +#endif // PLATFORM_V2_PUBLIC_LOGGING_H_ diff --git a/cpp/platform_v2/public/logging_test.cc b/cpp/platform_v2/public/logging_test.cc new file mode 100644 index 00000000..fc010372 --- /dev/null +++ b/cpp/platform_v2/public/logging_test.cc @@ -0,0 +1,12 @@ +#include "platform_v2/public/logging.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +TEST(LoggingTest, CanLog) { + NEARBY_LOG(INFO, "message"); +} + +} diff --git a/cpp/platform_v2/public/multi_thread_executor.h b/cpp/platform_v2/public/multi_thread_executor.h new file mode 100644 index 00000000..f43ffc98 --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ + +#include "platform_v2/api/platform.h" +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- +class MultiThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + explicit MultiThreadExecutor(int max_parallelism) + : SubmittableExecutor( + Platform::CreateMultiThreadExecutor(max_parallelism)) {} + MultiThreadExecutor(MultiThreadExecutor&&) = default; + MultiThreadExecutor& operator=(MultiThreadExecutor&&) = default; + ~MultiThreadExecutor() override = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/multi_thread_executor_test.cc b/cpp/platform_v2/public/multi_thread_executor_test.cc new file mode 100644 index 00000000..914aa363 --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor_test.cc @@ -0,0 +1,94 @@ +#include "platform_v2/public/multi_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { +const int kMaxThreads = 5; +} + +TEST(MultiThreadExecutorTest, ConsructorDestructorWorks) { + MultiThreadExecutor executor(kMaxThreads); +} + +TEST(MultiThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + MultiThreadExecutor executor(kMaxThreads); + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(MultiThreadExecutorTest, JobsExecuteInParallel) { + absl::Mutex mutex; + absl::CondVar thread_cond; + absl::CondVar test_cond; + MultiThreadExecutor executor(kMaxThreads); + int count = 0; + + for (int i = 0; i < kMaxThreads; ++i) { + executor.Execute([&count, &mutex, &test_cond, &thread_cond]() { + absl::MutexLock lock(&mutex); + count++; + test_cond.Signal(); + thread_cond.Wait(&mutex); + count--; + test_cond.Signal(); + }); + } + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count < kMaxThreads) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + + EXPECT_EQ(count, kMaxThreads); + thread_cond.SignalAll(); + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count > 0) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + EXPECT_EQ(count, 0); +} + +TEST(MultiThreadExecutorTest, CanSubmit) { + MultiThreadExecutor executor(kMaxThreads); + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/mutex.h b/cpp/platform_v2/public/mutex.h new file mode 100644 index 00000000..99333a27 --- /dev/null +++ b/cpp/platform_v2/public/mutex.h @@ -0,0 +1,64 @@ +#ifndef PLATFORM_V2_PUBLIC_MUTEX_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// This is a classic mutex can be acquired at most once. +// Atttempt to acuire mutex from the same thread that is holding it will likely +// cause a deadlock. +class ABSL_LOCKABLE Mutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + explicit Mutex(bool check = true) + : impl_(Platform::CreateMutex(check ? Mode::kRegular + : Mode::kRegularNoCheck)) {} + Mutex(Mutex&&) = default; + Mutex& operator=(Mutex&&) = default; + ~Mutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class ConditionVariable; + friend class MutexLock; + std::unique_ptr impl_; +}; + +// This mutex is compatible with Java definition: +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +// This mutex may be acuired multiple times by a thread that is already holding +// it without blocking. +// It needs to be released equal number of times before any other thread could +// successfully acquire it. +class ABSL_LOCKABLE RecursiveMutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + RecursiveMutex() : impl_(Platform::CreateMutex(Mode::kRecursive)) {} + RecursiveMutex(RecursiveMutex&&) = default; + RecursiveMutex& operator=(RecursiveMutex&&) = default; + ~RecursiveMutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class MutexLock; + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_H_ diff --git a/cpp/platform_v2/public/mutex_lock.h b/cpp/platform_v2/public/mutex_lock.h new file mode 100644 index 00000000..2275ee56 --- /dev/null +++ b/cpp/platform_v2/public/mutex_lock.h @@ -0,0 +1,31 @@ +#ifndef PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/public/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE MutexLock final { + public: + explicit MutexLock(Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + explicit MutexLock(RecursiveMutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + ~MutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/public/mutex_test.cc b/cpp/platform_v2/public/mutex_test.cc new file mode 100644 index 00000000..9928f01d --- /dev/null +++ b/cpp/platform_v2/public/mutex_test.cc @@ -0,0 +1,103 @@ +#include "platform_v2/public/mutex.h" + +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +class MutexTest : public testing::Test { + public: + void VerifyStepReached(int expected) { + absl::MutexLock lock(&step_mutex_); + absl::Time deadline = absl::Now() + kTimeToWait; + while (step_ != expected) { + if (step_cond_.WaitWithDeadline(&step_mutex_, deadline)) break; + } + EXPECT_EQ(step_, expected); + // Make sure we are not progressing further. + absl::SleepFor(kTimeToWait); + EXPECT_EQ(step_, expected); + } + + protected: + SingleThreadExecutor executor_; + const absl::Duration kTimeToWait = absl::Milliseconds(200); + std::atomic_int step_ = 0; + absl::Mutex step_mutex_; + absl::CondVar step_cond_; +}; + +TEST_F(MutexTest, ConstructorDestructorWorks) { + Mutex test_mutex; + SUCCEED(); +} + +TEST_F(MutexTest, BasicLockingWorks) { + Mutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() { + step_ = 1; + step_cond_.Signal(); + test_mutex.Lock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); + }); + VerifyStepReached(1); + test_mutex.Unlock(); + VerifyStepReached(2); +} + +#ifdef THREAD_SANITIZER +TEST_F(MutexTest, DISABLED_DoubleLockIsDeadlock) +ABSL_NO_THREAD_SAFETY_ANALYSIS { +#else +TEST_F(MutexTest, DoubleLockIsDeadlock) ABSL_NO_THREAD_SAFETY_ANALYSIS { +#endif + Mutex test_mutex{/*check=*/false}; // Disable run-time deadlock detection. + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + step_ = 2; + step_cond_.Signal(); // We acquired the test lock. + test_mutex.Lock(); // Deadlock. (Main thread should save us). + step_ = 3; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor proceed to step 2. + VerifyStepReached(2); + test_mutex.Unlock(); // Bring executor out of deadlock. + VerifyStepReached(3); + test_mutex.Unlock(); // Unlock before shutdown. +} + +TEST_F(MutexTest, DoubleLockIsNotDeadlock) { + RecursiveMutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + test_mutex.Lock(); + test_mutex.Unlock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor continue. + VerifyStepReached(2); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/pipe.cc b/cpp/platform_v2/public/pipe.cc new file mode 100644 index 00000000..f9ae3b11 --- /dev/null +++ b/cpp/platform_v2/public/pipe.cc @@ -0,0 +1,21 @@ +#include "platform_v2/public/pipe.h" + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +namespace { +using Platform = api::ImplementationPlatform; +} + +Pipe::Pipe() { + auto mutex = Platform::CreateMutex(api::Mutex::Mode::kRegular); + auto cond = Platform::CreateConditionVariable(mutex.get()); + Setup(std::move(mutex), std::move(cond)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/pipe.h b/cpp/platform_v2/public/pipe.h new file mode 100644 index 00000000..a80eda19 --- /dev/null +++ b/cpp/platform_v2/public/pipe.h @@ -0,0 +1,23 @@ +#ifndef PLATFORM_V2_PUBLIC_PIPE_H_ +#define PLATFORM_V2_PUBLIC_PIPE_H_ + +#include "platform_v2/base/base_pipe.h" + +namespace location { +namespace nearby { + +// See for details: +// TODO(apolyudov): replace with cs/ link once it becomes available. +// https://critique-ng.corp.google.com/cl/310492721/depot/google3/platform_v2/base/base_pipe.h +class Pipe final : public BasePipe { + public: + Pipe(); + ~Pipe() override = default; + Pipe(Pipe&&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_PIPE_H_ diff --git a/cpp/platform_v2/public/pipe_test.cc b/cpp/platform_v2/public/pipe_test.cc new file mode 100644 index 00000000..c8a7af89 --- /dev/null +++ b/cpp/platform_v2/public/pipe_test.cc @@ -0,0 +1,332 @@ +#include "platform_v2/public/pipe.h" + +#include + +#include +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/base/runnable.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PipeTest, ConstructorDestructorWorks) { + Pipe pipe; + SUCCEED(); +} + +TEST(PipeTest, SimpleWriteRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, WriteEndClosedBeforeRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Close the write end before the read end has even begun reading. + EXPECT_TRUE(output_stream.Close().Ok()); + + // We should still be able to read what was written. + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); + + // And after that, we should get our indication that all the data that could + // ever be read, has already been read. + read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_TRUE(read_data.result().Empty()); +} + +TEST(PipeTest, ReadEndClosedBeforeWrite) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // Close the read end before the write end has even begun writing. + EXPECT_TRUE(input_stream.Close().Ok()); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, SizedReadMoreThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Even though we ask for double of what's there in the first chunk, we should + // get back only what's there in that first chunk, and that's alright. + ExceptionOr read_data = input_stream.Read(data.size() * 2); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, SizedReadLessThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data_first_part("ABCD"); + std::string data_second_part("EFGHIJ"); + std::string data = data_first_part + data_second_part; + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // When we ask for less than what's there in the first chunk, we should get + // back exactly what we asked for, with the remainder still being available + // for the next read. + std::int64_t desired_size = data_first_part.size(); + ExceptionOr first_read_data = input_stream.Read(desired_size); + EXPECT_TRUE(first_read_data.ok()); + EXPECT_EQ(data_first_part, std::string(first_read_data.result())); + + // Now read the remainder, and get everything that ought to have been left. + ExceptionOr second_read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(second_read_data.ok()); + EXPECT_EQ(data_second_part, std::string(second_read_data.result())); +} + +TEST(PipeTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + + input_stream.Close(); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(!read_data.ok()); + EXPECT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +TEST(PipeTest, WriteAfterOutputStreamClosed) { + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + output_stream.Close(); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, RepeatedClose) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); +} + +class Thread { + public: + Thread() : thread_(), attr_(), runnable_() { + pthread_attr_init(&attr_); + pthread_attr_setdetachstate(&attr_, PTHREAD_CREATE_JOINABLE); + } + ~Thread() { pthread_attr_destroy(&attr_); } + + void Start(Runnable runnable) { + runnable_ = runnable; + + pthread_create(&thread_, &attr_, Thread::Body, this); + } + + void Join() { pthread_join(thread_, nullptr); } + + private: + static void* Body(void* args) { + reinterpret_cast(args)->runnable_(); + return nullptr; + } + + pthread_t thread_; + pthread_attr_t attr_; + Runnable runnable_; +}; + +TEST(PipeTest, ReadBlockedUntilWrite) { + using CrossThreadBool = std::atomic_bool; + + class ReaderRunnable { + public: + ReaderRunnable(InputStream* input_stream, + absl::string_view expected_read_data, + CrossThreadBool* ok_for_read_to_unblock) + : input_stream_(input_stream), + expected_read_data_(expected_read_data), + ok_for_read_to_unblock_(ok_for_read_to_unblock) {} + ~ReaderRunnable() = default; + + // Signature "void()" satisfies Runnable. + void operator()() { + ExceptionOr read_data = input_stream_->Read(Pipe::kChunkSize); + + // Make sure read() doesn't return before it's appropriate. + if (!*ok_for_read_to_unblock_) { + FAIL() << "read() unblocked before it was supposed to."; + } + + // And then run our normal set of checks to make sure the read() was + // successful. + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(expected_read_data_, std::string(read_data.result())); + } + + private: + InputStream* input_stream_; + const std::string expected_read_data_; + CrossThreadBool* ok_for_read_to_unblock_; + }; + + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // State shared between this thread (the writer) and reader_thread. + CrossThreadBool ok_for_read_to_unblock = false; + std::string data("ABCD"); + + // Kick off reader_thread. + Thread reader_thread; + reader_thread.Start( + ReaderRunnable(&pipe.GetInputStream(), data, &ok_for_read_to_unblock)); + + // Introduce a delay before we actually write anything. + absl::SleepFor(absl::Seconds(5)); + // Mark that we're done with the delay, and that the write is about to occur + // (this is slightly earlier than it ought to be, but there's no way to + // atomically set this from within the implementation of write(), and doing it + // after is too late for the purposes of this test). + ok_for_read_to_unblock = true; + + // Perform the actual write. + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // And wait for reader_thread to finish. + reader_thread.Join(); +} + +TEST(PipeTest, ConcurrentWriteAndRead) { + class BaseRunnable { + protected: + explicit BaseRunnable(const std::vector& chunks) + : chunks_(chunks), prng_() {} + virtual ~BaseRunnable() = default; + + void RandomSleep() { + // Generate a random sleep between 100 and 1000 milliseconds. + absl::SleepFor(absl::Milliseconds(BoundedUint32(100, 1000))); + } + + const std::vector& chunks_; + + private: + // Both ends of the bounds are inclusive. + std::uint32_t BoundedUint32(std::uint32_t lower_bound, + std::uint32_t upper_bound) { + return (prng_.NextUint32() % (upper_bound - lower_bound + 1)) + + lower_bound; + } + + Prng prng_; + }; + + class WriterRunnable : public BaseRunnable { + public: + WriterRunnable(OutputStream* output_stream, + const std::vector& chunks) + : BaseRunnable(chunks), output_stream_(output_stream) {} + ~WriterRunnable() override = default; + + void operator()() { + for (auto& chunk : chunks_) { + RandomSleep(); // Random pauses before each write. + EXPECT_TRUE(output_stream_->Write(ByteArray(chunk)).Ok()); + } + + RandomSleep(); // A random pause before closing the writer end. + EXPECT_TRUE(output_stream_->Close().Ok()); + } + + private: + OutputStream* output_stream_; + }; + + class ReaderRunnable : public BaseRunnable { + public: + ReaderRunnable(InputStream* input_stream, + const std::vector& chunks) + : BaseRunnable(chunks), input_stream_(input_stream) {} + ~ReaderRunnable() override = default; + + void operator()() { + // First, calculate what we expect to receive, in total. + std::string expected_data; + for (auto& chunk : chunks_) { + expected_data += chunk; + } + + // Then, start actually receiving. + std::string actual_data; + while (true) { + RandomSleep(); // Random pauses before each read. + ExceptionOr read_data = + input_stream_->Read(Pipe::kChunkSize); + if (read_data.ok()) { + ByteArray result = read_data.result(); + if (result.Empty()) { + break; // Normal exit from the read loop. + } + actual_data += std::string(result); + } else { + break; // Erroneous exit from the read loop. + } + } + + // And once we're done, check that we got everything we expected. + EXPECT_EQ(expected_data, actual_data); + } + + private: + InputStream* input_stream_; + }; + + Pipe pipe; + + std::vector chunks; + chunks.push_back("ABCD"); + chunks.push_back("EFGH"); + chunks.push_back("IJKL"); + + Thread writer_thread; + Thread reader_thread; + writer_thread.Start(WriterRunnable(&pipe.GetOutputStream(), chunks)); + reader_thread.Start(ReaderRunnable(&pipe.GetInputStream(), chunks)); + writer_thread.Join(); + reader_thread.Join(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/scheduled_executor.h b/cpp/platform_v2/public/scheduled_executor.h new file mode 100644 index 00000000..3048f16e --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor.h @@ -0,0 +1,75 @@ +#ifndef PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor final { + public: + using Platform = api::ImplementationPlatform; + + ScheduledExecutor() : impl_(Platform::CreateScheduledExecutor()) {} + ScheduledExecutor(ScheduledExecutor&& other) { *this = std::move(other); } + ~ScheduledExecutor() { + MutexLock lock(&mutex_); + DoShutdown(); + } + + ScheduledExecutor& operator=(ScheduledExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + DoShutdown(); + } + + Cancelable Schedule(Runnable&& runnable, absl::Duration duration) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return impl_ ? Cancelable(impl_->Schedule(std::move(runnable), duration)) + : Cancelable(); + } + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/scheduled_executor_test.cc b/cpp/platform_v2/public/scheduled_executor_test.cc new file mode 100644 index 00000000..9efb844a --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor_test.cc @@ -0,0 +1,100 @@ +#include "platform_v2/public/scheduled_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +TEST(ScheduledExecutorTest, ConsructorDestructorWorks) { + ScheduledExecutor executor; +} + +TEST(ScheduledExecutorTest, CanExecute) { + absl::Mutex mutex; + absl::CondVar cond; + std::atomic_bool done = false; + ScheduledExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(ScheduledExecutorTest, CanSchedule) { + ScheduledExecutor executor; + std::atomic_int value = 0; + absl::Mutex mutex; + absl::CondVar cond; + // schedule job due in 100 ms. + executor.Schedule( + [&value, &cond]() { + EXPECT_EQ(value, 1); + value = 5; + cond.Signal(); + }, + absl::Milliseconds(100)); + // schedule job due in 10 ms; must fire before the first one. + executor.Schedule( + [&value]() { + EXPECT_EQ(value, 0); + value = 1; + }, + absl::Milliseconds(10)); + { + // wait for the final job to unblock us. + absl::MutexLock lock(&mutex); + cond.WaitWithTimeout(&mutex, absl::Milliseconds(1000)); + } + EXPECT_EQ(value, 5); +} + +TEST(ScheduledExecutorTest, CanCancel) { + ScheduledExecutor executor; + std::atomic_int value = 0; + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(cancelable.Cancel()); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_EQ(value, 0); +} + +TEST(ScheduledExecutorTest, FailToCancel) { + absl::Mutex mutex; + absl::CondVar cond; + ScheduledExecutor executor; + std::atomic_int value = 0; + // Schedule job in 10ms, which will we will attempt to cancel later. + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + // schedule another job to test results of the first one, in 50ms from now. + executor.Schedule( + [&cancelable, &cond]() { + EXPECT_FALSE(cancelable.Cancel()); + // Wake up main thread. + cond.Signal(); + }, + absl::Milliseconds(50)); + { + absl::MutexLock lock(&mutex); + cond.Wait(&mutex); + } + EXPECT_EQ(value, 1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/single_thread_executor.h b/cpp/platform_v2/public/single_thread_executor.h new file mode 100644 index 00000000..d9f4e0f9 --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor.h @@ -0,0 +1,26 @@ +#ifndef PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- +class SingleThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + SingleThreadExecutor() + : SubmittableExecutor(Platform::CreateSingleThreadExecutor()) {} + ~SingleThreadExecutor() override = default; + SingleThreadExecutor(SingleThreadExecutor&&) = default; + SingleThreadExecutor& operator=(SingleThreadExecutor&&) = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/single_thread_executor_test.cc b/cpp/platform_v2/public/single_thread_executor_test.cc new file mode 100644 index 00000000..eedbe576 --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor_test.cc @@ -0,0 +1,71 @@ +#include "platform_v2/public/single_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +TEST(SingleThreadExecutorTest, ConsructorDestructorWorks) { + SingleThreadExecutor executor; +} + +TEST(SingleThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + SingleThreadExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(SingleThreadExecutorTest, JobsExecuteInOrder) { + std::vector results; + SingleThreadExecutor executor; + + for (int i = 0; i < 10; ++i) { + executor.Execute([i, &results]() { results.push_back(i); }); + } + + absl::CondVar cond; + std::atomic_bool done = false; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); + EXPECT_EQ(results, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); +} + +TEST(SingleThreadExecutorTest, CanSubmit) { + SingleThreadExecutor executor; + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/submittable_executor.h b/cpp/platform_v2/public/submittable_executor.h new file mode 100644 index 00000000..04a0c085 --- /dev/null +++ b/cpp/platform_v2/public/submittable_executor.h @@ -0,0 +1,96 @@ +#ifndef PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ + +#include +#include +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/base/callable.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutor +// - SingleThreadExecutor +class SubmittableExecutor : public api::SubmittableExecutor { + public: + ~SubmittableExecutor() override { + MutexLock lock(&mutex_); + DoShutdown(); + } + SubmittableExecutor(SubmittableExecutor&& other) { *this = std::move(other); } + SubmittableExecutor& operator=(SubmittableExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + DoShutdown(); + } + + // Submits a callable for execution. + // When execution completes, return value is assigned to the passed future. + // Future must outlive the whole execution chain. + template + bool Submit(Callable&& callable, Future* future) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + bool submitted = DoSubmit([callable{std::move(callable)}, future]() { + ExceptionOr result = callable(); + if (result.ok()) { + future->Set(result.result()); + } else { + future->SetException({result.exception()}); + } + }); + if (!submitted) { + // complete immediately with kExecution exception value. + future->SetException({Exception::kExecution}); + } + return submitted; + } + + protected: + explicit SubmittableExecutor(std::unique_ptr impl) + : impl_(std::move(impl)) {} + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + bool DoSubmit(Runnable&& wrapped_callable) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) override { + return impl_ ? impl_->DoSubmit(std::move(wrapped_callable)) : false; + } + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/system_clock.h b/cpp/platform_v2/public/system_clock.h new file mode 100644 index 00000000..f1b95bad --- /dev/null +++ b/cpp/platform_v2/public/system_clock.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ + +#include "platform_v2/api/system_clock.h" + +#endif // PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ diff --git a/proto/BUILD b/proto/BUILD index 6446d8f9..b5d3eadb 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -40,6 +40,21 @@ java_proto_library( deps = [":discovery_enums_proto"], ) +proto_library( + name = "error_code_enums_proto", + srcs = ["error_code_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +java_lite_proto_library( + name = "error_code_enums_java_proto_lite", + deps = [":error_code_enums_proto"], +) + proto_library( name = "connections_enums_proto", srcs = ["connections_enums.proto"], @@ -156,6 +171,11 @@ java_lite_proto_library( deps = [":sharing_enums_proto"], ) +java_proto_library( + name = "sharing_enums_java_proto", + deps = [":sharing_enums_proto"], +) + proto_library( name = "nearby_event_codes_proto", srcs = ["nearby_event_codes.proto"], diff --git a/proto/bootstrap_enums.proto b/proto/bootstrap_enums.proto index 9c378983..9f942b8e 100644 --- a/proto/bootstrap_enums.proto +++ b/proto/bootstrap_enums.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "BootstrapEnums"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Medium used for offline socket. enum SocketMedium { diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index b5d2901e..04f99cb7 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -236,4 +236,6 @@ message PairedKeyEncryptionFrame { message MediumMetadata { // True if local device supports 5GHz. optional bool supports_5_ghz = 1; + // WiFi Lan BSSID + optional string bssid = 2; } diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 461d312c..a7f1ff1c 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -20,6 +20,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "ConnectionsEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The type of event being logged. // Lightweight START_* and STOP_* events track instances of potential crashes diff --git a/proto/connections_enums_proto_config.asciipb b/proto/connections_enums_proto_config.asciipb index b5ea0aa5..702328c1 100644 --- a/proto/connections_enums_proto_config.asciipb +++ b/proto/connections_enums_proto_config.asciipb @@ -1,5 +1,7 @@ optimize_mode: LITE_RUNTIME allowed_enum: "location.nearby.proto.connections.Medium" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeResult" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeErrorStage" allowed_enum: "location.nearby.proto.connections.DisconnectionReason" allowed_enum: "location.nearby.proto.connections.PayloadStatus" diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index 08423b4e..7d229dce 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // NEXT ID: 132 enum DiscoveryEvent { diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto new file mode 100644 index 00000000..5e45283b --- /dev/null +++ b/proto/error_code_enums.proto @@ -0,0 +1,133 @@ +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "ErrorCodeEnums"; +option objc_class_prefix = "GNCP"; + +// The type of the error. +// It help to sort error codes to different types to analyze and also impact the +// logcat print it as Warning or Severe. +enum ErrorType { + UNKNOWN_TYPE = 0; + + // The error should not happen on production, it's like the input is null or + // not invalid or it's a unexpected API call. For example, start advertising + // with empty service ID or start advertising with the same service ID twice. + DEVELOPING = 1; + + // It’s about the device's capabilities, some devices may not support the + // feature Nearby used. E.g. The device not support BLE advertising + DEVICE = 2; + + // The failure return from the system or library API we used to communicate + // with the medium. E.g. get null OS objects or call the API but get a + // negative return value which indicates that the system does not allow to do + // that now. + SYSTEM = 3; + + // The network related failure. E.g. get an EOF exception while reading pipe + // or fail to create connection. + NETWORK = 4; + + // This may not be a failure, it can be the things we are interested in, like + // to count how many BLE advertisements the device received in a specified + // period and how many different advertisements in it, it can help us to know + // the user under a clean or dirty environment. + OTHERS = 5; +} + +// The event which the error occurs on. +enum Event { + UNKNOWN_EVENT = 0; + START_ADVERTISING = 1; + STOP_ADVERTISING = 2; + START_LISTENING_INCOMING_CONNECTION = 3; + STOP_LISTENING_INCOMING_CONNECTION = 4; + START_DISCOVERING = 5; + STOP_DISCOVERING = 6; + CONNECT = 7; + DISCONNECT = 8; + ACCEPT_CONNECTION = 9; + REJECT_CONNECTION = 10; + SEND_PAYLOAD = 11; + CANCEL_PAYLOAD = 12; + RECEIVE_PAYLOAD = 13; +} + +// The error to identify the common failure for all mediums. The range between 0 +// and 30. +enum CommonError { + UNKNOWN_ERROR = 0; + + // The common error for all mediums, the range between 0 and 30. + + // Developing error, the input with invalid format or empty. + INVALID_PARAMETER = 1; + // Device error, the BLE not available on this device. + BLE_NOT_AVAILABLE = 2; + // System error, the medium in the unexpected state, e.g. we have check the + // medium is on, after then it suddently off and cause Nearby + // Connection failed. + UNEXPECTED_MEDIUM_STATE = 3; + + // Reserved 4 to 30 +} + +// The error for event START_ADVERTISING. The range between 31 and 99. +enum StartAdvertisingError { + // Developing error, not allow to advertising fast pair model id and sharing + // fast advertisement at the same time, they are both use fast + // advertisement, and only allow 1 fast advertisement at the same time. + MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED = 31; + // System error, there's already someone advertising fast advertisement, not + // allow to start another one. + FAST_ADVERTISEMENT_ALREADY_ADVERTISED = 32; + // Developing error, this service ID already requested, should not request + // it again without stop advertising. + DUPLICATE_ADVERTISING_REQUESTED = 33; + // System error, failed to start GATT server + START_GATT_SERVER_FAILED = 34; + // System error, all advertising slot ran out, can't available for new + // regular advertisement. + BLE_MAX_GATT_ADVERTISEMENT_SLOT_REACHED = 35; + // System error, failed to start advertising for legacy advertisements + START_LEGACY_ADVERTISING_FAILED = 36; + // System error, start advertising for legacy advertisements but timed out + START_LEGACY_ADVERTISING_TIMEOUT = 37; + // System error, failed to start advertising for extended advertisements + START_EXTENDED_ADVERTISING_FAILED = 38; + // System error, start advertising for extended advertisements but timed out + START_EXTENDED_ADVERTISING_TIMEOUT = 39; + + // Next ID :40 +} + +enum Description { + UNKNOWN = 0; + NULL_SERVICE_ID = 1; + NULL_ADVERTISEMENT_BYTES = 2; + CONNECTIONS_FEATURE_DISABLED = 3; + STALE_SDK_VERSION = 4; + FEATURE_BLUETOOTH_NOT_SUPPORTED = 5; + FEATURE_BLUETOOTH_LE_NOT_SUPPORTED = 6; + NULL_BLUETOOTH_MANAGER = 7; + NULL_BLUETOOTH_ADAPTER = 8; + INVALID_FAST_PAIR_MODEL_ID = 9; + INVALID_FAST_ADVERTISEMENT_DATA = 10; + INVALID_ADVERTISEMENT_HEADER_DATA = 11; + INVALID_REGULAR_ADVERTISEMENT_DATA = 12; + NULL_BLUETOOTH_LE_ADVERTISER_COMPAT = 13; + ADVERTISE_FAILED_ALREADY_STARTED = 14; + ADVERTISE_FAILED_DATA_TOO_LARGE = 15; + ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 16; + ADVERTISE_FAILED_INTERNAL_ERROR = 17; + ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 18; + INTERRUPTED_EXCEPTION = 19; + EXECUTION_EXCEPTION = 20; +} diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 51eef973..63045db8 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "MagicPairEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Enums related to logged events. For event codes, see NearbyEventCodes. message MagicPairEvent { diff --git a/proto/nearby_client_enums.proto b/proto/nearby_client_enums.proto index 59dc9116..36bda7dc 100644 --- a/proto/nearby_client_enums.proto +++ b/proto/nearby_client_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyClientEnums"; option objc_class_prefix = "GNCP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The user type that is logging. enum UserType { diff --git a/proto/nearby_event_codes.proto b/proto/nearby_event_codes.proto index 91610ae5..0c6f78d0 100644 --- a/proto/nearby_event_codes.proto +++ b/proto/nearby_event_codes.proto @@ -8,6 +8,7 @@ option (logs_proto.file_not_used_for_logging_except_enums) = true; option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "NearbyEventCodes"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // Event codes for the NEARBY log source. See: // http://google3/wireless/android/play/playlog/proto/event_code_enums.proto diff --git a/proto/setup_enums.proto b/proto/setup_enums.proto index 2bb334ca..d821ce49 100644 --- a/proto/setup_enums.proto +++ b/proto/setup_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SetupEnums"; option objc_class_prefix = "GNSP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. // The type of event being logged. // Lightweight START_* and STOP_* events track instances of potential crashes diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index fd517938..98e5df0d 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -9,6 +9,7 @@ option java_api_version = 2; option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "SharingEnums"; option objc_class_prefix = "GNSHP"; +option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. /* We use event based logging (an event object can be constructed and logged @@ -108,6 +109,21 @@ enum EventType { // Set data usage preference. SET_DATA_USAGE = 28; + + // Receiver dismisses a fast initialization + DISMISS_FAST_INITIALIZATION = 29; + + // Cancel connection. + CANCEL_CONNECTION = 30; +} + +// Event category to differentiate whether this comes from sender or receiver, +// whether this is for communication flow, or for settings. +enum EventCategory { + UNKNOWN_EVENT_CATEGORY = 0; + SENDING_EVENT = 1; + RECEIVING_EVENT = 2; + SETTINGS_EVENT = 3; } // Status of nearby sharing. @@ -234,6 +250,7 @@ enum ServerResponseState { SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5; SERVER_RESPONSE_STATUS_UNAVAILABLE = 6; SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7; + SERVER_RESPONSE_STATUS_INVALID_ARGUMENT = 9; // For GoogleAuthException. SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8; From b13316ba93038bfc2d4944974a1fdc445a6cc412 Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Tue, 2 Jun 2020 12:12:18 +0100 Subject: [PATCH 19/52] Add lite_runtime option for protos Open sourced protos have additional lite_runtime optimization to avoid depending on the full protobuf library which is heavier. Change-Id: Ia4d96bcb792b56d0cae78142cd515f0d7fb3d006 --- script/oss.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/script/oss.py b/script/oss.py index f1768def..bcc1673d 100755 --- a/script/oss.py +++ b/script/oss.py @@ -107,6 +107,7 @@ def post_process_oss_files(path, args): ("_portable_proto.pb.h", ".pb.h"), (".proto.h", ".pb.h"), ) + for root, dirs, files in os.walk(path): if top_level and top_dirs: # we must convert cpp/ and proto/ subtrees. @@ -125,6 +126,8 @@ def post_process_oss_files(path, args): lines=[] google3_ignore = False with open(fname, "r") as f: + add_proto_lite_runtime = args.proto_lite_runtime and fname.endswith(".proto") + for line in f: orig = line @@ -149,6 +152,13 @@ def post_process_oss_files(path, args): if google3_ignore: modified = True continue + + if add_proto_lite_runtime and line.startswith("option "): + lines.append("option optimize_for = LITE_RUNTIME;") + modified = True + # LITE_RUNTIME should be added only once per file. + add_proto_lite_runtime = False + lines.append(line) if args.fix_oss_headers: @@ -158,6 +168,7 @@ def post_process_oss_files(path, args): prefix, offset = options lines = add_copyright(lines, prefix, offset) modified = True + if modified: with open(fname, "w") as f: for line in lines: @@ -178,6 +189,7 @@ def main(): parser.add_argument('--no-copy', action='store_true', default=False) parser.add_argument('--no-subst', action='store_true', default=False) parser.add_argument('--no-recurse', action='store_true', default=False) + parser.add_argument('--proto-lite-runtime', action='store_true', default=False) args = parser.parse_args() if args.google3_filter: print("google3-specific code will be removed") From cffbc04508d4a01cb0a393c201fb73478acfcf9f Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Wed, 3 Jun 2020 18:13:33 +0100 Subject: [PATCH 20/52] Roll forward to cl/314549634 Change-Id: I4d1e7eacd5fa4078a094571ad6d5f51e422535ff --- cpp/core/BUILD | 1 + cpp/core/check_compilation.cc | 2 +- cpp/core/internal/internal_payload_factory.cc | 10 +- cpp/core/internal/mediums/uuid.h | 6 +- cpp/core/internal/mediums/webrtc/BUILD | 8 +- .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 2 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- cpp/core/strategy.cc | 2 +- cpp/core_v2/BUILD | 11 +- cpp/core_v2/internal/BUILD | 9 +- cpp/core_v2/internal/base_pcp_handler.cc | 896 +++++++++++++++++- cpp/core_v2/internal/base_pcp_handler.h | 159 +++- cpp/core_v2/internal/base_pcp_handler_test.cc | 174 +++- cpp/core_v2/internal/ble_advertisement.h | 19 +- .../internal/ble_advertisement_test.cc | 176 ++-- cpp/core_v2/internal/bluetooth_device_name.cc | 187 ++++ cpp/core_v2/internal/bluetooth_device_name.h | 73 ++ .../internal/bluetooth_device_name_test.cc | 149 +++ cpp/core_v2/internal/mediums/BUILD | 14 +- .../mediums/ble_advertisement_header.h | 3 +- .../mediums/ble_advertisement_header_test.cc | 103 +- cpp/core_v2/internal/mediums/ble_packet.h | 3 +- .../internal/mediums/ble_packet_test.cc | 48 +- cpp/core_v2/internal/mediums/ble_peripheral.h | 3 +- .../internal/mediums/ble_peripheral_test.cc | 4 +- cpp/core_v2/internal/mediums/bloom_filter.cc | 91 ++ cpp/core_v2/internal/mediums/bloom_filter.h | 87 ++ .../internal/mediums/bloom_filter_test.cc | 193 ++++ cpp/core_v2/internal/mediums/webrtc/BUILD | 27 +- .../mediums/webrtc/connection_flow.cc | 134 +++ .../internal/mediums/webrtc/connection_flow.h | 133 +++ .../mediums/webrtc/connection_flow_test.cc | 32 + .../mediums/webrtc/data_channel_listener.h | 31 + .../webrtc/local_ice_candidate_listener.h | 25 + .../webrtc/peer_connection_observer_impl.cc | 68 ++ .../webrtc/peer_connection_observer_impl.h | 48 + .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 2 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- cpp/core_v2/internal/offline_frames.cc | 2 +- cpp/core_v2/internal/pcp_handler.h | 4 +- .../internal/service_controller_router.cc | 13 +- cpp/core_v2/internal/wifi_lan_service_info.cc | 66 +- cpp/core_v2/internal/wifi_lan_service_info.h | 15 +- .../internal/wifi_lan_service_info_test.cc | 106 +-- cpp/core_v2/payload_test.cc | 4 +- cpp/platform/BUILD | 6 +- cpp/platform/api/BUILD | 2 +- cpp/platform/api/platform.h | 6 +- cpp/platform/api/webrtc.h | 2 +- cpp/platform/impl/g3/BUILD | 1 + cpp/platform/impl/g3/platform.cc | 21 +- cpp/platform/impl/sample/BUILD | 1 + cpp/platform/impl/sample/sample_platform.cc | 21 +- cpp/platform/impl/shared/BUILD | 28 + cpp/platform/{ => impl/shared}/file_impl.cc | 2 +- cpp/platform/{ => impl/shared}/file_impl.h | 6 +- .../{ => impl/shared}/file_impl_test.cc | 2 +- cpp/platform_v2/api/BUILD | 53 +- cpp/platform_v2/api/ble_v2.h | 8 +- cpp/platform_v2/api/bluetooth_classic.h | 81 +- cpp/platform_v2/api/platform.h | 10 +- cpp/platform_v2/api/webrtc.h | 52 +- cpp/platform_v2/base/BUILD | 41 +- cpp/platform_v2/base/base_input_stream.cc | 85 ++ cpp/platform_v2/base/base_input_stream.h | 44 + cpp/platform_v2/base/base_pipe.cc | 1 - cpp/platform_v2/base/byte_array.h | 4 +- cpp/platform_v2/base/exception.h | 5 +- cpp/platform_v2/base/logging.h | 6 + cpp/platform_v2/base/medium_environment.cc | 191 ++++ cpp/platform_v2/base/medium_environment.h | 113 +++ cpp/platform_v2/impl/g3/BUILD | 83 +- cpp/platform_v2/impl/g3/bluetooth_adapter.cc | 59 +- cpp/platform_v2/impl/g3/bluetooth_adapter.h | 11 +- cpp/platform_v2/impl/g3/medium_environment.cc | 32 - cpp/platform_v2/impl/g3/medium_environment.h | 47 - cpp/platform_v2/impl/g3/platform.cc | 30 +- cpp/platform_v2/impl/g3/webrtc.cc | 36 + cpp/platform_v2/impl/g3/webrtc.h | 35 + cpp/platform_v2/impl/shared/BUILD | 31 +- .../{public => impl/shared}/file.cc | 6 +- cpp/platform_v2/impl/shared/file.h | 53 ++ .../{public => impl/shared}/file_test.cc | 4 +- cpp/platform_v2/public/BUILD | 39 +- cpp/platform_v2/public/file.h | 36 +- cpp/platform_v2/public/future.h | 4 +- cpp/platform_v2/public/logging.h | 2 +- cpp/platform_v2/public/webrtc.h | 44 + proto/connections/offline_wire_formats.proto | 4 + proto/sharing_enums.proto | 2 + 92 files changed, 3804 insertions(+), 697 deletions(-) create mode 100644 cpp/core_v2/internal/bluetooth_device_name.cc create mode 100644 cpp/core_v2/internal/bluetooth_device_name.h create mode 100644 cpp/core_v2/internal/bluetooth_device_name_test.cc create mode 100644 cpp/core_v2/internal/mediums/bloom_filter.cc create mode 100644 cpp/core_v2/internal/mediums/bloom_filter.h create mode 100644 cpp/core_v2/internal/mediums/bloom_filter_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/connection_flow.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/connection_flow.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h rename cpp/platform/{ => impl/shared}/file_impl.cc (97%) rename cpp/platform/{ => impl/shared}/file_impl.h (88%) rename cpp/platform/{ => impl/shared}/file_impl_test.cc (98%) create mode 100644 cpp/platform_v2/base/base_input_stream.cc create mode 100644 cpp/platform_v2/base/base_input_stream.h create mode 100644 cpp/platform_v2/base/logging.h create mode 100644 cpp/platform_v2/base/medium_environment.cc create mode 100644 cpp/platform_v2/base/medium_environment.h delete mode 100644 cpp/platform_v2/impl/g3/medium_environment.cc delete mode 100644 cpp/platform_v2/impl/g3/medium_environment.h create mode 100644 cpp/platform_v2/impl/g3/webrtc.cc create mode 100644 cpp/platform_v2/impl/g3/webrtc.h rename cpp/platform_v2/{public => impl/shared}/file.cc (91%) create mode 100644 cpp/platform_v2/impl/shared/file.h rename cpp/platform_v2/{public => impl/shared}/file_test.cc (97%) create mode 100644 cpp/platform_v2/public/webrtc.h diff --git a/cpp/core/BUILD b/cpp/core/BUILD index f0bb59b5..9491718c 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -51,6 +51,7 @@ cc_library( "//platform:utils", "//platform/api", "//platform/impl/g3", + "//platform/impl/shared:file", "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", ], diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index 7bca2966..584c6cb4 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -7,7 +7,7 @@ #include "core/status.h" #include "platform/api/platform.h" #include "platform/byte_array.h" -#include "platform/file_impl.h" +#include "platform/impl/shared/file_impl.h" #include "platform/impl/shared/sample/sample_wifi_medium.h" #include "platform/port/string.h" #include "platform/ptr.h" diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc index c7cd5a45..dd5c6cdb 100644 --- a/cpp/core/internal/internal_payload_factory.cc +++ b/cpp/core/internal/internal_payload_factory.cc @@ -4,10 +4,11 @@ #include "core/payload.h" #include "platform/api/condition_variable.h" +#include "platform/api/input_file.h" #include "platform/api/lock.h" +#include "platform/api/output_file.h" #include "platform/byte_array.h" #include "platform/exception.h" -#include "platform/file_impl.h" #include "platform/pipe.h" namespace location { @@ -287,10 +288,9 @@ Ptr InternalPayloadFactory::createIncoming( } case PayloadTransferFrame::PayloadHeader::FILE: { - const std::string payload_path = Platform::getPayloadPath(payload_id); - Ptr input_file = MakePtr(new InputFileImpl( - payload_path, payload_transfer_frame.payload_header().total_size())); - Ptr output_file = MakePtr(new OutputFileImpl(payload_path)); + Ptr output_file = Platform::createOutputFile(payload_id); + Ptr input_file = Platform::createInputFile( + payload_id, payload_transfer_frame.payload_header().total_size()); ConstPtr payload = MakeConstPtr( new Payload(payload_id, MakeConstPtr(new Payload::File(input_file)))); return MakePtr(new IncomingFileInternalPayload( diff --git a/cpp/core/internal/mediums/uuid.h b/cpp/core/internal/mediums/uuid.h index 5742e6c4..5a7a95e7 100644 --- a/cpp/core/internal/mediums/uuid.h +++ b/cpp/core/internal/mediums/uuid.h @@ -17,17 +17,17 @@ namespace connections { template class UUID { public: - explicit UUID(const string& data); + explicit UUID(const std::string& data); UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits); ~UUID(); // Returns the canonical textual representation // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the // UUID. - string str(); + std::string str(); private: - string data_; + std::string data_; }; } // namespace connections 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/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.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h index 5a55e9d9..d0ec4104 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 { 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/strategy.cc b/cpp/core/strategy.cc index dfa1637c..6101a3e2 100644 --- a/cpp/core/strategy.cc +++ b/cpp/core/strategy.cc @@ -25,7 +25,7 @@ bool Strategy::isValid() const { return kP2PStar == *this || kP2PCluster == *this || kP2PPointToPoint == *this; } -string Strategy::getName() const { +std::string Strategy::getName() const { if (Strategy::kP2PCluster == *this) { return "P2P_CLUSTER"; } else if (Strategy::kP2PStar == *this) { diff --git a/cpp/core_v2/BUILD b/cpp/core_v2/BUILD index 12a7a8fe..eed3c011 100644 --- a/cpp/core_v2/BUILD +++ b/cpp/core_v2/BUILD @@ -12,8 +12,9 @@ cc_library( deps = [ ":core_types", "//core_v2/internal", - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//absl/strings", "//absl/time", "//absl/types:span", @@ -38,8 +39,9 @@ cc_library( ], deps = [ "//platform_v2/base", - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//absl/strings", "//absl/types:variant", ], @@ -62,9 +64,10 @@ cc_test( "//core_v2/internal", "//core_v2/internal:internal_test", "//platform_v2/base", - "//platform_v2/impl/g3", - "//platform_v2/public", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//testing/base/public:gunit_main", "//absl/strings", "//absl/time", diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index e375d8a1..3a1f78d5 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -4,6 +4,7 @@ cc_library( "base_endpoint_channel.cc", "base_pcp_handler.cc", "ble_advertisement.cc", + "bluetooth_device_name.cc", "client_proxy.cc", "encryption_runner.cc", "endpoint_channel_manager.cc", @@ -16,6 +17,7 @@ cc_library( "base_endpoint_channel.h", "base_pcp_handler.h", "ble_advertisement.h", + "bluetooth_device_name.h", "client_proxy.h", "encryption_runner.h", "endpoint_channel.h", @@ -36,8 +38,9 @@ cc_library( "//core_v2:core_types", "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//proto:connections_enums_portable_proto", "//securegcm:ukey2", "//absl/base:core_headers", @@ -71,6 +74,7 @@ cc_test( "base_endpoint_channel_test.cc", "base_pcp_handler_test.cc", "ble_advertisement_test.cc", + "bluetooth_device_name_test.cc", "client_proxy_test.cc", "encryption_runner_test.cc", "endpoint_channel_manager_test.cc", @@ -87,8 +91,9 @@ cc_test( "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", "//platform_v2/impl/g3", # build_cleaner: keep - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//proto:connections_enums_portable_proto", "//securegcm:ukey2", "//testing/base/public:gunit", diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 99482b77..e57801e6 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -18,6 +18,9 @@ namespace location { namespace nearby { namespace connections { +using ::location::nearby::proto::connections::Medium; +using ::securegcm::UKey2Handshake; + BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager) : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} @@ -75,7 +78,7 @@ Status BasePcpHandler::StartDiscovery(ClientProxy* client, const DiscoveryListener& listener) { Future response; RunOnPcpHandlerThread( - [this, client, service_id, options, listener, &response]() { + [this, client, service_id, options, &listener, &response]() { // Ask the implementation to attempt to start discovery. auto result = StartDiscoveryImpl(client, service_id, options); if (!result.status.Ok()) { @@ -138,6 +141,897 @@ void BasePcpHandler::RunOnPcpHandlerThread(Runnable runnable) { serial_executor_.Execute(std::move(runnable)); } +EncryptionRunner::ResultListener BasePcpHandler::GetResultListener() { + return { + .on_success_cb = + [this](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + RunOnPcpHandlerThread([this, endpoint_id, + raw_ukey2 = ukey2.release(), auth_token, + raw_auth_token]() mutable { + OnEncryptionSuccessRunnable( + endpoint_id, std::unique_ptr(raw_ukey2), + auth_token, raw_auth_token); + }); + }, + .on_failure_cb = + [this](const string& endpoint_id, EndpointChannel* channel) { + RunOnPcpHandlerThread([this, endpoint_id, channel]() { + OnEncryptionFailureRunnable(endpoint_id, channel); + }); + }, + }; +} + +void BasePcpHandler::OnEncryptionSuccessRunnable( + const string& endpoint_id, std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + // Quick fail if we've been removed from pending connections while we were + // busy running UKEY2. + auto it = pending_connections_.find(endpoint_id); + if (it == pending_connections_.end()) { + NEARBY_LOG(INFO, + "Connection not found on UKEY negotination complete; id=%s", + endpoint_id.c_str()); + return; + } + + BasePcpHandler::PendingConnectionInfo& connection_info = it->second; + + if (!ukey2) { + // Fail early, if there is no crypto context. + ProcessPreConnectionResultFailure(connection_info.client, endpoint_id); + return; + } + + connection_info.SetCryptoContext(std::move(ukey2)); + NEARBY_LOG(INFO, "Register encrypted connection; wait for response; id=%s", + endpoint_id.c_str()); + + // Set ourselves up so that we receive all acceptance/rejection messages + handle_ = endpoint_manager_->RegisterFrameProcessor( + V1Frame::CONNECTION_RESPONSE, this); + + // Now we register our endpoint so that we can listen for both sides to + // accept. + endpoint_manager_->RegisterEndpoint( + connection_info.client, endpoint_id, + { + .remote_endpoint_name = connection_info.remote_endpoint_name, + .authentication_token = auth_token, + .raw_authentication_token = raw_auth_token, + .is_incoming_connection = connection_info.is_incoming, + }, + std::move(connection_info.channel), connection_info.listener); + + if (connection_info.result != nullptr) { + NEARBY_LOG(INFO, "Connection established; Finalising future OK"); + connection_info.result->Set({Status::kSuccess}); + connection_info.result = nullptr; + } +} + +void BasePcpHandler::OnEncryptionFailureRunnable( + const string& endpoint_id, EndpointChannel* endpoint_channel) { + auto it = pending_connections_.find(endpoint_id); + if (it == pending_connections_.end()) { + NEARBY_LOG(INFO, + "Connection not found on UKEY negotination complete; id=%s", + endpoint_id.c_str()); + return; + } + + BasePcpHandler::PendingConnectionInfo& info = it->second; + // We had a bug here, caused by a race with EncryptionRunner. We now verify + // the EndpointChannel to avoid it. In a simultaneous connection, we clean + // up one of the two EndpointChannels and then update our pendingConnections + // with the winning channel's state. Closing a channel that was in the + // middle of EncryptionRunner would trigger onEncryptionFailed, and, since + // the map had already updated with the winning EndpointChannel, we closed + // it too by accident. + if (*endpoint_channel != *info.channel) { + NEARBY_LOG( + INFO, "Not destroying channel [mismatch]: passed=%s; expected=%s", + endpoint_channel->GetName().c_str(), info.channel->GetName().c_str()); + return; + } + + ProcessPreConnectionInitiationFailure(endpoint_id, info.channel.get(), + {Status::kEndpointIoError}, + info.result.get()); + info.result.reset(); +} + +Status BasePcpHandler::RequestConnection(ClientProxy* client, + const string& endpoint_id, + const ConnectionRequestInfo& info) { + Future result; + RunOnPcpHandlerThread([this, client, &info, endpoint_id, &result]() { + absl::Time start_time = SystemClock::ElapsedRealtime(); + + // If we already have a pending connection, then we shouldn't allow any more + // outgoing connections to this endpoint. + if (pending_connections_.count(endpoint_id)) { + NEARBY_LOG(INFO, "Connection already exists: id=%s", endpoint_id.c_str()); + result.Set({Status::kAlreadyConnectedToEndpoint}); + return; + } + + // If our child class says we can't send any more outgoing connections, + // listen to them. + if (ShouldEnforceTopologyConstraints() && + !CanSendOutgoingConnection(client)) { + NEARBY_LOG(INFO, "Outgoing connection not allowed: id=%s", + endpoint_id.c_str()); + result.Set({Status::kOutOfOrderApiCall}); + return; + } + + auto endpoint = GetDiscoveredEndpoint(endpoint_id); + if (endpoint == nullptr) { + NEARBY_LOG(INFO, "Discovered endpoint not found: id=%s", + endpoint_id.c_str()); + result.Set({Status::kEndpointUnknown}); + return; + } + + auto connect_impl_result = ConnectImpl(client, endpoint); + std::unique_ptr channel = + std::move(connect_impl_result.endpoint_channel); + + if (channel == nullptr) { + NEARBY_LOG(INFO, "Endpoint channel not available: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionInitiationFailure( + endpoint_id, channel.get(), connect_impl_result.status, &result); + return; + } + + NEARBY_LOG(INFO, "Sending connection request: id=%s", endpoint_id.c_str()); + // Generate the nonce to use for this connection. + std::int32_t nonce = prng_.NextInt32(); + + // The first message we have to send, after connecting, is to tell the + // endpoint about ourselves. + Exception write_exception = WriteConnectionRequestFrame( + channel.get(), client->GenerateLocalEndpointId(), info.name, nonce, + GetConnectionMediumsByPriority()); + if (!write_exception.Ok()) { + NEARBY_LOG(INFO, "Failed to send connection request: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionInitiationFailure( + endpoint_id, channel.get(), {Status::kEndpointIoError}, &result); + return; + } + + NEARBY_LOG(INFO, "adding connection to pending set: id=%s", + endpoint_id.c_str()); + + // We've successfully connected to the device, and are now about to jump on + // to the EncryptionRunner thread to start running our encryption protocol. + // We'll mark ourselves as pending in case we get another call to + // requestConnection or OnIncomingConnection, so that we can cancel the + // connection if needed. + EndpointChannel* endpoint_channel = + pending_connections_ + .emplace(endpoint_id, + PendingConnectionInfo{ + .client = client, + .remote_endpoint_name = endpoint->endpoint_name, + .nonce = nonce, + .is_incoming = false, + .start_time = start_time, + .listener = info.listener, + .result = MakeSwapper(&result), + .channel = std::move(channel), + }) + .first->second.channel.get(); + + NEARBY_LOG(INFO, "Initiating secure connection: id=%s", + endpoint_id.c_str()); + // Next, we'll set up encryption. When it's done, our future will return and + // requestConnection() will finish. + encryption_runner_.StartClient(client, endpoint_id, endpoint_channel, + GetResultListener()); + }); + NEARBY_LOG(INFO, "Waiting for connection to complete: id=%s", + endpoint_id.c_str()); + auto status = + WaitForResult(absl::StrCat("requestConnection(", endpoint_id, ")"), + client->GetClientId(), &result); + NEARBY_LOG(INFO, "Wait is complete: id=%s; status=%d", endpoint_id.c_str(), + status.value); + return status; +} + +BasePcpHandler::DiscoveredEndpoint* BasePcpHandler::GetDiscoveredEndpoint( + const string& endpoint_id) { + auto it = discovered_endpoints_.find(endpoint_id); + if (it == discovered_endpoints_.end()) { + return nullptr; + } + return it->second.get(); +} + +void BasePcpHandler::PendingConnectionInfo::SetCryptoContext( + std::unique_ptr ukey2) { + this->ukey2 = std::move(ukey2); +} + +bool BasePcpHandler::HasOutgoingConnections(ClientProxy* client) const { + for (const auto& item : pending_connections_) { + auto& connection = item.second; + if (!connection.is_incoming) { + return true; + } + } + return client->GetNumOutgoingConnections() > 0; +} + +bool BasePcpHandler::HasIncomingConnections(ClientProxy* client) const { + for (const auto& item : pending_connections_) { + auto& connection = item.second; + if (connection.is_incoming) { + return true; + } + } + return client->GetNumIncomingConnections() > 0; +} + +bool BasePcpHandler::CanSendOutgoingConnection(ClientProxy* client) const { + return true; +} + +bool BasePcpHandler::CanReceiveIncomingConnection(ClientProxy* client) const { + return true; +} + +Exception BasePcpHandler::WriteConnectionRequestFrame( + EndpointChannel* endpoint_channel, const string& local_endpoint_id, + const string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums) { + return endpoint_channel->Write(parser::ForConnectionRequest( + local_endpoint_id, local_endpoint_name, nonce, supported_mediums)); +} + +void BasePcpHandler::ProcessPreConnectionInitiationFailure( + const string& endpoint_id, EndpointChannel* channel, Status status, + Future* result) { + if (channel != nullptr) { + channel->Close(); + } + + pending_connections_.erase(endpoint_id); + + if (result != nullptr) { + NEARBY_LOG(INFO, "Connection failed; aborting future"); + result->Set(status); + } +} + +void BasePcpHandler::ProcessPreConnectionResultFailure( + ClientProxy* client, const string& endpoint_id) { + auto item = pending_connections_.extract(endpoint_id); + endpoint_manager_->DiscardEndpoint(client, endpoint_id); + client->OnConnectionRejected(endpoint_id, {Status::kError}); +} + +bool BasePcpHandler::ShouldEnforceTopologyConstraints() const { + // Topology constraints only matter for the advertiser. + // For discoverers, we'll always enforce them. + if (advertising_options_.strategy.IsNone()) { + return true; + } + + return advertising_options_.enforce_topology_constraints; +} + +bool BasePcpHandler::AutoUpgradeBandwidth() const { + if (advertising_options_.strategy.IsNone()) { + return true; + } + + return advertising_options_.auto_upgrade_bandwidth; +} + +Status BasePcpHandler::AcceptConnection( + ClientProxy* client, const string& endpoint_id, + const PayloadListener& payload_listener) { + Future response; + RunOnPcpHandlerThread( + [this, client, endpoint_id, payload_listener, &response]() { + NEARBY_LOG(INFO, "AcceptConnection: id=%s", endpoint_id.c_str()); + if (!pending_connections_.count(endpoint_id)) { + NEARBY_LOG(INFO, "AcceptConnection: no pending connection for id=%s", + endpoint_id.c_str()); + response.Set({Status::kEndpointUnknown}); + return; + } + auto& connection_info = pending_connections_[endpoint_id]; + + // By this point in the flow, connection_info.channel has been + // nulled out because ownership of that EndpointChannel was passed on to + // EndpointChannelManager via a call to + // EndpointManager::registerEndpoint(), so we now need to get access to + // the EndpointChannel from the authoritative owner. + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (channel == nullptr) { + NEARBY_LOG( + ERROR, + "Channel destroyed before Accept; bring down connection: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionResultFailure(client, endpoint_id); + response.Set({Status::kEndpointUnknown}); + return; + } + + Exception write_exception = + channel->Write(parser::ForConnectionResponse(Status::kSuccess)); + if (!write_exception.Ok()) { + NEARBY_LOG(INFO, "AcceptConnection: failed to send response: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionResultFailure(client, endpoint_id); + response.Set({Status::kEndpointIoError}); + return; + } + + NEARBY_LOG(INFO, "AcceptConnection: accepting locally: id=%s", + endpoint_id.c_str()); + connection_info.LocalEndpointAcceptedConnection(endpoint_id, + payload_listener); + EvaluateConnectionResult(client, endpoint_id, + false /* can_close_immediately */); + response.Set({Status::kSuccess}); + }); + + return WaitForResult(absl::StrCat("acceptConnection(", endpoint_id, ")"), + client->GetClientId(), &response); +} + +Status BasePcpHandler::RejectConnection(ClientProxy* client, + const string& endpoint_id) { + Future response; + RunOnPcpHandlerThread([this, client, endpoint_id, &response]() { + NEARBY_LOG(INFO, "RejectConnection: id=%s", endpoint_id.c_str()); + if (!pending_connections_.count(endpoint_id)) { + NEARBY_LOG(INFO, "RejectConnection: no pending connection for id=%s", + endpoint_id.c_str()); + response.Set({Status::kEndpointUnknown}); + return; + } + auto& connection_info = pending_connections_[endpoint_id]; + + // By this point in the flow, connection_info->endpoint_channel_ has been + // nulled out because ownership of that EndpointChannel was passed on to + // EndpointChannelManager via a call to + // EndpointManager::registerEndpoint(), so we now need to get access to the + // EndpointChannel from the authoritative owner. + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (channel == nullptr) { + NEARBY_LOG( + ERROR, + "Channel destroyed before Reject; bring down connection: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionResultFailure(client, endpoint_id); + response.Set({Status::kEndpointUnknown}); + return; + } + + Exception write_exception = channel->Write( + parser::ForConnectionResponse(Status::kConnectionRejected)); + if (!write_exception.Ok()) { + NEARBY_LOG(INFO, "RejectConnection: failed to send response: id=%s", + endpoint_id.c_str()); + ProcessPreConnectionResultFailure(client, endpoint_id); + response.Set({Status::kEndpointIoError}); + return; + } + + NEARBY_LOG(INFO, "RejectConnection: rejecting locally: id=%s", + endpoint_id.c_str()); + connection_info.LocalEndpointRejectedConnection(endpoint_id); + EvaluateConnectionResult(client, endpoint_id, + false /* can_close_immediately */); + response.Set({Status::kSuccess}); + }); + + return WaitForResult(absl::StrCat("rejectConnection(", endpoint_id, ")"), + client->GetClientId(), &response); +} + +// proto::connections::Medium BasePcpHandler::GetBandwidthUpgradeMedium() { +// return bandwidth_upgrade_medium_.Get(); +//} + +void BasePcpHandler::OnIncomingFrame(const OfflineFrame& frame, + const string& endpoint_id, + ClientProxy* client, + proto::connections::Medium medium) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, endpoint_id, frame, &latch]() { + NEARBY_LOG(INFO, "OnConnectionResponse: id=%s", endpoint_id.c_str()); + + if (client->HasRemoteEndpointResponded(endpoint_id)) { + NEARBY_LOG(INFO, "OnConnectionResponse: already handled; id=%s", + endpoint_id.c_str()); + return; + } + + const ConnectionResponseFrame& connection_response = + frame.v1().connection_response(); + + if (connection_response.status() == Status::kSuccess) { + NEARBY_LOG(INFO, "OnConnectionResponse: remote accepted; id=%s", + endpoint_id.c_str()); + client->RemoteEndpointAcceptedConnection(endpoint_id); + } else { + NEARBY_LOG(INFO, + "OnConnectionResponse: remote rejected; id=%s; status=%d", + endpoint_id.c_str(), connection_response.status()); + client->RemoteEndpointRejectedConnection(endpoint_id); + } + + EvaluateConnectionResult(client, endpoint_id, + /* can_close_immediately= */ true); + + latch.CountDown(); + }); + WaitForLatch("OnIncomingFrame()", &latch); +} + +void BasePcpHandler::OnEndpointDisconnect(ClientProxy* client, + const string& endpoint_id, + CountDownLatch* barrier) { + RunOnPcpHandlerThread([this, client, endpoint_id, barrier]() { + auto item = pending_alarms_.find(endpoint_id); + if (item != pending_alarms_.end()) { + auto& alarm = item->second; + alarm.Cancel(); + pending_alarms_.erase(item); + } + ProcessPreConnectionResultFailure(client, endpoint_id); + barrier->CountDown(); + }); +} + +ConnectionOptions BasePcpHandler::GetConnectionOptions() const { + return advertising_options_; +} + +void BasePcpHandler::OnEndpointFound( + ClientProxy* client, + std::unique_ptr endpoint) { + // Check if we've seen this endpoint ID before. + std::string& endpoint_id = endpoint->endpoint_id; + BasePcpHandler::DiscoveredEndpoint* previously_discovered_endpoint = + GetDiscoveredEndpoint(endpoint_id); + + NEARBY_LOG(INFO, "OnEndpointFound: id='%s' [enter]", endpoint_id.c_str()); + if (previously_discovered_endpoint == nullptr) { + // 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)) + .first->second; + + NEARBY_LOG(INFO, "Adding new endpoint: id=%s", endpoint_id.c_str()); + // And, as it's the first time, report it to the client. + client->OnEndpointFound( + owned_endpoint->service_id, owned_endpoint->endpoint_id, + owned_endpoint->endpoint_name, owned_endpoint->medium); + } else if (previously_discovered_endpoint->endpoint_name != + endpoint->endpoint_name) { + // If we've already seen this endpoint before, check if there was a name + // change. If there was, report the previous endpoint as lost. + NEARBY_LOG(INFO, "Switch to new endpoint: id=%s", endpoint_id.c_str()); + + OnEndpointLost(client, *previously_discovered_endpoint); + OnEndpointFound(client, std::move(endpoint)); + } else { + // Otherwise, we need to see if the medium we discovered the endpoint over + // this time is better than the medium we originally discovered the endpoint + // over. + 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)); + } + } +} + +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); + if (discovered_endpoint == nullptr) { + NEARBY_LOG(INFO, "No previous endpoint (nothing to lose): id=%s", + endpoint.endpoint_id.c_str()); + return; + } + + // Validate that the cached endpoint has the same name as the one reported as + // onLost. If the name differs, then no-op. This likely means that the remote + // device changed their name. We reported onFound for the new name and are + // just now figuring out that we lost the old name. + if (discovered_endpoint->endpoint_name != endpoint.endpoint_name) { + NEARBY_LOG(INFO, "Previous endpoint name mismatch; passed=%s; expected=%s", + endpoint.endpoint_name.c_str(), + discovered_endpoint->endpoint_name.c_str()); + return; + } + + auto item = discovered_endpoints_.extract(endpoint.endpoint_id); + client->OnEndpointLost(endpoint.service_id, endpoint.endpoint_id); +} + +bool BasePcpHandler::IsPreferred( + const BasePcpHandler::DiscoveredEndpoint& new_endpoint, + const BasePcpHandler::DiscoveredEndpoint& old_endpoint) { + std::vector mediums = + GetConnectionMediumsByPriority(); + // As we iterate through the list of mediums, we see if we run into the new + // endpoint's medium or the old endpoint's medium first. + for (const auto& medium : mediums) { + if (medium == new_endpoint.medium) { + // The new endpoint's medium came first. It's preferred! + return true; + } + + if (medium == old_endpoint.medium) { + // The old endpoint's medium came first. Stick with the old endpoint! + return false; + } + } + NEARBY_LOG(FATAL, "Failed to determine preferred medium; bailing out"); + return false; +} + +Exception BasePcpHandler::OnIncomingConnection( + ClientProxy* client, const string& remote_device_name, + std::unique_ptr channel, + proto::connections::Medium medium) { + absl::Time start_time = SystemClock::ElapsedRealtime(); + + // Fixes an NPE in ClientProxy.OnConnectionResult. The crash happened when + // the client stopped advertising and we nulled out state, followed by an + // incoming connection where we attempted to check that state. + if (!client->IsAdvertising()) { + NEARBY_LOG(WARNING, + "Ignoring incoming connection because client 0x%" PRIX64 + " is no longer advertising.", + client->GetClientId()); + return {Exception::kIo}; + } + + // Endpoints connecting to us will always tell us about themselves first. + ExceptionOr wrapped_frame = + ReadConnectionRequestFrame(channel.get()); + + if (!wrapped_frame.ok()) { + if (wrapped_frame.exception()) { + NEARBY_LOG( + ERROR, + "Failed to parse incoming connection request; client_id=0x%" PRIX64 + "; device=%s", + client->GetClientId(), remote_device_name.c_str()); + ProcessPreConnectionInitiationFailure("", channel.get(), {Status::kError}, + nullptr); + return {Exception::kSuccess}; + } + return wrapped_frame.GetException(); + } + + OfflineFrame& frame = wrapped_frame.result(); + const ConnectionRequestFrame& connection_request = + frame.v1().connection_request(); + NEARBY_LOG(ERROR, + "Incoming connection request; client_id=0x%" PRIX64 + "; device=%s; id=%s", + client->GetClientId(), remote_device_name.c_str(), + connection_request.endpoint_id().c_str()); + if (client->IsConnectedToEndpoint(connection_request.endpoint_id())) { + return {Exception::kIo}; + } + + // If we've already sent out a connection request to this endpoint, then this + // is where we need to decide which connection to break. + if (BreakTie(client, connection_request.endpoint_id(), + connection_request.nonce(), channel.get())) { + return {Exception::kSuccess}; + } + + // If our child class says we can't accept any more incoming connections, + // listen to them. + if (ShouldEnforceTopologyConstraints() && + !CanReceiveIncomingConnection(client)) { + return {Exception::kIo}; + } + + // The ConnectionRequest frame has two fields that both contain the + // EndpointInfo. The legacy field stores it as a string while the newer field + // stores it as a byte array. We'll attempt to grab from the newer field, but + // will accept the older string if it's all that exists. + const std::string endpoint_name = connection_request.has_endpoint_info() + ? connection_request.endpoint_info() + : connection_request.endpoint_name(); + + // We've successfully connected to the device, and are now about to jump on to + // the EncryptionRunner thread to start running our encryption protocol. We'll + // mark ourselves as pending in case we get another call to requestConnection + // or OnIncomingConnection, so that we can cancel the connection if needed. + auto* owned_channel = + pending_connections_ + .emplace(connection_request.endpoint_id(), + PendingConnectionInfo{ + .client = client, + .remote_endpoint_name = endpoint_name, + .nonce = connection_request.nonce(), + .is_incoming = true, + .start_time = start_time, + .listener = advertising_listener_, + .supported_mediums = + parser::ConnectionRequestMediumsToMediums( + connection_request), + .channel = std::move(channel), + }) + .first->second.channel.get(); + + // Next, we'll set up encryption. + encryption_runner_.StartServer(client, connection_request.endpoint_id(), + owned_channel, GetResultListener()); + return {Exception::kSuccess}; +} + +bool BasePcpHandler::BreakTie(ClientProxy* client, const string& endpoint_id, + std::int32_t incoming_nonce, + EndpointChannel* endpoint_channel) { + auto it = pending_connections_.find(endpoint_id); + if (it != pending_connections_.end()) { + BasePcpHandler::PendingConnectionInfo& info = it->second; + + NEARBY_LOG(INFO, "BreakTie: id=%s", endpoint_id.c_str()); + // Break the lowest connection. In the (extremely) rare case of a tie, break + // both. + if (info.nonce > incoming_nonce) { + // Our connection won! Clean up their connection. + endpoint_channel->Close(); + + NEARBY_LOG(INFO, "BreakTie: We won; id=%s", endpoint_id.c_str()); + return true; + } else if (info.nonce < incoming_nonce) { + // Aw, we lost. Clean up our connection, and then we'll let their + // connection continue on. + ProcessTieBreakLoss(client, endpoint_id, &info); + + NEARBY_LOG(INFO, "BreakTie: We lost; id=%s", endpoint_id.c_str()); + } else { + // Oh. Huh. We both lost. Well, that's awkward. We'll clean up both and + // just force the devices to retry. + endpoint_channel->Close(); + + ProcessTieBreakLoss(client, endpoint_id, &info); + + NEARBY_LOG(INFO, "BreakTie: Both lost; id=%s", endpoint_id.c_str()); + return true; + } + } + + return false; +} + +void BasePcpHandler::ProcessTieBreakLoss( + ClientProxy* client, const string& endpoint_id, + BasePcpHandler::PendingConnectionInfo* info) { + ProcessPreConnectionInitiationFailure(endpoint_id, info->channel.get(), + {Status::kEndpointIoError}, + info->result.get()); + info->result = nullptr; + ProcessPreConnectionResultFailure(client, endpoint_id); +} + +void BasePcpHandler::InitiateBandwidthUpgrade( + ClientProxy* client, const string& endpoint_id, + const std::vector& supported_mediums) { + // When we successfully connect to a remote endpoint and a bandwidth upgrade + // medium has not yet been decided, we'll pick the highest bandwidth medium + // supported by both us and the remote endpoint. Once we pick a medium, all + // future connections will use it too. eg. If we chose Wifi LAN, we'll attempt + // to upgrade the 2nd, 3rd, etc remote endpoints with Wifi LAN even if they're + // on a different network (or had a better medium). This is a quick and easy + // way to prevent mediums, like Wifi Hotspot, from interfering with active + // connections (although it's suboptimal for bandwidth throughput). When all + // endpoints disconnect, we reset the bandwidth upgrade medium. + if (bandwidth_upgrade_medium_.Get() == + proto::connections::Medium::UNKNOWN_MEDIUM) { + bandwidth_upgrade_medium_.Set(ChooseBestUpgradeMedium(supported_mediums)); + } + + if (AutoUpgradeBandwidth() && (bandwidth_upgrade_medium_.Get() != + proto::connections::Medium::UNKNOWN_MEDIUM)) { + // TODO(apolyudov): Bring bandwidth upgrade back, when it is ready. + // bandwidth_upgrade_->InitiateBandwidthUpgradeForEndpoint( + // client, endpoint_id, bandwidth_upgrade_medium_.Get()); + } +} + +proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( + const std::vector& their_supported_mediums) { + // If the remote side did not report their supported mediums, choose an + // appropriate default. + std::vector their_mediums = + their_supported_mediums; + if (their_supported_mediums.empty()) { + their_mediums.push_back(GetDefaultUpgradeMedium()); + } + + // Otherwise, pick the best medium we support. + std::vector my_mediums = + GetConnectionMediumsByPriority(); + for (const auto& my_medium : my_mediums) { + for (const auto& their_medium : their_mediums) { + if (my_medium == their_medium) { + return my_medium; + } + } + } + + return proto::connections::Medium::UNKNOWN_MEDIUM; +} + +void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, + const string& endpoint_id, + bool can_close_immediately) { + // Short-circuit immediately if we're not in an actionable state yet. We will + // be called again once the other side has made their decision. + if (!client->IsConnectionAccepted(endpoint_id) && + !client->IsConnectionRejected(endpoint_id)) { + if (!client->HasLocalEndpointResponded(endpoint_id)) { + NEARBY_LOG(INFO, "ConnectionResult: local client did not respond; id=%s", + endpoint_id.c_str()); + } else if (!client->HasRemoteEndpointResponded(endpoint_id)) { + NEARBY_LOG(INFO, "ConnectionResult: remote client did not respond; id=%s", + endpoint_id.c_str()); + } + return; + } + + // Clean up the endpoint channel from our list of 'pending' connections. It's + // no longer pending. + auto it = pending_connections_.find(endpoint_id); + if (it == pending_connections_.end()) { + NEARBY_LOG(INFO, "No pending connection to evaluate; id=%s", + endpoint_id.c_str()); + return; + } + + auto pair = pending_connections_.extract(it); + BasePcpHandler::PendingConnectionInfo& connection_info = pair.mapped(); + bool is_connection_accepted = client->IsConnectionAccepted(endpoint_id); + + Status response_code; + if (is_connection_accepted) { + NEARBY_LOG(INFO, "Pending connection accepted; id=%s", endpoint_id.c_str()); + response_code = {Status::kSuccess}; + + // Both sides have accepted, so we can now start talking over encrypted + // channels + // Now, after both parties accepted connection (presumably after verifying & + // matching security tokens), we are allowed to extract the shared key. + auto ukey2 = std::move(connection_info.ukey2); + 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. + // If it did, it's a UKEY2 protocol bug. + + channel_manager_->EncryptChannelForEndpoint(endpoint_id, + std::move(context)); + } else { + NEARBY_LOG(INFO, "Pending connection rejected; id=%s", endpoint_id.c_str()); + response_code = {Status::kConnectionRejected}; + } + + // Invoke the client callback to let it know of the connection result. + if (response_code.Ok()) { + client->OnConnectionAccepted(endpoint_id); + } else { + client->OnConnectionRejected(endpoint_id, response_code); + } + + // If the connection failed, clean everything up and short circuit. + if (!is_connection_accepted) { + // Clean up the channel in EndpointManager if it's no longer required. + if (can_close_immediately) { + endpoint_manager_->DiscardEndpoint(client, endpoint_id); + } else { + pending_alarms_.emplace( + endpoint_id, + CancelableAlarm( + "BasePcpHandler.evaluateConnectionResult() delayed close", + [this, client, endpoint_id]() { + endpoint_manager_->DiscardEndpoint(client, endpoint_id); + }, + kRejectedConnectionCloseDelay, &alarm_executor_)); + } + + return; + } + + // Kick off the bandwidth upgrade for incoming connections. + if (connection_info.is_incoming) { + InitiateBandwidthUpgrade(client, endpoint_id, + connection_info.supported_mediums); + } +} + +ExceptionOr BasePcpHandler::ReadConnectionRequestFrame( + EndpointChannel* endpoint_channel) { + if (endpoint_channel == nullptr) { + return ExceptionOr(Exception::kIo); + } + + // To avoid a device connecting but never sending their introductory frame, we + // time out the connection after a certain amount of time. + CancelableAlarm timeout_alarm( + absl::StrCat("PcpHandler(", this->GetStrategy().GetName(), + ")::ReadConnectionRequestFrame"), + [endpoint_channel]() { endpoint_channel->Close(); }, + kConnectionRequestReadTimeout, &alarm_executor_); + // Do a blocking read to try and find the ConnectionRequestFrame + ExceptionOr wrapped_bytes = endpoint_channel->Read(); + timeout_alarm.Cancel(); + + if (!wrapped_bytes.ok()) { + return ExceptionOr(wrapped_bytes.exception()); + } + + ByteArray bytes = std::move(wrapped_bytes.result()); + ExceptionOr wrapped_frame = parser::FromBytes(bytes); + if (wrapped_frame.GetException().Raised(Exception::kInvalidProtocolBuffer)) { + return ExceptionOr(Exception::kIo); + } + + OfflineFrame& frame = wrapped_frame.result(); + if (V1Frame::CONNECTION_REQUEST != parser::GetFrameType(frame)) { + return ExceptionOr(Exception::kIo); + } + + return wrapped_frame; +} + +///////////////////// BasePcpHandler::PendingConnectionInfo /////////////////// + +BasePcpHandler::PendingConnectionInfo::~PendingConnectionInfo() { + if (result != nullptr) { + NEARBY_LOG(INFO, "Future was not set; destroying info"); + result->Set({Status::kError}); + } + + if (channel != nullptr) { + channel->Close(proto::connections::DisconnectionReason::SHUTDOWN); + } + + // Destroy crypto context now; for some reason, crypto context destructor + // segfaults if it is not destroyed here. + this->ukey2.reset(); +} + +void BasePcpHandler::PendingConnectionInfo::LocalEndpointAcceptedConnection( + const string& endpoint_id, const PayloadListener& payload_listener) { + client->LocalEndpointAcceptedConnection(endpoint_id, payload_listener); +} + +void BasePcpHandler::PendingConnectionInfo::LocalEndpointRejectedConnection( + const string& endpoint_id) { + client->LocalEndpointRejectedConnection(endpoint_id); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index e4df32f3..1d9dd32b 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -81,9 +81,8 @@ class BasePcpHandler : public PcpHandler, BasePcpHandler(BasePcpHandler&&) = delete; BasePcpHandler& operator=(BasePcpHandler&&) = delete; - // We have been asked by the client to start advertising. Once we successfully - // start advertising, we'll change the ClientProxy's state. - // ConnectionListener (info.listener) will be notified in case of any event. + // Starts advertising. Once successfully started, changes ClientProxy's state. + // Notifies ConnectionListener (info.listener) in case of any event. // See // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 Status StartAdvertising(ClientProxy* client_proxy, @@ -91,58 +90,52 @@ class BasePcpHandler : public PcpHandler, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; - // If Advertising is active, stop it, and change CLientProxy state, - // otherwise do nothing. + // Stops Advertising is active, and changes CLientProxy state, + // otherwise does nothing. void StopAdvertising(ClientProxy* client_proxy) override; - // Start discovery of endpoints that may be advertising. - // Update ClientProxy state once discovery started. + // Starts discovery of endpoints that may be advertising. + // Updates ClientProxy state once discovery started. // DiscoveryListener will get called in case of any event. Status StartDiscovery(ClientProxy* client_proxy, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; - // If Discovery is active, stop it, and change CLientProxy state, - // otherwise do nothing. + // Stops Discovery if it is active, and changes CLientProxy state, + // otherwise does nothing. void StopDiscovery(ClientProxy* client_proxy) override; - // If remote endpoint has been successfully discovered, request it to form a - // connection, update state on ClientProxy. + // Requests a newly discoveered remote endpoint it to form a connection. + // Updates state on ClientProxy. Status RequestConnection(ClientProxy* client_proxy, const std::string& endpoint_id, - const ConnectionRequestInfo& info) override { - return Status{Status::kError}; - } + const ConnectionRequestInfo& info) override; - // Either party may call this to accept connection on their part. + // Called by either party to accept connection on their part. // Until both parties call it, connection will not reach a data phase. - // Update state in ClientProxy. + // Updates state in ClientProxy. Status AcceptConnection(ClientProxy* client_proxy, const std::string& endpoint_id, - const PayloadListener& payload_listener) override { - return Status{Status::kError}; - } + const PayloadListener& payload_listener) override; - // Either party may call this to accept connection on their part. + // Called by either party to reject connection on their part. // If either party does call it, connection will terminate. - // Update state in ClientProxy. + // Updates state in ClientProxy. Status RejectConnection(ClientProxy* client_proxy, - const std::string& endpoint_id) override { - return Status{Status::kError}; - } + const std::string& endpoint_id) override; // @EndpointManagerReaderThread void OnIncomingFrame(const OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, - proto::connections::Medium medium) override {} + proto::connections::Medium medium) override; // Called when an endpoint disconnects while we're waiting for both sides to // approve/reject the connection. // @EndpointManagerThread void OnEndpointDisconnect(ClientProxy* client_proxy, const std::string& endpoint_id, - CountDownLatch* barrier) override {} + CountDownLatch* barrier) override; protected: // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). @@ -156,14 +149,11 @@ 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) - class DiscoveredEndpoint { - public: - virtual ~DiscoveredEndpoint() = default; - - virtual std::string GetEndpointId() const = 0; - virtual std::string GetEndpointName() const = 0; - virtual std::string GetServiceId() const = 0; - virtual proto::connections::Medium GetMedium() const = 0; + struct DiscoveredEndpoint { + std::string endpoint_id; + std::string endpoint_name; + std::string service_id; + proto::connections::Medium medium; }; struct ConnectImplResult { @@ -183,13 +173,19 @@ class BasePcpHandler : public PcpHandler, // @PcpHandlerThread void OnEndpointLost(ClientProxy* client_proxy, - const DiscoveredEndpoint* endpoint); + const DiscoveredEndpoint& endpoint); Exception OnIncomingConnection( ClientProxy* client_proxy, const std::string& remote_device_name, std::unique_ptr endpoint_channel, proto::connections::Medium medium); // throws Exception::IO + virtual bool HasOutgoingConnections(ClientProxy* client_proxy) const; + virtual bool HasIncomingConnections(ClientProxy* client_proxy) const; + + virtual bool CanSendOutgoingConnection(ClientProxy* client_proxy) const; + virtual bool CanReceiveIncomingConnection(ClientProxy* client_proxy) const; + // @PcpHandlerThread virtual StartOperationResult StartAdvertisingImpl( ClientProxy* client_proxy, const std::string& service_id, @@ -218,6 +214,74 @@ class BasePcpHandler : public PcpHandler, EndpointChannelManager* channel_manager_; private: + struct PendingConnectionInfo { + PendingConnectionInfo() = default; + PendingConnectionInfo(PendingConnectionInfo&& other) = default; + PendingConnectionInfo& operator=(PendingConnectionInfo&&) = default; + ~PendingConnectionInfo(); + + // Passes crypto context that we acquired in DH session for temporary + // ownership here. + void SetCryptoContext(std::unique_ptr ukey2); + + // Pass Accept notification to client. + void LocalEndpointAcceptedConnection( + const std::string& endpoint_id, + const PayloadListener& payload_listener); + + // Pass Reject notification to client. + void LocalEndpointRejectedConnection(const std::string& endpoint_id); + + // Client state tracker to report events to. Never changes. Always valid. + ClientProxy* client = nullptr; + // Peer endpoint name, or empty, if not discovered yet. May change. + std::string remote_endpoint_name; + std::int32_t nonce = 0; + bool is_incoming = false; + absl::Time start_time {absl::InfinitePast()}; + // Client callbacks. Always valid. + ConnectionListener listener; + + // Only set for outgoing connections. If set, we must call + // result->Set() when connection is established, or rejected. + Swapper> result = nullptr; + + // Only (possibly) vector for incoming connections. + std::vector supported_mediums; + + // Keep track of a channel before we pass it to EndpointChannelManager. + std::unique_ptr channel; + + // Crypto context; initially empty; established first thing after channel + // creation by running UKey2 session. While it is in progress, we keep track + // of channel ourselves. Once it is done, we pass channel over to + // EndpointChannelManager. We keep crypto context until connection is + // accepted. Crypto context is passed over to channel_manager_ before + // switching to connected state, where Payload may be exchanged. + std::unique_ptr ukey2; + }; + + // @EncryptionRunnerThread + // Called internally when DH session has negotiated a key successfully. + void OnEncryptionSuccessImpl(const std::string& endpoint_id, + std::unique_ptr ukey2, + const std::string& auth_token, + const ByteArray& raw_auth_token); + + // @EncryptionRunnerThread + // Called internally when DH session was not able to negotiate a key. + void OnEncryptionFailureImpl(const std::string& endpoint_id, + EndpointChannel* channel); + + EncryptionRunner::ResultListener GetResultListener(); + + void OnEncryptionSuccessRunnable( + const std::string& endpoint_id, + std::unique_ptr ukey2, + const std::string& auth_token, const ByteArray& raw_auth_token); + void OnEncryptionFailureRunnable(const std::string& endpoint_id, + EndpointChannel* endpoint_channel); + static Exception WriteConnectionRequestFrame( EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, const std::string& local_endpoint_name, std::int32_t nonce, @@ -236,6 +300,25 @@ class BasePcpHandler : public PcpHandler, bool IsPreferred(const BasePcpHandler::DiscoveredEndpoint& new_endpoint, const BasePcpHandler::DiscoveredEndpoint& old_endpoint); + // Returns true, if connection party should respect the specified topology. + bool ShouldEnforceTopologyConstraints() const; + + // Returns true, if connection party should attempt to upgrade itself to + // use a higher bandwidth medium, if it is available. + bool AutoUpgradeBandwidth() const; + + // Returns true if the incoming connection should be killed. This only + // happens when an incoming connection arrives while we have an outgoing + // connection to the same endpoint and we need to stop one connection. + bool BreakTie(ClientProxy* client, const std::string& endpoint_id, + std::int32_t incoming_nonce, EndpointChannel* channel); + // We're not sure how far our outgoing connection has gotten. We may (or may + // not) have called ClientProxy::OnConnectionInitiated. Therefore, we'll + // call both preInit and preResult failures. + void ProcessTieBreakLoss(ClientProxy* client_proxy, + const std::string& endpoint_id, + PendingConnectionInfo* info); + // Called when an incoming connection has been accepted by both sides. // // @param client_proxy The client @@ -285,6 +368,12 @@ class BasePcpHandler : public PcpHandler, ScheduledExecutor alarm_executor_; SingleThreadExecutor serial_executor_; + // A map of endpoint id -> PendingConnectionInfo. Entries in this map imply + // that there is an active connection to the endpoint and we're waiting for + // both sides to accept before allowing payloads through. Once the fate of + // the connection is decided (either accepted or rejected), it should be + // removed from this map. + absl::flat_hash_map pending_connections_; // A map of endpoint id -> DiscoveredEndpoint. absl::flat_hash_map> discovered_endpoints_; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 756ea76b..8da33159 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -64,8 +64,18 @@ class MockPcpHandler : public BasePcpHandler { using BasePcpHandler::DiscoveredEndpoint; using BasePcpHandler::StartOperationResult; - MOCK_METHOD(Strategy, GetStrategy, (), (override)); - MOCK_METHOD(Pcp, GetPcp, (), (override)); + MOCK_METHOD(Strategy, GetStrategy, (), (const override)); + MOCK_METHOD(Pcp, GetPcp, (), (const override)); + + MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), + (const, override)); + MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), + (const, override)); + + MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), + (const, override)); + MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), + (const, override)); MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, (ClientProxy * client, const string& service_id, @@ -91,18 +101,12 @@ class MockPcpHandler : public BasePcpHandler { std::unique_ptr endpoint) { BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); } - void OnEndpointLost(ClientProxy* client, DiscoveredEndpoint* endpoint) { + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { BasePcpHandler::OnEndpointLost(client, endpoint); } }; -class MockDiscoveredEndpoint final : public MockPcpHandler::DiscoveredEndpoint { - public: - MOCK_METHOD(std::string, GetEndpointId, (), (const override)); - MOCK_METHOD(std::string, GetEndpointName, (), (const override)); - MOCK_METHOD(std::string, GetServiceId, (), (const override)); - MOCK_METHOD(Medium, GetMedium, (), (const override)); -}; +using MockDiscoveredEndpoint = MockPcpHandler::DiscoveredEndpoint; class BasePcpHandlerTest : public ::testing::Test { protected: @@ -193,8 +197,7 @@ class BasePcpHandlerTest : public ::testing::Test { EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); EXPECT_CALL(*channel_a, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_a, IsPaused) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); EXPECT_CALL(*channel_b, Read()) .WillRepeatedly(Invoke( [channel = channel_b.get()]() { return channel->DoRead(); })); @@ -206,11 +209,56 @@ class BasePcpHandlerTest : public ::testing::Test { EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); EXPECT_CALL(*channel_b, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_b, IsPaused) - .WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); return std::make_pair(std::move(channel_a), std::move(channel_b)); } + void RequestConnection(const std::string& endpoint_id, + std::unique_ptr channel_a, + MockEndpointChannel* channel_b, ClientProxy* client, + MockPcpHandler* pcp_handler) { + ConnectionRequestInfo info{ + .name = "ABCD", + .listener = connection_listener_, + }; + EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); + EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*pcp_handler, GetStrategy) + .WillRepeatedly(Return(Strategy::kP2pCluster)); + EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); + EXPECT_CALL(*pcp_handler, ConnectImpl) + .WillOnce( + Invoke([&channel_a](ClientProxy* client, + MockPcpHandler::DiscoveredEndpoint* endpoint) { + return MockPcpHandler::ConnectImplResult{ + .medium = Medium::BLE, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel_a), + }; + })); + // 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, + })); + auto other_client = std::make_unique(); + + // Run peer crypto in advance, if channel_b is provided. + // Otherwise stay in not-encrypted state. + if (channel_b != nullptr) { + encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, + {}); + } + EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Stopping Encryption Runner"); + } + Pipe pipe_a_; Pipe pipe_b_; MockConnectionListener mock_connection_listener_; @@ -281,6 +329,104 @@ TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { EXPECT_FALSE(client->IsDiscovering()); } +TEST_F(BasePcpHandlerTest, RequestConnectionChangesState) { + std::string endpoint_id{"1234"}; + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + 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.get(), pcp_handler.get()); + NEARBY_LOG(INFO, "RequestConnection complete"); + channel_b->Close(); +} + +TEST_F(BasePcpHandlerTest, AcceptConnectionChangesState) { + std::string endpoint_id{"1234"}; + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + 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.get(), pcp_handler.get()); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler->AcceptConnection(client.get(), endpoint_id, {}), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); +} + +TEST_F(BasePcpHandlerTest, RejectConnectionChangesState) { + std::string endpoint_id{"1234"}; + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto& channel_b = channel_pair.second; + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); + RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), + client.get(), pcp_handler.get()); + NEARBY_LOG(INFO, "Attempting to reject connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler->RejectConnection(client.get(), endpoint_id), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); +} + +TEST_F(BasePcpHandlerTest, OnIncomingFrameChangesState) { + std::string endpoint_id{"1234"}; + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + 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.get(), pcp_handler.get()); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); + EXPECT_EQ(pcp_handler->AcceptConnection(client.get(), endpoint_id, {}), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); + auto frame = + parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); + pcp_handler->OnIncomingFrame(frame.result(), endpoint_id, client.get(), + Medium::BLE); + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); +} + +TEST_F(BasePcpHandlerTest, OnEndpointDisconnectChangesState) { + std::string endpoint_id{"1234"}; + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto& channel_b = channel_pair.second; + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); + RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), + client.get(), pcp_handler.get()); + NEARBY_LOG(INFO, "Simulating disconnect event: id=%s", endpoint_id.c_str()); + CountDownLatch latch(1); + pcp_handler->OnEndpointDisconnect(client.get(), endpoint_id, &latch); + channel_b->Close(); + EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); +} + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h index 2a86082e..885261bc 100644 --- a/cpp/core_v2/internal/ble_advertisement.h +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -47,24 +47,21 @@ class BleAdvertisement { const std::string& endpoint_name, const std::string& bluetooth_mac_address); explicit BleAdvertisement(const ByteArray& ble_advertisement_bytes); - ~BleAdvertisement() = default; - BleAdvertisement(const BleAdvertisement&) = default; BleAdvertisement& operator=(const BleAdvertisement&) = default; BleAdvertisement(BleAdvertisement&&) = default; BleAdvertisement& operator=(BleAdvertisement&&) = default; + ~BleAdvertisement() = default; explicit operator ByteArray() const; - inline bool IsValid() const { return !endpoint_id_.empty(); } - inline Version GetVersion() const { return version_; } - inline Pcp GetPcp() const { return pcp_; } - inline ByteArray GetServiceIdHash() const{ return service_id_hash_; } - inline std::string GetEndpointId() const { return endpoint_id_; } - inline std::string GetEndpointName() const { return endpoint_name_; } - inline std::string GetBluetoothMacAddress() const { - return bluetooth_mac_address_; - } + bool IsValid() const { return !endpoint_id_.empty(); } + Version GetVersion() const { return version_; } + Pcp GetPcp() const { return pcp_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + std::string GetEndpointId() const { return endpoint_id_; } + std::string GetEndpointName() const { return endpoint_name_; } + std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; } private: std::uint32_t ComputeEndpointNameLength( diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc index 9ff3ffea..d2fd5228 100644 --- a/cpp/core_v2/internal/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -9,21 +9,19 @@ namespace { const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; const Pcp kPcp = Pcp::kP2pCluster; -const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +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"; TEST(BleAdvertisementTest, ConstructionWorks) { - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); - auto is_valid = ble_advertisement.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); @@ -35,14 +33,12 @@ TEST(BleAdvertisementTest, ConstructionWorks) { TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { std::string empty_endpoint_name; - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - empty_endpoint_name, kBluetoothMacAddress); - auto is_valid = ble_advertisement.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, empty_endpoint_name, + kBluetoothMacAddress}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); @@ -52,16 +48,14 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { } TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { - std::string emoji_endpoint_name("\u0001F450 \u0001F450"); + std::string emoji_endpoint_name{"\u0001F450 \u0001F450"}; - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - emoji_endpoint_name, kBluetoothMacAddress); - auto is_valid = ble_advertisement.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, emoji_endpoint_name, + kBluetoothMacAddress}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); @@ -74,70 +68,56 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, 'x'); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - long_endpoint_name, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, long_endpoint_name, + kBluetoothMacAddress}; - auto is_valid = ble_advertisement.IsValid(); - - EXPECT_FALSE(is_valid); + EXPECT_FALSE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(bad_version, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{bad_version, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; - auto is_valid = ble_advertisement.IsValid(); - - EXPECT_FALSE(is_valid); + EXPECT_FALSE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, bad_pcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, bad_pcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; - auto is_valid = ble_advertisement.IsValid(); - - EXPECT_FALSE(is_valid); + EXPECT_FALSE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { std::string empty_bluetooth_mac_address = ""; - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, empty_bluetooth_mac_address); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + empty_bluetooth_mac_address}; - auto is_valid = ble_advertisement.IsValid(); - - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { std::string bad_bluetooth_mac_address = "022:00"; - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, bad_bluetooth_mac_address); - auto is_valid = ble_advertisement.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + bad_bluetooth_mac_address}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); @@ -148,17 +128,15 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // Serialize good data into a good Ble Advertisement. - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto org_ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement org_ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; auto ble_advertisement_bytes = ByteArray(org_ble_advertisement); - auto ble_advertisement = BleAdvertisement(ble_advertisement_bytes); - auto is_valid = ble_advertisement.IsValid(); + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); @@ -171,11 +149,10 @@ TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // in the future. TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { // Serialize good data into a good Ble Advertisement. - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Add bytes to the end of the valid Ble advertisement. @@ -187,10 +164,9 @@ TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { ble_advertisement_bytes.data(), ble_advertisement_bytes.size()); - auto long_ble_advertisement = BleAdvertisement(long_ble_advertisement_bytes); - auto is_valid = long_ble_advertisement.IsValid(); + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(long_ble_advertisement.IsValid()); EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); @@ -201,55 +177,47 @@ TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { } TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { - auto ble_advertisement = BleAdvertisement(ByteArray()); - auto is_valid = ble_advertisement.IsValid(); + BleAdvertisement ble_advertisement{ByteArray{}}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { // Serialize good data into a good Ble Advertisement. - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Shorten the valid Ble Advertisement. - auto short_ble_advertisement_bytes( - ByteArray(ble_advertisement_bytes.data(), - BleAdvertisement::kMinAdvertisementLength - 1)); + ByteArray short_ble_advertisement_bytes{ + ble_advertisement_bytes.data(), + BleAdvertisement::kMinAdvertisementLength - 1}; - auto short_ble_advertisement = - BleAdvertisement(short_ble_advertisement_bytes); - auto is_valid = short_ble_advertisement.IsValid(); + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(short_ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionFromByesWithWrongEndpointNameLengthFails) { // Serialize good data into a good Ble Advertisement. - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto ble_advertisement = - BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, - kEndpointName, kBluetoothMacAddress); + ByteArray service_id_hash{kServiceIDHashBytes}; + BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, + kEndPointID, kEndpointName, + kBluetoothMacAddress}; auto ble_advertisement_bytes = ByteArray(ble_advertisement); // Corrupt the EndpointNameLength bits. - std::string corrupt_ble_advertisement_string(ble_advertisement_bytes.data(), - ble_advertisement_bytes.size()); + auto corrupt_ble_advertisement_string = std::string(ble_advertisement_bytes); corrupt_ble_advertisement_string[8] ^= 0x0FF; auto corrupt_ble_advertisement_bytes = ByteArray(corrupt_ble_advertisement_string); - auto corrupt_ble_advertisement = - BleAdvertisement(corrupt_ble_advertisement_bytes); - auto is_valid = corrupt_ble_advertisement.IsValid(); + BleAdvertisement corrupt_ble_advertisement{corrupt_ble_advertisement_bytes}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(corrupt_ble_advertisement.IsValid()); } } // namespace diff --git a/cpp/core_v2/internal/bluetooth_device_name.cc b/cpp/core_v2/internal/bluetooth_device_name.cc new file mode 100644 index 00000000..857c9cf4 --- /dev/null +++ b/cpp/core_v2/internal/bluetooth_device_name.cc @@ -0,0 +1,187 @@ +#include "core_v2/internal/bluetooth_device_name.h" + +#include + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.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, + absl::string_view endpoint_name) { + if (version != Version::kV1 || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + service_id_hash.size() != kServiceIdHashLength) { + return; + } + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + endpoint_id_ = endpoint_id; + service_id_hash_ = service_id_hash; + endpoint_name_ = endpoint_name; +} + +BluetoothDeviceName::BluetoothDeviceName( + absl::string_view bluetooth_device_name_string) { + ByteArray bluetooth_device_name_bytes = + Base64Utils::Decode(bluetooth_device_name_string); + + if (bluetooth_device_name_bytes.Empty()) { + NEARBY_LOG( + INFO, + "Cannot deserialize BluetoothDeviceName: failed Base64 decoding of %s", + std::string(bluetooth_device_name_string).c_str()); + return; + } + + if (bluetooth_device_name_bytes.size() > kMaxBluetoothDeviceNameLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BluetoothDeviceName: expecting max %d raw " + "bytes, got %" PRIu64, + kMaxBluetoothDeviceNameLength, + bluetooth_device_name_bytes.size()); + return; + } + + if (bluetooth_device_name_bytes.size() < kMinBluetoothDeviceNameLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BluetoothDeviceName: expecting min %d raw " + "bytes, got %" PRIu64, + kMinBluetoothDeviceNameLength, + bluetooth_device_name_bytes.size()); + return; + } + + // 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; + } +} + +BluetoothDeviceName::operator std::string() const { + if (!IsValid()) { + return ""; + } + + std::string usable_endpoint_name(endpoint_name_); + if (endpoint_name_.size() > kMaxEndpointNameLength) { + NEARBY_LOG(INFO, + "While serializing Advertisement, truncating Endpoint Name %s " + "(%lu bytes) down to %d bytes", + endpoint_name_.c_str(), endpoint_name_.size(), + kMaxEndpointNameLength); + 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); + + return Base64Utils::Encode(ByteArray{std::move(out)}); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/bluetooth_device_name.h b/cpp/core_v2/internal/bluetooth_device_name.h new file mode 100644 index 00000000..b92d433a --- /dev/null +++ b/cpp/core_v2/internal/bluetooth_device_name.h @@ -0,0 +1,73 @@ +#ifndef CORE_V2_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ +#define CORE_V2_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ + +#include + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Bluetooth device name used in Advertising + +// Discovery. +// +//

See go/nearby-offline-data-interchange-formats for the specification. +class BluetoothDeviceName { + public: + // Versions of the BluetoothDeviceName. + enum class Version { + kUndefined = 0, + kV1 = 1, + // Version is only allocated 3 bits in the BluetoothDeviceName, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + + BluetoothDeviceName() = default; + BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name); + explicit BluetoothDeviceName(absl::string_view bluetooth_device_name_string); + BluetoothDeviceName(const BluetoothDeviceName&) = default; + BluetoothDeviceName& operator=(const BluetoothDeviceName&) = default; + BluetoothDeviceName(BluetoothDeviceName&&) = default; + BluetoothDeviceName& operator=(BluetoothDeviceName&&) = default; + ~BluetoothDeviceName() = default; + + explicit operator std::string() const; + + bool IsValid() const { return !endpoint_id_.empty(); } + Version GetVersion() const { return version_; } + Pcp GetPcp() const { return pcp_; } + std::string GetEndpointId() const { return endpoint_id_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + std::string GetEndpointName() const { return endpoint_name_; } + + private: + static constexpr int kMaxBluetoothDeviceNameLength = 147; + static constexpr int kEndpointIdLength = 4; + static constexpr int kReservedLength = 7; + static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kMinBluetoothDeviceNameLength = + kMaxBluetoothDeviceNameLength - kMaxEndpointNameLength; + + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kEndpointNameLengthBitmask = 0x0FF; + + Version version_{Version::kUndefined}; + Pcp pcp_{Pcp::kUnknown}; + std::string endpoint_id_; + ByteArray service_id_hash_; + std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLUETOOTH_DEVICE_NAME_H_ diff --git a/cpp/core_v2/internal/bluetooth_device_name_test.cc b/cpp/core_v2/internal/bluetooth_device_name_test.cc new file mode 100644 index 00000000..69196b46 --- /dev/null +++ b/cpp/core_v2/internal/bluetooth_device_name_test.cc @@ -0,0 +1,149 @@ +#include "core_v2/internal/bluetooth_device_name.h" + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +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!"; + +TEST(BluetoothDeviceNameTest, ConstructionWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, + service_id_hash, kEndPointName}; + + EXPECT_TRUE(bluetooth_device_name.IsValid()); + EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); + EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp()); + EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); + EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); + EXPECT_EQ(kEndPointName, bluetooth_device_name.GetEndpointName()); +} + +TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{ + kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_name}; + + EXPECT_TRUE(bluetooth_device_name.IsValid()); + EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); + EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp()); + EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); + EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); + EXPECT_EQ(empty_endpoint_name, bluetooth_device_name.GetEndpointName()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{bad_version, kPcp, kEndPointID, + service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) { + auto bad_pcp = static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{kVersion, bad_pcp, kEndPointID, + service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, short_endpoint_id, + service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, long_endpoint_id, + service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = "\x0a\x0b"; + + ByteArray short_service_id_hash{short_service_id_hash_bytes}; + BluetoothDeviceName bluetooth_device_name{ + kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; + + ByteArray long_service_id_hash{long_service_id_hash_bytes}; + BluetoothDeviceName bluetooth_device_name{ + kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortStringLength) { + char bluetooth_device_name_string[] = "X"; + + ByteArray bluetooth_device_name_bytes{bluetooth_device_name_string}; + BluetoothDeviceName bluetooth_device_name{ + Base64Utils::Encode(bluetooth_device_name_bytes)}; + + EXPECT_FALSE(bluetooth_device_name.IsValid()); +} + +TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { + // Serialize good data into a good Bluetooth Device Name. + ByteArray service_id_hash{kServiceIDHashBytes}; + BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, + service_id_hash, kEndPointName}; + auto bluetooth_device_name_string = std::string(bluetooth_device_name); + + // Base64-decode the good Bluetooth Device Name. + ByteArray bluetooth_device_name_bytes = + Base64Utils::Decode(bluetooth_device_name_string); + // Corrupt the EndpointNameLength bits (120-127) by reversing all of them. + std::string corrupt_string(bluetooth_device_name_bytes.data(), + bluetooth_device_name_bytes.size()); + corrupt_string[15] ^= 0x0FF; + // Base64-encode the corrupted bytes into a corrupt Bluetooth Device Name. + ByteArray corrupt_bluetooth_device_name_bytes{corrupt_string.data(), + corrupt_string.size()}; + std::string corrupt_bluetooth_device_name_string( + Base64Utils::Encode(corrupt_bluetooth_device_name_bytes)); + + // And deserialize the corrupt Bluetooth Device Name. + BluetoothDeviceName corrupt_bluetooth_device_name( + corrupt_bluetooth_device_name_string); + + EXPECT_TRUE(corrupt_bluetooth_device_name.IsValid()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index 5a33fe85..c15fc9bb 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -5,6 +5,7 @@ cc_library( "ble_advertisement.cc", "ble_advertisement_header.cc", "ble_packet.cc", + "bloom_filter.cc", "bluetooth_radio.cc", "uuid.cc", ], @@ -14,6 +15,7 @@ cc_library( "ble_advertisement_header.h", "ble_packet.h", "ble_peripheral.h", + "bloom_filter.h", "bluetooth_radio.h", "lost_entity_tracker.h", "uuid.h", @@ -23,12 +25,15 @@ cc_library( ], deps = [ "//platform_v2/base", - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//absl/container:flat_hash_map", "//absl/container:flat_hash_set", + "//absl/numeric:int128", "//absl/strings", "//absl/time", + "//smhasher:libmurmur3", ], ) @@ -41,7 +46,8 @@ cc_library( ], deps = [ "//platform_v2/base", - "//platform_v2/public", + "//platform_v2/public:comm", + "//platform_v2/public:types", ], ) @@ -53,6 +59,7 @@ cc_test( "ble_advertisement_test.cc", "ble_packet_test.cc", "ble_peripheral_test.cc", + "bloom_filter_test.cc", "bluetooth_radio_test.cc", "lost_entity_tracker_test.cc", "uuid_test.cc", @@ -62,8 +69,9 @@ cc_test( ":mediums", "//platform_v2/base", "//platform_v2/impl/g3", # build_cleaner: keep - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", + "//platform_v2/public:types", "//testing/base/public:gunit_main", "//absl/time", ], diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.h b/cpp/core_v2/internal/mediums/ble_advertisement_header.h index aa8163df..bcec8d55 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header.h +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.h @@ -43,12 +43,11 @@ class BleAdvertisementHeader { const ByteArray &advertisement_hash); explicit BleAdvertisementHeader( const std::string &ble_advertisement_header_string); - ~BleAdvertisementHeader() = default; - BleAdvertisementHeader(const BleAdvertisementHeader &) = default; BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default; BleAdvertisementHeader(BleAdvertisementHeader &&) = default; BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default; + ~BleAdvertisementHeader() = default; // Produces an encoded binary string which can be decoded by the explicit // constructor. The returned string is empty if BleAdvertisementHeader is not 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 30bfe536..36999641 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc @@ -16,11 +16,11 @@ constexpr char kServiceIDBloomFilter[] = constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d"; TEST(BleAdvertisementHeaderTest, ConstructionWorks) { - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(kAdvertisementHash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; EXPECT_TRUE(ble_advertisement_header.IsValid()); EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); @@ -34,11 +34,11 @@ 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{kServiceIDBloomFilter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( - bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash}; EXPECT_FALSE(ble_advertisement_header.IsValid()); } @@ -47,12 +47,12 @@ 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 short_service_id_bloom_filter_bytes{short_service_id_bloom_filter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( + BleAdvertisementHeader ble_advertisement_header{ kVersion, kNumSlots, short_service_id_bloom_filter_bytes, - advertisement_hash); + advertisement_hash}; EXPECT_FALSE(ble_advertisement_header.IsValid()); } @@ -62,11 +62,11 @@ TEST(BleAdvertisementHeaderTest, char long_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"; - ByteArray service_id_bloom_filter(long_service_id_bloom_filter); - ByteArray advertisement_hash(kAdvertisementHash); + ByteArray service_id_bloom_filter{long_service_id_bloom_filter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; EXPECT_FALSE(ble_advertisement_header.IsValid()); } @@ -74,38 +74,37 @@ TEST(BleAdvertisementHeaderTest, TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { char short_advertisement_hash[] = "\x0a\x0b\x0c"; - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(short_advertisement_hash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{short_advertisement_hash}; - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; EXPECT_FALSE(ble_advertisement_header.IsValid()); } TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { - char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\0x0e"; + char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\x0e"; - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(long_advertisement_hash, - sizeof(long_advertisement_hash) / sizeof(char)); - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{long_advertisement_hash}; + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; EXPECT_FALSE(ble_advertisement_header.IsValid()); } TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(kAdvertisementHash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader org_ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader org_ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; auto ble_advertisement_header_string = std::string(org_ble_advertisement_header); - auto ble_advertisement_header = - BleAdvertisementHeader(ble_advertisement_header_string); + BleAdvertisementHeader ble_advertisement_header{ + ble_advertisement_header_string}; EXPECT_TRUE(ble_advertisement_header.IsValid()); EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); @@ -117,24 +116,24 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { } TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(kAdvertisementHash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; auto ble_advertisement_header_string = std::string(ble_advertisement_header); // Base64 decode the string, add a character, and then re-encode it. ByteArray ble_advertisement_header_bytes = Base64Utils::Decode(ble_advertisement_header_string); - ByteArray long_ble_advertisement_header_bytes( - ble_advertisement_header_bytes.size() + 1); + ByteArray long_ble_advertisement_header_bytes{ + ble_advertisement_header_bytes.size() + 1}; long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes); - std::string long_ble_advertisement_header_string = - Base64Utils::Encode(long_ble_advertisement_header_bytes); + std::string long_ble_advertisement_header_string{ + Base64Utils::Encode(long_ble_advertisement_header_bytes)}; - auto long_ble_advertisement_header = - BleAdvertisementHeader(long_ble_advertisement_header_string); + BleAdvertisementHeader long_ble_advertisement_header{ + long_ble_advertisement_header_string}; EXPECT_TRUE(long_ble_advertisement_header.IsValid()); EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion()); @@ -146,25 +145,25 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { } TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { - ByteArray service_id_bloom_filter(kServiceIDBloomFilter); - ByteArray advertisement_hash(kAdvertisementHash); + ByteArray service_id_bloom_filter{kServiceIDBloomFilter}; + ByteArray advertisement_hash{kAdvertisementHash}; - BleAdvertisementHeader ble_advertisement_header( - kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + BleAdvertisementHeader ble_advertisement_header{ + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash}; auto ble_advertisement_header_string = std::string(ble_advertisement_header); // Base64 decode the string, remove a character, and then re-encode it. ByteArray ble_advertisement_header_bytes = Base64Utils::Decode(ble_advertisement_header_string); - ByteArray short_ble_advertisement_header_bytes( - ble_advertisement_header_bytes.size() - 1); + ByteArray short_ble_advertisement_header_bytes{ + ble_advertisement_header_bytes.size() - 1}; short_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes); - std::string short_ble_advertisement_header_string = - Base64Utils::Encode(short_ble_advertisement_header_bytes); + std::string short_ble_advertisement_header_string{ + Base64Utils::Encode(short_ble_advertisement_header_bytes)}; - auto short_ble_advertisement_header = - BleAdvertisementHeader(short_ble_advertisement_header_string); + BleAdvertisementHeader short_ble_advertisement_header{ + short_ble_advertisement_header_string}; EXPECT_FALSE(short_ble_advertisement_header.IsValid()); } diff --git a/cpp/core_v2/internal/mediums/ble_packet.h b/cpp/core_v2/internal/mediums/ble_packet.h index 159f6349..bbdae131 100644 --- a/cpp/core_v2/internal/mediums/ble_packet.h +++ b/cpp/core_v2/internal/mediums/ble_packet.h @@ -22,12 +22,11 @@ class BlePacket { BlePacket() = default; BlePacket(const ByteArray& service_id_hash, const ByteArray& data); explicit BlePacket(const ByteArray& ble_packet_byte); - ~BlePacket() = default; - BlePacket(const BlePacket&) = default; BlePacket& operator=(const BlePacket&) = default; BlePacket(BlePacket&&) = default; BlePacket& operator=(BlePacket&&) = default; + ~BlePacket() = default; explicit operator ByteArray() const; diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_packet_test.cc index b9a1c858..b5e33d45 100644 --- a/cpp/core_v2/internal/mediums/ble_packet_test.cc +++ b/cpp/core_v2/internal/mediums/ble_packet_test.cc @@ -11,10 +11,10 @@ constexpr char kServiceIDHash[] = "\x0a\x0b\x0c"; constexpr char kData[] = "\x01\x02\x03\x04\x05"; TEST(BlePacketTest, ConstructionWorks) { - ByteArray service_id_hash(kServiceIDHash); - ByteArray data(kData); + ByteArray service_id_hash{kServiceIDHash}; + ByteArray data{kData}; - BlePacket ble_packet(service_id_hash, data); + BlePacket ble_packet{service_id_hash, data}; EXPECT_TRUE(ble_packet.IsValid()); EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); @@ -22,12 +22,12 @@ TEST(BlePacketTest, ConstructionWorks) { } TEST(BlePacketTest, ConstructionWorksWithEmptyData) { - char empty_data[] = {}; + char empty_data[] = ""; - ByteArray service_id_hash(kServiceIDHash); - ByteArray data(empty_data); + ByteArray service_id_hash{kServiceIDHash}; + ByteArray data{empty_data}; - BlePacket ble_packet(service_id_hash, data); + BlePacket ble_packet{service_id_hash, data}; EXPECT_TRUE(ble_packet.IsValid()); EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); @@ -37,8 +37,8 @@ TEST(BlePacketTest, ConstructionWorksWithEmptyData) { TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { char short_service_id_hash[] = "\x0a\x0b"; - ByteArray service_id_hash(short_service_id_hash); - ByteArray data(kData); + ByteArray service_id_hash{short_service_id_hash}; + ByteArray data{kData}; BlePacket ble_packet(service_id_hash, data); @@ -48,22 +48,22 @@ TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { char long_service_id_hash[] = "\x0a\x0b\x0c\x0d"; - ByteArray service_id_hash(long_service_id_hash); - ByteArray data(kData); + ByteArray service_id_hash{long_service_id_hash}; + ByteArray data{kData}; - BlePacket ble_packet(service_id_hash, data); + BlePacket ble_packet{service_id_hash, data}; EXPECT_FALSE(ble_packet.IsValid()); } TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { - ByteArray service_id_hash(kServiceIDHash); - ByteArray data(kData); + ByteArray service_id_hash{kServiceIDHash}; + ByteArray data{kData}; - BlePacket org_ble_packet(service_id_hash, data); - ByteArray ble_packet_bytes(org_ble_packet); + BlePacket org_ble_packet{service_id_hash, data}; + ByteArray ble_packet_bytes{org_ble_packet}; - BlePacket ble_packet(ble_packet_bytes); + BlePacket ble_packet{ble_packet_bytes}; EXPECT_TRUE(ble_packet.IsValid()); EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); @@ -71,22 +71,22 @@ TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { } TEST(BlePacketTest, ConstructionFromNullBytesFails) { - BlePacket ble_packet(ByteArray{}); + BlePacket ble_packet{ByteArray{}}; EXPECT_FALSE(ble_packet.IsValid()); } TEST(BlePacketTest, ConstructionFromShortLengthDataFails) { - ByteArray service_id_hash(kServiceIDHash); - ByteArray data(kData); + ByteArray service_id_hash{kServiceIDHash}; + ByteArray data{kData}; - BlePacket org_ble_packet(service_id_hash, data); - ByteArray org_ble_packet_bytes(org_ble_packet); + BlePacket org_ble_packet{service_id_hash, data}; + ByteArray org_ble_packet_bytes{org_ble_packet}; // Cut off the packet so that it's too short - ByteArray short_ble_packet_bytes(ByteArray(org_ble_packet_bytes.data(), 2)); + ByteArray short_ble_packet_bytes{ByteArray{org_ble_packet_bytes.data(), 2}}; - BlePacket short_ble_packet(short_ble_packet_bytes); + BlePacket short_ble_packet{short_ble_packet_bytes}; EXPECT_FALSE(short_ble_packet.IsValid()); } diff --git a/cpp/core_v2/internal/mediums/ble_peripheral.h b/cpp/core_v2/internal/mediums/ble_peripheral.h index 01d0b594..520b93ca 100644 --- a/cpp/core_v2/internal/mediums/ble_peripheral.h +++ b/cpp/core_v2/internal/mediums/ble_peripheral.h @@ -12,12 +12,11 @@ class BlePeripheral { public: BlePeripheral() = default; explicit BlePeripheral(const ByteArray& id) : id_(id) {} - ~BlePeripheral() = default; - BlePeripheral(const BlePeripheral&) = default; BlePeripheral& operator=(const BlePeripheral&) = default; BlePeripheral(BlePeripheral&&) = default; BlePeripheral& operator=(BlePeripheral&&) = default; + ~BlePeripheral() = default; bool IsValid() const { return !id_.Empty(); } ByteArray GetId() const { return id_; } diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc index d43c375a..887e115e 100644 --- a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc +++ b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc @@ -11,9 +11,9 @@ namespace { const char kId[] = "AB12"; TEST(BlePeripheralTest, ConstructionWorks) { - ByteArray id(kId); + ByteArray id{kId}; - BlePeripheral ble_peripheral(id); + BlePeripheral ble_peripheral{id}; EXPECT_TRUE(ble_peripheral.IsValid()); EXPECT_EQ(id, ble_peripheral.GetId()); diff --git a/cpp/core_v2/internal/mediums/bloom_filter.cc b/cpp/core_v2/internal/mediums/bloom_filter.cc new file mode 100644 index 00000000..b2f08fc9 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bloom_filter.cc @@ -0,0 +1,91 @@ +#include "core_v2/internal/mediums/bloom_filter.h" + +#include "absl/numeric/int128.h" +#include "absl/strings/numbers.h" +#include "smhasher/MurmurHash3.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BloomFilterBase::BloomFilterBase(const ByteArray& bytes, BitSet* bit_set) + : bits_(bit_set) { + const char* bytes_read_ptr = bytes.data(); + for (size_t byte_index = 0; byte_index < bytes.size(); byte_index++) { + for (size_t bit_index = 0; bit_index < 8; bit_index++) { + bits_->Set((byte_index * 8) + bit_index, + (*bytes_read_ptr >> bit_index) & 0x01); + } + bytes_read_ptr++; + } +} + +BloomFilterBase::operator ByteArray() const { + // Gets a binary string representation of the bitset where the leftmost + // character corresponds to bitset position (total size) - 1. + // + // If the bitset's internal representation is: + // [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11] + // The string representation will be outputted like this: + // "1 0 1 0 1 0 0 0 1 1 0 0" + std::string bitset_binary_string = bits_->ToString(); + + ByteArray result_bytes(GetMinBytesForBits()); + char* result_bytes_write_ptr = result_bytes.data(); + // We go through the string backwards because the rightmost character + // corresponds to position 0 in the bitset. + for (size_t i = bits_->Size(); i > 0; i -= 8) { + std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8); + std::uint32_t byte_value; + absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value, + /* base= */ 2); + *result_bytes_write_ptr = static_cast(byte_value & 0x000000FF); + result_bytes_write_ptr++; + } + return result_bytes; +} + +void BloomFilterBase::Add(const std::string& s) { + std::vector hashes = GetHashes(s); + for (int32_t hash : hashes) { + size_t position = static_cast(hash) % bits_->Size(); + bits_->Set(position, true); + } +} + +bool BloomFilterBase::PossiblyContains(const std::string& s) { + std::vector hashes = GetHashes(s); + for (int32_t hash : hashes) { + size_t position = static_cast(hash) % bits_->Size(); + if (!bits_->Test(position)) { + return false; + } + } + return true; +} + +std::vector BloomFilterBase::GetHashes(const std::string& s) { + std::vector hashes(kHasherNumberOfRepetitions, 0); + + absl::uint128 hash128; + MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128); + std::uint64_t hash64 = + absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash + std::int32_t hash1 = static_cast( + hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash + std::int32_t hash2 = static_cast( + (hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash + for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) { + std::int32_t combinedHash = static_cast(hash1 + (i * hash2)); + // Flip all the bits if it's negative (guaranteed positive number) + if (combinedHash < 0) combinedHash = ~combinedHash; + hashes[i - 1] = combinedHash; + } + return hashes; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bloom_filter.h b/cpp/core_v2/internal/mediums/bloom_filter.h new file mode 100644 index 00000000..da65f652 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bloom_filter.h @@ -0,0 +1,87 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +/** + * A bloom filter that gives access to the underlying BitSet. The implementation + * is copied from our Java version of Bloom filter, which in turn copies from + * Guava's BloomFilter. + * + * BloomFilter is templatized on the size of the byte array and not the size of + * the bit set to ensure the bit set's length is a multiple of 8 (and can + * neatly be returned as a ByteArray). + */ +class BloomFilterBase { + public: + explicit operator ByteArray() const; + + void Add(const std::string& s); + bool PossiblyContains(const std::string& s); + + protected: + class BitSet { + public: + virtual ~BitSet() = default; + virtual std::string ToString() const = 0; + virtual void Set(size_t pos, bool value) = 0; + virtual bool Test(size_t pos) const = 0; + virtual size_t Size() const = 0; + }; + + BloomFilterBase(const ByteArray& bytes, BitSet* bit_set); + virtual ~BloomFilterBase() = default; + + constexpr static int kHasherNumberOfRepetitions = 5; + std::vector GetHashes(const std::string& s); + + private: + int GetMinBytesForBits() const { return (bits_->Size() + 7) >> 3; } + + BitSet* bits_; +}; + +template +class BloomFilter final : public BloomFilterBase { + public: + BloomFilter() : BloomFilterBase(ByteArray{}, &bits_) {} + explicit BloomFilter(const ByteArray& bytes) + : BloomFilterBase(bytes, &bits_) {} + BloomFilter(const BloomFilter&) = default; + BloomFilter& operator=(const BloomFilter&) = default; + BloomFilter(BloomFilter&& other) : BloomFilterBase(ByteArray{}, &bits_) { + *this = std::move(other); + } + BloomFilter& operator=(BloomFilter&& other) { + std::swap((*this).bits_, other.bits_); + return *this; + } + ~BloomFilter() override = default; + + private: + class BitSetImpl final : public BitSet { + public: + std::string ToString() const override { return bits_.to_string(); } + void Set(size_t pos, bool value) override { bits_.set(pos, value); } + bool Test(size_t pos) const override { return bits_.test(pos); } + size_t Size() const override { return bits_.size(); } + + private: + std::bitset bits_; + } bits_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_ diff --git a/cpp/core_v2/internal/mediums/bloom_filter_test.cc b/cpp/core_v2/internal/mediums/bloom_filter_test.cc new file mode 100644 index 00000000..b839d499 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bloom_filter_test.cc @@ -0,0 +1,193 @@ +#include "core_v2/internal/mediums/bloom_filter.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const size_t kByteArrayLength = 100; + +TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) { + BloomFilter bloom_filter; + + ByteArray bloom_filter_bytes(bloom_filter); + std::string empty_string(kByteArrayLength, '\0'); + + EXPECT_EQ(empty_string, std::string(bloom_filter_bytes)); +} + +TEST(BloomFilterTest, EmptyFilterNeverContains) { + BloomFilter bloom_filter; + + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1")); + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2")); + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddSuccess) { + BloomFilter bloom_filter; + + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1")); + + bloom_filter.Add("ELEMENT_1"); + + EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1")); +} + +TEST(BloomFilterTest, AddOnlyGivenArg) { + BloomFilter bloom_filter; + + bloom_filter.Add("ELEMENT_1"); + + EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1")); + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2")); + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddMultipleArgs) { + BloomFilter bloom_filter; + + bloom_filter.Add("ELEMENT_1"); + bloom_filter.Add("ELEMENT_2"); + + EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1")); + EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_2")); + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3")); +} + +TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) { + BloomFilter<10> bloom_filter; + + bloom_filter.Add("ELEMENT_1"); + bloom_filter.Add("ELEMENT_2"); + bloom_filter.Add("ELEMENT_3"); + + ByteArray bloom_filter_bytes(bloom_filter); + std::string empty_string(kByteArrayLength, '\0'); + + EXPECT_NE(std::string(bloom_filter_bytes), empty_string); +} + +TEST(BloomFilterTest, CopyConstructorAndAssignmentSuccess) { + BloomFilter bloom_filter; + + EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1")); + + bloom_filter.Add("ELEMENT_1"); + + BloomFilter bloom_filter_copy_1{bloom_filter}; + BloomFilter bloom_filter_copy_2 = bloom_filter; + + EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1")); + EXPECT_TRUE(bloom_filter_copy_1.PossiblyContains("ELEMENT_1")); + EXPECT_TRUE(bloom_filter_copy_2.PossiblyContains("ELEMENT_1")); +} + +TEST(BloomFilterTest, MoveConstructorSuccess) { + BloomFilter bloom_filter; + + bloom_filter.Add("ELEMENT_1"); + + BloomFilter bloom_filter_move{std::move(bloom_filter)}; + + EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1")); +} + +TEST(BloomFilterTest, MoveAssignmentSuccess) { + BloomFilter bloom_filter; + + bloom_filter.Add("ELEMENT_1"); + + BloomFilter bloom_filter_move = std::move(bloom_filter); + + EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1")); +} + +/** + * This test was added because of a bug where the BloomFilter doesn't utilize + * all bits given. Functionally, the filter still works, but we just have a much + * higher false positive rate. The bug was caused by confusing bit length and + * byte length, which made our BloomFilter only set bits on the first byteLength + * (bitLength / 8) bits rather than the whole bitLength bits. + * + *

Here, we're verifying that the bits set are somewhat scattered. So instead + * of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting + * something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0]. + */ +TEST(BloomFilterTest, RandomnessNoEndBias) { + BloomFilter bloom_filter; + + // Add one element to our BloomFilter. + bloom_filter.Add("ELEMENT_1"); + + std::int32_t non_zero_count = 0; + std::int32_t longest_zero_streak = 0; + std::int32_t current_zero_streak = 0; + + // Record the amount of non-zero bytes and the longest streak of zero bytes in + // the resulting BloomFilter. This is an approximation of reasonable + // distribution since we're recording by bytes instead of bits. + ByteArray bloom_filter_bytes(bloom_filter); + const char* bloom_filter_bytes_read_ptr = bloom_filter_bytes.data(); + for (int i = 0; i < bloom_filter_bytes.size(); i++) { + if (*bloom_filter_bytes_read_ptr == '\0') { + current_zero_streak++; + } else { + // Increment the number of non-zero bytes we've seen, update the longest + // zero streak, and then reset the current zero streak. + non_zero_count++; + longest_zero_streak = std::max(longest_zero_streak, current_zero_streak); + current_zero_streak = 0; + } + bloom_filter_bytes_read_ptr++; + } + // Update the longest zero streak again for the tail case. + longest_zero_streak = std::min(longest_zero_streak, current_zero_streak); + + // Since randomness is hard to measure within one unit test, we instead do a + // sanity check. All non-zero bytes should not be packed into one end of the + // array. + // + // In this case, the size of one end is approximated to be: + // kByteArrayLength / nonZeroCount. + // Therefore, the longest zero streak should be less than: + // kByteArrayLength - one end of the array. + std::int32_t longest_acceptable_zero_streak = + kByteArrayLength - (kByteArrayLength / non_zero_count); + + EXPECT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak); +} + +TEST(BloomFilterTest, RandomnessFalsePositiveRate) { + BloomFilter<10> bloom_filter; + + // Add 5 distinct elements to the BloomFilter. + bloom_filter.Add("ELEMENT_1"); + bloom_filter.Add("ELEMENT_2"); + bloom_filter.Add("ELEMENT_3"); + bloom_filter.Add("ELEMENT_4"); + bloom_filter.Add("ELEMENT_5"); + + std::int32_t false_positives = 0; + // Now test 100 other elements and record the number of false positives. + for (int i = 5; i < 105; i++) { + false_positives += + bloom_filter.PossiblyContains("ELEMENT_" + std::to_string(i)) ? 1 : 0; + } + + // We expect the false positive rate to be 3% with 5 elements in a 10 byte + // filter. Thus, we give a little leeway and verify that the false positive + // rate is no more than 5%. + EXPECT_LE(false_positives, 5); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD index 9e8cc9e8..9805da7e 100644 --- a/cpp/core_v2/internal/mediums/webrtc/BUILD +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -1,29 +1,41 @@ cc_library( name = "webrtc", srcs = [ + "connection_flow.cc", + "peer_connection_observer_impl.cc", "webrtc_socket.cc", ], hdrs = [ + "connection_flow.h", + "data_channel_listener.h", + "local_ice_candidate_listener.h", + "peer_connection_observer_impl.h", "webrtc_socket.h", ], deps = [ "//core_v2:core_types", "//platform_v2/base", - "//platform_v2/public", + "//platform_v2/public:comm", "//platform_v2/public:logging", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//platform_v2/public:types", + "//absl/memory", + "//webrtc/api:libjingle_peerconnection_api", ], ) cc_test( name = "webrtc_test", - srcs = ["webrtc_socket_test.cc"], + srcs = [ + "connection_flow_test.cc", + "webrtc_socket_test.cc", + ], deps = [ ":webrtc", "//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", + "//webrtc/api:libjingle_peerconnection_api", ], ) @@ -34,7 +46,8 @@ cc_test( ":peer_id", "//platform_v2/base", "//platform_v2/impl/g3", #buildcleaner: keep - "//platform_v2/public", + "//platform_v2/public:comm", + "//platform_v2/public:types", "//testing/base/public:gunit_main", ], ) @@ -48,7 +61,7 @@ cc_test( "//platform_v2/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 ], ) @@ -71,6 +84,6 @@ cc_library( ":peer_id", "//platform_v2/base", "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc new file mode 100644 index 00000000..6a673574 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -0,0 +1,134 @@ +#include "core_v2/internal/mediums/webrtc/connection_flow.h" + +#include + +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/webrtc.h" +#include "absl/memory/memory.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +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)); + if (connection_flow->InitPeerConnection(webrtc_medium)) { + return connection_flow; + } + + return nullptr; +} + +ConnectionFlow::ConnectionFlow( + LocalIceCandidateListener local_ice_candidate_listener, + DataChannelListener data_channel_listener, + SingleThreadExecutor* single_threaded_executor) + : 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(); +} + +std::unique_ptr +ConnectionFlow::CreateAnswer() { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement + + return std::unique_ptr(); +} + +bool ConnectionFlow::SetLocalSessionDescription( + std::unique_ptr sdp) { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement + + return false; +} + +void ConnectionFlow::OnOfferReceived( + std::unique_ptr offer) { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement +} + +void ConnectionFlow::OnAnswerReceived( + std::unique_ptr answer) { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement +} + +bool ConnectionFlow::OnRemoteIceCandidatesReceived( + std::vector ice_candidates) { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement + + return false; +} + +api::ListenableFuture>* +ConnectionFlow::GetDataChannel() { + return static_cast< + api::ListenableFuture>*>( + &data_channel_future_); +} + +bool ConnectionFlow::Close() { + MutexLock lock(&mutex_); + + // TODO(bfranz): Implement + + return false; +} + +bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { + Future success_future; + webrtc_medium.CreatePeerConnection( + &peer_connection_observer_, + [this, &success_future]( + rtc::scoped_refptr peer_connection) { + peer_connection_ = peer_connection; + success_future.Set(true); + }); + + ExceptionOr result = success_future.Get(kTimeout); + return result.ok() && result.result(); +} + +void ConnectionFlow::OnSignalingStable() { + // TODO(bfranz): Implement +} + +void ConnectionFlow::ProcessOnPeerConnectionChange( + webrtc::PeerConnectionInterface::PeerConnectionState new_state) { + // TODO(bfranz): Implement +} + +webrtc::DataChannelObserver* ConnectionFlow::CreateDataChannelObserver( + rtc::scoped_refptr data_channel) { + // TODO(bfranz): Implement + + return nullptr; +} +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h new file mode 100644 index 00000000..7f5ca6dc --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h @@ -0,0 +1,133 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_ + +#include + +#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_connection_observer_impl.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/api/data_channel_interface.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +/** + * Flow for an offerer: + * + *

+ * + *

Flow for an answerer: + * + *

    + *
  • INITIALIZED: After construction. + *
  • RECEIVED_OFFER: After onOfferReceived(). + *
  • CREATING_ANSWER: After CreateAnswer(). Local ice candidate collection + * begins. + *
  • WAITING_TO_CONNECT: Until the data channel actually connects. + * Remote ice candidates should be added with OnRemoteIceCandidatesReceived as + * they are gathered. + *
  • CONNECTED: We successfully connected to the remote + * data channel. + *
  • ENDED: The final state that can occur from any of the + * previous states if we disconnect at any point in the flow. + *
+ */ +class ConnectionFlow { + public: + // 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; + + // Create the offer that will be sent to the remote. Mirrors the behaviour of + // PeerConnectionInterface::CreateOffer. + std::unique_ptr 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_); + // Set the local session description. |sdp| was created via CreateOffer() + // or CreateAnswer(). + bool SetLocalSessionDescription( + std::unique_ptr 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) + 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) + 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_); + // Get a future for the data channel. + api::ListenableFuture>* + 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(); + 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); + + private: + ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener, + DataChannelListener data_channel_listener, + SingleThreadExecutor* single_threaded_executor); + + // TODO(bfranz): Consider whether this needs to be configurable per platform + static constexpr absl::Duration kTimeout = absl::Milliseconds(250); + + bool InitPeerConnection(WebRtcMedium& webrtc_medium); + + DataChannelListener data_channel_listener_; + + Future> data_channel_future_; + + PeerConnectionObserverImpl peer_connection_observer_; + rtc::scoped_refptr peer_connection_; + + Mutex mutex_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc new file mode 100644 index 00000000..3b0895bf --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc @@ -0,0 +1,32 @@ +#include "core_v2/internal/mediums/webrtc/connection_flow.h" + +#include + +#include "platform_v2/public/webrtc.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +TEST(ConnectionFlowTest, Create) { + LocalIceCandidateListener local_ice_candidate_listener; + DataChannelListener data_channel_listener; + SingleThreadExecutor executor; + WebRtcMedium webrtc_medium; + + std::unique_ptr connection_flow = ConnectionFlow::Create( + std::move(local_ice_candidate_listener), std::move(data_channel_listener), + &executor, webrtc_medium); + + EXPECT_NE(connection_flow, nullptr); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h b/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h new file mode 100644 index 00000000..2c4cec68 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_listener.h @@ -0,0 +1,31 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_ + +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Callbacks from the data channel. +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(); + + // Called when the data channel indicates that the buffered amount has + // changed. + std::function data_channel_buffered_amount_changed_cb = + DefaultCallback<>(); +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_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 new file mode 100644 index 00000000..62adf483 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h @@ -0,0 +1,25 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_ + +#include "core_v2/listeners.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Callbacks from local ice candidate collection. +struct LocalIceCandidateListener { + // Called when a new local ice candidate has been found. + std::function + local_ice_candidate_found_cb = location::nearby::DefaultCallback< + const webrtc::IceCandidateInterface*>(); +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_ 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 new file mode 100644 index 00000000..e6c5980d --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.cc @@ -0,0 +1,68 @@ +#include "core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h" + +#include "core_v2/internal/mediums/webrtc/connection_flow.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +PeerConnectionObserverImpl::PeerConnectionObserverImpl( + ConnectionFlow* connection_flow, + LocalIceCandidateListener local_ice_candidate_listener, + SingleThreadExecutor* executor) + : connection_flow_(connection_flow), + local_ice_candidate_listener_(std::move(local_ice_candidate_listener)), + single_threaded_signaling_offloader_(executor) {} + +void PeerConnectionObserverImpl::OnIceCandidate( + const webrtc::IceCandidateInterface* candidate) { + NEARBY_LOG(INFO, "OnIceCandidate"); + local_ice_candidate_listener_.local_ice_candidate_found_cb(candidate); +} + +void PeerConnectionObserverImpl::OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) { + NEARBY_LOG(INFO, "OnSignalingChange: %d", new_state); + + OffloadFromSignalingThread([this, new_state]() { + if (new_state == webrtc::PeerConnectionInterface::SignalingState::kStable) + connection_flow_->OnSignalingStable(); + }); +} + +void PeerConnectionObserverImpl::OnDataChannel( + rtc::scoped_refptr data_channel) { + NEARBY_LOG(INFO, "OnDataChannel"); + + data_channel->RegisterObserver( + connection_flow_->CreateDataChannelObserver(data_channel)); +} + +void PeerConnectionObserverImpl::OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) { + NEARBY_LOG(INFO, "OnIceGatheringChange: %d", new_state); +} + +void PeerConnectionObserverImpl::OnConnectionChange( + webrtc::PeerConnectionInterface::PeerConnectionState new_state) { + NEARBY_LOG(INFO, "OnConnectionChange: %d", new_state); + + OffloadFromSignalingThread([this, new_state]() { + connection_flow_->ProcessOnPeerConnectionChange(new_state); + }); +} + +void PeerConnectionObserverImpl ::OnRenegotiationNeeded() { + NEARBY_LOG(INFO, "OnRenegotiationNeeded"); +} + +void PeerConnectionObserverImpl::OffloadFromSignalingThread(Runnable runnable) { + single_threaded_signaling_offloader_->Execute(std::move(runnable)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location 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 new file mode 100644 index 00000000..7c30ef7b --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h @@ -0,0 +1,48 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_ + +#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h" +#include "platform_v2/public/single_thread_executor.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class ConnectionFlow; + +class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver { + public: + ~PeerConnectionObserverImpl() override = default; + PeerConnectionObserverImpl( + ConnectionFlow* connection_flow, + LocalIceCandidateListener local_ice_candidate_listener, + SingleThreadExecutor* executor); + + // webrtc::PeerConnectionObserver: + void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override; + void OnSignalingChange( + webrtc::PeerConnectionInterface::SignalingState new_state) override; + void OnDataChannel( + rtc::scoped_refptr data_channel) override; + void OnIceGatheringChange( + webrtc::PeerConnectionInterface::IceGatheringState new_state) override; + void OnConnectionChange( + webrtc::PeerConnectionInterface::PeerConnectionState new_state) override; + void OnRenegotiationNeeded() override; + + private: + void OffloadFromSignalingThread(Runnable runnable); + + ConnectionFlow* connection_flow_; + LocalIceCandidateListener local_ice_candidate_listener_; + SingleThreadExecutor* single_threaded_signaling_offloader_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_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.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h index e5d90939..c0268f65 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 { 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/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 792922bb..fc5c5572 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -3,7 +3,7 @@ #include #include -#include "core/internal/message_lite.h" +#include "google/protobuf/message_lite.h" #include "platform_v2/base/byte_array.h" namespace location { diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h index 3666360d..dd753ee7 100644 --- a/cpp/core_v2/internal/pcp_handler.h +++ b/cpp/core_v2/internal/pcp_handler.h @@ -30,10 +30,10 @@ class PcpHandler { virtual ~PcpHandler() = default; // Return strategy supported by this protocol. - virtual Strategy GetStrategy() = 0; + virtual Strategy GetStrategy() const = 0; // Return concrete variant of protocol. - virtual Pcp GetPcp() = 0; + virtual Pcp GetPcp() const = 0; // We have been asked by the client to start advertising. Once we successfully // start advertising, we'll change the ClientProxy's state. diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc index dd1c044b..9b4a3d25 100644 --- a/cpp/core_v2/internal/service_controller_router.cc +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -190,24 +190,25 @@ void ServiceControllerRouter::SendPayload( // We have to capture it by value inside the lambda, and pass it over to // the executor as an std::function instance. // Lambda must be copyable, in order ot satisfy std::function<> requirements. - // To make it so, we need Payload wrapped by a copyable wrapper. + // To make it so, we need Payload wrapped by a copyable wrapper. // std::shared_ptr<> is used, because it is copyable. auto shared_payload = std::make_shared(std::move(payload)); + const std::vector endpoints = + std::vector(endpoint_ids.begin(), endpoint_ids.end()); + RouteToServiceController( - [this, client, shared_payload, - endpoint_ids = std::vector(endpoint_ids.begin(), endpoint_ids.end()), - &callback]() { + [this, client, shared_payload, endpoints, &callback]() { if (!ClientHasAcquiredServiceController(client)) { callback.result_cb({Status::kOutOfOrderApiCall}); return; } - if (!ClientHasConnectionToAtLeastOneEndpoint(client, endpoint_ids)) { + if (!ClientHasConnectionToAtLeastOneEndpoint(client, endpoints)) { callback.result_cb({Status::kEndpointUnknown}); return; } - service_controller_->SendPayload(client, endpoint_ids, + service_controller_->SendPayload(client, endpoints, std::move(*shared_payload)); // At this point, we've queued up the send Payload request with the diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc index f034eeea..398840d9 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -33,7 +33,7 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, version_ = version; pcp_ = pcp; service_id_hash_ = service_id_hash; - endpoint_id_ = endpoint_id; + endpoint_id_ = std::string(endpoint_id); } WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { @@ -41,14 +41,14 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { if (service_info_bytes.Empty()) { NEARBY_LOG( - ERROR, + INFO, "Cannot deserialize WifiLanServiceInfo: failed Base64 decoding of %s", std::string(service_info_string).c_str()); return; } if (service_info_bytes.size() > kMaxLanServiceNameLength) { - NEARBY_LOG(ERROR, + NEARBY_LOG(INFO, "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " "bytes, got %" PRIu64, kMaxLanServiceNameLength, service_info_bytes.size()); @@ -56,7 +56,7 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { } if (service_info_bytes.size() < kMinLanServiceNameLength) { - NEARBY_LOG(ERROR, + NEARBY_LOG(INFO, "Cannot deserialize WifiLanServiceInfo: expecting min %d raw " "bytes, got %" PRIu64, kMinLanServiceNameLength, service_info_bytes.size()); @@ -96,7 +96,7 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { // the air, or older versions of GmsCore intermingling with newer // ones. NEARBY_LOG( - ERROR, + INFO, "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", pcp_); break; @@ -107,8 +107,7 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over // the air, or older versions of GmsCore intermingling with newer ones. NEARBY_LOG( - ERROR, - "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", + INFO, "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", version_); break; } @@ -119,9 +118,7 @@ WifiLanServiceInfo::operator std::string() const { return ""; } - ByteArray wifi_lan_service_info_name_bytes(kMinLanServiceNameLength); - auto* wifi_lan_service_info_name_bytes_write_ptr = - wifi_lan_service_info_name_bytes.data(); + std::string out; // The upper 3 bits are the Version. auto version_and_pcp_byte = static_cast( @@ -129,50 +126,15 @@ WifiLanServiceInfo::operator std::string() const { // The lower 5 bits are the PCP. version_and_pcp_byte |= static_cast(static_cast(pcp_) & kPcpBitmask); - *wifi_lan_service_info_name_bytes_write_ptr = version_and_pcp_byte; - wifi_lan_service_info_name_bytes_write_ptr++; - switch (pcp_) { - case Pcp::kP2pCluster: // Fall through - case Pcp::kP2pStar: // Fall through - case Pcp::kP2pPointToPoint: - // The next 32 bits are the endpoint_id. - if (endpoint_id_.size() != kEndpointIdLength) { - NEARBY_LOG( - ERROR, - "Cannot serialize WifiLanServiceInfo: V1 Endpoint ID %s (%" PRIu64 - " bytes) should be exactly %d bytes", - endpoint_id_.c_str(), endpoint_id_.size(), kEndpointIdLength); - return ""; - } - memcpy(wifi_lan_service_info_name_bytes_write_ptr, endpoint_id_.data(), - kEndpointIdLength); - wifi_lan_service_info_name_bytes_write_ptr += kEndpointIdLength; + 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(" "); - // The next 24 bits are the service_id_hash. - if (service_id_hash_.size() != kServiceIdHashLength) { - NEARBY_LOG( - ERROR, - "Cannot serialize WifiLanServiceInfo: V1 ServiceID hash (%" PRIu64 - " bytes) should be exactly %d bytes", - service_id_hash_.size(), kServiceIdHashLength); - return ""; - } - memcpy(wifi_lan_service_info_name_bytes_write_ptr, - service_id_hash_.data(), kServiceIdHashLength); - wifi_lan_service_info_name_bytes_write_ptr += kServiceIdHashLength; - - // The next bits are the endpoint_name. - // TODO(edwinwu): Implements to parse endpoint_name. - break; - default: - NEARBY_LOG(ERROR, - "Cannot serialize WifiLanServiceInfo: unsupported V1 PCP %d", - pcp_); - return ""; - } - - return Base64Utils::Encode(wifi_lan_service_info_name_bytes); + return Base64Utils::Encode(ByteArray{std::move(out)}); } } // namespace connections diff --git a/cpp/core_v2/internal/wifi_lan_service_info.h b/cpp/core_v2/internal/wifi_lan_service_info.h index 21f1f1bb..b841e7bd 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.h +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -30,21 +30,20 @@ class WifiLanServiceInfo { const ByteArray& service_id_hash, absl::string_view endpoint_name); explicit WifiLanServiceInfo(absl::string_view service_info_string); - ~WifiLanServiceInfo() = default; - WifiLanServiceInfo(const WifiLanServiceInfo&) = default; WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; WifiLanServiceInfo(WifiLanServiceInfo&&) = default; WifiLanServiceInfo& operator=(WifiLanServiceInfo&&) = default; + ~WifiLanServiceInfo() = default; explicit operator std::string() const; - inline bool IsValid() const { return !endpoint_id_.empty(); } - inline Version GetVersion() const { return version_; } - inline Pcp GetPcp() const { return pcp_; } - inline std::string GetEndpointId() const { return endpoint_id_; } - inline std::string GetEndpointName() const { return endpoint_name_; } - inline ByteArray GetServiceIdHash() const { return service_id_hash_; } + bool IsValid() const { return !endpoint_id_.empty(); } + Version GetVersion() const { return version_; } + Pcp GetPcp() const { return pcp_; } + std::string GetEndpointId() const { return endpoint_id_; } + std::string GetEndpointName() const { return endpoint_name_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } private: // The maximum length of encrypted WifiLanServiceInfo string. 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 b5aee9aa..5589089f 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info_test.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -14,18 +14,16 @@ namespace { const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; const Pcp kPcp = Pcp::kP2pCluster; const char kEndPointID[] = "AB12"; -const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; // TODO(edwinwu): Temp to set empty string for endpoint_name. const char kEndPointName[] = ""; TEST(WifiLanServiceInfoTest, ConstructionWorks) { - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, + service_id_hash, kEndPointName}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(wifi_lan_service_info.IsValid()); EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); @@ -33,16 +31,14 @@ TEST(WifiLanServiceInfoTest, ConstructionWorks) { } TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto org_wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); - auto wifi_lan_service_info_string = std::string(org_wifi_lan_service_info); + ByteArray service_id_hash{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}; - auto wifi_lan_service_info = WifiLanServiceInfo(wifi_lan_service_info_string); - auto is_valid = wifi_lan_service_info.IsValid(); + WifiLanServiceInfo wifi_lan_service_info{wifi_lan_service_info_string}; - EXPECT_TRUE(is_valid); + EXPECT_TRUE(wifi_lan_service_info.IsValid()); EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); @@ -52,89 +48,71 @@ TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName); + ByteArray service_id_hash{kServiceIDHashBytes}; + WifiLanServiceInfo wifi_lan_service_info{bad_version, kPcp, kEndPointID, + service_id_hash, kEndPointName}; - auto is_valid = wifi_lan_service_info.IsValid(); - - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, bad_pcp, kEndPointID, + service_id_hash, kEndPointName}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { std::string short_endpoint_id("AB1"); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, short_endpoint_id, + service_id_hash, kEndPointName}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { std::string long_endpoint_id("AB12X"); - auto service_id_hash = ByteArray(kServiceIDHashBytes, - sizeof(kServiceIDHashBytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray service_id_hash{kServiceIDHashBytes}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, long_endpoint_id, + service_id_hash, kEndPointName}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { - char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + char short_service_id_hash_bytes[] = "\x0a\x0b"; - auto short_service_id_hash = - ByteArray(short_service_id_hash_bytes, - sizeof(short_service_id_hash_bytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray short_service_id_hash{short_service_id_hash_bytes}; + WifiLanServiceInfo wifi_lan_service_info{ + kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { - char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; - auto long_service_id_hash = - ByteArray(long_service_id_hash_bytes, - sizeof(long_service_id_hash_bytes) / sizeof(char)); - auto wifi_lan_service_info = WifiLanServiceInfo( - kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray long_service_id_hash{long_service_id_hash_bytes}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, + long_service_id_hash, kEndPointName}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortStringLength) { - char wifi_lan_service_info_string[] = {'X'}; + char wifi_lan_service_info_string[] = {'X', '\0'}; - auto wifi_lan_service_info_bytes = - ByteArray(wifi_lan_service_info_string, - sizeof(wifi_lan_service_info_string) / sizeof(char)); - auto wifi_lan_service_info = - WifiLanServiceInfo(Base64Utils::Encode(wifi_lan_service_info_bytes)); - auto is_valid = wifi_lan_service_info.IsValid(); + ByteArray wifi_lan_service_info_bytes{wifi_lan_service_info_string}; + WifiLanServiceInfo wifi_lan_service_info{ + Base64Utils::Encode(wifi_lan_service_info_bytes)}; - EXPECT_FALSE(is_valid); + EXPECT_FALSE(wifi_lan_service_info.IsValid()); } } // namespace diff --git a/cpp/core_v2/payload_test.cc b/cpp/core_v2/payload_test.cc index 498efb7b..a839320f 100644 --- a/cpp/core_v2/payload_test.cc +++ b/cpp/core_v2/payload_test.cc @@ -28,7 +28,7 @@ TEST(PayloadTest, SupportsByteArrayType) { } TEST(PayloadTest, SupportsFileType) { - InputFile* raw_file = new InputFile("/path/to/file", 0); + InputFile* raw_file = new InputFile(/*payload_id=*/23, 0); std::unique_ptr file(raw_file); Payload payload(std::move(file)); EXPECT_EQ(payload.GetType(), Payload::Type::kFile); @@ -38,7 +38,7 @@ TEST(PayloadTest, SupportsFileType) { } TEST(PayloadTest, SupportsStreamType) { - InputFile* raw_file = new InputFile("/path/to/file", 0); + InputFile* raw_file = new InputFile(/*payload_id=*/17, 0); std::unique_ptr stream(raw_file); Payload payload(std::move(stream)); EXPECT_EQ(payload.GetType(), Payload::Type::kStream); diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index a0d279b6..2e9bcb30 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -3,7 +3,6 @@ cc_library( srcs = [ "base64_utils.cc", "cancelable_alarm.cc", - "file_impl.cc", "pipe.cc", "prng.cc", "reliability_utils.cc", @@ -11,7 +10,6 @@ cc_library( hdrs = [ "base64_utils.h", "cancelable_alarm.h", - "file_impl.h", "pipe.h", "prng.h", "reliability_utils.h", @@ -63,7 +61,7 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", - "//platform_v2/public:__pkg__", + "//platform_v2/base:__pkg__", ], deps = [ "//absl/base", @@ -78,7 +76,6 @@ cc_test( "atomic_reference_test.cc", "byte_array_test.cc", "container_of_test.cc", - "file_impl_test.cc", "pipe_test.cc", "prng_test.cc", "ptr_test.cc", @@ -86,7 +83,6 @@ cc_test( ], deps = [ ":utils", - "//file/util:temp_path", "//platform:types", "//platform/api", "//platform/impl/g3", 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/platform.h b/cpp/platform/api/platform.h index 70260c7f..080b5ce0 100644 --- a/cpp/platform/api/platform.h +++ b/cpp/platform/api/platform.h @@ -12,7 +12,9 @@ #include "platform/api/condition_variable.h" #include "platform/api/count_down_latch.h" #include "platform/api/hash_utils.h" +#include "platform/api/input_file.h" #include "platform/api/lock.h" +#include "platform/api/output_file.h" #include "platform/api/scheduled_executor.h" #include "platform/api/server_sync.h" #include "platform/api/settable_future_def.h" @@ -71,6 +73,9 @@ class ImplementationPlatform { static Ptr createHashUtils(); static Ptr createThreadUtils(); static Ptr createSystemClock(); + static Ptr createInputFile(std::int64_t payload_id, + std::int64_t total_size); + static Ptr createOutputFile(std::int64_t payload_id); // Java-like Executors // Type aliases used to API 1.0 compatibility. @@ -96,7 +101,6 @@ class ImplementationPlatform { static Ptr createWebRtcSignalingMessenger( const std::string& self_id); static std::string getDeviceId(); - static std::string getPayloadPath(int64_t payload_id); }; } // namespace platform 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/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD index e043b58e..c231beae 100644 --- a/cpp/platform/impl/g3/BUILD +++ b/cpp/platform/impl/g3/BUILD @@ -15,6 +15,7 @@ cc_library( "//platform:types", "//platform/api", "//platform/impl/shared:atomic_boolean", + "//platform/impl/shared:file", "//platform/impl/shared:posix_condition_variable", "//platform/impl/shared:posix_lock", "//platform/port:string", diff --git a/cpp/platform/impl/g3/platform.cc b/cpp/platform/impl/g3/platform.cc index b261cbe0..abd700f6 100644 --- a/cpp/platform/impl/g3/platform.cc +++ b/cpp/platform/impl/g3/platform.cc @@ -25,6 +25,7 @@ #include "platform/impl/g3/settable_future_impl.h" #include "platform/impl/g3/system_clock_impl.h" #include "platform/impl/shared/atomic_boolean_impl.h" +#include "platform/impl/shared/file_impl.h" #include "platform/impl/shared/posix_condition_variable.h" #include "platform/impl/shared/posix_lock.h" #include "platform/port/string.h" @@ -37,6 +38,12 @@ namespace location { namespace nearby { namespace platform { +namespace { +std::string getPayloadPath(std::int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} +} // namespace + Ptr ImplementationPlatform::createSingleThreadExecutor() { return Ptr(/*new SingleThreadExecutorImpl()*/); } @@ -87,6 +94,16 @@ Ptr ImplementationPlatform::createAtomicBoolean( return Ptr(new AtomicBooleanImpl(initial_value)); } +Ptr ImplementationPlatform::createInputFile( + std::int64_t payload_id, std::int64_t total_size) { + return MakePtr(new InputFileImpl(getPayloadPath(payload_id), total_size)); +} + +Ptr ImplementationPlatform::createOutputFile( + std::int64_t payload_id) { + return MakePtr(new OutputFileImpl(getPayloadPath(payload_id))); +} + Ptr ImplementationPlatform::createBluetoothClassicMedium() { return Ptr(); @@ -128,10 +145,6 @@ std::string ImplementationPlatform::getDeviceId() { return "google3"; } -std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); -} - } // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index 1ace2e42..dc3922f7 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -14,6 +14,7 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", + "//platform/impl/shared:file", "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", "//absl/time", diff --git a/cpp/platform/impl/sample/sample_platform.cc b/cpp/platform/impl/sample/sample_platform.cc index dc660c46..4ea9a98d 100644 --- a/cpp/platform/impl/sample/sample_platform.cc +++ b/cpp/platform/impl/sample/sample_platform.cc @@ -20,6 +20,7 @@ #include "platform/cancelable.h" #include "platform/impl/sample/atomic_reference_impl.h" #include "platform/impl/sample/settable_future_impl.h" +#include "platform/impl/shared/file_impl.h" #include "platform/impl/shared/sample/sample_wifi_medium.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -30,6 +31,12 @@ namespace location { namespace nearby { namespace platform { +namespace { +std::string getPayloadPath(std::int64_t payload_id) { + return "/tmp/sample-" + std::to_string(payload_id); +} +} // namespace + Ptr ImplementationPlatform::createScheduledExecutor() { return Ptr{}; } @@ -80,6 +87,16 @@ Ptr ImplementationPlatform::createAtomicBoolean( return Ptr{}; } +Ptr ImplementationPlatform::createInputFile( + std::int64_t payload_id, std::int64_t total_size) { + return MakePtr(new InputFileImpl(getPayloadPath(payload_id), total_size)); +} + +Ptr ImplementationPlatform::createOutputFile( + std::int64_t payload_id) { + return MakePtr(new OutputFileImpl(getPayloadPath(payload_id))); +} + Ptr ImplementationPlatform::createBluetoothClassicMedium() { return Ptr(); @@ -116,10 +133,6 @@ Ptr ImplementationPlatform::createHashUtils() { std::string ImplementationPlatform::getDeviceId() { return "sample"; } -std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { - return "/tmp/sample-" + std::to_string(payload_id); -} - } // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/shared/BUILD b/cpp/platform/impl/shared/BUILD index fb1850b2..c65c458d 100644 --- a/cpp/platform/impl/shared/BUILD +++ b/cpp/platform/impl/shared/BUILD @@ -43,3 +43,31 @@ cc_library( ], deps = ["//platform/api"], ) + +cc_library( + name = "file", + srcs = ["file_impl.cc"], + hdrs = ["file_impl.h"], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform/api", + ], +) + +cc_test( + name = "file_test", + timeout = "short", + srcs = [ + "file_impl_test.cc", + ], + deps = [ + ":file", + "//file/util:temp_path", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/platform/file_impl.cc b/cpp/platform/impl/shared/file_impl.cc similarity index 97% rename from cpp/platform/file_impl.cc rename to cpp/platform/impl/shared/file_impl.cc index 67bab338..2a21e8ea 100644 --- a/cpp/platform/file_impl.cc +++ b/cpp/platform/impl/shared/file_impl.cc @@ -1,4 +1,4 @@ -#include "platform/file_impl.h" +#include "platform/impl/shared/file_impl.h" #include #include diff --git a/cpp/platform/file_impl.h b/cpp/platform/impl/shared/file_impl.h similarity index 88% rename from cpp/platform/file_impl.h rename to cpp/platform/impl/shared/file_impl.h index 702cf7d0..5c2f33f7 100644 --- a/cpp/platform/file_impl.h +++ b/cpp/platform/impl/shared/file_impl.h @@ -1,5 +1,5 @@ -#ifndef PLATFORM_FILE_IMPL_H_ -#define PLATFORM_FILE_IMPL_H_ +#ifndef PLATFORM_IMPL_SHARED_FILE_IMPL_H_ +#define PLATFORM_IMPL_SHARED_FILE_IMPL_H_ #include #include @@ -43,4 +43,4 @@ class OutputFileImpl final : public OutputFile { } // namespace nearby } // namespace location -#endif // PLATFORM_FILE_IMPL_H_ +#endif // PLATFORM_IMPL_SHARED_FILE_IMPL_H_ diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/impl/shared/file_impl_test.cc similarity index 98% rename from cpp/platform/file_impl_test.cc rename to cpp/platform/impl/shared/file_impl_test.cc index d4a5b339..b2ce22e1 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/impl/shared/file_impl_test.cc @@ -1,4 +1,4 @@ -#include "platform/file_impl.h" +#include "platform/impl/shared/file_impl.h" #include #include diff --git a/cpp/platform_v2/api/BUILD b/cpp/platform_v2/api/BUILD index c9b0e5a4..a0d9013f 100644 --- a/cpp/platform_v2/api/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -1,12 +1,8 @@ cc_library( - name = "api", + name = "types", hdrs = [ "atomic_boolean.h", "atomic_reference.h", - "ble.h", - "ble_v2.h", - "bluetooth_adapter.h", - "bluetooth_classic.h", "cancelable.h", "condition_variable.h", "count_down_latch.h", @@ -17,12 +13,32 @@ cc_library( "listenable_future.h", "mutex.h", "output_file.h", - "platform.h", "scheduled_executor.h", - "server_sync.h", "settable_future.h", "submittable_executor.h", "system_clock.h", + ], + visibility = [ + "//platform_v2/base:__pkg__", + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//absl/base:core_headers", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "comm", + hdrs = [ + "ble.h", + "ble_v2.h", + "bluetooth_adapter.h", + "bluetooth_classic.h", + "server_sync.h", "webrtc.h", "wifi.h", "wifi_lan.h", @@ -30,14 +46,29 @@ cc_library( visibility = [ "//platform_v2/base:__pkg__", "//platform_v2/impl:__subpackages__", - "//platform_v2/public:__subpackages__", + "//platform_v2/public:__pkg__", ], deps = [ "//platform_v2/base", - "//absl/base:core_headers", "//absl/strings", - "//absl/time", + "//absl/types:optional", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_library( + name = "platform", + hdrs = [ + "platform.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + ":comm", + ":types", + "//absl/strings", "//absl/types:any", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform_v2/api/ble_v2.h b/cpp/platform_v2/api/ble_v2.h index 8858037e..ae094d1e 100644 --- a/cpp/platform_v2/api/ble_v2.h +++ b/cpp/platform_v2/api/ble_v2.h @@ -5,13 +5,13 @@ #include #include #include -#include #include #include #include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" namespace location { namespace nearby { @@ -119,7 +119,7 @@ class ClientGattConnection { // // It is okay for duplicate services to exist, as long as the specified // characteristic UUID is unique among all services of the same UUID. - virtual std::optional GetCharacteristic( + virtual absl::optional GetCharacteristic( absl::string_view service_uuid, absl::string_view characteristic_uuid) = 0; @@ -127,7 +127,7 @@ class ClientGattConnection { // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue() // // Reads a GATT characteristic. No value is returned upon error. - virtual std::optional ReadCharacteristic( + virtual absl::optional ReadCharacteristic( const GattCharacteristic& characteristic) = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) @@ -209,7 +209,7 @@ class GattServer { // descriptor and subscribe for characteristic changes. For more information // about this descriptor, please go to: // https://www.bluetooth.com/specifications/Gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.Gatt.client_characteristic_configuration.xml - virtual std::optional CreateCharacteristic( + virtual absl::optional CreateCharacteristic( absl::string_view service_uuid, absl::string_view characteristic_uuid, const std::set& permissions, const std::set& properties) = 0; diff --git a/cpp/platform_v2/api/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h index 8919dc8b..fa3a6061 100644 --- a/cpp/platform_v2/api/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -7,8 +7,8 @@ #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" namespace location { namespace nearby { @@ -17,7 +17,7 @@ namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice { public: - virtual ~BluetoothDevice() {} + virtual ~BluetoothDevice() = default; // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() virtual std::string GetName() const = 0; @@ -26,32 +26,45 @@ class BluetoothDevice { // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. class BluetoothSocket { public: - virtual ~BluetoothSocket() {} + virtual ~BluetoothSocket() = default; - // Returns the InputStream of the BluetoothSocket. + // NOTE: + // It is an undefined behavior if GetInputStream() or GetOutputStream() is + // called for a not-connected BluetoothSocket, i.e. any object that is not + // returned by BluetoothClassicMedium::ConnectToService() for client side or + // BluetoothServerSocket::Accept() for server side of connection. + + // Returns the InputStream of this connected BluetoothSocket. virtual InputStream& GetInputStream() = 0; - // Returns the OutputStream of the BluetoothSocket. + // Returns the OutputStream of this connected BluetoothSocket. virtual OutputStream& GetOutputStream() = 0; - // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close() - // + // Closes both input and output streams, marks Socket as closed. + // After this call object should be treated as not connected. // Returns Exception::kIo on error, Exception::kSuccess otherwise. virtual Exception Close() = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() - virtual BluetoothDevice& GetRemoteDevice() = 0; + // Returns valid BluetoothDevice pointer if there is a connection, and + // nullptr otherwise. + virtual BluetoothDevice* GetRemoteDevice() = 0; }; // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. class BluetoothServerSocket { public: - virtual ~BluetoothServerSocket() {} + virtual ~BluetoothServerSocket() = default; // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() // - // returns Exception::kIo on error. - virtual ExceptionOr> Accept() = 0; + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + virtual std::unique_ptr Accept() = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() // @@ -63,31 +76,33 @@ class BluetoothServerSocket { // medium. class BluetoothClassicMedium { public: - virtual ~BluetoothClassicMedium() {} + virtual ~BluetoothClassicMedium() = default; - class DiscoveryCallback { - public: - virtual ~DiscoveryCallback() {} - - // BluetoothDevice* is not owned by callbacks. - // Pointer is guaranteed to remain valid for the duration of a call. - virtual void OnDeviceDiscovered(BluetoothDevice* device) = 0; - virtual void OnDeviceNameChanged(BluetoothDevice* device) = 0; - virtual void OnDeviceLost(BluetoothDevice* device) = 0; + struct DiscoveryCallback { + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + std::function device_discovered_cb = + DefaultCallback(); + std::function device_name_changed_cb = + DefaultCallback(); + std::function device_lost_cb = + DefaultCallback(); }; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() // // Returns true once the process of discovery has been initiated. - // - // Does not take ownership of the passed-in discovery_callback -- destroying - // that is up to the caller. - virtual bool StartDiscovery(const DiscoveryCallback& discovery_callback) = 0; + virtual bool StartDiscovery(DiscoveryCallback discovery_callback) = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() // // Returns true once discovery is well and truly stopped; after this returns, // there must be no more invocations of the DiscoveryCallback passed in to - // startDiscovery(). + // StartDiscovery(). virtual bool StopDiscovery() = 0; // A combination of @@ -101,10 +116,10 @@ class BluetoothClassicMedium { // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) // UUID. // - // On success, returns a new BluetoothSocket, wrapped in a ExceptionOr object. - // On error, returns Exception object. - virtual ExceptionOr> ConnectToService( - BluetoothDevice* remote_device, absl::string_view service_uuid) = 0; + // On success, returns a new BluetoothSocket. + // On error, returns nullptr. + virtual std::unique_ptr ConnectToService( + BluetoothDevice& remote_device, const std::string& service_uuid) = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord // @@ -114,9 +129,9 @@ class BluetoothClassicMedium { // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) // UUID. // - // Returns Exception::kIo on error. - virtual ExceptionOr> ListenForService( - absl::string_view service_name, absl::string_view service_uuid) = 0; + // Returns nullptr error. + virtual std::unique_ptr ListenForService( + const std::string& service_name, const std::string& service_uuid) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index ef217692..f710897f 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -14,7 +14,9 @@ #include "platform_v2/api/condition_variable.h" #include "platform_v2/api/count_down_latch.h" #include "platform_v2/api/crypto.h" +#include "platform_v2/api/input_file.h" #include "platform_v2/api/mutex.h" +#include "platform_v2/api/output_file.h" #include "platform_v2/api/scheduled_executor.h" #include "platform_v2/api/server_sync.h" #include "platform_v2/api/settable_future.h" @@ -41,6 +43,7 @@ class ImplementationPlatform { // - condition variable (must work with regular mutex only) // - Future : to synchronize on Callable schduled to execute. // - CountDownLatch : to ensure at least N threads are waiting. + // - file I/O static std::unique_ptr> CreateAtomicReferenceAny( absl::any initial_value); static std::unique_ptr> CreateSettableFutureAny(); @@ -50,6 +53,9 @@ class ImplementationPlatform { 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, + std::int64_t total_size); + static std::unique_ptr CreateOutputFile(std::int64_t payload_id); // Java-like Executors static std::unique_ptr CreateSingleThreadExecutor(); @@ -65,10 +71,8 @@ class ImplementationPlatform { static std::unique_ptr CreateServerSyncMedium(); static std::unique_ptr CreateWifiMedium(); static std::unique_ptr CreateWifiLanMedium(); - static std::unique_ptr - CreateWebRtcSignalingMessenger(absl::string_view self_id); + static std::unique_ptr CreateWebRtcMedium(); static std::string GetDeviceId(); - static std::string GetPayloadPath(std::int64_t payload_id); }; } // namespace api diff --git a/cpp/platform_v2/api/webrtc.h b/cpp/platform_v2/api/webrtc.h index ee507e9d..d07bc699 100644 --- a/cpp/platform_v2/api/webrtc.h +++ b/cpp/platform_v2/api/webrtc.h @@ -1,10 +1,11 @@ #ifndef PLATFORM_V2_API_WEBRTC_H_ #define PLATFORM_V2_API_WEBRTC_H_ -#include +#include #include "platform_v2/base/byte_array.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "absl/strings/string_view.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { @@ -12,33 +13,32 @@ namespace api { class WebRtcSignalingMessenger { public: + using OnSignalingMessageCallback = std::function; + virtual ~WebRtcSignalingMessenger() = default; - /** Called whenever we receive an inbox message from tachyon. */ - class SignalingMessageListener { - public: - virtual ~SignalingMessageListener() = default; - - virtual void OnSignalingMessage(const ByteArray& message) = 0; - }; - - class IceServersListener { - public: - virtual ~IceServersListener() = default; - - virtual void OnIceServersFetched( - std::vector - ice_servers) = 0; - }; - - virtual bool RegisterSignaling() = 0; - virtual bool UnregisterSignaling() = 0; - virtual bool SendMessage(std::string_view peer_id, + virtual bool SendMessage(absl::string_view peer_id, const ByteArray& message) = 0; - virtual bool StartReceivingMessages( - const SignalingMessageListener& listener) = 0; - virtual void GetIceServers( - const IceServersListener& ice_servers_listener) = 0; + + virtual bool StartReceivingMessages(OnSignalingMessageCallback listener) = 0; + virtual void StopReceivingMessages() = 0; +}; + +class WebRtcMedium { + public: + using PeerConnectionCallback = + std::function)>; + + virtual ~WebRtcMedium() = default; + + // Creates and returns a new webrtc::PeerConnectionInterface object via + // |callback|. + virtual void CreatePeerConnection(webrtc::PeerConnectionObserver* observer, + PeerConnectionCallback callback) = 0; + + // Returns a signaling messenger for sending WebRTC signaling messages. + virtual std::unique_ptr GetSignalingMessenger( + absl::string_view self_id) = 0; }; } // namespace api diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD index d11245eb..81c320fa 100644 --- a/cpp/platform_v2/base/BUILD +++ b/cpp/platform_v2/base/BUILD @@ -24,6 +24,7 @@ cc_library( "//platform_v2/api:__subpackages__", ], deps = [ + "//absl/meta:type_traits", "//absl/strings", "//absl/time", ], @@ -32,9 +33,11 @@ cc_library( cc_library( name = "util", srcs = [ + "base_input_stream.cc", "base_pipe.cc", ], hdrs = [ + "base_input_stream.h", "base_mutex_lock.h", "base_pipe.h", ], @@ -44,11 +47,47 @@ cc_library( ], deps = [ ":base", - "//platform_v2/api", + "//platform_v2/api:types", "//absl/base:core_headers", ], ) +cc_library( + name = "logging", + hdrs = [ + "logging.h", + ], + visibility = [ + "//platform_v2:__subpackages__", + ], + deps = [ + "//platform:logging", + ], +) + +cc_library( + name = "test_util", + testonly = True, + srcs = [ + "medium_environment.cc", + ], + hdrs = [ + "medium_environment.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + ":base", + ":logging", + "//platform_v2/api:comm", + "//platform_v2/public:types", + "//absl/container:flat_hash_map", + ], +) + cc_test( name = "platform_base_test", srcs = [ diff --git a/cpp/platform_v2/base/base_input_stream.cc b/cpp/platform_v2/base/base_input_stream.cc new file mode 100644 index 00000000..7c78bb36 --- /dev/null +++ b/cpp/platform_v2/base/base_input_stream.cc @@ -0,0 +1,85 @@ +#include "platform_v2/base/base_input_stream.h" + +namespace location { +namespace nearby { + +ExceptionOr BaseInputStream::Read(std::int64_t size) { + if (!IsAvailable(size)) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray read_bytes{static_cast(size)}; + if (read_bytes.CopyAt(/*offset=*/0, buffer_, + /*source_offset=*/position_)) { + position_ += size; + return ExceptionOr{read_bytes}; + } else { + return ExceptionOr{Exception::kIo}; + } +} + +std::uint8_t BaseInputStream::ReadUint8() { + constexpr int byte_size = sizeof(std::uint8_t); + ByteArray read_bytes = ReadBytes(byte_size); + if (read_bytes.Empty() || read_bytes.size() != byte_size) { + return -1; + } + + return read_bytes.data()[0]; +} + +std::uint16_t BaseInputStream::ReadUint16() { + constexpr int byte_size = sizeof(std::uint16_t); + ByteArray read_bytes = ReadBytes(byte_size); + if (read_bytes.Empty() || read_bytes.size() != byte_size) { + return -1; + } + + // Convert from network order. + const char *data = read_bytes.data(); + return static_cast(data[0]) << 8 | static_cast(data[1]); +} + +std::uint32_t BaseInputStream::ReadUint32() { + constexpr int byte_size = sizeof(std::uint32_t); + ByteArray read_bytes = ReadBytes(byte_size); + if (read_bytes.Empty() || read_bytes.size() != byte_size) { + return -1; + } + + // Convert from network order. + const char *data = read_bytes.data(); + return static_cast(data[0]) << 24 | + static_cast(data[1]) << 16 | + static_cast(data[2]) << 8 | static_cast(data[3]); +} + +std::uint64_t BaseInputStream::ReadUint64() { + constexpr int byte_size = sizeof(std::uint64_t); + ByteArray read_bytes = ReadBytes(byte_size); + if (read_bytes.Empty() || read_bytes.size() != byte_size) { + return -1; + } + + // Convert from network order. + const char *data = read_bytes.data(); + return static_cast(data[0]) << 56 | + static_cast(data[1]) << 48 | + static_cast(data[2]) << 40 | + static_cast(data[3]) << 32 | + static_cast(data[4]) << 24 | + static_cast(data[5]) << 16 | + static_cast(data[6]) << 8 | static_cast(data[7]); +} + +ByteArray BaseInputStream::ReadBytes(int size) { + ExceptionOr read_bytes_result = Read(size); + if (!read_bytes_result.ok()) { + return ByteArray{}; + } + + return read_bytes_result.GetResult(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base_input_stream.h b/cpp/platform_v2/base/base_input_stream.h new file mode 100644 index 00000000..12044b4d --- /dev/null +++ b/cpp/platform_v2/base/base_input_stream.h @@ -0,0 +1,44 @@ +#ifndef PLATFORM_V2_BASE_BASE_INPUT_STREAM_H_ +#define PLATFORM_V2_BASE_BASE_INPUT_STREAM_H_ + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" + +namespace location { +namespace nearby { + +// A base {@link InputStream } for reading the contents of a byte array. +class BaseInputStream : public InputStream { + public: + explicit BaseInputStream(ByteArray &buffer) : buffer_{buffer} {} + BaseInputStream(const BaseInputStream &) = delete; + BaseInputStream &operator=(const BaseInputStream &) = delete; + ~BaseInputStream() override { Close(); } + + ExceptionOr Read(std::int64_t size) override; + + Exception Close() override { + // Do nothing. + return {Exception::kSuccess}; + } + + std::uint8_t ReadUint8(); + std::uint16_t ReadUint16(); + std::uint32_t ReadUint32(); + std::uint64_t ReadUint64(); + bool IsAvailable(int size) const { + return buffer_.size() - position_ >= size; + } + + private: + ByteArray ReadBytes(int size); + + ByteArray &buffer_; + int position_{0}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_INPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/base_pipe.cc b/cpp/platform_v2/base/base_pipe.cc index e97ace56..bd20f935 100644 --- a/cpp/platform_v2/base/base_pipe.cc +++ b/cpp/platform_v2/base/base_pipe.cc @@ -1,6 +1,5 @@ #include "platform_v2/base/base_pipe.h" -#include "platform_v2/api/platform.h" #include "platform_v2/base/base_mutex_lock.h" #include "platform_v2/base/input_stream.h" #include "platform_v2/base/output_stream.h" diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h index 81036f24..19063505 100644 --- a/cpp/platform_v2/base/byte_array.h +++ b/cpp/platform_v2/base/byte_array.h @@ -19,7 +19,9 @@ class ByteArray { ByteArray& operator=(ByteArray&&) = default; // Create ByteArray from string. - explicit ByteArray(absl::string_view source) { data_ = source; } + explicit ByteArray(absl::string_view source) { + SetData(source.data(), source.size()); + } // Create default-initialized ByteArray of a given size. explicit ByteArray(size_t size) { SetData(size); } diff --git a/cpp/platform_v2/base/exception.h b/cpp/platform_v2/base/exception.h index c9e73425..382c5728 100644 --- a/cpp/platform_v2/base/exception.h +++ b/cpp/platform_v2/base/exception.h @@ -1,9 +1,10 @@ #ifndef PLATFORM_V2_BASE_EXCEPTION_H_ #define PLATFORM_V2_BASE_EXCEPTION_H_ -#include #include +#include "absl/meta/type_traits.h" + namespace location { namespace nearby { @@ -64,7 +65,7 @@ class ExceptionOr { ExceptionOr(Exception exception) : exception_{exception} {} // NOLINT // If there exists explicit conversion from from U to T, // then allow explicit conversion from ExceptionOr to ExceptionOr. - template ()})>> + template ()})>> explicit ExceptionOr(ExceptionOr value) { if (!value.ok()) { exception_ = value.GetException(); diff --git a/cpp/platform_v2/base/logging.h b/cpp/platform_v2/base/logging.h new file mode 100644 index 00000000..f86e1a2e --- /dev/null +++ b/cpp/platform_v2/base/logging.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_V2_BASE_LOGGING_H_ +#define PLATFORM_V2_BASE_LOGGING_H_ + +#include "platform/logging.h" + +#endif // PLATFORM_V2_BASE_LOGGING_H_ diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc new file mode 100644 index 00000000..6c47cc33 --- /dev/null +++ b/cpp/platform_v2/base/medium_environment.cc @@ -0,0 +1,191 @@ +#include "platform_v2/base/medium_environment.h" + +#include +#include +#include +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/logging.h" +#include "platform_v2/public/count_down_latch.h" + +namespace location { +namespace nearby { + +MediumEnvironment& MediumEnvironment::Instance() { + static std::aligned_storage_t + storage; + static MediumEnvironment* env = new (&storage) MediumEnvironment(); + return *env; +} + +void MediumEnvironment::Reset() { + RunOnMediumEnvironmentThread([this]() { + bluetooth_adapters_.clear(); + bluetooth_mediums_.clear(); + }); + Sync(); +} + +void MediumEnvironment::Sync(bool enable_notifications) { + enable_notifications_ = enable_notifications; + int count = 0; + do { + CountDownLatch latch(1); + count = job_count_ + 1; + // We are about to schedule one last job. + // When it is done, counter must be equal to count. + // However, if pending jobs schedule anything else, + // it will be pending after us. + // If we want to ensure we are completely idle, then we have to + // repeat sync, until this becomes true. + RunOnMediumEnvironmentThread([&latch]() { latch.CountDown(); }); + latch.Await(); + } while (count < job_count_); + NEARBY_LOG(INFO, "MediumEnvironment::Sync(): done [count=%d]", count); +} + +void MediumEnvironment::OnBluetoothAdapterChangedState( + api::BluetoothAdapter& adapter, api::BluetoothDevice& adapter_device, + std::string name, bool enabled, api::BluetoothAdapter::ScanMode mode) { + RunOnMediumEnvironmentThread([this, &adapter, &adapter_device, + name = std::move(name), enabled, mode]() { + NEARBY_LOG(INFO, + "[adapter=%p, device=%p] update: name=%s, enabled=%d, mode=%d", + &adapter, &adapter_device, name.c_str(), enabled, mode); + for (auto& [medium, info] : bluetooth_mediums_) { + // Do not send notification to medium that owns this adapter. + 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); + } + // 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 + // (since it is owned by the correspoinding Medium, and mediums lifetime + // matches Core lifetime). + bluetooth_adapters_.emplace(&adapter, &adapter_device); + }); +} + +void MediumEnvironment::OnDeviceStateChanged( + BluetoothMediumContext& info, api::BluetoothDevice& device, + const std::string& name, api::BluetoothAdapter::ScanMode mode, + bool enabled) { + 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()); + if (mode == api::BluetoothAdapter::ScanMode::kConnectableDiscoverable && + enabled) { + // New device is turned on, and is in discoverable state. + // Store device name, and report it as discovered. + info.devices.emplace(&device, name); + if (enable_notifications_) { + RunOnMediumEnvironmentThread( + [&info, &device]() { info.callback.device_discovered_cb(device); }); + } + } + } else { + NEARBY_LOG( + INFO, + "G3 OnDeviceStateChanged [device impl=%p]: exisitng device; notify=%d", + &device, enable_notifications_.load()); + auto& discovered_name = item->second; + if (mode == api::BluetoothAdapter::ScanMode::kConnectableDiscoverable && + enabled) { + if (name != discovered_name) { + // Known device is turned on, and is in discoverable state. + // Store device name, and report it as renamed. + item->second = name; + if (enable_notifications_) { + RunOnMediumEnvironmentThread([&info, &device]() { + info.callback.device_name_changed_cb(device); + }); + } + } else { + // Device is in discovery mode, so we are reporting it anyway. + if (enable_notifications_) { + RunOnMediumEnvironmentThread([&info, &device]() { + info.callback.device_discovered_cb(device); + }); + } + } + } + if (!enabled) { + // Known device is turned off. + // Erase it from the map, and report as lost. + if (enable_notifications_) { + RunOnMediumEnvironmentThread( + [&info, &device]() { info.callback.device_lost_cb(device); }); + } + info.devices.erase(item); + } + } +} + +void MediumEnvironment::RunOnMediumEnvironmentThread( + std::function runnable) { + job_count_++; + executor_.Execute(std::move(runnable)); +} + +void MediumEnvironment::RegisterBluetoothMedium( + api::BluetoothClassicMedium& medium, + api::BluetoothAdapter& medium_adapter) { + RunOnMediumEnvironmentThread([this, &medium, &medium_adapter]() { + auto& context = bluetooth_mediums_ + .insert({&medium, + BluetoothMediumContext{ + .adapter = &medium_adapter, + }}) + .first->second; + auto* owned_adapter = context.adapter; + NEARBY_LOG(INFO, "Registered: medium=%p; adapter=%p", &medium, + owned_adapter); + for (auto& [adapter, device] : bluetooth_adapters_) { + if (adapter == nullptr) continue; + OnDeviceStateChanged(context, *device, adapter->GetName(), + adapter->GetScanMode(), adapter->IsEnabled()); + } + }); +} + +void MediumEnvironment::UpdateBluetoothMedium( + api::BluetoothClassicMedium& medium, BluetoothDiscoveryCallback callback) { + RunOnMediumEnvironmentThread([this, &medium, + callback = std::move(callback)]() { + auto item = bluetooth_mediums_.find(&medium); + if (item == bluetooth_mediums_.end()) return; + auto& context = item->second; + context.callback = std::move(callback); + auto* owned_adapter = context.adapter; + NEARBY_LOG( + INFO, + "Updated: this=%p; medium=%p; adapter=%p; name=%s; enabled=%d; mode=%d", + this, &medium, owned_adapter, owned_adapter->GetName().c_str(), + 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()); + } + }); +} + +void MediumEnvironment::UnregisterBluetoothMedium( + api::BluetoothClassicMedium& medium) { + RunOnMediumEnvironmentThread([this, &medium]() { + auto item = bluetooth_mediums_.extract(&medium); + if (item.empty()) return; + auto& context = item.mapped(); + NEARBY_LOG(INFO, "Unregistered medium for device=%s", + context.adapter->GetName().c_str()); + }); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h new file mode 100644 index 00000000..b00eafc2 --- /dev/null +++ b/cpp/platform_v2/base/medium_environment.h @@ -0,0 +1,113 @@ +#ifndef PLATFORM_V2_BASE_MEDIUM_ENVIRONMENT_H_ +#define PLATFORM_V2_BASE_MEDIUM_ENVIRONMENT_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/listeners.h" +#include "platform_v2/public/single_thread_executor.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { + +// MediumEnvironment is a simulated environment which allows multiple instances +// of simulated HW devices to "work" together as if they are physical. +// For each medium type it provides necessary methods to implement +// advertising, discovery and establishment of a data link. +// NOTE: this code depends on public:types target. +class MediumEnvironment { + public: + using BluetoothDiscoveryCallback = + api::BluetoothClassicMedium::DiscoveryCallback; + MediumEnvironment(const MediumEnvironment&) = delete; + MediumEnvironment& operator=(const MediumEnvironment&) = delete; + + // Creates and returns a reference to the global test environment instance. + static MediumEnvironment& Instance(); + + // Clears state. No notifications are sent. + void Reset(); + + // Waits for all previously scheduled jobs to finish. + // This method works as a barrier that guarantees that after it returns, all + // the activities that started before it was called, or while it was running + // are ended. This means that system is at the state of relaxation when this + // code returns. It requires external stimulus to get out of relaxation state. + // + // If enable_notifications is true (default), simulation environment + // will send all future notification events to all registered objects, + // whenever protocol requires that. This is expected behavior. + // If enabled_notifications is false, future event notifications will not be + // sent to registered instances. This is useful for protocol shutdown, + // where we no longer care about notifications, and where notifications may + // otherwise be delivered after the notification source or target lifeteme has + // ended, and cause undefined behavior. + void Sync(bool enable_notifications = true); + + // Adds an adapter to internal container. + // Notify BluetoothClassicMediums if any that adapter state has changed. + void OnBluetoothAdapterChangedState(api::BluetoothAdapter& adapter, + api::BluetoothDevice& adapter_device, + std::string name, bool enabled, + api::BluetoothAdapter::ScanMode mode); + + // Adds medium-related info to allow for adapter discovery to work. + // This provides acccess to this medium from other mediums, when protocol + // expects they should communicate. + void RegisterBluetoothMedium(api::BluetoothClassicMedium& medium, + api::BluetoothAdapter& medium_adapter); + + // Updates callback info to allow for dispatch of discovery events. + // + // Invokes callback asynchronously when any changes happen to discoverable + // devices, or if the defice is turned off, whether or not it is discoverable, + // if it was ever reported as discoverable. + // + // This should be called when discoverable state changes. + // with user-specified callback when discovery is enabled, and with default + // (empty) callback otherwise. + void UpdateBluetoothMedium(api::BluetoothClassicMedium& medium, + BluetoothDiscoveryCallback callback); + + // Removes medium-related info. This should correspond to device power off. + void UnregisterBluetoothMedium(api::BluetoothClassicMedium& medium); + + private: + struct BluetoothMediumContext { + BluetoothDiscoveryCallback callback; + api::BluetoothAdapter* adapter = nullptr; + // discovered device vs device name map. + absl::flat_hash_map devices; + }; + + // 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 + // destructor is not scheduled for execution at exit. + MediumEnvironment() = default; + ~MediumEnvironment() = default; + + void OnDeviceStateChanged(BluetoothMediumContext& info, + api::BluetoothDevice& device, + const std::string& name, + api::BluetoothAdapter::ScanMode mode, bool enabled); + void RunOnMediumEnvironmentThread(std::function runnable); + + std::atomic_int job_count_ = 0; + std::atomic_bool enable_notifications_ = false; + SingleThreadExecutor executor_; + + // The following data members are accessed in the context of a private + // executor_ thread. + absl::flat_hash_map + bluetooth_adapters_; + absl::flat_hash_map + bluetooth_mediums_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_MEDIUM_ENVIRONMENT_H_ diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 8f99b81f..58b31cb9 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -1,38 +1,32 @@ cc_library( - name = "g3", + name = "types", + testonly = True, srcs = [ + "scheduled_executor.cc", + "system_clock.cc", + ], + hdrs = [ "atomic_boolean.h", "atomic_reference_any.h", - "bluetooth_adapter.cc", - "bluetooth_adapter.h", "condition_variable.h", "count_down_latch.h", - "medium_environment.cc", - "medium_environment.h", "multi_thread_executor.h", "mutex.h", - "platform.cc", - "scheduled_executor.cc", + "pipe.h", "scheduled_executor.h", "settable_future_any.h", "single_thread_executor.h", - "system_clock.cc", ], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//core_v2:__subpackages__", - "//platform_v2:__subpackages__", + "//platform_v2/impl/g3:__pkg__", ], deps = [ - ":crypto", # build_cleaner: keep - "//platform_v2/api", + "//platform_v2/api:platform", + "//platform_v2/api:types", "//platform_v2/base", + "//platform_v2/base:util", "//platform_v2/impl/shared:posix_mutex", "//absl/base:core_headers", - "//absl/container:flat_hash_map", - "//absl/container:flat_hash_set", - "//absl/memory", - "//absl/strings", "//absl/synchronization", "//absl/time", "//absl/types:any", @@ -40,8 +34,36 @@ cc_library( ], ) +cc_library( + name = "comm", + testonly = True, + srcs = [ + "bluetooth_adapter.cc", + "webrtc.cc", + ], + hdrs = [ + "bluetooth_adapter.h", + "webrtc.h", + ], + visibility = [ + "//platform_v2/impl/g3:__pkg__", + ], + deps = [ + ":types", + "//platform_v2/api:comm", + "//platform_v2/base:test_util", + "//absl/base:core_headers", + "//absl/strings", + "//absl/synchronization", + "//webrtc/api:create_peerconnection_factory", #buildcleaner: keep + "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/api/task_queue:default_task_queue_factory", + ], +) + cc_library( name = "crypto", + testonly = True, srcs = [ "crypto.cc", ], @@ -49,9 +71,34 @@ cc_library( "//platform_v2/g3:__pkg__", ], deps = [ - "//platform_v2/api", + "//platform_v2/api:types", "//platform_v2/base", "//absl/strings", "//openssl:crypto", ], ) + +cc_library( + name = "g3", + testonly = True, + srcs = [ + "platform.cc", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":comm", + ":crypto", # build_cleaner: keep + ":types", + "//platform_v2/api:comm", + "//platform_v2/api:platform", + "//platform_v2/api:types", + "//platform_v2/impl/shared:file", + "//absl/base:core_headers", + "//absl/memory", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc index 16059f53..505ead84 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -2,7 +2,7 @@ #include -#include "platform_v2/impl/g3/medium_environment.h" +#include "platform_v2/base/medium_environment.h" namespace location { namespace nearby { @@ -11,15 +11,22 @@ namespace g3 { BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) : adapter_(*adapter) {} +BluetoothAdapter::~BluetoothAdapter() { SetStatus(Status::kDisabled); } + std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } -bool BluetoothAdapter::SetStatus(Status status) ABSL_LOCKS_EXCLUDED(mutex_) { - absl::MutexLock lock(&mutex_); - enabled_ = (status == Status::kEnabled); - RunOnCallbackThread([this]() { - auto& env = MediumEnvironment::Instance(); - env.OnBluetoothAdapterChangedState(*this); - }); +bool BluetoothAdapter::SetStatus(Status status) { + BluetoothAdapter::ScanMode mode; + bool enabled = status == Status::kEnabled; + std::string name; + { + absl::MutexLock lock(&mutex_); + enabled_ = enabled; + name = name_; + mode = mode_; + } + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this, device_, name, enabled, mode); return true; } @@ -34,13 +41,17 @@ BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { } bool BluetoothAdapter::SetScanMode(BluetoothAdapter::ScanMode mode) { - absl::MutexLock lock(&mutex_); - if (enabled_) return false; - mode_ = mode; - RunOnCallbackThread([this]() { - auto& env = MediumEnvironment::Instance(); - env.OnBluetoothAdapterChangedState(*this); - }); + bool enabled; + std::string name; + { + absl::MutexLock lock(&mutex_); + mode_ = mode; + name = name_; + enabled = enabled_; + } + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this, device_, std::move(name), enabled, + mode); return true; } @@ -50,13 +61,17 @@ std::string BluetoothAdapter::GetName() const { } bool BluetoothAdapter::SetName(absl::string_view name) { - absl::MutexLock lock(&mutex_); - if (enabled_) return false; - name_ = name; - RunOnCallbackThread([this]() { - auto& env = MediumEnvironment::Instance(); - env.OnBluetoothAdapterChangedState(*this); - }); + BluetoothAdapter::ScanMode mode; + bool enabled; + { + absl::MutexLock lock(&mutex_); + name_ = name; + enabled = enabled_; + mode = mode_; + } + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this, device_, std::string(name), enabled, + mode); return true; } diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h index 2654df4b..9747d7e0 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.h +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -24,7 +24,7 @@ class BluetoothDevice : public api::BluetoothDevice { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() std::string GetName() const override; - BluetoothAdapter& GetAdapter(); + BluetoothAdapter& GetAdapter() { return adapter_; } private: // Only BluetoothAdapter may instantiate BluetoothDevice. @@ -41,8 +41,8 @@ class BluetoothAdapter : public api::BluetoothAdapter { using Status = api::BluetoothAdapter::Status; using ScanMode = api::BluetoothAdapter::ScanMode; - BluetoothAdapter() = default; - ~BluetoothAdapter() override = default; + explicit BluetoothAdapter() = default; + ~BluetoothAdapter() override; // Synchronously sets the status of the BluetoothAdapter to 'status', and // returns true if the operation was a success. @@ -71,16 +71,11 @@ class BluetoothAdapter : public api::BluetoothAdapter { BluetoothDevice& GetDevice() { return device_; } private: - void RunOnCallbackThread(std::function runnable) { - serial_executor_.Execute(std::move(runnable)); - } - mutable absl::Mutex mutex_; BluetoothDevice device_{this}; ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; bool enabled_ ABSL_GUARDED_BY(mutex_) = false; - SingleThreadExecutor serial_executor_; }; } // namespace g3 diff --git a/cpp/platform_v2/impl/g3/medium_environment.cc b/cpp/platform_v2/impl/g3/medium_environment.cc deleted file mode 100644 index 5512a4fc..00000000 --- a/cpp/platform_v2/impl/g3/medium_environment.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include "platform_v2/impl/g3/medium_environment.h" - -namespace location { -namespace nearby { -namespace g3 { - -MediumEnvironment& MediumEnvironment::Instance() { - static std::aligned_storage_t - storage; - static MediumEnvironment* env = new (&storage) MediumEnvironment(); - return *env; -} - -void MediumEnvironment::Reset() { - absl::MutexLock lock(&mutex_); - bluetooth_adapters_.clear(); -} - -void MediumEnvironment::OnBluetoothAdapterChangedState( - BluetoothAdapter& adapter) { - absl::MutexLock lock(&mutex_); - // We don't care if there is an adapter already since all we store is a - // pointer. - bluetooth_adapters_.emplace(&adapter); - // TODO(apolyudov): Add event propagation code when Medium registration is - // implemented. -} - -} // namespace g3 -} // namespace nearby -} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.h b/cpp/platform_v2/impl/g3/medium_environment.h deleted file mode 100644 index 3f3f73c6..00000000 --- a/cpp/platform_v2/impl/g3/medium_environment.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ -#define PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ - -#include -#include -#include - -#include "platform_v2/api/bluetooth_classic.h" -#include "platform_v2/impl/g3/bluetooth_adapter.h" -#include "absl/container/flat_hash_map.h" -#include "absl/container/flat_hash_set.h" -#include "absl/synchronization/mutex.h" - -namespace location { -namespace nearby { -namespace g3 { - -// MediumEnvironment is a simulated environment which allowes multiple instances -// of simulated HW devices to "work" together as if they are physical. -// For each medium type it provides necessary methods to implement -// advertising, discovery and establishment of a data link. -class MediumEnvironment { - public: - ~MediumEnvironment() = default; - // Singleton constructor/accessor. - static MediumEnvironment& Instance(); - - // Clear state. No notifications are sent. - void Reset() ABSL_LOCKS_EXCLUDED(mutex_); - - // Add an adapter to internal container. - // Notify BluetoothClassicMediums if any that adapter state has changed. - void OnBluetoothAdapterChangedState(BluetoothAdapter& adapter) - ABSL_LOCKS_EXCLUDED(mutex_); - - private: - MediumEnvironment() = default; - absl::Mutex mutex_; - absl::flat_hash_set bluetooth_adapters_ - ABSL_GUARDED_BY(mutex_); -}; - -} // namespace g3 -} // namespace nearby -} // namespace location - -#endif // PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index 412dbacb..73673f77 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -28,6 +28,8 @@ #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/shared/file.h" #include "absl/base/integral_types.h" #include "absl/memory/memory.h" #include "absl/time/time.h" @@ -36,6 +38,12 @@ namespace location { namespace nearby { namespace api { +namespace { +std::string GetPayloadPath(std::int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} +} // namespace + std::unique_ptr ImplementationPlatform::CreateSingleThreadExecutor() { return absl::make_unique(); @@ -76,6 +84,17 @@ std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( return absl::make_unique(initial_value); } +std::unique_ptr ImplementationPlatform::CreateInputFile( + std::int64_t 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) { + return absl::make_unique(GetPayloadPath(payload_id)); +} + std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium() { return std::unique_ptr(); @@ -102,11 +121,8 @@ std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return std::unique_ptr(); } -std::unique_ptr -ImplementationPlatform::CreateWebRtcSignalingMessenger( - absl::string_view self_id) { - return std::unique_ptr( - /*new FCMSignalingMessenger()*/); +std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { + return absl::make_unique(); } std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { @@ -127,10 +143,6 @@ std::string ImplementationPlatform::GetDeviceId() { return "google3"; } -std::string ImplementationPlatform::GetPayloadPath(int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); -} - } // namespace api } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/impl/g3/webrtc.cc b/cpp/platform_v2/impl/g3/webrtc.cc new file mode 100644 index 00000000..6e70be50 --- /dev/null +++ b/cpp/platform_v2/impl/g3/webrtc.cc @@ -0,0 +1,36 @@ +#include "platform_v2/impl/g3/webrtc.h" + +#include "webrtc/api/task_queue/default_task_queue_factory.h" + +namespace location { +namespace nearby { +namespace g3 { + +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"; + + webrtc::PeerConnectionFactoryDependencies factory_dependencies; + factory_dependencies.task_queue_factory = + webrtc::CreateDefaultTaskQueueFactory(); + factory_dependencies.signaling_thread = signaling_thread.release(); + + callback(webrtc::CreateModularPeerConnectionFactory( + std::move(factory_dependencies)) + ->CreatePeerConnection(rtc_config, std::move(dependencies))); +} + +std::unique_ptr +WebRtcMedium::GetSignalingMessenger(absl::string_view self_id) { + // TODO(bfranz): Implement + return nullptr; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/webrtc.h b/cpp/platform_v2/impl/g3/webrtc.h new file mode 100644 index 00000000..053a30b8 --- /dev/null +++ b/cpp/platform_v2/impl/g3/webrtc.h @@ -0,0 +1,35 @@ +#ifndef PLATFORM_V2_IMPL_G3_WEBRTC_H_ +#define PLATFORM_V2_IMPL_G3_WEBRTC_H_ + +#include + +#include "platform_v2/api/webrtc.h" +#include "absl/strings/string_view.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace g3 { + +class WebRtcMedium : public api::WebRtcMedium { + public: + using PeerConnectionCallback = api::WebRtcMedium::PeerConnectionCallback; + + WebRtcMedium() = default; + ~WebRtcMedium() override = default; + + // Creates and returns a new webrtc::PeerConnectionInterface object via + // |callback|. + void CreatePeerConnection(webrtc::PeerConnectionObserver* observer, + PeerConnectionCallback callback) override; + + // Returns a signaling messenger for sending WebRTC signaling messages. + std::unique_ptr GetSignalingMessenger( + absl::string_view self_id) override; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_WEBRTC_H_ diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD index 013a0192..83d54e78 100644 --- a/cpp/platform_v2/impl/shared/BUILD +++ b/cpp/platform_v2/impl/shared/BUILD @@ -9,10 +9,7 @@ cc_library( visibility = [ "//platform_v2/impl:__subpackages__", ], - deps = [ - "//platform_v2/api", - "//platform_v2/base", - ], + deps = ["//platform_v2/api:types"], ) cc_library( @@ -28,7 +25,31 @@ cc_library( ], deps = [ ":posix_mutex", - "//platform_v2/api", + "//platform_v2/api:types", + ], +) + +cc_library( + name = "file", + srcs = ["file.cc"], + hdrs = ["file.h"], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api:types", "//platform_v2/base", + "//absl/strings", + ], +) + +cc_test( + name = "file_test", + srcs = ["file_test.cc"], + deps = [ + ":file", + "//file/util:temp_path", + "//platform_v2/base", + "//testing/base/public:gunit_main", ], ) diff --git a/cpp/platform_v2/public/file.cc b/cpp/platform_v2/impl/shared/file.cc similarity index 91% rename from cpp/platform_v2/public/file.cc rename to cpp/platform_v2/impl/shared/file.cc index 63e5bc8c..50571b02 100644 --- a/cpp/platform_v2/public/file.cc +++ b/cpp/platform_v2/impl/shared/file.cc @@ -1,4 +1,4 @@ -#include "platform_v2/public/file.h" +#include "platform_v2/impl/shared/file.h" #include #include @@ -8,6 +8,7 @@ namespace location { namespace nearby { +namespace shared { // InputFile @@ -47,7 +48,7 @@ Exception InputFile::Close() { // OutputFile -OutputFile::OutputFile(absl::string_view path) : file_(path) {} +OutputFile::OutputFile(absl::string_view path) : file_(std::string(path)) {} Exception OutputFile::Write(const ByteArray& data) { if (!file_.is_open()) { @@ -75,5 +76,6 @@ Exception OutputFile::Close() { return {Exception::kSuccess}; } +} // namespace shared } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/impl/shared/file.h b/cpp/platform_v2/impl/shared/file.h new file mode 100644 index 00000000..69e491ce --- /dev/null +++ b/cpp/platform_v2/impl/shared/file.h @@ -0,0 +1,53 @@ +#ifndef PLATFORM_V2_IMPL_SHARED_FILE_H_ +#define PLATFORM_V2_IMPL_SHARED_FILE_H_ + +#include +#include + +#include "platform_v2/api/input_file.h" +#include "platform_v2/api/output_file.h" +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace shared { + +class InputFile final : public api::InputFile { + public: + explicit InputFile(const std::string& path, std::int64_t size); + ~InputFile() override = default; + InputFile(InputFile&&) = default; + InputFile& operator=(InputFile&&) = default; + + ExceptionOr Read(std::int64_t size) override; + std::string GetFilePath() const override { return path_; } + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + private: + std::ifstream file_; + std::string path_; + std::int64_t total_size_; +}; + +class OutputFile final : public api::OutputFile { + public: + explicit OutputFile(absl::string_view path); + ~OutputFile() override = default; + OutputFile(OutputFile&&) = default; + OutputFile& operator=(OutputFile&&) = default; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + std::ofstream file_; +}; + +} // namespace shared +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_FILE_H_ diff --git a/cpp/platform_v2/public/file_test.cc b/cpp/platform_v2/impl/shared/file_test.cc similarity index 97% rename from cpp/platform_v2/public/file_test.cc rename to cpp/platform_v2/impl/shared/file_test.cc index d7d0a77d..69f7975e 100644 --- a/cpp/platform_v2/public/file_test.cc +++ b/cpp/platform_v2/impl/shared/file_test.cc @@ -1,4 +1,4 @@ -#include "platform_v2/public/file.h" +#include "platform_v2/impl/shared/file.h" #include #include @@ -11,6 +11,7 @@ namespace location { namespace nearby { +namespace shared { class FileTest : public ::testing::Test { protected: @@ -127,5 +128,6 @@ TEST_F(FileTest, OutputFile_Close) { EXPECT_EQ(output_file.Write(bytes), Exception{Exception::kIo}); } +} // namespace shared } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 5e260224..204d713a 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -1,13 +1,11 @@ cc_library( - name = "public", + name = "types", srcs = [ - "file.cc", "pipe.cc", ], hdrs = [ "atomic_boolean.h", "atomic_reference.h", - "bluetooth_adapter.h", "cancelable.h", "cancelable_alarm.h", "condition_variable.h", @@ -26,19 +24,38 @@ cc_library( ], visibility = [ "//core_v2:__subpackages__", - "//platform_v2/impl:__subpackages__", + "//platform_v2/base:__pkg__", + "//platform_v2/public:__pkg__", ], deps = [ - "//platform_v2/api", + "//platform_v2/api:platform", + "//platform_v2/api:types", "//platform_v2/base", "//platform_v2/base:util", "//absl/base:core_headers", - "//absl/strings", "//absl/time", "//absl/types:any", ], ) +cc_library( + name = "comm", + hdrs = [ + "bluetooth_adapter.h", + "webrtc.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + "//platform_v2/api:comm", + "//platform_v2/api:platform", + "//absl/strings", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + cc_library( name = "logging", hdrs = [ @@ -50,7 +67,7 @@ cc_library( "//platform_v2:__subpackages__", ], deps = [ - "//platform:logging", + "//platform_v2/base:logging", ], ) @@ -62,7 +79,6 @@ cc_test( "bluetooth_adapter_test.cc", "count_down_latch_test.cc", "crypto_test.cc", - "file_test.cc", "future_test.cc", "logging_test.cc", "multi_thread_executor_test.cc", @@ -73,13 +89,12 @@ cc_test( ], shard_count = 16, deps = [ + ":comm", ":logging", - ":public", - "//file/util:temp_path", + ":types", "//platform_v2/base", - "//platform_v2/impl/g3", + "//platform_v2/impl/g3", # build_cleaner: keep "//testing/base/public:gunit_main", - "//absl/strings", "//absl/synchronization", "//absl/time", ], diff --git a/cpp/platform_v2/public/file.h b/cpp/platform_v2/public/file.h index 1f8dbce3..59a46282 100644 --- a/cpp/platform_v2/public/file.h +++ b/cpp/platform_v2/public/file.h @@ -2,47 +2,53 @@ #define PLATFORM_V2_PUBLIC_FILE_H_ #include -#include +#include +#include #include "platform_v2/api/input_file.h" #include "platform_v2/api/output_file.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" -#include "absl/strings/string_view.h" namespace location { namespace nearby { class InputFile final : public api::InputFile { public: - explicit InputFile(const std::string& path, std::int64_t size); + using Platform = api::ImplementationPlatform; + InputFile(std::int64_t payload_id, std::int64_t size) + : impl_(Platform::CreateInputFile(payload_id, size)) {} ~InputFile() override = default; InputFile(InputFile&&) = default; InputFile& operator=(InputFile&&) = default; - ExceptionOr Read(std::int64_t size) override; - std::string GetFilePath() const override { return path_; } - std::int64_t GetTotalSize() const override { return total_size_; } - Exception Close() override; + 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(); } private: - std::ifstream file_; - std::string path_; - std::int64_t total_size_; + std::unique_ptr impl_; }; class OutputFile final : public api::OutputFile { public: - explicit OutputFile(absl::string_view path); + using Platform = api::ImplementationPlatform; + explicit OutputFile(std::int64_t payload_id) + : impl_(Platform::CreateOutputFile(payload_id)) {} ~OutputFile() override = default; OutputFile(OutputFile&&) = default; OutputFile& operator=(OutputFile&&) = default; - Exception Write(const ByteArray& data) override; - Exception Flush() override; - Exception Close() override; + Exception Write(const ByteArray& data) override { return impl_->Write(data); } + Exception Flush() override { return impl_->Flush(); } + Exception Close() override { return impl_->Close(); } private: - std::ofstream file_; + std::unique_ptr impl_; }; } // namespace nearby diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h index aca9975f..fcd7b0ba 100644 --- a/cpp/platform_v2/public/future.h +++ b/cpp/platform_v2/public/future.h @@ -33,7 +33,7 @@ class Future final : public api::SettableFuture { ExceptionOr Get() override { auto ret_val = impl_->Get(); if (ret_val.ok()) { - T result = std::any_cast(ret_val.result()); + T result = absl::any_cast(ret_val.result()); return ExceptionOr{result}; } else { return ExceptionOr{ret_val.exception()}; @@ -46,7 +46,7 @@ class Future final : public api::SettableFuture { ExceptionOr Get(absl::Duration timeout) override { auto ret_val = impl_->Get(timeout); if (ret_val.ok()) { - T result = std::any_cast(ret_val.result()); + T result = absl::any_cast(ret_val.result()); return ExceptionOr{result}; } else { return ExceptionOr{ret_val.exception()}; diff --git a/cpp/platform_v2/public/logging.h b/cpp/platform_v2/public/logging.h index 5a9b4767..cde3df05 100644 --- a/cpp/platform_v2/public/logging.h +++ b/cpp/platform_v2/public/logging.h @@ -1,6 +1,6 @@ #ifndef PLATFORM_V2_PUBLIC_LOGGING_H_ #define PLATFORM_V2_PUBLIC_LOGGING_H_ -#include "platform/logging.h" +#include "platform_v2/base/logging.h" #endif // PLATFORM_V2_PUBLIC_LOGGING_H_ diff --git a/cpp/platform_v2/public/webrtc.h b/cpp/platform_v2/public/webrtc.h new file mode 100644 index 00000000..a5bc50de --- /dev/null +++ b/cpp/platform_v2/public/webrtc.h @@ -0,0 +1,44 @@ +#ifndef PLATFORM_V2_PUBLIC_WEBRTC_H_ +#define PLATFORM_V2_PUBLIC_WEBRTC_H_ + +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/webrtc.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { + +class WebRtcMedium final { + public: + using PeerConnectionCallback = api::WebRtcMedium::PeerConnectionCallback; + + WebRtcMedium() : impl_(api::ImplementationPlatform::CreateWebRtcMedium()) {} + ~WebRtcMedium() = default; + WebRtcMedium(WebRtcMedium&&) = delete; + WebRtcMedium& operator=(WebRtcMedium&&) = delete; + + // Creates and returns a new webrtc::PeerConnectionInterface object via + // |callback|. + void CreatePeerConnection(webrtc::PeerConnectionObserver* observer, + PeerConnectionCallback callback) { + impl_->CreatePeerConnection(observer, std::move(callback)); + } + + // Returns a signaling messenger for sending WebRTC signaling messages. + std::unique_ptr GetSignalingMessenger( + absl::string_view self_id) { + return impl_->GetSignalingMessenger(self_id); + } + + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_WEBRTC_H_ diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index 04f99cb7..e44e11b2 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -200,11 +200,15 @@ message BandwidthUpgradeNegotiationFrame { optional BluetoothCredentials bluetooth_credentials = 4; optional WifiAwareCredentials wifi_aware_credentials = 5; optional WifiDirectCredentials wifi_direct_credentials = 6; + + // Disable Encryption for this upgrade medium to improve throughput. + optional bool supports_disabling_encryption = 7; } // Accompanies CLIENT_INTRODUCTION events. message ClientIntroduction { optional string endpoint_id = 1; + optional bool supports_disabling_encryption = 2; } optional EventType event_type = 1; diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index 98e5df0d..6e87d13d 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -224,6 +224,8 @@ enum LogSource { // Represents the OEM partners (like Samsung) that we're working with to // verify functionality on their devices. OEM_DEVICES = 4; + // Represents the device for debugging. + DEBUG_DEVICES = 5; } // The Fast Share server action name. From 7e19ffbab782b1328405462391c574d8502f10a3 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 3 Jun 2020 18:22:40 -0700 Subject: [PATCH 21/52] Release based on cl/313536507. Signed-off-by: Alexey Polyudov Change-Id: I83ec7dee1a7ef6f4bdfd47482c94d03910525b7e --- cpp/core/BUILD | 4 +- cpp/core/CMakeLists.txt | 5 +- cpp/core/check_compilation.cc | 8 +- cpp/core/core.h | 19 +- cpp/core/internal/BUILD | 41 +- cpp/core/internal/CMakeLists.txt | 14 +- cpp/core/internal/bandwidth_upgrade_handler.h | 4 +- .../internal/bandwidth_upgrade_manager.cc | 20 +- cpp/core/internal/bandwidth_upgrade_manager.h | 21 +- .../base_bandwidth_upgrade_handler.cc | 105 ++-- .../internal/base_bandwidth_upgrade_handler.h | 34 +- cpp/core/internal/base_endpoint_channel.cc | 94 ++-- cpp/core/internal/base_endpoint_channel.h | 8 +- .../internal/base_endpoint_channel_test.cc | 20 +- cpp/core/internal/base_pcp_handler.cc | 25 +- cpp/core/internal/base_pcp_handler.h | 13 +- cpp/core/internal/ble_endpoint_channel.cc | 33 +- cpp/core/internal/ble_endpoint_channel.h | 12 +- .../internal/bluetooth_endpoint_channel.cc | 31 +- .../internal/bluetooth_endpoint_channel.h | 12 +- cpp/core/internal/encryption_runner.cc | 44 +- cpp/core/internal/endpoint_channel_manager.cc | 100 ++-- cpp/core/internal/endpoint_channel_manager.h | 14 +- cpp/core/internal/endpoint_manager.cc | 2 +- cpp/core/internal/endpoint_manager.h | 4 +- cpp/core/internal/internal_payload_factory.cc | 15 +- cpp/core/internal/medium_manager.cc | 129 ++++- cpp/core/internal/medium_manager.h | 40 ++ cpp/core/internal/mediums/BUILD | 39 +- cpp/core/internal/mediums/CMakeLists.txt | 2 +- .../mediums/advertisement_read_result_test.cc | 54 +- cpp/core/internal/mediums/ble_v2.cc | 4 +- cpp/core/internal/mediums/ble_v2.h | 6 +- .../mediums/lost_entity_tracker_test.cc | 18 +- cpp/core/internal/mediums/mediums.cc | 8 +- cpp/core/internal/mediums/mediums.h | 4 + cpp/core/internal/mediums/utils.cc | 22 + cpp/core/internal/mediums/utils.h | 2 + cpp/core/internal/mediums/webrtc/BUILD | 91 ++++ cpp/core/internal/mediums/webrtc/peer_id.cc | 55 ++ cpp/core/internal/mediums/webrtc/peer_id.h | 50 ++ .../internal/mediums/webrtc/peer_id_test.cc | 90 ++++ .../mediums/webrtc/signaling_frames.cc | 139 +++++ .../mediums/webrtc/signaling_frames.h | 63 +++ .../mediums/webrtc/signaling_frames_test.cc | 198 +++++++ .../internal/mediums/webrtc/webrtc_socket.cc | 153 ++++++ .../internal/mediums/webrtc/webrtc_socket.h | 118 +++++ .../mediums/webrtc/webrtc_socket_test.cc | 169 ++++++ cpp/core/internal/mediums/wifi_lan.cc | 227 ++++++++ cpp/core/internal/mediums/wifi_lan.h | 174 +++++++ cpp/core/internal/message_lite.h | 20 + cpp/core/internal/offline_frames.cc | 5 +- .../internal/offline_service_controller.cc | 4 +- .../internal/offline_service_controller.h | 5 +- cpp/core/internal/p2p_cluster_pcp_handler.cc | 316 ++++++++++- cpp/core/internal/p2p_cluster_pcp_handler.h | 270 +++++++--- .../p2p_point_to_point_pcp_handler.cc | 4 +- .../internal/p2p_point_to_point_pcp_handler.h | 4 +- cpp/core/internal/p2p_star_pcp_handler.cc | 4 +- cpp/core/internal/p2p_star_pcp_handler.h | 9 +- cpp/core/internal/pcp_manager.cc | 4 +- cpp/core/internal/pcp_manager.h | 4 +- .../internal/wifi_lan_endpoint_channel.cc | 63 +++ cpp/core/internal/wifi_lan_endpoint_channel.h | 60 +++ cpp/core/internal/wifi_lan_upgrade_handler.cc | 4 +- cpp/core/internal/wifi_lan_upgrade_handler.h | 27 +- cpp/core_v2/BUILD | 87 ++++ cpp/core_v2/core.cc | 121 +++++ cpp/core_v2/core.h | 222 ++++++++ cpp/core_v2/core_test.cc | 58 +++ cpp/core_v2/internal/BUILD | 115 ++++ cpp/core_v2/internal/base_endpoint_channel.cc | 284 ++++++++++ cpp/core_v2/internal/base_endpoint_channel.h | 127 +++++ .../internal/base_endpoint_channel_test.cc | 356 +++++++++++++ cpp/core_v2/internal/base_pcp_handler.cc | 157 ++++++ cpp/core_v2/internal/base_pcp_handler.h | 337 ++++++++++++ cpp/core_v2/internal/base_pcp_handler_test.cc | 301 +++++++++++ cpp/core_v2/internal/ble_advertisement.cc | 236 +++++++++ cpp/core_v2/internal/ble_advertisement.h | 104 ++++ .../internal/ble_advertisement_test.cc | 272 ++++++++++ cpp/core_v2/internal/client_proxy.cc | 475 +++++++++++++++++ cpp/core_v2/internal/client_proxy.h | 231 ++++++++ cpp/core_v2/internal/client_proxy_test.cc | 371 +++++++++++++ cpp/core_v2/internal/encryption_runner.cc | 382 ++++++++++++++ cpp/core_v2/internal/encryption_runner.h | 86 +++ .../internal/encryption_runner_test.cc | 142 +++++ cpp/core_v2/internal/endpoint_channel.h | 88 ++++ .../internal/endpoint_channel_manager.cc | 151 ++++++ .../internal/endpoint_channel_manager.h | 169 ++++++ .../internal/endpoint_channel_manager_test.cc | 31 ++ cpp/core_v2/internal/endpoint_manager.cc | 491 ++++++++++++++++++ cpp/core_v2/internal/endpoint_manager.h | 232 +++++++++ cpp/core_v2/internal/endpoint_manager_test.cc | 256 +++++++++ cpp/core_v2/internal/mediums/BUILD | 84 +++ .../mediums/advertisement_read_result.cc | 139 +++++ .../mediums/advertisement_read_result.h | 104 ++++ .../mediums/advertisement_read_result_test.cc | 143 +++++ .../internal/mediums/ble_advertisement.cc | 215 ++++++++ .../internal/mediums/ble_advertisement.h | 114 ++++ .../mediums/ble_advertisement_header.cc | 132 +++++ .../mediums/ble_advertisement_header.h | 98 ++++ .../mediums/ble_advertisement_header_test.cc | 190 +++++++ .../mediums/ble_advertisement_test.cc | 237 +++++++++ cpp/core_v2/internal/mediums/ble_packet.cc | 73 +++ cpp/core_v2/internal/mediums/ble_packet.h | 65 +++ .../internal/mediums/ble_packet_test.cc | 111 ++++ cpp/core_v2/internal/mediums/ble_peripheral.h | 50 ++ .../internal/mediums/ble_peripheral_test.cc | 47 ++ .../internal/mediums/bluetooth_radio.cc | 118 +++++ .../internal/mediums/bluetooth_radio.h | 94 ++++ .../internal/mediums/bluetooth_radio_test.cc | 59 +++ .../internal/mediums/lost_entity_tracker.h | 94 ++++ .../mediums/lost_entity_tracker_test.cc | 137 +++++ cpp/core_v2/internal/mediums/utils.cc | 55 ++ cpp/core_v2/internal/mediums/utils.h | 36 ++ cpp/core_v2/internal/mediums/uuid.cc | 89 ++++ cpp/core_v2/internal/mediums/uuid.h | 59 +++ cpp/core_v2/internal/mediums/uuid_test.cc | 70 +++ cpp/core_v2/internal/mediums/webrtc/BUILD | 90 ++++ .../internal/mediums/webrtc/peer_id.cc | 52 ++ cpp/core_v2/internal/mediums/webrtc/peer_id.h | 49 ++ .../internal/mediums/webrtc/peer_id_test.cc | 56 ++ .../mediums/webrtc/signaling_frames.cc | 134 +++++ .../mediums/webrtc/signaling_frames.h | 58 +++ .../mediums/webrtc/signaling_frames_test.cc | 196 +++++++ .../internal/mediums/webrtc/webrtc_socket.cc | 115 ++++ .../internal/mediums/webrtc/webrtc_socket.h | 115 ++++ .../mediums/webrtc/webrtc_socket_test.cc | 168 ++++++ .../internal/mock_service_controller.h | 85 +++ cpp/core_v2/internal/offline_frames.cc | 265 ++++++++++ cpp/core_v2/internal/offline_frames.h | 75 +++ cpp/core_v2/internal/offline_frames_test.cc | 266 ++++++++++ cpp/core_v2/internal/pcp.h | 40 ++ cpp/core_v2/internal/pcp_handler.h | 102 ++++ cpp/core_v2/internal/service_controller.h | 91 ++++ .../internal/service_controller_router.cc | 397 ++++++++++++++ .../internal/service_controller_router.h | 125 +++++ .../service_controller_router_test.cc | 390 ++++++++++++++ cpp/core_v2/internal/wifi_lan_service_info.cc | 194 +++++++ cpp/core_v2/internal/wifi_lan_service_info.h | 95 ++++ .../internal/wifi_lan_service_info_test.cc | 157 ++++++ cpp/core_v2/listeners.h | 194 +++++++ cpp/core_v2/listeners_test.cc | 59 +++ cpp/core_v2/options.h | 44 ++ cpp/core_v2/params.h | 41 ++ cpp/core_v2/payload.h | 99 ++++ cpp/core_v2/payload_test.cc | 90 ++++ cpp/core_v2/status.h | 59 +++ cpp/core_v2/status_test.cc | 58 +++ cpp/core_v2/strategy.cc | 61 +++ cpp/core_v2/strategy.h | 76 +++ cpp/core_v2/strategy_test.cc | 55 ++ cpp/platform/BUILD | 83 +-- cpp/platform/CMakeLists.txt | 24 +- cpp/platform/api/BUILD | 8 +- cpp/platform/api/atomic_reference.h | 41 +- cpp/platform/api/atomic_reference_def.h | 41 ++ cpp/platform/api/ble_v2.h | 2 +- cpp/platform/api/multi_thread_executor.h | 6 +- cpp/platform/api/platform.h | 120 +++++ cpp/platform/api/scheduled_executor.h | 6 +- cpp/platform/api/server_sync.h | 2 +- cpp/platform/api/settable_future.h | 57 +- cpp/platform/api/settable_future_def.h | 45 ++ cpp/platform/api/single_thread_executor.h | 6 +- cpp/platform/api/submittable_executor.h | 51 +- cpp/platform/api/submittable_executor_def.h | 49 ++ cpp/platform/api/webrtc.h | 7 +- cpp/platform/api/wifi_lan.h | 20 +- cpp/platform/api2/submittable_executor.h | 56 -- cpp/platform/atomic_reference_test.cc | 94 ++++ cpp/platform/byte_array.h | 2 +- cpp/platform/cancelable_alarm.cc | 22 +- cpp/platform/cancelable_alarm.h | 12 +- cpp/platform/exception.h | 8 +- cpp/platform/file_impl.h | 2 +- cpp/platform/file_impl_test.cc | 2 +- cpp/platform/impl/default/CMakeLists.txt | 65 --- cpp/platform/impl/default/default_platform.h | 40 -- cpp/platform/impl/g3/BUILD | 40 ++ cpp/platform/impl/g3/CMakeLists.txt | 41 ++ cpp/platform/impl/g3/atomic_reference_impl.h | 52 ++ cpp/platform/impl/g3/platform.cc | 154 ++++++ cpp/platform/impl/g3/settable_future_impl.h | 108 ++++ .../g3/system_clock_impl.h} | 17 +- cpp/platform/impl/sample/BUILD | 10 +- .../impl/sample/atomic_reference_impl.h | 39 ++ cpp/platform/impl/sample/sample_platform.cc | 139 +++++ cpp/platform/impl/sample/sample_platform.h | 155 ------ .../impl/sample/settable_future_impl.h | 49 ++ cpp/platform/impl/{default => shared}/BUILD | 46 +- cpp/platform/impl/shared/CMakeLists.txt | 81 +++ .../impl/shared/atomic_boolean_impl.h | 47 ++ .../posix_condition_variable.cc} | 12 +- .../posix_condition_variable.h} | 16 +- .../default_lock.cc => shared/posix_lock.cc} | 10 +- .../default_lock.h => shared/posix_lock.h} | 14 +- cpp/platform/impl/shared/sample/BUILD | 36 ++ .../impl/shared/sample/CMakeLists.txt | 31 ++ .../{ => shared}/sample/sample_wifi_medium.cc | 2 +- .../{ => shared}/sample/sample_wifi_medium.h | 6 +- cpp/platform/pipe.cc | 59 +-- cpp/platform/pipe.h | 7 - cpp/platform/pipe_test.cc | 14 +- cpp/platform/ptr.h | 27 +- cpp/platform/ptr_test.cc | 5 +- cpp/platform/settable_future_test.cc | 98 ++++ cpp/{platform/api2 => platform_v2/api}/BUILD | 50 +- .../api2 => platform_v2/api}/CMakeLists.txt | 0 .../api2 => platform_v2/api}/atomic_boolean.h | 19 +- .../api}/atomic_reference.h | 14 +- cpp/{platform/api2 => platform_v2/api}/ble.h | 19 +- .../api2 => platform_v2/api}/ble_v2.h | 16 +- .../api}/bluetooth_adapter.h | 17 +- .../api}/bluetooth_classic.h | 18 +- .../mutex.h => platform_v2/api/cancelable.h} | 21 +- .../api}/condition_variable.h | 10 +- .../api}/count_down_latch.h | 12 +- .../hash_utils.h => platform_v2/api/crypto.h} | 14 +- .../api2 => platform_v2/api}/executor.h | 16 +- .../api2 => platform_v2/api}/future.h | 12 +- .../api2 => platform_v2/api}/input_file.h | 16 +- .../api}/listenable_future.h | 19 +- cpp/platform_v2/api/mutex.h | 55 ++ .../api2 => platform_v2/api}/output_file.h | 14 +- cpp/platform_v2/api/platform.h | 92 ++++ .../api}/scheduled_executor.h | 23 +- .../api2 => platform_v2/api}/server_sync.h | 10 +- .../api}/settable_future.h | 12 +- cpp/platform_v2/api/submittable_executor.h | 47 ++ .../api2 => platform_v2/api}/system_clock.h | 19 +- .../api2 => platform_v2/api}/webrtc.h | 12 +- cpp/{platform/api2 => platform_v2/api}/wifi.h | 12 +- cpp/platform_v2/api/wifi_lan.h | 101 ++++ cpp/platform_v2/base/BUILD | 87 ++++ cpp/platform_v2/base/base64_utils.cc | 41 ++ cpp/platform_v2/base/base64_utils.h | 33 ++ cpp/platform_v2/base/base_mutex_lock.h | 40 ++ cpp/platform_v2/base/base_pipe.cc | 110 ++++ cpp/platform_v2/base/base_pipe.h | 142 +++++ cpp/platform_v2/base/byte_array.h | 95 ++++ cpp/platform_v2/base/byte_array_test.cc | 82 +++ .../base/callable.h} | 26 +- cpp/platform_v2/base/exception.h | 111 ++++ cpp/platform_v2/base/exception_test.cc | 120 +++++ .../api2 => platform_v2/base}/input_stream.h | 19 +- cpp/platform_v2/base/listeners.h | 34 ++ .../api2 => platform_v2/base}/output_stream.h | 16 +- cpp/platform_v2/base/prng.cc | 59 +++ cpp/platform_v2/base/prng.h | 37 ++ cpp/platform_v2/base/prng_test.cc | 41 ++ cpp/platform_v2/base/runnable.h | 33 ++ .../api2 => platform_v2/base}/socket.h | 12 +- cpp/platform_v2/config/BUILD | 35 ++ cpp/platform_v2/config/config.h | 36 ++ cpp/platform_v2/config/string.h | 26 + cpp/platform_v2/impl/g3/BUILD | 71 +++ cpp/platform_v2/impl/g3/atomic_boolean.h | 44 ++ .../impl/g3/atomic_reference_any.h | 60 +++ cpp/platform_v2/impl/g3/bluetooth_adapter.cc | 79 +++ cpp/platform_v2/impl/g3/bluetooth_adapter.h | 104 ++++ cpp/platform_v2/impl/g3/condition_variable.h | 47 ++ cpp/platform_v2/impl/g3/count_down_latch.h | 73 +++ cpp/platform_v2/impl/g3/crypto.cc | 53 ++ cpp/platform_v2/impl/g3/medium_environment.cc | 46 ++ cpp/platform_v2/impl/g3/medium_environment.h | 61 +++ .../impl/g3/multi_thread_executor.h | 68 +++ cpp/platform_v2/impl/g3/mutex.h | 61 +++ cpp/platform_v2/impl/g3/pipe.h | 44 ++ cpp/platform_v2/impl/g3/platform.cc | 150 ++++++ cpp/platform_v2/impl/g3/scheduled_executor.cc | 79 +++ cpp/platform_v2/impl/g3/scheduled_executor.h | 56 ++ cpp/platform_v2/impl/g3/settable_future_any.h | 118 +++++ .../impl/g3}/single_thread_executor.h | 19 +- cpp/platform_v2/impl/g3/system_clock.cc | 30 ++ cpp/platform_v2/impl/shared/BUILD | 48 ++ .../impl/shared/posix_condition_variable.cc | 44 ++ .../impl/shared/posix_condition_variable.h | 45 ++ cpp/platform_v2/impl/shared/posix_mutex.cc | 40 ++ cpp/platform_v2/impl/shared/posix_mutex.h | 45 ++ cpp/platform_v2/public/BUILD | 100 ++++ cpp/platform_v2/public/atomic_boolean.h | 48 ++ cpp/platform_v2/public/atomic_boolean_test.cc | 38 ++ cpp/platform_v2/public/atomic_reference.h | 54 ++ .../public/atomic_reference_test.cc | 89 ++++ cpp/platform_v2/public/bluetooth_adapter.h | 77 +++ .../public/bluetooth_adapter_test.cc | 58 +++ cpp/platform_v2/public/cancelable.h | 50 ++ cpp/platform_v2/public/cancelable_alarm.h | 70 +++ cpp/platform_v2/public/condition_variable.h | 50 ++ cpp/platform_v2/public/count_down_latch.h | 54 ++ .../public/count_down_latch_test.cc | 62 +++ cpp/platform_v2/public/crypto.h | 20 + cpp/platform_v2/public/crypto_test.cc | 48 ++ cpp/platform_v2/public/file.cc | 93 ++++ cpp/platform_v2/public/file.h | 65 +++ cpp/platform_v2/public/file_test.cc | 145 ++++++ cpp/platform_v2/public/future.h | 77 +++ cpp/platform_v2/public/future_test.cc | 116 +++++ cpp/platform_v2/public/logging.h | 20 + cpp/platform_v2/public/logging_test.cc | 26 + .../public/multi_thread_executor.h | 42 ++ .../public/multi_thread_executor_test.cc | 108 ++++ cpp/platform_v2/public/mutex.h | 78 +++ cpp/platform_v2/public/mutex_lock.h | 45 ++ cpp/platform_v2/public/mutex_test.cc | 117 +++++ .../public/pipe.cc} | 18 +- cpp/platform_v2/public/pipe.h | 36 ++ cpp/platform_v2/public/pipe_test.cc | 346 ++++++++++++ cpp/platform_v2/public/scheduled_executor.h | 89 ++++ .../public/scheduled_executor_test.cc | 114 ++++ .../public/single_thread_executor.h | 40 ++ .../public/single_thread_executor_test.cc | 85 +++ cpp/platform_v2/public/submittable_executor.h | 110 ++++ cpp/platform_v2/public/system_clock.h | 20 + proto/BUILD | 20 + proto/connections/offline_wire_formats.proto | 2 + proto/connections_enums_proto_config.asciipb | 2 + proto/error_code_enums.proto | 147 ++++++ proto/sharing_enums.proto | 16 + script/oss.py | 18 + 321 files changed, 23019 insertions(+), 1376 deletions(-) create mode 100644 cpp/core/internal/mediums/webrtc/BUILD create mode 100644 cpp/core/internal/mediums/webrtc/peer_id.cc create mode 100644 cpp/core/internal/mediums/webrtc/peer_id.h create mode 100644 cpp/core/internal/mediums/webrtc/peer_id_test.cc create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames.cc create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames.h create mode 100644 cpp/core/internal/mediums/webrtc/signaling_frames_test.cc create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket.cc create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket.h create mode 100644 cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc create mode 100644 cpp/core/internal/mediums/wifi_lan.cc create mode 100644 cpp/core/internal/mediums/wifi_lan.h create mode 100644 cpp/core/internal/message_lite.h create mode 100644 cpp/core/internal/wifi_lan_endpoint_channel.cc create mode 100644 cpp/core/internal/wifi_lan_endpoint_channel.h create mode 100644 cpp/core_v2/BUILD create mode 100644 cpp/core_v2/core.cc create mode 100644 cpp/core_v2/core.h create mode 100644 cpp/core_v2/core_test.cc create mode 100644 cpp/core_v2/internal/BUILD create mode 100644 cpp/core_v2/internal/base_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/base_endpoint_channel.h create mode 100644 cpp/core_v2/internal/base_endpoint_channel_test.cc create mode 100644 cpp/core_v2/internal/base_pcp_handler.cc create mode 100644 cpp/core_v2/internal/base_pcp_handler.h create mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc create mode 100644 cpp/core_v2/internal/ble_advertisement.cc create mode 100644 cpp/core_v2/internal/ble_advertisement.h create mode 100644 cpp/core_v2/internal/ble_advertisement_test.cc create mode 100644 cpp/core_v2/internal/client_proxy.cc create mode 100644 cpp/core_v2/internal/client_proxy.h create mode 100644 cpp/core_v2/internal/client_proxy_test.cc create mode 100644 cpp/core_v2/internal/encryption_runner.cc create mode 100644 cpp/core_v2/internal/encryption_runner.h create mode 100644 cpp/core_v2/internal/encryption_runner_test.cc create mode 100644 cpp/core_v2/internal/endpoint_channel.h create mode 100644 cpp/core_v2/internal/endpoint_channel_manager.cc create mode 100644 cpp/core_v2/internal/endpoint_channel_manager.h create mode 100644 cpp/core_v2/internal/endpoint_channel_manager_test.cc create mode 100644 cpp/core_v2/internal/endpoint_manager.cc create mode 100644 cpp/core_v2/internal/endpoint_manager.h create mode 100644 cpp/core_v2/internal/endpoint_manager_test.cc create mode 100644 cpp/core_v2/internal/mediums/BUILD create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result.cc create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result.h create mode 100644 cpp/core_v2/internal/mediums/advertisement_read_result_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement.h create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header.h create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_packet.cc create mode 100644 cpp/core_v2/internal/mediums/ble_packet.h create mode 100644 cpp/core_v2/internal/mediums/ble_packet_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_peripheral.h create mode 100644 cpp/core_v2/internal/mediums/ble_peripheral_test.cc create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio.cc create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio.h create mode 100644 cpp/core_v2/internal/mediums/bluetooth_radio_test.cc create mode 100644 cpp/core_v2/internal/mediums/lost_entity_tracker.h create mode 100644 cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc create mode 100644 cpp/core_v2/internal/mediums/utils.cc create mode 100644 cpp/core_v2/internal/mediums/utils.h create mode 100644 cpp/core_v2/internal/mediums/uuid.cc create mode 100644 cpp/core_v2/internal/mediums/uuid.h create mode 100644 cpp/core_v2/internal/mediums/uuid_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/BUILD create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h create mode 100644 cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc create mode 100644 cpp/core_v2/internal/mock_service_controller.h create mode 100644 cpp/core_v2/internal/offline_frames.cc create mode 100644 cpp/core_v2/internal/offline_frames.h create mode 100644 cpp/core_v2/internal/offline_frames_test.cc create mode 100644 cpp/core_v2/internal/pcp.h create mode 100644 cpp/core_v2/internal/pcp_handler.h create mode 100644 cpp/core_v2/internal/service_controller.h create mode 100644 cpp/core_v2/internal/service_controller_router.cc create mode 100644 cpp/core_v2/internal/service_controller_router.h create mode 100644 cpp/core_v2/internal/service_controller_router_test.cc create mode 100644 cpp/core_v2/internal/wifi_lan_service_info.cc create mode 100644 cpp/core_v2/internal/wifi_lan_service_info.h create mode 100644 cpp/core_v2/internal/wifi_lan_service_info_test.cc create mode 100644 cpp/core_v2/listeners.h create mode 100644 cpp/core_v2/listeners_test.cc create mode 100644 cpp/core_v2/options.h create mode 100644 cpp/core_v2/params.h create mode 100644 cpp/core_v2/payload.h create mode 100644 cpp/core_v2/payload_test.cc create mode 100644 cpp/core_v2/status.h create mode 100644 cpp/core_v2/status_test.cc create mode 100644 cpp/core_v2/strategy.cc create mode 100644 cpp/core_v2/strategy.h create mode 100644 cpp/core_v2/strategy_test.cc create mode 100644 cpp/platform/api/atomic_reference_def.h create mode 100644 cpp/platform/api/platform.h create mode 100644 cpp/platform/api/settable_future_def.h create mode 100644 cpp/platform/api/submittable_executor_def.h delete mode 100644 cpp/platform/api2/submittable_executor.h create mode 100644 cpp/platform/atomic_reference_test.cc delete mode 100644 cpp/platform/impl/default/CMakeLists.txt delete mode 100644 cpp/platform/impl/default/default_platform.h create mode 100644 cpp/platform/impl/g3/CMakeLists.txt create mode 100644 cpp/platform/impl/g3/atomic_reference_impl.h create mode 100644 cpp/platform/impl/g3/platform.cc create mode 100644 cpp/platform/impl/g3/settable_future_impl.h rename cpp/platform/{api2/thread_utils.h => impl/g3/system_clock_impl.h} (68%) create mode 100644 cpp/platform/impl/sample/atomic_reference_impl.h create mode 100644 cpp/platform/impl/sample/sample_platform.cc delete mode 100644 cpp/platform/impl/sample/sample_platform.h create mode 100644 cpp/platform/impl/sample/settable_future_impl.h rename cpp/platform/impl/{default => shared}/BUILD (60%) create mode 100644 cpp/platform/impl/shared/CMakeLists.txt create mode 100644 cpp/platform/impl/shared/atomic_boolean_impl.h rename cpp/platform/impl/{default/default_condition_variable.cc => shared/posix_condition_variable.cc} (72%) rename cpp/platform/impl/{default/default_condition_variable.h => shared/posix_condition_variable.h} (68%) rename cpp/platform/impl/{default/default_lock.cc => shared/posix_lock.cc} (78%) rename cpp/platform/impl/{default/default_lock.h => shared/posix_lock.h} (76%) create mode 100644 cpp/platform/impl/shared/sample/BUILD create mode 100644 cpp/platform/impl/shared/sample/CMakeLists.txt rename cpp/platform/impl/{ => shared}/sample/sample_wifi_medium.cc (98%) rename cpp/platform/impl/{ => shared}/sample/sample_wifi_medium.h (92%) create mode 100644 cpp/platform/settable_future_test.cc rename cpp/{platform/api2 => platform_v2/api}/BUILD (61%) rename cpp/{platform/api2 => platform_v2/api}/CMakeLists.txt (100%) rename cpp/{platform/api2 => platform_v2/api}/atomic_boolean.h (65%) rename cpp/{platform/api2 => platform_v2/api}/atomic_reference.h (75%) rename cpp/{platform/api2 => platform_v2/api}/ble.h (91%) rename cpp/{platform/api2 => platform_v2/api}/ble_v2.h (98%) rename cpp/{platform/api2 => platform_v2/api}/bluetooth_adapter.h (86%) rename cpp/{platform/api2 => platform_v2/api}/bluetooth_classic.h (92%) rename cpp/{platform/api2/mutex.h => platform_v2/api/cancelable.h} (65%) rename cpp/{platform/api2 => platform_v2/api}/condition_variable.h (85%) rename cpp/{platform/api2 => platform_v2/api}/count_down_latch.h (82%) rename cpp/{platform/api2/hash_utils.h => platform_v2/api/crypto.h} (75%) rename cpp/{platform/api2 => platform_v2/api}/executor.h (76%) rename cpp/{platform/api2 => platform_v2/api}/future.h (85%) rename cpp/{platform/api2 => platform_v2/api}/input_file.h (73%) rename cpp/{platform/api2 => platform_v2/api}/listenable_future.h (72%) create mode 100644 cpp/platform_v2/api/mutex.h rename cpp/{platform/api2 => platform_v2/api}/output_file.h (74%) create mode 100644 cpp/platform_v2/api/platform.h rename cpp/{platform/api2 => platform_v2/api}/scheduled_executor.h (58%) rename cpp/{platform/api2 => platform_v2/api}/server_sync.h (92%) rename cpp/{platform/api2 => platform_v2/api}/settable_future.h (78%) create mode 100644 cpp/platform_v2/api/submittable_executor.h rename cpp/{platform/api2 => platform_v2/api}/system_clock.h (63%) rename cpp/{platform/api2 => platform_v2/api}/webrtc.h (87%) rename cpp/{platform/api2 => platform_v2/api}/wifi.h (93%) create mode 100644 cpp/platform_v2/api/wifi_lan.h create mode 100644 cpp/platform_v2/base/BUILD create mode 100644 cpp/platform_v2/base/base64_utils.cc create mode 100644 cpp/platform_v2/base/base64_utils.h create mode 100644 cpp/platform_v2/base/base_mutex_lock.h create mode 100644 cpp/platform_v2/base/base_pipe.cc create mode 100644 cpp/platform_v2/base/base_pipe.h create mode 100644 cpp/platform_v2/base/byte_array.h create mode 100644 cpp/platform_v2/base/byte_array_test.cc rename cpp/{platform/api2/multi_thread_executor.h => platform_v2/base/callable.h} (59%) create mode 100644 cpp/platform_v2/base/exception.h create mode 100644 cpp/platform_v2/base/exception_test.cc rename cpp/{platform/api2 => platform_v2/base}/input_stream.h (68%) create mode 100644 cpp/platform_v2/base/listeners.h rename cpp/{platform/api2 => platform_v2/base}/output_stream.h (69%) create mode 100644 cpp/platform_v2/base/prng.cc create mode 100644 cpp/platform_v2/base/prng.h create mode 100644 cpp/platform_v2/base/prng_test.cc create mode 100644 cpp/platform_v2/base/runnable.h rename cpp/{platform/api2 => platform_v2/base}/socket.h (81%) create mode 100644 cpp/platform_v2/config/BUILD create mode 100644 cpp/platform_v2/config/config.h create mode 100644 cpp/platform_v2/config/string.h create mode 100644 cpp/platform_v2/impl/g3/BUILD create mode 100644 cpp/platform_v2/impl/g3/atomic_boolean.h create mode 100644 cpp/platform_v2/impl/g3/atomic_reference_any.h create mode 100644 cpp/platform_v2/impl/g3/bluetooth_adapter.cc create mode 100644 cpp/platform_v2/impl/g3/bluetooth_adapter.h create mode 100644 cpp/platform_v2/impl/g3/condition_variable.h create mode 100644 cpp/platform_v2/impl/g3/count_down_latch.h create mode 100644 cpp/platform_v2/impl/g3/crypto.cc create mode 100644 cpp/platform_v2/impl/g3/medium_environment.cc create mode 100644 cpp/platform_v2/impl/g3/medium_environment.h create mode 100644 cpp/platform_v2/impl/g3/multi_thread_executor.h create mode 100644 cpp/platform_v2/impl/g3/mutex.h create mode 100644 cpp/platform_v2/impl/g3/pipe.h create mode 100644 cpp/platform_v2/impl/g3/platform.cc create mode 100644 cpp/platform_v2/impl/g3/scheduled_executor.cc create mode 100644 cpp/platform_v2/impl/g3/scheduled_executor.h create mode 100644 cpp/platform_v2/impl/g3/settable_future_any.h rename cpp/{platform/api2 => platform_v2/impl/g3}/single_thread_executor.h (61%) create mode 100644 cpp/platform_v2/impl/g3/system_clock.cc create mode 100644 cpp/platform_v2/impl/shared/BUILD create mode 100644 cpp/platform_v2/impl/shared/posix_condition_variable.cc create mode 100644 cpp/platform_v2/impl/shared/posix_condition_variable.h create mode 100644 cpp/platform_v2/impl/shared/posix_mutex.cc create mode 100644 cpp/platform_v2/impl/shared/posix_mutex.h create mode 100644 cpp/platform_v2/public/BUILD create mode 100644 cpp/platform_v2/public/atomic_boolean.h create mode 100644 cpp/platform_v2/public/atomic_boolean_test.cc create mode 100644 cpp/platform_v2/public/atomic_reference.h create mode 100644 cpp/platform_v2/public/atomic_reference_test.cc create mode 100644 cpp/platform_v2/public/bluetooth_adapter.h create mode 100644 cpp/platform_v2/public/bluetooth_adapter_test.cc create mode 100644 cpp/platform_v2/public/cancelable.h create mode 100644 cpp/platform_v2/public/cancelable_alarm.h create mode 100644 cpp/platform_v2/public/condition_variable.h create mode 100644 cpp/platform_v2/public/count_down_latch.h create mode 100644 cpp/platform_v2/public/count_down_latch_test.cc create mode 100644 cpp/platform_v2/public/crypto.h create mode 100644 cpp/platform_v2/public/crypto_test.cc create mode 100644 cpp/platform_v2/public/file.cc create mode 100644 cpp/platform_v2/public/file.h create mode 100644 cpp/platform_v2/public/file_test.cc create mode 100644 cpp/platform_v2/public/future.h create mode 100644 cpp/platform_v2/public/future_test.cc create mode 100644 cpp/platform_v2/public/logging.h create mode 100644 cpp/platform_v2/public/logging_test.cc create mode 100644 cpp/platform_v2/public/multi_thread_executor.h create mode 100644 cpp/platform_v2/public/multi_thread_executor_test.cc create mode 100644 cpp/platform_v2/public/mutex.h create mode 100644 cpp/platform_v2/public/mutex_lock.h create mode 100644 cpp/platform_v2/public/mutex_test.cc rename cpp/{platform/impl/default/default_platform.cc => platform_v2/public/pipe.cc} (62%) create mode 100644 cpp/platform_v2/public/pipe.h create mode 100644 cpp/platform_v2/public/pipe_test.cc create mode 100644 cpp/platform_v2/public/scheduled_executor.h create mode 100644 cpp/platform_v2/public/scheduled_executor_test.cc create mode 100644 cpp/platform_v2/public/single_thread_executor.h create mode 100644 cpp/platform_v2/public/single_thread_executor_test.cc create mode 100644 cpp/platform_v2/public/submittable_executor.h create mode 100644 cpp/platform_v2/public/system_clock.h create mode 100644 proto/error_code_enums.proto diff --git a/cpp/core/BUILD b/cpp/core/BUILD index 549ac92a..14a0302e 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -63,7 +63,9 @@ cc_library( ":types", "//platform:types", "//platform:utils", - "//platform/impl/sample", + "//platform/api", + "//platform/impl/g3", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", ], ) diff --git a/cpp/core/CMakeLists.txt b/cpp/core/CMakeLists.txt index 71077e0e..9ccb4a96 100644 --- a/cpp/core/CMakeLists.txt +++ b/cpp/core/CMakeLists.txt @@ -62,8 +62,9 @@ target_link_libraries(core_build_test absl::strings core core_types - platform_impl_default_lock - platform_impl_sample + platform_impl_g3 + platform_impl_shared_posix_lock + platform_impl_shared_sample platform_port_string platform_types platform_utils diff --git a/cpp/core/check_compilation.cc b/cpp/core/check_compilation.cc index e65ba161..bbc7328c 100644 --- a/cpp/core/check_compilation.cc +++ b/cpp/core/check_compilation.cc @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - #include #include "core/core.h" @@ -20,9 +19,10 @@ #include "core/params.h" #include "core/payload.h" #include "core/status.h" +#include "platform/api/platform.h" #include "platform/byte_array.h" #include "platform/file_impl.h" -#include "platform/impl/sample/sample_platform.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -30,6 +30,8 @@ namespace location { namespace nearby { namespace connections { +using TestPlatform = platform::ImplementationPlatform; + class ResultListenerImpl : public ResultListener { public: void onResult(Status::Value status) override {} @@ -67,7 +69,7 @@ class PayloadListenerImpl : public PayloadListener { }; void check_compilation() { - Core core; + Core core; const string name = "name"; const string service_id = "service_id"; diff --git a/cpp/core/core.h b/cpp/core/core.h index 6453780e..0d6a243e 100644 --- a/cpp/core/core.h +++ b/cpp/core/core.h @@ -46,15 +46,20 @@ namespace connections { * SystemClock * ConditionVariable * - * The Platform class must also provide typedefs for the following subset of - * primitives to identify the concrete classes: + * A sample Platform definitions can be found at + * //platform/impl/shared/sample/sample_platform.cc * - * SingleThreadExecutorType - * MultiThreadExecutorType - * ScheduledExecutorType + * It is no longer necessary to parametrize system types with a platform type. + * New, recommended approach is to define platform support by implementing + * static methods of "location::nearby::platform::ImplementationPlatform" class. + * every library class that needs platform support, must include platform + * header "platform/api/platform.h" and use it. + * To keep textual compatibility, one could define the following alias + * "using Platform = platform::ImplementationPlatform;". + * this will replace the "template " declaration. * - * A sample Platform class can be found at - * //platform/impl/sample/sample_platform.h + * As an added benefit, this will allow to not include *.cc files from *.h, + * and let more static analysis happen at compiler stage. */ template class Core { diff --git a/cpp/core/internal/BUILD b/cpp/core/internal/BUILD index 75800414..d933184d 100644 --- a/cpp/core/internal/BUILD +++ b/cpp/core/internal/BUILD @@ -15,38 +15,39 @@ cc_library( name = "internal", srcs = [ + "bandwidth_upgrade_manager.cc", + "base_bandwidth_upgrade_handler.cc", + "base_endpoint_channel.cc", "ble_advertisement.cc", + "ble_endpoint_channel.cc", "bluetooth_device_name.cc", + "bluetooth_endpoint_channel.cc", + "endpoint_channel_manager.cc", "internal_payload.cc", "internal_payload.h", "loop_runner.cc", "loop_runner.h", "offline_frames.cc", + "wifi_lan_endpoint_channel.cc", "wifi_lan_service_info.cc", ], hdrs = [ "bandwidth_upgrade_handler.h", - "bandwidth_upgrade_manager.cc", "bandwidth_upgrade_manager.h", - "base_bandwidth_upgrade_handler.cc", "base_bandwidth_upgrade_handler.h", - "base_endpoint_channel.cc", "base_endpoint_channel.h", "base_pcp_handler.cc", "base_pcp_handler.h", "ble_advertisement.h", "ble_compat.h", - "ble_endpoint_channel.cc", "ble_endpoint_channel.h", "bluetooth_device_name.h", - "bluetooth_endpoint_channel.cc", "bluetooth_endpoint_channel.h", "client_proxy.cc", "client_proxy.h", "encryption_runner.cc", "encryption_runner.h", "endpoint_channel.h", - "endpoint_channel_manager.cc", "endpoint_channel_manager.h", "endpoint_manager.cc", "endpoint_manager.h", @@ -72,6 +73,7 @@ cc_library( "service_controller.h", "service_controller_router.cc", "service_controller_router.h", + "wifi_lan_endpoint_channel.h", "wifi_lan_service_info.h", "wifi_lan_upgrade_handler.cc", "wifi_lan_upgrade_handler.h", @@ -87,7 +89,6 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", - "//platform/port:down_cast", "//platform/port:string", "//proto:connections_enums_portable_proto", "//net/proto2/compat/public:proto2_lite", @@ -96,13 +97,29 @@ cc_library( ], ) +# TODO(apolyudov): remove when api v2 rework is done. +cc_library( + name = "message_lite", + hdrs = [ + "message_lite.h", + ], + visibility = [ + "//core:__subpackages__", + "//core_v2:__subpackages__", + ], + deps = [ + "//net/proto2/compat/public:proto2_lite", + ], +) + cc_test( name = "base_endpoint_channel_test", srcs = ["base_endpoint_channel_test.cc"], deps = [ ":internal", "//platform:utils", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//proto:connections_enums_portable_proto", "//testing/base/public:gunit_main", ], @@ -114,6 +131,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -124,6 +143,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":internal", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -135,6 +156,8 @@ cc_test( deps = [ ":internal", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", ], @@ -149,6 +172,8 @@ cc_test( ":internal", "//proto/connections:offline_wire_formats_portable_proto", "//platform:types", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/CMakeLists.txt b/cpp/core/internal/CMakeLists.txt index d10c1062..cd9ea813 100644 --- a/cpp/core/internal/CMakeLists.txt +++ b/cpp/core/internal/CMakeLists.txt @@ -16,14 +16,21 @@ add_library(core_internal STATIC) target_sources(core_internal PRIVATE + bandwidth_upgrade_manager.cc + base_bandwidth_upgrade_handler.cc + base_endpoint_channel.cc ble_advertisement.cc + ble_endpoint_channel.cc bluetooth_device_name.cc + bluetooth_endpoint_channel.cc + endpoint_channel_manager.cc internal_payload.cc internal_payload.h loop_runner.cc loop_runner.h offline_frames.cc wifi_lan_service_info.cc + wifi_lan_endpoint_channel.cc PUBLIC bandwidth_upgrade_handler.h bandwidth_upgrade_manager.h @@ -53,6 +60,7 @@ target_sources(core_internal pcp_manager.h service_controller.h service_controller_router.h + wifi_lan_endpoint_channel.h wifi_lan_upgrade_handler.h ) @@ -90,9 +98,9 @@ target_link_libraries(core_internal_test gmock gtest gtest_main - platform_impl_default - platform_impl_default_cond_var - platform_impl_default_lock + platform_impl_g3 + platform_impl_shared_posix_condition_variable + platform_impl_shared_posix_lock platform_port_string platform_utils ) diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h index 901bdc4a..60e1f270 100644 --- a/cpp/core/internal/bandwidth_upgrade_handler.h +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -18,6 +18,7 @@ #include "core/internal/client_proxy.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform/api/count_down_latch.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "proto/connections_enums.pb.h" @@ -27,9 +28,10 @@ namespace connections { // Defines the set of methods that need to be implemented to handle the // per-Medium-specific operations needed to upgrade an EndpointChannel. -template class BandwidthUpgradeHandler { public: + using Platform = platform::ImplementationPlatform; + virtual ~BandwidthUpgradeHandler() {} // Reverts any changes made to the device in the process of upgrading diff --git a/cpp/core/internal/bandwidth_upgrade_manager.cc b/cpp/core/internal/bandwidth_upgrade_manager.cc index 62cf32aa..b164998f 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.cc +++ b/cpp/core/internal/bandwidth_upgrade_manager.cc @@ -20,38 +20,32 @@ namespace location { namespace nearby { namespace connections { -template -BandwidthUpgradeManager::BandwidthUpgradeManager( +BandwidthUpgradeManager::BandwidthUpgradeManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager) : endpoint_manager_(endpoint_manager), bandwidth_upgrade_handlers_(), current_bandwidth_upgrade_handler_() {} -template -BandwidthUpgradeManager::~BandwidthUpgradeManager() { +BandwidthUpgradeManager::~BandwidthUpgradeManager() { // TODO(ahlee): Make sure we don't repeat the mistake fixed in cl/201883908. } -template -void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( +void BandwidthUpgradeManager::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id, proto::connections::Medium medium) {} -template -void BandwidthUpgradeManager::processIncomingOfflineFrame( +void BandwidthUpgradeManager::processIncomingOfflineFrame( ConstPtr offline_frame, const string& from_endpoint_id, Ptr > to_client_proxy, proto::connections::Medium current_medium) {} -template -void BandwidthUpgradeManager::processEndpointDisconnection( +void BandwidthUpgradeManager::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( +bool BandwidthUpgradeManager::setCurrentBandwidthUpgradeHandler( proto::connections::Medium medium) { return false; } diff --git a/cpp/core/internal/bandwidth_upgrade_manager.h b/cpp/core/internal/bandwidth_upgrade_manager.h index 6715c2a9..b0a3e1a2 100644 --- a/cpp/core/internal/bandwidth_upgrade_manager.h +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -23,6 +23,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "proto/connections/offline_wire_formats.pb.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -33,14 +34,15 @@ namespace connections { // Manages all known {@link BandwidthUpgradeHandler} implementations, delegating // operations to the appropriate one as per the parameters passed in. -template class BandwidthUpgradeManager - : public EndpointManager::IncomingOfflineFrameProcessor { + : public EndpointManager< + platform::ImplementationPlatform>::IncomingOfflineFrameProcessor { public: - BandwidthUpgradeManager( - Ptr > medium_manager, - Ptr > endpoint_channel_manager, - Ptr > endpoint_manager); + using Platform = platform::ImplementationPlatform; + + BandwidthUpgradeManager(Ptr> medium_manager, + Ptr endpoint_channel_manager, + Ptr> endpoint_manager); ~BandwidthUpgradeManager() override; // This is the point on the initiator side where the @@ -64,17 +66,14 @@ class BandwidthUpgradeManager bool setCurrentBandwidthUpgradeHandler(proto::connections::Medium medium); Ptr > endpoint_manager_; - typedef std::map > > + typedef std::map> BandwidthUpgradeHandlersMap; BandwidthUpgradeHandlersMap bandwidth_upgrade_handlers_; - Ptr > current_bandwidth_upgrade_handler_; + Ptr current_bandwidth_upgrade_handler_; }; } // namespace connections } // namespace nearby } // namespace location -#include "core/internal/bandwidth_upgrade_manager.cc" - #endif // CORE_INTERNAL_BANDWIDTH_UPGRADE_MANAGER_H_ diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.cc b/cpp/core/internal/base_bandwidth_upgrade_handler.cc index 85650c0f..0664ad9b 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.cc +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -18,138 +18,115 @@ namespace location { namespace nearby { namespace connections { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace base_bandwidth_upgrade_handler { -template class RevertRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class InitiateBandwidthUpgradeForEndpointRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessEndpointDisconnectionRunnable : public Runnable { public: - void run() {} + void run() override {} }; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable : public Runnable { public: - void run() {} + void run() override {} }; } // namespace base_bandwidth_upgrade_handler -template -BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager) +BaseBandwidthUpgradeHandler::BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager) : endpoint_channel_manager_(endpoint_channel_manager), - alarm_executor_(), - serial_executor_(), + alarm_executor_(nullptr), + serial_executor_(nullptr), previous_endpoint_channels_(), in_progress_upgrades_(), safe_to_close_write_timestamps_() {} -template -BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} +BaseBandwidthUpgradeHandler::~BaseBandwidthUpgradeHandler() {} -template -void BaseBandwidthUpgradeHandler::revert() {} +void BaseBandwidthUpgradeHandler::revert() {} -template -void BaseBandwidthUpgradeHandler::processEndpointDisconnection( +void BaseBandwidthUpgradeHandler::processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, Ptr process_disconnection_barrier) {} -template -void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( +void BaseBandwidthUpgradeHandler::initiateBandwidthUpgradeForEndpoint( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradeNegotiationFrame( - ConstPtr - bandwidth_upgrade_negotiation, - Ptr > to_client_proxy, - const string& from_endpoint_id, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradeNegotiationFrame( + ConstPtr bandwidth_upgrade_negotiation, + Ptr > to_client_proxy, const string& from_endpoint_id, + proto::connections::Medium current_medium) {} -template -Ptr > -BaseBandwidthUpgradeHandler::getEndpointChannelManager() { +Ptr +BaseBandwidthUpgradeHandler::getEndpointChannelManager() { return endpoint_channel_manager_; } -template -void BaseBandwidthUpgradeHandler::onIncomingConnection( +void BaseBandwidthUpgradeHandler::onIncomingConnection( Ptr incoming_socket_connection) {} -template -void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( +void BaseBandwidthUpgradeHandler::runOnBandwidthUpgradeHandlerThread( Ptr runnable) {} -template -void BaseBandwidthUpgradeHandler::runUpgradeProtocol( +void BaseBandwidthUpgradeHandler::runUpgradeProtocol( Ptr > client_proxy, const string& endpoint_id, Ptr new_endpoint_channel) {} -template -void BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEvent( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info, - proto::connections::Medium current_medium) {} +void BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEvent( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info, + proto::connections::Medium current_medium) {} -template -Ptr BaseBandwidthUpgradeHandler:: - processBandwidthUpgradePathAvailableEventInternal( - const string& endpoint_id, Ptr > client_proxy, - ConstPtr - upgrade_path_info) { +Ptr +BaseBandwidthUpgradeHandler::processBandwidthUpgradePathAvailableEventInternal( + const string& endpoint_id, Ptr > client_proxy, + ConstPtr + upgrade_path_info) { return Ptr(); } -template -void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( +void BaseBandwidthUpgradeHandler::processLastWriteToPriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( +void BaseBandwidthUpgradeHandler::processSafeToClosePriorChannelEvent( Ptr > client_proxy, const string& endpoint_id) {} -template -std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( +std::int64_t BaseBandwidthUpgradeHandler::calculateCloseDelay( const string& endpoint_id) { return 0; } -template -std::int64_t -BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( +std::int64_t BaseBandwidthUpgradeHandler::getMillisSinceSafeCloseWritten( const string& endpoint_id) { return 0; } // TODO(ahlee): This will differ from the Java code as we don't have to handle // analytics in the C++ code. -template -void BaseBandwidthUpgradeHandler:: +void BaseBandwidthUpgradeHandler:: attemptToRecordBandwidthUpgradeErrorForUnknownEndpoint( proto::connections::BandwidthUpgradeResult result, proto::connections::BandwidthUpgradeErrorStage error_stage) {} // TODO(ahlee): This will differ from the Java code (previously threw an // UpgradeException). -template Ptr -BaseBandwidthUpgradeHandler::readClientIntroductionFrame( +BaseBandwidthUpgradeHandler::readClientIntroductionFrame( Ptr endpoint_channel) { return Ptr(); } diff --git a/cpp/core/internal/base_bandwidth_upgrade_handler.h b/cpp/core/internal/base_bandwidth_upgrade_handler.h index 867656cd..78dba420 100644 --- a/cpp/core/internal/base_bandwidth_upgrade_handler.h +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -33,13 +33,9 @@ namespace connections { namespace base_bandwidth_upgrade_handler { -template class RevertRunnable; -template class InitiateBandwidthUpgradeForEndpointRunnable; -template class ProcessEndpointDisconnectionRunnable; -template class ProcessBandwidthUpgradeNegotiationFrameRunnable; } // namespace base_bandwidth_upgrade_handler @@ -69,26 +65,28 @@ class ProcessBandwidthUpgradeNegotiationFrameRunnable; // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the // other, and upon doing so, close the prior EndpointChannel. // -template -class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { +class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { public: - BaseBandwidthUpgradeHandler( - Ptr > endpoint_channel_manager); - ~BaseBandwidthUpgradeHandler(); + using Platform = platform::ImplementationPlatform; - void revert(); + explicit BaseBandwidthUpgradeHandler( + Ptr endpoint_channel_manager); + ~BaseBandwidthUpgradeHandler() override; + + void revert() override; void processEndpointDisconnection( Ptr > client_proxy, const string& endpoint_id, - Ptr process_disconnection_barrier); + Ptr process_disconnection_barrier) override; // Initiates the bandwidth upgrade and sends an UPGRADE_PATH_AVAILABLE // OfflineFrame. void initiateBandwidthUpgradeForEndpoint( - Ptr > client_proxy, const string& endpoint_id); + Ptr > client_proxy, + const string& endpoint_id) override; void processBandwidthUpgradeNegotiationFrame( ConstPtr bandwidth_upgrade_negotiation, Ptr > to_client_proxy, const string& from_endpoint_id, - proto::connections::Medium current_medium); + proto::connections::Medium current_medium) override; protected: // Represents the incoming Socket the Initiator has gotten after initializing @@ -131,7 +129,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { // @BandwidthUpgradeHandlerThread virtual proto::connections::Medium getUpgradeMedium() = 0; - Ptr > getEndpointChannelManager(); + Ptr getEndpointChannelManager(); // Common functionality to take an incoming connection and go through the // upgrade process. // @BandwidthUpgradeHandlerThread @@ -140,15 +138,11 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { void runOnBandwidthUpgradeHandlerThread(Ptr runnable); private: - template friend class base_bandwidth_upgrade_handler::RevertRunnable; - template friend class base_bandwidth_upgrade_handler:: InitiateBandwidthUpgradeForEndpointRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessEndpointDisconnectionRunnable; - template friend class base_bandwidth_upgrade_handler:: ProcessBandwidthUpgradeNegotiationFrameRunnable; @@ -176,7 +170,7 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { Ptr readClientIntroductionFrame(Ptr endpoint_channel); - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; ScopedPtr > alarm_executor_; ScopedPtr > serial_executor_; // Stores each upgraded endpoint's previous EndpointChannel (that was @@ -198,6 +192,4 @@ class BaseBandwidthUpgradeHandler : public BandwidthUpgradeHandler { } // namespace nearby } // namespace location -#include "core/internal/base_bandwidth_upgrade_handler.cc" - #endif // CORE_INTERNAL_BASE_BANDWIDTH_UPGRADE_HANDLER_H_ diff --git a/cpp/core/internal/base_endpoint_channel.cc b/cpp/core/internal/base_endpoint_channel.cc index 7d15c17d..a4b6f0b6 100644 --- a/cpp/core/internal/base_endpoint_channel.cc +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -16,6 +16,7 @@ #include +#include "platform/api/platform.h" #include "platform/synchronized.h" #include "proto/connections_enums.pb.h" @@ -25,6 +26,8 @@ namespace connections { namespace { +using Platform = platform::ImplementationPlatform; + std::int32_t bytesToInt(ConstPtr bytes) { const char* int_bytes = bytes->getData(); @@ -47,36 +50,36 @@ ConstPtr intToBytes(std::int32_t value) { return MakeConstPtr(new ByteArray(int_bytes, sizeof(int_bytes))); } -ExceptionOr > readExactly(Ptr reader, - std::int64_t size) { +ExceptionOr> readExactly(Ptr reader, + std::int64_t size) { string buffer; std::int64_t remaining_size = size; while (remaining_size > 0) { - ExceptionOr > read_bytes = reader->read(remaining_size); + ExceptionOr> read_bytes = reader->read(remaining_size); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); // In Java, EOFException is a sub-variant of IOException. if (scoped_read_bytes.isNull() || scoped_read_bytes->size() == 0) { - return ExceptionOr >(Exception::IO); + return ExceptionOr>(Exception::IO); } buffer.append(scoped_read_bytes->getData(), scoped_read_bytes->size()); remaining_size -= scoped_read_bytes->size(); } - return ExceptionOr >( + return ExceptionOr>( MakeConstPtr(new ByteArray(buffer.data(), buffer.size()))); } ExceptionOr readInt(Ptr reader) { - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader, sizeof(std::int32_t)); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { @@ -84,7 +87,7 @@ ExceptionOr readInt(Ptr reader) { } } // Avoid leaks. - ScopedPtr > scoped_read_bytes(read_bytes.result()); + ScopedPtr> scoped_read_bytes(read_bytes.result()); return ExceptionOr(bytesToInt(scoped_read_bytes.get())); } @@ -96,10 +99,9 @@ Exception::Value writeInt(Ptr writer, std::int32_t value) { } // namespace // TODO(b/150763574): Move implementatiopn to header or .inc file. -template -BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, - Ptr reader, - Ptr writer) +BaseEndpointChannel::BaseEndpointChannel(absl::string_view channel_name, + Ptr reader, + Ptr writer) : last_read_timestamp_(-1), channel_name_(channel_name), system_clock_(Platform::createSystemClock()), @@ -114,8 +116,7 @@ BaseEndpointChannel::BaseEndpointChannel(const string& channel_name, Platform::createConditionVariable(is_paused_lock_.get())), is_paused_(Platform::createAtomicBoolean(false)) {} -template -BaseEndpointChannel::~BaseEndpointChannel() { +BaseEndpointChannel::~BaseEndpointChannel() { // WARNING: Make sure to never access reader_ and writer_ from here. // // They're owned by the specialized *Socket classes that are in turn @@ -128,14 +129,13 @@ BaseEndpointChannel::~BaseEndpointChannel() { // of this class). } -template -ExceptionOr > BaseEndpointChannel::read() { +ExceptionOr> BaseEndpointChannel::read() { Synchronized s(reader_lock_.get()); ExceptionOr read_int = readInt(reader_); if (!read_int.ok()) { if (Exception::IO == read_int.exception()) { - return ExceptionOr >(read_int.exception()); + return ExceptionOr>(read_int.exception()); } } @@ -145,11 +145,11 @@ ExceptionOr > BaseEndpointChannel::read() { return ExceptionOr>(Exception::IO); } - ExceptionOr > read_bytes = + ExceptionOr> read_bytes = readExactly(reader_, read_int.result()); if (!read_bytes.ok()) { if (Exception::IO == read_bytes.exception()) { - return ExceptionOr >(read_bytes.exception()); + return ExceptionOr>(read_bytes.exception()); } } @@ -168,7 +168,7 @@ ExceptionOr > BaseEndpointChannel::read() { // short-circuit out of here on error. read_bytes_result.destroy(); if (decoded_bytes == nullptr) { - return ExceptionOr >( + return ExceptionOr>( Exception::INVALID_PROTOCOL_BUFFER); } read_bytes_result = MakeConstPtr( @@ -176,16 +176,14 @@ ExceptionOr > BaseEndpointChannel::read() { } last_read_timestamp_ = system_clock_->elapsedRealtime(); - return ExceptionOr >(read_bytes_result); + return ExceptionOr>(read_bytes_result); } -template -Exception::Value BaseEndpointChannel::write( - ConstPtr data) { +Exception::Value BaseEndpointChannel::write(ConstPtr data) { Synchronized s(writer_lock_.get()); // Avoid leaks. - ScopedPtr > scoped_data(data); + ScopedPtr> scoped_data(data); if (isPaused()) { blockUntilUnpaused(); @@ -205,7 +203,7 @@ Exception::Value BaseEndpointChannel::write( data_to_write = scoped_data.release(); } // Avoid leaks. - ScopedPtr > scoped_data_to_write(data_to_write); + ScopedPtr> scoped_data_to_write(data_to_write); Exception::Value write_exception = writeInt( writer_, static_cast(scoped_data_to_write->size())); @@ -232,8 +230,7 @@ Exception::Value BaseEndpointChannel::write( return Exception::NONE; } -template -void BaseEndpointChannel::close() { +void BaseEndpointChannel::close() { // WARNING WARNING WARNING // // This block deviates from the corresponding Java code. @@ -260,8 +257,7 @@ void BaseEndpointChannel::close() { // TODO(tracyzhou): Add logging. } -template -void BaseEndpointChannel::close( +void BaseEndpointChannel::close( proto::connections::DisconnectionReason reason) { // WARNING WARNING WARNING // @@ -273,8 +269,7 @@ void BaseEndpointChannel::close( // TODO(tracyzhou): Add logging. } -template -string BaseEndpointChannel::getType() { +string BaseEndpointChannel::getType() { string subtype = isEncryptionEnabled() ? "ENCRYPTED_" : ""; switch (getMedium()) { case proto::connections::Medium::BLUETOOTH: @@ -292,46 +287,32 @@ string BaseEndpointChannel::getType() { } } -template -string BaseEndpointChannel::getName() { - return channel_name_; -} +string BaseEndpointChannel::getName() { return channel_name_; } -template -void BaseEndpointChannel::enableEncryption( +void BaseEndpointChannel::enableEncryption( Ptr encryption_context) { assert(!encryption_context.isNull()); encryption_context_->set(encryption_context); } -template -bool BaseEndpointChannel::isPaused() { - return is_paused_->get(); -} +bool BaseEndpointChannel::isPaused() { return is_paused_->get(); } -template -void BaseEndpointChannel::pause() { - is_paused_->set(true); -} +void BaseEndpointChannel::pause() { is_paused_->set(true); } -template -void BaseEndpointChannel::resume() { +void BaseEndpointChannel::resume() { is_paused_->set(false); unblockPausedWriter(); } -template -std::int64_t BaseEndpointChannel::getLastReadTimestamp() { +std::int64_t BaseEndpointChannel::getLastReadTimestamp() { return last_read_timestamp_; } -template -bool BaseEndpointChannel::isEncryptionEnabled() { +bool BaseEndpointChannel::isEncryptionEnabled() { return !encryption_context_->get().isNull(); } -template -void BaseEndpointChannel::unblockPausedWriter() { +void BaseEndpointChannel::unblockPausedWriter() { Synchronized s(is_paused_lock_.get()); // Notify to tell the thread calling wait() to check again. @@ -343,8 +324,7 @@ void BaseEndpointChannel::unblockPausedWriter() { is_paused_condition_variable_->notify(); } -template -void BaseEndpointChannel::blockUntilUnpaused() { +void BaseEndpointChannel::blockUntilUnpaused() { Synchronized s(is_paused_lock_.get()); // For more on how this works, see diff --git a/cpp/core/internal/base_endpoint_channel.h b/cpp/core/internal/base_endpoint_channel.h index b7ffec64..98e6edce 100644 --- a/cpp/core/internal/base_endpoint_channel.h +++ b/cpp/core/internal/base_endpoint_channel.h @@ -30,15 +30,15 @@ #include "platform/ptr.h" #include "proto/connections_enums.pb.h" #include "securegcm/d2d_connection_context_v1.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { namespace connections { -template class BaseEndpointChannel : public EndpointChannel { public: - BaseEndpointChannel(const string& channel_name, Ptr reader, + BaseEndpointChannel(absl::string_view channel_name, Ptr reader, Ptr writer); ~BaseEndpointChannel() override; @@ -83,7 +83,7 @@ class BaseEndpointChannel : public EndpointChannel { private: // Used to sanity check that our frame sizes are reasonable. - static const std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB bool isEncryptionEnabled(); void unblockPausedWriter(); @@ -121,6 +121,4 @@ class BaseEndpointChannel : public EndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/base_endpoint_channel.cc" - #endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/base_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc index 697d480c..7f0460d4 100644 --- a/cpp/core/internal/base_endpoint_channel_test.cc +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -14,7 +14,7 @@ #include "core/internal/base_endpoint_channel.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "platform/pipe.h" #include "proto/connections_enums.pb.h" #include "gmock/gmock.h" @@ -25,21 +25,7 @@ namespace nearby { namespace connections { namespace { -class TestPlatform : public DefaultPlatform { - public: - static SystemClock* createSystemClock() { return nullptr; } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr> createAtomicReference(const T& initial_value) { - return Ptr>(); - } -}; - -class TestEndpointChannel : public BaseEndpointChannel { +class TestEndpointChannel : public BaseEndpointChannel { public: explicit TestEndpointChannel(Ptr input_stream) : BaseEndpointChannel("channel", input_stream, Ptr()) {} @@ -48,7 +34,7 @@ class TestEndpointChannel : public BaseEndpointChannel { MOCK_METHOD(void, closeImpl, (), (override)); }; -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/core/internal/base_pcp_handler.cc b/cpp/core/internal/base_pcp_handler.cc index b83c2409..01544124 100644 --- a/cpp/core/internal/base_pcp_handler.cc +++ b/cpp/core/internal/base_pcp_handler.cc @@ -62,12 +62,7 @@ class StartAdvertisingCallable : public Callable { service_id_(service_id), local_endpoint_name_(local_endpoint_name), options_(options), - // Convert the passed in connection_lifecycle_listener Ptr into a - // reference counted one. The advertising session and any connected - // endpoints need a handle to the same connection_lifecycle_listener, so - // there is no clear model of who actually owns the listener. - connection_lifecycle_listener_( - MakeRefCountedPtr(&(*connection_lifecycle_listener))) {} + connection_lifecycle_listener_(connection_lifecycle_listener) {} ExceptionOr call() override { // Ask the implementation to attempt to start advertising. @@ -689,8 +684,8 @@ const std::int64_t template BasePCPHandler::BasePCPHandler( Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : endpoint_manager_(endpoint_manager), endpoint_channel_manager_(endpoint_channel_manager), bandwidth_upgrade_manager_(bandwidth_upgrade_manager), @@ -1151,6 +1146,14 @@ Exception::Value BasePCPHandler::onIncomingConnection( return Exception::IO; } + // The ConnectionRequest frame has two fields that both contain the + // EndpointInfo. The legacy field stores it as a string while the newer field + // stores it as a byte array. We'll attempt to grab from the newer field, but + // will accept the older string if it's all that exists. + const std::string& endpoint_name = connection_request.has_endpoint_info() + ? connection_request.endpoint_info() + : connection_request.endpoint_name(); + // We've successfully connected to the device, and are now about to jump on to // the EncryptionRunner thread to start running our encryption protocol. We'll // mark ourselves as pending in case we get another call to requestConnection @@ -1160,7 +1163,7 @@ Exception::Value BasePCPHandler::onIncomingConnection( .insert(std::make_pair( connection_request.endpoint_id(), PendingConnectionInfo::newIncomingPendingConnectionInfo( - client_proxy, connection_request.endpoint_name(), + client_proxy, endpoint_name, scoped_endpoint_channel.release(), connection_request.nonce(), start_time_millis, advertising_connection_lifecycle_listener_, OfflineFrames::connectionRequestMediumsToMediums( @@ -1392,7 +1395,7 @@ void BasePCPHandler::evaluateConnectionResult( } else { pending_rejected_connection_close_alarms_.insert(std::make_pair( endpoint_id, - MakePtr(new CancelableAlarm( + MakePtr(new CancelableAlarm( "BasePCPHandler.evaluateConnectionResult() delayed close", MakePtr( new base_pcp_handler:: @@ -1421,7 +1424,7 @@ BasePCPHandler::readConnectionRequestFrame( // To avoid a device connecting but never sending their introductory frame, we // time out the connection after a certain amount of time. - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "PCPHandler(" + this->getStrategy().getName() + ").readConnectionRequestFrame", MakePtr( diff --git a/cpp/core/internal/base_pcp_handler.h b/cpp/core/internal/base_pcp_handler.h index 17e9223f..a4370194 100644 --- a/cpp/core/internal/base_pcp_handler.h +++ b/cpp/core/internal/base_pcp_handler.h @@ -83,10 +83,9 @@ class BasePCPHandler public EndpointManager::IncomingOfflineFrameProcessor { public: // TODO(tracyzhou): Add SecureRandom. - BasePCPHandler( - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + BasePCPHandler(Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~BasePCPHandler() override; // We have been asked by the client to start advertising. Once we successfully @@ -253,8 +252,8 @@ class BasePCPHandler virtual proto::connections::Medium getDefaultUpgradeMedium() = 0; Ptr > endpoint_manager_; - Ptr > endpoint_channel_manager_; - Ptr > bandwidth_upgrade_manager_; + Ptr endpoint_channel_manager_; + Ptr bandwidth_upgrade_manager_; private: template @@ -487,7 +486,7 @@ class BasePCPHandler // reading the message (in which case, this alarm should be cancelled as it's // no longer needed), but this alarm is the fallback in case that doesn't // happen. - typedef std::map > > + typedef std::map > PendingRejectedConnectionCloseAlarmsMap; PendingRejectedConnectionCloseAlarmsMap pending_rejected_connection_close_alarms_; diff --git a/cpp/core/internal/ble_endpoint_channel.cc b/cpp/core/internal/ble_endpoint_channel.cc index 73618165..f99d27de 100644 --- a/cpp/core/internal/ble_endpoint_channel.cc +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -20,42 +20,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BLEEndpointChannel::createOutgoing( +Ptr BLEEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -Ptr > -BLEEndpointChannel::createIncoming( +Ptr BLEEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket) { - return MakePtr( - new BLEEndpointChannel(channel_name, ble_socket)); + return MakePtr(new BLEEndpointChannel(channel_name, ble_socket)); } -template -BLEEndpointChannel::BLEEndpointChannel( - const string& channel_name, Ptr ble_socket) - : BaseEndpointChannel(channel_name, - ble_socket->getInputStream(), - ble_socket->getOutputStream()), +BLEEndpointChannel::BLEEndpointChannel(const string& channel_name, + Ptr ble_socket) + : BaseEndpointChannel(channel_name, ble_socket->getInputStream(), + ble_socket->getOutputStream()), ble_socket_(ble_socket) {} -template -BLEEndpointChannel::~BLEEndpointChannel() {} +BLEEndpointChannel::~BLEEndpointChannel() {} -template -proto::connections::Medium BLEEndpointChannel::getMedium() { +proto::connections::Medium BLEEndpointChannel::getMedium() { return proto::connections::Medium::BLE; } -template -void BLEEndpointChannel::closeImpl() { +void BLEEndpointChannel::closeImpl() { Exception::Value exception = ble_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/ble_endpoint_channel.h b/cpp/core/internal/ble_endpoint_channel.h index a966f433..18dddb4a 100644 --- a/cpp/core/internal/ble_endpoint_channel.h +++ b/cpp/core/internal/ble_endpoint_channel.h @@ -18,6 +18,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/ble.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -26,13 +27,14 @@ namespace location { namespace nearby { namespace connections { -template -class BLEEndpointChannel : public BaseEndpointChannel { +class BLEEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr ble_socket); @@ -53,6 +55,4 @@ class BLEEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/ble_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/bluetooth_endpoint_channel.cc b/cpp/core/internal/bluetooth_endpoint_channel.cc index 59cfd897..0b4c19b5 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.cc +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -20,42 +20,31 @@ namespace location { namespace nearby { namespace connections { -template -Ptr > -BluetoothEndpointChannel::createOutgoing( +Ptr BluetoothEndpointChannel::createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -Ptr > -BluetoothEndpointChannel::createIncoming( +Ptr BluetoothEndpointChannel::createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket) { - return MakePtr( - new BluetoothEndpointChannel(channel_name, bluetooth_socket)); + return MakePtr(new BluetoothEndpointChannel(channel_name, bluetooth_socket)); } -template -BluetoothEndpointChannel::BluetoothEndpointChannel( +BluetoothEndpointChannel::BluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) - : BaseEndpointChannel(channel_name, - bluetooth_socket->getInputStream(), - bluetooth_socket->getOutputStream()), + : BaseEndpointChannel(channel_name, bluetooth_socket->getInputStream(), + bluetooth_socket->getOutputStream()), bluetooth_socket_(bluetooth_socket) {} -template -BluetoothEndpointChannel::~BluetoothEndpointChannel() {} +BluetoothEndpointChannel::~BluetoothEndpointChannel() {} -template -proto::connections::Medium BluetoothEndpointChannel::getMedium() { +proto::connections::Medium BluetoothEndpointChannel::getMedium() { return proto::connections::Medium::BLUETOOTH; } -template -void BluetoothEndpointChannel::closeImpl() { +void BluetoothEndpointChannel::closeImpl() { Exception::Value exception = bluetooth_socket_->close(); if (exception != Exception::NONE) { if (exception == Exception::IO) { diff --git a/cpp/core/internal/bluetooth_endpoint_channel.h b/cpp/core/internal/bluetooth_endpoint_channel.h index 74d75bb6..52245226 100644 --- a/cpp/core/internal/bluetooth_endpoint_channel.h +++ b/cpp/core/internal/bluetooth_endpoint_channel.h @@ -18,6 +18,7 @@ #include "core/internal/base_endpoint_channel.h" #include "core/internal/medium_manager.h" #include "platform/api/bluetooth_classic.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "proto/connections_enums.pb.h" @@ -26,13 +27,14 @@ namespace location { namespace nearby { namespace connections { -template -class BluetoothEndpointChannel : public BaseEndpointChannel { +class BluetoothEndpointChannel : public BaseEndpointChannel { public: - static Ptr > createOutgoing( + using Platform = platform::ImplementationPlatform; + + static Ptr createOutgoing( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); - static Ptr > createIncoming( + static Ptr createIncoming( Ptr > medium_manager, const string& channel_name, Ptr bluetooth_socket); @@ -54,6 +56,4 @@ class BluetoothEndpointChannel : public BaseEndpointChannel { } // namespace nearby } // namespace location -#include "core/internal/bluetooth_endpoint_channel.cc" - #endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/encryption_runner.cc b/cpp/core/internal/encryption_runner.cc index 3536d17a..fc9b6ab7 100644 --- a/cpp/core/internal/encryption_runner.cc +++ b/cpp/core/internal/encryption_runner.cc @@ -115,7 +115,7 @@ class ServerRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startServer() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -126,7 +126,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -135,7 +135,7 @@ class ServerRunnable : public Runnable { if (!client_init.ok()) { if (Exception::IO == client_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -152,7 +152,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -165,7 +165,7 @@ class ServerRunnable : public Runnable { // Java code throws a HandshakeException. if (server_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -174,7 +174,7 @@ class ServerRunnable : public Runnable { if (Exception::NONE != write_exception) { if (Exception::IO == write_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -188,7 +188,7 @@ class ServerRunnable : public Runnable { if (!client_finish.ok()) { if (Exception::IO == client_finish.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -203,7 +203,7 @@ class ServerRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -216,7 +216,7 @@ class ServerRunnable : public Runnable { MakePtr(server.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -227,8 +227,8 @@ class ServerRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } @@ -272,7 +272,7 @@ class ClientRunnable : public Runnable { encryption_result_listener_(encryption_result_listener) {} void run() override { - CancelableAlarm timeout_alarm( + CancelableAlarm timeout_alarm( "EncryptionRunner.startClient() timeout", MakePtr(new CancelableAlarmRunnable( client_proxy_, endpoint_id_, endpoint_channel_)), @@ -284,7 +284,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -294,7 +294,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_init == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -303,7 +303,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_init_exception) { if (Exception::IO == write_init_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -317,7 +317,7 @@ class ClientRunnable : public Runnable { if (!server_init.ok()) { if (Exception::IO == server_init.exception()) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -333,7 +333,7 @@ class ClientRunnable : public Runnable { if (parse_result.alert_to_send != nullptr) { handleAlertException(parse_result); } - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -346,7 +346,7 @@ class ClientRunnable : public Runnable { // Java code throws a HandshakeException. if (client_finish == nullptr) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } @@ -356,7 +356,7 @@ class ClientRunnable : public Runnable { if (Exception::NONE != write_finish_exception) { if (Exception::IO == write_finish_exception) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -370,7 +370,7 @@ class ClientRunnable : public Runnable { MakePtr(client.release()), encryption_result_listener_.get())) { logException(); - handleHandshakeOrIOException(timeout_alarm); + handleHandshakeOrIOException(&timeout_alarm); return; } } @@ -381,8 +381,8 @@ class ClientRunnable : public Runnable { endpoint_id_.c_str()); } - void handleHandshakeOrIOException(CancelableAlarm& timeout_alarm) { - timeout_alarm.cancel(); + void handleHandshakeOrIOException(CancelableAlarm* timeout_alarm) { + timeout_alarm->cancel(); encryption_result_listener_->onEncryptionFailure(endpoint_id_, endpoint_channel_); } diff --git a/cpp/core/internal/endpoint_channel_manager.cc b/cpp/core/internal/endpoint_channel_manager.cc index ebd753de..e887e9ce 100644 --- a/cpp/core/internal/endpoint_channel_manager.cc +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -16,21 +16,20 @@ #include "core/internal/ble_endpoint_channel.h" #include "core/internal/bluetooth_endpoint_channel.h" +#include "core/internal/wifi_lan_endpoint_channel.h" #include "platform/synchronized.h" namespace location { namespace nearby { namespace connections { -template -EndpointChannelManager::EndpointChannelManager( +EndpointChannelManager::EndpointChannelManager( Ptr > medium_manager) : lock_(Platform::createLock()), medium_manager_(medium_manager), channel_state_(new ChannelState()) {} -template -EndpointChannelManager::~EndpointChannelManager() { +EndpointChannelManager::~EndpointChannelManager() { Synchronized s(lock_.get()); // TODO(tracyzhou): logger.atDebug().log("Initiating shutdown of @@ -40,40 +39,47 @@ EndpointChannelManager::~EndpointChannelManager() { // down."); } -template Ptr -EndpointChannelManager::createOutgoingBluetoothEndpointChannel( +EndpointChannelManager::createOutgoingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createOutgoing( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createOutgoing(medium_manager_, channel_name, + bluetooth_socket); } -template Ptr -EndpointChannelManager::createIncomingBluetoothEndpointChannel( +EndpointChannelManager::createIncomingBluetoothEndpointChannel( const string& channel_name, Ptr bluetooth_socket) { - return BluetoothEndpointChannel::createIncoming( - medium_manager_, channel_name, bluetooth_socket); + return BluetoothEndpointChannel::createIncoming(medium_manager_, channel_name, + bluetooth_socket); } -template -Ptr -EndpointChannelManager::createOutgoingBLEEndpointChannel( +Ptr EndpointChannelManager::createOutgoingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createOutgoing(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createOutgoing(medium_manager_, channel_name, + ble_socket); } -template -Ptr -EndpointChannelManager::createIncomingBLEEndpointChannel( +Ptr EndpointChannelManager::createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket) { - return BLEEndpointChannel::createIncoming(medium_manager_, - channel_name, ble_socket); + return BLEEndpointChannel::createIncoming(medium_manager_, channel_name, + ble_socket); } -template -void EndpointChannelManager::registerChannelForEndpoint( +Ptr +EndpointChannelManager::CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateOutgoing( + medium_manager_, channel_name, wifi_lan_socket); +} + +Ptr +EndpointChannelManager::CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket) { + return WifiLanEndpointChannel::CreateIncoming( + medium_manager_, channel_name, wifi_lan_socket); +} + +void EndpointChannelManager::registerChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -88,9 +94,7 @@ void EndpointChannelManager::registerChannelForEndpoint( } #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED -template -Ptr -EndpointChannelManager::replaceChannelForEndpoint( +Ptr EndpointChannelManager::replaceChannelForEndpoint( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { Synchronized s(lock_.get()); @@ -110,8 +114,7 @@ EndpointChannelManager::replaceChannelForEndpoint( } #endif -template -bool EndpointChannelManager::encryptChannelForEndpoint( +bool EndpointChannelManager::encryptChannelForEndpoint( const string& endpoint_id, Ptr encryption_context) { Synchronized s(lock_.get()); @@ -139,16 +142,14 @@ bool EndpointChannelManager::encryptChannelForEndpoint( return true; } -template -Ptr EndpointChannelManager::getChannelForEndpoint( +Ptr EndpointChannelManager::getChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); return channel_state_->getChannelForEndpoint(endpoint_id); } -template -void EndpointChannelManager::setActiveEndpointChannel( +void EndpointChannelManager::setActiveEndpointChannel( Ptr > client_proxy, const string& endpoint_id, Ptr endpoint_channel) { #ifdef BANDWIDTH_UPGRADE_MANAGER_IMPLEMENTED @@ -169,8 +170,7 @@ void EndpointChannelManager::setActiveEndpointChannel( channel_state_->updateChannelForEndpoint(endpoint_id, endpoint_channel)); } -template -void EndpointChannelManager::encryptChannel( +void EndpointChannelManager::encryptChannel( const string& endpoint_id, Ptr endpoint_channel, Ptr encryption_context) { // TODO(tracyzhou): Add logging. @@ -179,8 +179,7 @@ void EndpointChannelManager::encryptChannel( ///////////////////////////////// ChannelState ///////////////////////////////// -template -EndpointChannelManager::ChannelState::~ChannelState() { +EndpointChannelManager::ChannelState::~ChannelState() { while (!endpoint_id_to_metadata_.empty()) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.begin(); @@ -190,15 +189,13 @@ EndpointChannelManager::ChannelState::~ChannelState() { } } -template -bool EndpointChannelManager::ChannelState::isEndpointEncrypted( +bool EndpointChannelManager::ChannelState::isEndpointEncrypted( const string& endpoint_id) { return !getEncryptionContextForEndpoint(endpoint_id).isNull(); } -template Ptr -EndpointChannelManager::ChannelState::updateChannelForEndpoint( +EndpointChannelManager::ChannelState::updateChannelForEndpoint( const string& endpoint_id, Ptr endpoint_channel) { Ptr previous_endpoint_channel; Ptr endpoint_metadata; @@ -222,11 +219,10 @@ EndpointChannelManager::ChannelState::updateChannelForEndpoint( return scoped_previous_endpoint_channel.release(); } -template -Ptr EndpointChannelManager:: - ChannelState::updateEncryptionContextForEndpoint( - const string& endpoint_id, - Ptr encryption_context) { +Ptr +EndpointChannelManager::ChannelState::updateEncryptionContextForEndpoint( + const string& endpoint_id, + Ptr encryption_context) { Ptr previous_encryption_context; Ptr endpoint_metadata; @@ -248,8 +244,7 @@ Ptr EndpointChannelManager:: return scoped_previous_encryption_context.release(); } -template -bool EndpointChannelManager::ChannelState::removeEndpoint( +bool EndpointChannelManager::ChannelState::removeEndpoint( const string& endpoint_id, proto::connections::DisconnectionReason reason) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -263,9 +258,8 @@ bool EndpointChannelManager::ChannelState::removeEndpoint( return true; } -template Ptr -EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( +EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -276,9 +270,8 @@ EndpointChannelManager::ChannelState::getEncryptionContextForEndpoint( return it->second->encryption_context; } -template Ptr -EndpointChannelManager::ChannelState::getChannelForEndpoint( +EndpointChannelManager::ChannelState::getChannelForEndpoint( const string& endpoint_id) { typename EndpointIdToMetadataMap::iterator it = endpoint_id_to_metadata_.find(endpoint_id); @@ -289,8 +282,7 @@ EndpointChannelManager::ChannelState::getChannelForEndpoint( return it->second->endpoint_channel; } -template -bool EndpointChannelManager::unregisterChannelForEndpoint( +bool EndpointChannelManager::unregisterChannelForEndpoint( const string& endpoint_id) { Synchronized s(lock_.get()); diff --git a/cpp/core/internal/endpoint_channel_manager.h b/cpp/core/internal/endpoint_channel_manager.h index dff4d7b0..64e8aabe 100644 --- a/cpp/core/internal/endpoint_channel_manager.h +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -23,6 +23,8 @@ #include "platform/api/ble.h" #include "platform/api/bluetooth_classic.h" #include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" #include "platform/port/string.h" #include "platform/ptr.h" #include "securegcm/d2d_connection_context_v1.h" @@ -36,10 +38,11 @@ namespace connections { // // The factory methods would be static, but for the fact that they need to use // the MediumManager. -template class EndpointChannelManager { public: - explicit EndpointChannelManager(Ptr > medium_manager); + using Platform = platform::ImplementationPlatform; + + explicit EndpointChannelManager(Ptr> medium_manager); ~EndpointChannelManager(); Ptr createOutgoingBluetoothEndpointChannel( @@ -52,6 +55,11 @@ class EndpointChannelManager { Ptr createIncomingBLEEndpointChannel( const string& channel_name, Ptr ble_socket); + Ptr CreateOutgoingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket); + Ptr CreateIncomingWifiLanEndpointChannel( + const string& channel_name, Ptr wifi_lan_socket); + // Registers the initial EndpointChannel to be associated with an endpoint; // if there already exists a previously-associated EndpointChannel, that will // be closed before continuing the registration. @@ -152,6 +160,4 @@ class EndpointChannelManager { } // namespace nearby } // namespace location -#include "core/internal/endpoint_channel_manager.cc" - #endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core/internal/endpoint_manager.cc b/cpp/core/internal/endpoint_manager.cc index 2586533f..64e13721 100644 --- a/cpp/core/internal/endpoint_manager.cc +++ b/cpp/core/internal/endpoint_manager.cc @@ -480,7 +480,7 @@ const std::int32_t EndpointManager::kMaxConcurrentEndpoints = 50; template EndpointManager::EndpointManager( - Ptr> endpoint_channel_manager) + Ptr endpoint_channel_manager) : thread_utils_(Platform::createThreadUtils()), system_clock_(Platform::createSystemClock()), endpoint_channel_manager_(endpoint_channel_manager), diff --git a/cpp/core/internal/endpoint_manager.h b/cpp/core/internal/endpoint_manager.h index 772acf13..40518334 100644 --- a/cpp/core/internal/endpoint_manager.h +++ b/cpp/core/internal/endpoint_manager.h @@ -109,7 +109,7 @@ class EndpointManager { }; explicit EndpointManager( - Ptr > endpoint_channel_manager); + Ptr endpoint_channel_manager); ~EndpointManager(); // Invoked from the constructors of the various *Manager components that make @@ -225,7 +225,7 @@ class EndpointManager { ScopedPtr > thread_utils_; ScopedPtr > system_clock_; - Ptr > endpoint_channel_manager_; + Ptr endpoint_channel_manager_; typedef std::map > IncomingOfflineFrameProcessorsMap; diff --git a/cpp/core/internal/internal_payload_factory.cc b/cpp/core/internal/internal_payload_factory.cc index 616b0ba9..3ce11096 100644 --- a/cpp/core/internal/internal_payload_factory.cc +++ b/cpp/core/internal/internal_payload_factory.cc @@ -122,7 +122,7 @@ class OutgoingStreamInternalPayload : public InternalPayload { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; template @@ -205,7 +205,7 @@ class OutgoingFileInternalPayload : public InternalPayload { void close() override { payload_->asFile()->asInputFile()->close(); } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; }; class IncomingFileInternalPayload : public InternalPayload { @@ -291,14 +291,13 @@ Ptr InternalPayloadFactory::createIncoming( case PayloadTransferFrame::PayloadHeader::STREAM: { // pipe will be auto-destroyed when it is no longer referenced. - auto pipe = MakeRefCountedPtr(new Pipe()); + auto pipe = MakeRefCountedPtr(new Pipe()); return MakePtr(new IncomingStreamInternalPayload( - MakeConstPtr(new Payload( - payload_id, - MakeConstPtr(new Payload::Stream( - Pipe::createInputStream(pipe))))), - Pipe::createOutputStream(pipe))); + MakeConstPtr( + new Payload(payload_id, MakeConstPtr(new Payload::Stream( + Pipe::createInputStream(pipe))))), + Pipe::createOutputStream(pipe))); } case PayloadTransferFrame::PayloadHeader::FILE: { diff --git a/cpp/core/internal/medium_manager.cc b/cpp/core/internal/medium_manager.cc index 40c18139..27a2ce06 100644 --- a/cpp/core/internal/medium_manager.cc +++ b/cpp/core/internal/medium_manager.cc @@ -24,13 +24,15 @@ template MediumManager::MediumManager() : mediums_(new Mediums()), bluetooth_classic_lock_(Platform::createLock()), - ble_lock_(Platform::createLock()) {} + ble_lock_(Platform::createLock()), + wifi_lan_lock_(Platform::createLock()) {} template MediumManager::~MediumManager() { // TODO(reznor): log.atDebug().log("Initiating shutdown of MediumManager."); Synchronized s1(bluetooth_classic_lock_.get()); Synchronized s2(ble_lock_.get()); + Synchronized s3(wifi_lan_lock_.get()); mediums_.destroy(); // TODO(reznor): log.atDebug().log("MediumManager has shut down."); @@ -370,6 +372,131 @@ Ptr MediumManager::connectToBlePeripheral( #endif } +// ~~~~~~~~~~~~~~~~~~~~~~~~ WIFILAN ~~~~~~~~~~~~~~~~~~~~~~~~ +template +bool MediumManager::IsWifiLanAvailable() { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAvailable(); +} + +template +bool MediumManager::StartWifiLanAdvertising( + absl::string_view service_id, absl::string_view service_info_name) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAdvertising(service_id, service_info_name); +} + +template +void MediumManager::StopWifiLanAdvertising( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAdvertising(service_id); +} + +template +class DiscoveredServiceCallback : public mediums::DiscoveredServiceCallback { + public: + typedef typename MediumManager::FoundWifiLanServiceProcessor + FoundWifiLanServiceProcessor; + + explicit DiscoveredServiceCallback( + Ptr found_wifi_lan_service_processor) + : found_wifi_lan_service_processor_(found_wifi_lan_service_processor) {} + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnFoundWifiLanService(wifi_lan_service); + } + + void OnServiceLost(Ptr wifi_lan_service) override { + found_wifi_lan_service_processor_->OnLostWifiLanService(wifi_lan_service); + } + + private: + ScopedPtr > + found_wifi_lan_service_processor_; +}; + +template +bool MediumManager::StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartDiscovery( + service_id, MakePtr(new DiscoveredServiceCallback( + found_wifi_lan_service_processor))); +} + +template +void MediumManager::StopWifiLanDiscovery( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopDiscovery(service_id); +} + +template +class WifiLanAcceptedConnectionCallback + : public mediums::WifiLan::AcceptedConnectionCallback { + public: + typedef typename MediumManager::IncomingWifiLanConnectionProcessor + IncomingWifiLanConnectionProcessor; + + explicit WifiLanAcceptedConnectionCallback( + Ptr + incoming_wifi_lan_connection_processor) + : incoming_wifi_lan_connection_processor_( + incoming_wifi_lan_connection_processor) {} + + void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) override { + incoming_wifi_lan_connection_processor_->OnIncomingWifiLanConnection( + socket); + } + + private: + ScopedPtr > + incoming_wifi_lan_connection_processor_; +}; + +template +bool MediumManager::IsListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->IsAcceptingConnections(service_id); +} + +template +bool MediumManager::StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->StartAcceptingConnections( + service_id, MakePtr(new WifiLanAcceptedConnectionCallback( + incoming_wifi_lan_connection_processor))); +} + +template +void MediumManager::StopListeningForIncomingWifiLanConnections( + absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + mediums_->wifi_lan()->StopAcceptingConnections(service_id); +} + +template +Ptr MediumManager::ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(wifi_lan_lock_.get()); + + return mediums_->wifi_lan()->Connect(wifi_lan_service, service_id); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/medium_manager.h b/cpp/core/internal/medium_manager.h index 002b6e1e..74c2cc00 100644 --- a/cpp/core/internal/medium_manager.h +++ b/cpp/core/internal/medium_manager.h @@ -136,6 +136,45 @@ class MediumManager { Ptr connectToBlePeripheral(Ptr ble_peripheral, const string& service_id); + // ~~~~~~~~~~~~~~~~~~~~~~~~ WIFI-LAN ~~~~~~~~~~~~~~~~~~~~~~~~ + + bool IsWifiLanAvailable(); + + bool StartWifiLanAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopWifiLanAdvertising(absl::string_view service_id); + + class FoundWifiLanServiceProcessor { + public: + virtual ~FoundWifiLanServiceProcessor() {} + + virtual void OnFoundWifiLanService( + Ptr wifi_lan_service) = 0; + virtual void OnLostWifiLanService(Ptr wifi_lan_service) = 0; + }; + + bool StartWifiLanDiscovery( + absl::string_view service_id, + Ptr found_wifi_lan_service_processor); + void StopWifiLanDiscovery(absl::string_view service_id); + + class IncomingWifiLanConnectionProcessor { + public: + virtual ~IncomingWifiLanConnectionProcessor() {} + + virtual void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) = 0; + }; + + bool IsListeningForIncomingWifiLanConnections(absl::string_view service_id); + bool StartListeningForIncomingWifiLanConnections( + absl::string_view service_id, Ptr + incoming_wifi_lan_connection_processor); + void StopListeningForIncomingWifiLanConnections(absl::string_view service_id); + + Ptr ConnectToWifiLanService( + Ptr wifi_lan_service, absl::string_view service_id); + private: // The destructor for this needs to be manually invoked after the locks below // are acquired, so it cannot be a ScopedPtr. @@ -143,6 +182,7 @@ class MediumManager { ScopedPtr > bluetooth_classic_lock_; ScopedPtr > ble_lock_; + ScopedPtr > wifi_lan_lock_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD index 4916b4a0..d7f2708f 100644 --- a/cpp/core/internal/mediums/BUILD +++ b/cpp/core/internal/mediums/BUILD @@ -12,6 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +cc_library( + name = "utils", + srcs = [ + "utils.cc", + ], + hdrs = [ + "utils.h", + ], + visibility = [ + "//core/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + cc_library( name = "mediums", srcs = [ @@ -19,8 +39,6 @@ cc_library( "ble_advertisement_header.cc", "ble_packet.cc", "ble_peripheral.cc", - "utils.cc", - "utils.h", ], hdrs = [ "advertisement_read_result.cc", @@ -48,9 +66,12 @@ cc_library( "mediums.h", "uuid.cc", "uuid.h", + "wifi_lan.cc", + "wifi_lan.h", ], visibility = ["//core/internal:__pkg__"], deps = [ + ":utils", "//platform:logging", "//platform:types", "//platform:utils", @@ -67,7 +88,8 @@ cc_test( srcs = ["advertisement_read_result_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", "//absl/time", ], @@ -79,6 +101,8 @@ cc_test( deps = [ ":mediums", "//platform:utils", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -88,6 +112,8 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -97,6 +123,8 @@ cc_test( srcs = ["ble_packet_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -106,6 +134,8 @@ cc_test( srcs = ["bloom_filter_test.cc"], deps = [ ":mediums", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) @@ -115,7 +145,8 @@ cc_test( srcs = ["lost_entity_tracker_test.cc"], deps = [ ":mediums", - "//platform/impl/default", + "//platform/api", + "//platform/impl/g3", "//testing/base/public:gunit_main", ], ) diff --git a/cpp/core/internal/mediums/CMakeLists.txt b/cpp/core/internal/mediums/CMakeLists.txt index e991f711..4936ff68 100644 --- a/cpp/core/internal/mediums/CMakeLists.txt +++ b/cpp/core/internal/mediums/CMakeLists.txt @@ -66,7 +66,7 @@ target_link_libraries(core_internal_mediums_test core_internal_mediums gtest gtest_main - platform_impl_default + platform_impl_g3 platform_utils ) diff --git a/cpp/core/internal/mediums/advertisement_read_result_test.cc b/cpp/core/internal/mediums/advertisement_read_result_test.cc index ecd4923d..47d3fba3 100644 --- a/cpp/core/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -14,7 +14,7 @@ #include "core/internal/mediums/advertisement_read_result.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -24,23 +24,7 @@ namespace nearby { namespace connections { namespace mediums { -class SampleSystemClock : public SystemClock { - public: - SampleSystemClock() {} - ~SampleSystemClock() override {} - - std::int64_t elapsedRealtime() override { - return absl::ToUnixMillis(absl::Now()); - } -}; - -class SamplePlatform { - public: - static Ptr createLock() { return DefaultPlatform::createLock(); } - static Ptr createSystemClock() { - return MakePtr(new SampleSystemClock()); - } -}; +using TestPlatform = platform::ImplementationPlatform; constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C}; @@ -53,16 +37,16 @@ const absl::Duration kAdvertisementMaxBackoffDuration = template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementMaxBackoffDurationMillis = + TestPlatform>::kAdvertisementMaxBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementMaxBackoffDuration); template <> const std::int64_t AdvertisementReadResult< - SamplePlatform>::kAdvertisementBaseBackoffDurationMillis = + TestPlatform>::kAdvertisementBaseBackoffDurationMillis = ToInt64Milliseconds(kAdvertisementBaseBackoffDuration); TEST(AdvertisementReadResultTest, AdvertisementExists) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -75,7 +59,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) { } TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int32_t slot = 6; @@ -84,23 +68,23 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { } TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), AdvertisementReadResult< - SamplePlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); + TestPlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Sleep for some time, but not long enough to warrant a retry. @@ -108,22 +92,22 @@ TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2)); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Sleep long enough to warrant a retry. absl::SleepFor(kAdvertisementBaseBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an additional failure so our backoff duration increases. @@ -134,11 +118,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { absl::SleepFor(kAdvertisementBaseBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::TOO_SOON); + AdvertisementReadResult::RetryStatus::TOO_SOON); } TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ false); // Record an absurd amount of failures so we hit the maximum backoff duration. @@ -151,11 +135,11 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { absl::SleepFor(kAdvertisementMaxBackoffDuration); ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(), - AdvertisementReadResult::RetryStatus::RETRY); + AdvertisementReadResult::RetryStatus::RETRY); } TEST(AdvertisementReadResultTest, GetDurationSinceRead) { - AdvertisementReadResult advertisement_read_result; + AdvertisementReadResult advertisement_read_result; advertisement_read_result.recordLastReadStatus(/* is_success= */ true); std::int64_t sleepTime = 420; diff --git a/cpp/core/internal/mediums/ble_v2.cc b/cpp/core/internal/mediums/ble_v2.cc index 7e491814..8de92bc1 100644 --- a/cpp/core/internal/mediums/ble_v2.cc +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -474,8 +474,8 @@ void BLEV2::stopScanning() { // TODO(b/112199086) Change to RecurringCancelableAlarm template -Ptr> BLEV2::createOnLostAlarm() { - return Ptr>(); +Ptr BLEV2::createOnLostAlarm() { + return Ptr(); } // Returns true if the device is currently accepting incoming BLE socket diff --git a/cpp/core/internal/mediums/ble_v2.h b/cpp/core/internal/mediums/ble_v2.h index 8f268959..8f384131 100644 --- a/cpp/core/internal/mediums/ble_v2.h +++ b/cpp/core/internal/mediums/ble_v2.h @@ -180,7 +180,7 @@ class BLEV2 { struct ScanningInfo { ScanningInfo(const string& service_id, Ptr scan_callback_facade, - Ptr> on_lost_alarm) + Ptr on_lost_alarm) : service_id(service_id), scan_callback_facade(scan_callback_facade), on_lost_alarm(on_lost_alarm) {} @@ -191,7 +191,7 @@ class BLEV2 { const string service_id; ScopedPtr> scan_callback_facade; // TODO(ahlee): Change to recurring cancelable alarm - ScopedPtr>> on_lost_alarm; + ScopedPtr> on_lost_alarm; }; struct AdvertisingInfo { @@ -250,7 +250,7 @@ class BLEV2 { Ptr ble_peripheral, ConstPtr advertisement_data); void processOnLostTimeout(); - Ptr> createOnLostAlarm(); + Ptr createOnLostAlarm(); bool isAdvertisementGattServerRunning(); bool startAdvertisementGattServer(const string& service_id, diff --git a/cpp/core/internal/mediums/lost_entity_tracker_test.cc b/cpp/core/internal/mediums/lost_entity_tracker_test.cc index 7da24e22..57d1a594 100644 --- a/cpp/core/internal/mediums/lost_entity_tracker_test.cc +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -14,7 +14,7 @@ #include "core/internal/mediums/lost_entity_tracker.h" -#include "platform/impl/default/default_platform.h" +#include "platform/api/platform.h" #include "gtest/gtest.h" namespace location { @@ -23,6 +23,8 @@ namespace connections { namespace mediums { namespace { +using TestPlatform = platform::ImplementationPlatform; + struct TestEntity { int id; @@ -32,7 +34,7 @@ struct TestEntity { }; TEST(LostEntityTracker, NoEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -55,7 +57,7 @@ TEST(LostEntityTracker, NoEntitiesLost) { } TEST(LostEntityTracker, AllEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -69,7 +71,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty()); // Go through a round without rediscovering any entities. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); @@ -77,7 +79,7 @@ TEST(LostEntityTracker, AllEntitiesLost) { } TEST(LostEntityTracker, SomeEntitiesLost) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_2(MakeConstPtr(new TestEntity(2))); ScopedPtr > entity_3(MakeConstPtr(new TestEntity(3))); @@ -94,7 +96,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { // was lost after the check. lost_entity_tracker.recordFoundEntity(entity_1.get()); lost_entity_tracker.recordFoundEntity(entity_3.get()); - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end()); ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end()); @@ -102,7 +104,7 @@ TEST(LostEntityTracker, SomeEntitiesLost) { } TEST(LostEntityTracker, SameEntityMultipleCopies) { - LostEntityTracker lost_entity_tracker; + LostEntityTracker lost_entity_tracker; ScopedPtr > entity_1(MakeConstPtr(new TestEntity(1))); ScopedPtr > entity_1_copy( MakeConstPtr(new TestEntity(1))); @@ -121,7 +123,7 @@ TEST(LostEntityTracker, SameEntityMultipleCopies) { // Go through a round without rediscovering any entities and verify that we // lost an entity equivalent to both copies of it. - typename LostEntityTracker::EntitySet + typename LostEntityTracker::EntitySet lost_entities = lost_entity_tracker.computeLostEntities(); ASSERT_EQ(lost_entities.size(), 1); ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end()); diff --git a/cpp/core/internal/mediums/mediums.cc b/cpp/core/internal/mediums/mediums.cc index 69c1d166..391024a9 100644 --- a/cpp/core/internal/mediums/mediums.cc +++ b/cpp/core/internal/mediums/mediums.cc @@ -24,7 +24,8 @@ Mediums::Mediums() bluetooth_classic_( new BluetoothClassic(bluetooth_radio_.get())), ble_(new BLE(bluetooth_radio_.get())), - ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())) {} + ble_v2_(new mediums::BLEV2(bluetooth_radio_.get())), + wifi_lan_(new mediums::WifiLan()) {} template Mediums::~Mediums() { @@ -51,6 +52,11 @@ Ptr > Mediums::bleV2() const { return ble_v2_.get(); } +template +Ptr > Mediums::wifi_lan() const { + return wifi_lan_.get(); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/mediums/mediums.h b/cpp/core/internal/mediums/mediums.h index 68c6c72d..29d04256 100644 --- a/cpp/core/internal/mediums/mediums.h +++ b/cpp/core/internal/mediums/mediums.h @@ -19,6 +19,7 @@ #include "core/internal/mediums/ble_v2.h" #include "core/internal/mediums/bluetooth_classic.h" #include "core/internal/mediums/bluetooth_radio.h" +#include "core/internal/mediums/wifi_lan.h" #include "platform/ptr.h" namespace location { @@ -41,6 +42,8 @@ class Mediums { Ptr > ble() const; // Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium. Ptr > bleV2() const; + // Returns a handle to the Wifi-Lan medium. + Ptr > wifi_lan() const; private: // The order of declaration is critical for both construction and @@ -55,6 +58,7 @@ class Mediums { ScopedPtr > > bluetooth_classic_; ScopedPtr > > ble_; ScopedPtr > > ble_v2_; + ScopedPtr > > wifi_lan_; }; } // namespace connections diff --git a/cpp/core/internal/mediums/utils.cc b/cpp/core/internal/mediums/utils.cc index 0d76a440..89adfcde 100644 --- a/cpp/core/internal/mediums/utils.cc +++ b/cpp/core/internal/mediums/utils.cc @@ -14,9 +14,11 @@ #include "core/internal/mediums/utils.h" +#include #include #include "platform/exception.h" +#include "platform/prng.h" #include "absl/strings/escaping.h" namespace location { @@ -62,6 +64,26 @@ ConstPtr Utils::legacySha256HashOnlyForPrinting( return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length); } +ConstPtr Utils::generateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.nextUInt32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return MakeConstPtr(new ByteArray(data)); +} + std::string Utils::bytesToPrintableHexString(ConstPtr bytes) { std::string hex_string( absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); diff --git a/cpp/core/internal/mediums/utils.h b/cpp/core/internal/mediums/utils.h index 264f1fa7..1ede0f2a 100644 --- a/cpp/core/internal/mediums/utils.h +++ b/cpp/core/internal/mediums/utils.h @@ -35,6 +35,8 @@ class Utils { static ConstPtr legacySha256HashOnlyForPrinting( Ptr hash_utils, ConstPtr source, size_t length); + static ConstPtr generateRandomBytes(size_t length); + private: static std::string bytesToPrintableHexString(ConstPtr bytes); }; diff --git a/cpp/core/internal/mediums/webrtc/BUILD b/cpp/core/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..4156d4d5 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/BUILD @@ -0,0 +1,91 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "webrtc", + hdrs = [ + "webrtc_socket.cc", + "webrtc_socket.h", + ], + deps = [ + "//platform:utils", + "//platform/api", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_library( + name = "peer_id", + srcs = ["peer_id.cc"], + hdrs = ["peer_id.h"], + deps = [ + "//core/internal/mediums:utils", + "//platform:types", + "//platform/api", + "//platform/port:string", + "//absl/strings", + ], +) + +cc_library( + name = "signaling_frames", + srcs = ["signaling_frames.cc"], + hdrs = ["signaling_frames.h"], + deps = [ + ":peer_id", + "//platform:types", + "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform:types", + "//platform/api", + "//platform/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//absl/strings", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform:types", + "//platform/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/pc:peerconnection", # buildcleaner: keep + ], +) diff --git a/cpp/core/internal/mediums/webrtc/peer_id.cc b/cpp/core/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..21d6ef99 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(ConstPtr bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes->getData(), bytes->size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +ConstPtr PeerId::FromRandom(Ptr hash_utils) { + return FromSeed(Utils::generateRandomBytes(kPeerIdLength), hash_utils); +} + +ConstPtr PeerId::FromSeed(ConstPtr seed, + Ptr hash_utils) { + ScopedPtr> full_hash( + Utils::sha256Hash(hash_utils, seed, kPeerIdLength)); + ScopedPtr> hashedSeed( + MakeConstPtr(new ByteArray(full_hash->getData(), kPeerIdLength / 2))); + return MakeConstPtr(new PeerId(BytesToStringUppercase(hashedSeed.get()))); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/peer_id.h b/cpp/core/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..e08cefe5 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static ConstPtr FromRandom(Ptr hash_utils); + static ConstPtr FromSeed(ConstPtr seed, + Ptr hash_utils); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core/internal/mediums/webrtc/peer_id_test.cc b/cpp/core/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..9ba658c9 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,90 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/peer_id.h" + +#include "platform/api/hash_utils.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +class MockHashUtils : public HashUtils { + public: + MOCK_METHOD(ConstPtr, md5, (const std::string& input), (override)); + MOCK_METHOD(ConstPtr, sha256, (const std::string& input), + (override)); +}; + +} // namespace + +TEST(PeerIdTest, GenerateRandomPeerId) { + // These are actual SHA-256 values for |seed| = "seed". + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::_)) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::_)); + + ConstPtr peer_id = PeerId::FromRandom(mock_hash_utils); + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "sesdfed"; + std::string hashed_output = + "19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + Ptr> mock_hash_utils( + MakePtr(new MockHashUtils())); + ON_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))) + .WillByDefault(testing::Return( + MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output))))); + EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed))); + + ConstPtr peer_id = + PeerId::FromSeed(MakeConstPtr(new ByteArray(seed)), mock_hash_utils); + + ASSERT_EQ(64, peer_id->GetId().size()); + ASSERT_EQ(expected_peer_id, peer_id->GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + ASSERT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.cc b/cpp/core/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..1a34a701 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,139 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ConstPtr FrameToByteArray( + const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return MakeConstPtr(new ByteArray(message.c_str(), message.size())); +} + +void SetSenderId(ConstPtr sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id->GetId()); +} + +ConstPtr DecodeIceCandidate( + const location::nearby::mediums::IceCandidate& ice_candidate_proto) { + webrtc::SdpParseError error; + return ConstPtr(webrtc::CreateIceCandidate( + ice_candidate_proto.sdp_mid(), ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.mutable_ready_for_signaling_poke(); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +Ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()) + .release()); +} + +Ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return MakePtr(webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()) + .release()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.h b/cpp/core/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..aae350f5 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,63 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace webrtc_frames { + +ConstPtr EncodeReadyForSignalingPoke(ConstPtr sender_id); + +ConstPtr EncodeOffer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& offer); +ConstPtr EncodeAnswer( + ConstPtr sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ConstPtr EncodeIceCandidates( + ConstPtr sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +Ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +Ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..384cf9f1 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,198 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core/internal/mediums/webrtc/peer_id.h" +#include "platform/ptr.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + ConstPtr sender_id(new PeerId("abc")); + ConstPtr encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_poke->getData(), encoded_poke->size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ConstPtr encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer->getData(), encoded_offer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignalingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + Ptr decoded_offer = DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + ConstPtr sender_id(new PeerId("abc")); + std::unique_ptr answer = + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp); + ConstPtr encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer->getData(), encoded_answer->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + Ptr decoded_answer = DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + ConstPtr sender_id(new PeerId("abc")); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate.get())); + } + ConstPtr encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates->getData(), encoded_candidates->size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> decoded_candidates = + DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..f3547d18 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,153 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +template +Exception::Value WebRtcSocket::OutputStreamImpl::write( + ConstPtr data) { + ScopedPtr> scoped_data(data); + + if (scoped_data->size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return Exception::IO; + } + + socket_->BlockUntilSufficientSpaceInBuffer(scoped_data->size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return Exception::IO; + } + + if (!socket_->SendMessage(scoped_data.release())) { + return Exception::IO; + } + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::flush() { + // Java implementation is empty. + return Exception::NONE; +} + +template +Exception::Value WebRtcSocket::OutputStreamImpl::close() { + socket_->close(); + return Exception::NONE; +} + +// WebRtcSocket +template +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), + data_channel_(std::move(data_channel)), + pipe_(MakeRefCountedPtr(new Pipe())), + incoming_data_piped_input_stream_(Pipe::createInputStream(pipe_)), + incoming_data_piped_output_stream_(Pipe::createOutputStream(pipe_)), + output_stream_(MakePtr(new OutputStreamImpl(this))), + closed_(Platform::createAtomicBoolean(false)), + backpressure_lock_(Platform::createLock()), + buffer_variable_( + Platform::createConditionVariable(backpressure_lock_.get())) {} + +template +Ptr WebRtcSocket::getInputStream() { + return incoming_data_piped_input_stream_.get(); +} + +template +Ptr WebRtcSocket::getOutputStream() { + return output_stream_.get(); +} + +template +void WebRtcSocket::close() { + if (IsClosed()) return; + + closed_->set(true); + incoming_data_piped_output_stream_->close(); + incoming_data_piped_input_stream_->close(); + data_channel_->Close(); + WakeUpWriter(); + if (!socket_closed_listener_.isNull()) { + socket_closed_listener_->OnSocketClosed(); + } +} + +template +void WebRtcSocket::NotifyDataChannelMsgReceived( + ConstPtr message) { + Exception::Value exception = + incoming_data_piped_output_stream_->write(message); + if (exception != Exception::NONE) close(); + + exception = incoming_data_piped_output_stream_->flush(); + if (exception != Exception::NONE) close(); +} + +template +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { + WakeUpWriter(); +} + +template +bool WebRtcSocket::SendMessage(ConstPtr data) { + ScopedPtr> scoped_data(data); + return data_channel_->Send(webrtc::DataBuffer( + std::string(scoped_data->getData(), scoped_data->size()))); +} + +template +bool WebRtcSocket::IsClosed() { + return closed_->get(); +} + +template +void WebRtcSocket::WakeUpWriter() { + Synchronized s(backpressure_lock_.get()); + buffer_variable_->notify(); +} + +template +void WebRtcSocket::SetOnSocketClosedListener( + Ptr listener) { + socket_closed_listener_ = listener; +} + +template +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + Synchronized s(backpressure_lock_.get()); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_->wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..dd3867c6 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include "platform/api/atomic_boolean.h" +#include "platform/api/input_stream.h" +#include "platform/api/output_stream.h" +#include "platform/api/socket.h" +#include "platform/pipe.h" +#include "webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +template +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + Ptr getInputStream() override; + Ptr getOutputStream() override; + void close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(ConstPtr message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + class SocketClosedListener { + public: + virtual ~SocketClosedListener() = default; + virtual void OnSocketClosed() = 0; + }; + void SetOnSocketClosedListener(Ptr listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) + : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception::Value write(ConstPtr data) override; + Exception::Value flush() override; + Exception::Value close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(ConstPtr data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Ptr pipe_; + ScopedPtr> incoming_data_piped_input_stream_; + ScopedPtr> incoming_data_piped_output_stream_; + + ScopedPtr> output_stream_; + + ScopedPtr> closed_; + + Ptr socket_closed_listener_; + + ScopedPtr> backpressure_lock_; + ScopedPtr> buffer_variable_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/webrtc/webrtc_socket.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..7789e6c1 --- /dev/null +++ b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,169 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform/api/platform.h" +#include "platform/byte_array.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +class MockSocketClosedListener + : public WebRtcSocket::SocketClosedListener { + public: + MOCK_METHOD(void, OnSocketClosed, ()); +}; + +TEST(WebRtcSocketTest, ReadFromSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr> result = + webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("Me"))); + webrtc_socket.NotifyDataChannelMsgReceived( + MakeConstPtr(new ByteArray("ssa"))); + webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("ge"))); + ExceptionOr> result; + + // This behaviour is different from the Java code + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "Me"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ssa"); + + result = webrtc_socket.getInputStream()->read(); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result()->asString(), "ge"); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::NONE); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + ConstPtr kMessage = MakeConstPtr(new ByteArray(kMaxDataSize + 1)); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + ScopedPtr> mock_listener( + MakePtr(new MockSocketClosedListener())); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.SetOnSocketClosedListener(mock_listener.get()); + + EXPECT_CALL(*mock_listener, OnSocketClosed()); + EXPECT_CALL(*mock_data_channel, Close()); + webrtc_socket.close(); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ConstPtr kMessage = MakeConstPtr(new ByteArray("Message")); + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.getOutputStream()->write(kMessage); + webrtc_socket.close(); + + EXPECT_EQ(webrtc_socket.getInputStream()->read().exception(), Exception::IO); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.cc b/cpp/core/internal/mediums/wifi_lan.cc new file mode 100644 index 00000000..b1890dec --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.cc @@ -0,0 +1,227 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/mediums/wifi_lan.h" + +#include "platform/synchronized.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +template +WifiLan::WifiLan() + : lock_(Platform::createLock()), + wifi_lan_medium_(Platform::createWifiLanMedium()) {} + +template +bool WifiLan::IsAvailable() { + Synchronized s(lock_.get()); + + return !wifi_lan_medium_.isNull(); +} + +template +bool WifiLan::StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) { + Synchronized s(lock_.get()); + + if (!IsAvailable()) { + return false; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StartAdvertising(service_id, + // wifi_lan_service_info_name)); + + advertising_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAdvertising(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAdvertising()) { + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopAdvertising(advertising_info_.service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.service_id.clear(); +} + +template +bool WifiLan::IsAdvertising() { + Synchronized s(lock_.get()); + + return !advertising_info_.service_id.empty(); +} + +template +bool WifiLan::StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback) { + Synchronized s(lock_.get()); + + if (discovered_service_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because a null parameter was passed in."); + return false; + } + + if (IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan + // discovering because we are already discovering."); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start WifiLan discovering + // because WifiLan isn't available."); + return false; + } + + // Avoid leaks. + ScopedPtr> + scoped_discovered_service_callback_bridge( + new DiscoveredServiceCallbackBridge(discovered_service_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartDiscovery( + // service_id, Ptr( + // discovered_service_callback_bridge.release())); + + discovering_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopDiscovery(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsDiscovering(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't turn off WifiLan + // discovering because we never started discovering."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // wifi_lan_medium_->StopDiscovery(discovering_info_.service_id); + // Reset our bundle of scanning state to mark that we're no longer scanning. + discovering_info_.service_id.clear(); +} + +template +bool WifiLan::IsDiscovering(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !discovering_info_.service_id.empty(); +} + +template +bool WifiLan::StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback) { + Synchronized s(lock_.get()); + + if (accepted_connection_callback.isNull() || service_id.empty()) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections because a null parameter was passed in."); + return false; + } + + if (IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atSevere().log("Refusing to start accepting + // WifiLan connections for %s because another WifiLan service socket is + // already in-progress.", service_id); + return false; + } + + if (!IsAvailable()) { + // TODO(b/149806065): logger.atSevere().log("Can't start accepting WifiLan + // connections for %s because WifiLan isn't available.", serviceId); + return false; + } + + ScopedPtr> + scoped_wifi_lan_accepted_connection_callback( + new WifiLanAcceptedConnectionCallback( + accepted_connection_callback)); + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // wifi_lan_medium_->StartAcceptingConnections( + // service_id, Ptr( + // wifi_lan_accepted_connection_callback.release())); + + accepting_connections_info_.service_id.assign(service_id.data()); + return false; +} + +template +void WifiLan::StopAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (!IsAcceptingConnections(service_id)) { + // TODO(b/149806065): logger.atDebug().log("Can't stop accepting WifiLan + // connections because it was never started."); + return; + } + + // TODO(b/149806065): Implements platform wifi-lan medium.); + // A possible implementation is: + // wifi_lan_medium_->StopAcceptingConnections( + // accepting_connections_info_.service_id); + + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.service_id.clear(); +} + +template +bool WifiLan::IsAcceptingConnections(absl::string_view service_id) { + Synchronized s(lock_.get()); + + return !accepting_connections_info_.service_id.empty(); +} + +template +Ptr WifiLan::Connect( + Ptr wifi_lan_service, absl::string_view service_id) { + Synchronized s(lock_.get()); + + if (wifi_lan_service.isNull() || service_id.empty()) { + return Ptr(); + } + + if (!IsAvailable()) { + return Ptr(); + } + + // TODO(b/149806065): Implements platform wifi-lan medium. + // A possible implementation is: + // return wifi_lan_medium_->Connect(wifi_lan_service, service_id); + return Ptr(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/mediums/wifi_lan.h b/cpp/core/internal/mediums/wifi_lan.h new file mode 100644 index 00000000..141a1620 --- /dev/null +++ b/cpp/core/internal/mediums/wifi_lan.h @@ -0,0 +1,174 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ +#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ + +#include + +#include "platform/api/lock.h" +#include "platform/api/wifi_lan.h" +#include "platform/byte_array.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(Ptr wifi_lan_service) = 0; + virtual void OnServiceLost(Ptr wifi_lan_service) = 0; +}; + +template +class WifiLan { + public: + WifiLan(); + virtual ~WifiLan() = default; + + bool IsAvailable(); + + bool StartAdvertising(absl::string_view service_id, + absl::string_view wifi_lan_service_info_name); + void StopAdvertising(absl::string_view service_id); + bool IsAdvertising(); + + bool StartDiscovery( + absl::string_view service_id, + Ptr discovered_service_callback); + void StopDiscovery(absl::string_view service_id); + bool IsDiscovering(absl::string_view service_id); + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(Ptr socket, + absl::string_view service_id) = 0; + }; + + bool StartAcceptingConnections( + absl::string_view service_id, + Ptr accepted_connection_callback); + void StopAcceptingConnections(absl::string_view service_id); + bool IsAcceptingConnections(absl::string_view service_id); + + Ptr Connect(Ptr wifi_lan_service, + absl::string_view service_id); + + private: + class DiscoveredServiceCallbackBridge + : public WifiLanMedium::DiscoveredServiceCallback { + public: + explicit DiscoveredServiceCallbackBridge( + Ptr discovered_service_callback) + : discovered_service_callback_(discovered_service_callback) {} + ~DiscoveredServiceCallbackBridge() override = default; + + void OnServiceDiscovered(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceDiscovered(wifi_lan_service); + } + void OnServiceLost(Ptr wifi_lan_service) override { + discovered_service_callback_->OnServiceLost(wifi_lan_service); + } + + private: + ScopedPtr> + discovered_service_callback_; + }; + + class WifiLanAcceptedConnectionCallback + : public WifiLanMedium::AcceptedConnectionCallback { + public: + explicit WifiLanAcceptedConnectionCallback( + Ptr accepted_connection_callback) + : accepted_connection_callback_(accepted_connection_callback) {} + ~WifiLanAcceptedConnectionCallback() override = default; + + void OnConnectionAccepted(Ptr wifi_lan_socket, + absl::string_view service_id) override { + accepted_connection_callback_->OnConnectionAccepted(wifi_lan_socket, + service_id); + } + + private: + ScopedPtr> + accepted_connection_callback_; + }; + + struct DiscoveringInfo { + DiscoveringInfo() = default; + explicit DiscoveringInfo(absl::string_view service_id) + : service_id(service_id) {} + ~DiscoveringInfo() = default; + + string service_id; + }; + + struct AdvertisingInfo { + AdvertisingInfo() = default; + explicit AdvertisingInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AdvertisingInfo() = default; + + string service_id; + }; + + struct AcceptingConnectionsInfo { + AcceptingConnectionsInfo() = default; + explicit AcceptingConnectionsInfo(absl::string_view service_id) + : service_id(service_id) {} + ~AcceptingConnectionsInfo() = default; + + string service_id; + }; + + // ------------ GENERAL ------------ + + ScopedPtr> lock_; + + // ---------- CORE WIFILAN------------ + + // The underlying, per-platform implementation. + ScopedPtr> wifi_lan_medium_; + + // ------------ DISCOVERY ------------ + + // discovering_info_ is not scoped because it's nullable. + DiscoveringInfo discovering_info_; + + // ------------ ADVERTISING ------------ + + // A bundle of state required to start/stop WifiLan service publishing. + AdvertisingInfo advertising_info_; + + // A bundle of state required to start/stop accepting WifiLan service + /// connections. + AcceptingConnectionsInfo accepting_connections_info_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#include "core/internal/mediums/wifi_lan.cc" + +#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_ diff --git a/cpp/core/internal/message_lite.h b/cpp/core/internal/message_lite.h new file mode 100644 index 00000000..5ce9d91e --- /dev/null +++ b/cpp/core/internal/message_lite.h @@ -0,0 +1,20 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MESSAGE_LITE_H_ +#define CORE_INTERNAL_MESSAGE_LITE_H_ + +#include "google/protobuf/message_lite.h" + +#endif // CORE_INTERNAL_MESSAGE_LITE_H_ diff --git a/cpp/core/internal/offline_frames.cc b/cpp/core/internal/offline_frames.cc index 36407111..36fd7831 100644 --- a/cpp/core/internal/offline_frames.cc +++ b/cpp/core/internal/offline_frames.cc @@ -75,7 +75,8 @@ ExceptionOrOfflineFrame OfflineFrames::fromBytes( ConstPtr offline_frame_bytes) { auto offline_frame = std::make_unique(); - if (!offline_frame->ParseFromString(offline_frame_bytes->asString())) { + if (!offline_frame->ParseFromArray(offline_frame_bytes->getData(), + offline_frame_bytes->size())) { return ExceptionOrOfflineFrame(Exception::INVALID_PROTOCOL_BUFFER); } @@ -92,6 +93,7 @@ V1Frame::FrameType OfflineFrames::getFrameType( return V1Frame::UNKNOWN_FRAME_TYPE; } +// TODO(b/155752436): Use byte array endpoint_info instead of endpoint_name. ConstPtr OfflineFrames::forConnectionRequest( const std::string &endpoint_id, const std::string &endpoint_name, std::int32_t nonce, @@ -99,6 +101,7 @@ ConstPtr OfflineFrames::forConnectionRequest( auto connection_request = std::make_unique(); connection_request->set_endpoint_id(endpoint_id); connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); connection_request->set_nonce(nonce); for (std::vector::const_iterator it = diff --git a/cpp/core/internal/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc index c7578e26..f90243a0 100644 --- a/cpp/core/internal/offline_service_controller.cc +++ b/cpp/core/internal/offline_service_controller.cc @@ -25,11 +25,11 @@ OfflineServiceController::OfflineServiceController() : ServiceController(), medium_manager_(new MediumManager()), endpoint_channel_manager_( - new EndpointChannelManager(medium_manager_.get())), + new EndpointChannelManager(medium_manager_.get())), endpoint_manager_( new EndpointManager(endpoint_channel_manager_.get())), payload_manager_(new PayloadManager(endpoint_manager_.get())), - bandwidth_upgrade_manager_(new BandwidthUpgradeManager( + bandwidth_upgrade_manager_(new BandwidthUpgradeManager( medium_manager_.get(), endpoint_channel_manager_.get(), endpoint_manager_.get())), pcp_manager_(new PCPManager( diff --git a/cpp/core/internal/offline_service_controller.h b/cpp/core/internal/offline_service_controller.h index 69d34c0c..cd4f32bf 100644 --- a/cpp/core/internal/offline_service_controller.h +++ b/cpp/core/internal/offline_service_controller.h @@ -82,11 +82,10 @@ class OfflineServiceController : public ServiceController { // on the destructors running (strictly) in the reverse order; a deviation // from that will lead to crashes at runtime. ScopedPtr > > medium_manager_; - ScopedPtr > > endpoint_channel_manager_; + ScopedPtr> endpoint_channel_manager_; ScopedPtr > > endpoint_manager_; ScopedPtr > > payload_manager_; - ScopedPtr > > - bandwidth_upgrade_manager_; + ScopedPtr> bandwidth_upgrade_manager_; ScopedPtr > > pcp_manager_; }; diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index c98d8041..beaec48b 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -13,7 +13,6 @@ // limitations under the License. #include "core/internal/p2p_cluster_pcp_handler.h" - #include "platform/api/hash_utils.h" namespace location { @@ -30,6 +29,11 @@ const BLEAdvertisement::Version::Value P2PClusterPCPHandler::kBleAdvertisementVersion = BLEAdvertisement::Version::V1; +template +const WifiLanServiceInfo::Version + P2PClusterPCPHandler::kWifiLanServiceInfoVersion = + WifiLanServiceInfo::Version::kV1; + template ConstPtr P2PClusterPCPHandler::generateHash( const string& source, size_t size) { @@ -49,8 +53,8 @@ template P2PClusterPCPHandler::P2PClusterPCPHandler( Ptr> medium_manager, Ptr> endpoint_manager, - Ptr> endpoint_channel_manager, - Ptr> bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : BasePCPHandler(endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), medium_manager_(medium_manager) {} @@ -72,6 +76,9 @@ template std::vector P2PClusterPCPHandler::getConnectionMediumsByPriority() { std::vector mediums; + if (medium_manager_->IsWifiLanAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } if (medium_manager_->isBluetoothAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } @@ -95,6 +102,15 @@ P2PClusterPCPHandler::startAdvertisingImpl( const AdvertisingOptions& options) { std::vector mediums_started_successfully; + ScopedPtr> scoped_wifi_lan_service_id_hash( + generateHash(service_id, WifiLanServiceInfo::kServiceIdHashLength)); + proto::connections::Medium wifi_lan_medium = StartWifiLanAdvertising( + client_proxy, service_id, scoped_wifi_lan_service_id_hash.get(), + local_endpoint_id, local_endpoint_name); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + ScopedPtr> scoped_bluetooth_service_id_hash( generateHash(service_id, BluetoothDeviceName::kServiceIdHashLength)); proto::connections::Medium bluetooth_medium = startBluetoothAdvertising( @@ -132,10 +148,14 @@ Status::Value P2PClusterPCPHandler::stopAdvertisingImpl( Ptr> client_proxy) { medium_manager_->stopBleAdvertising(client_proxy->getAdvertisingServiceId()); medium_manager_->turnOffBluetoothDiscoverability(); + medium_manager_->StopWifiLanAdvertising( + client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBleConnections( client_proxy->getAdvertisingServiceId()); medium_manager_->stopListeningForIncomingBluetoothConnections( client_proxy->getAdvertisingServiceId()); + medium_manager_->StopListeningForIncomingWifiLanConnections( + client_proxy->getAdvertisingServiceId()); return Status::SUCCESS; } @@ -146,6 +166,14 @@ P2PClusterPCPHandler::startDiscoveryImpl( const DiscoveryOptions& options) { std::vector mediums_started_successfully; + proto::connections::Medium wifi_lan_medium = + StartWifiLanDiscovery(MakePtr(new FoundWifiLanServiceProcessor( + self_, client_proxy, service_id)), + client_proxy, service_id); + if (proto::connections::UNKNOWN_MEDIUM != wifi_lan_medium) { + mediums_started_successfully.push_back(wifi_lan_medium); + } + proto::connections::Medium bluetooth_medium = startBluetoothDiscovery(MakePtr(new FoundBluetoothAdvertisementProcessor( self_, client_proxy, service_id)), @@ -184,6 +212,12 @@ typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::connectImpl( Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) { + Ptr wifi_lan_endpoint = + DowncastPtr(endpoint); + if (!wifi_lan_endpoint.isNull()) { + return WifiLanConnectImpl(client_proxy, wifi_lan_endpoint); + } + Ptr bluetooth_endpoint = DowncastPtr(endpoint); if (!bluetooth_endpoint.isNull()) { @@ -309,6 +343,60 @@ void P2PClusterPCPHandler::IncomingBleConnectionProcessor:: proto::connections::Medium::BLE); } +//////////// P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor ///////// +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + local_endpoint_name_(local_endpoint_name) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnection(Ptr wifi_lan_socket) { + pcp_handler_->runOnPCPHandlerThread( + MakePtr(new OnIncomingWifiLanConnectionRunnable( + pcp_handler_, client_proxy_, wifi_lan_socket))); +} + +template +P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + wifi_lan_socket_(wifi_lan_socket) {} + +template +void P2PClusterPCPHandler::IncomingWifiLanConnectionProcessor:: + OnIncomingWifiLanConnectionRunnable::run() { + string remote_service_name = + wifi_lan_socket_->GetRemoteWifiLanService()->GetName(); + ScopedPtr> scoped_wifi_lan_endpoint_channel( + pcp_handler_->endpoint_channel_manager_ + ->CreateIncomingWifiLanEndpointChannel(remote_service_name, + wifi_lan_socket_)); + if (!scoped_wifi_lan_endpoint_channel.isNull()) { + // TODO(b/149806065): Add logging. + } else { + Exception::Value exception = wifi_lan_socket_->Close(); + wifi_lan_socket_.destroy(); + if (Exception::NONE != exception) { + if (Exception::IO == exception) { + // TODO(b/149806065): Add logging. + } + } + } + pcp_handler_->onIncomingConnection(client_proxy_, remote_service_name, + scoped_wifi_lan_endpoint_channel.release(), + proto::connections::Medium::WIFI_LAN); +} + ///////// P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor ////////// template P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: @@ -596,6 +684,137 @@ void P2PClusterPCPHandler::FoundBleAdvertisementProcessor:: } } +////////// P2PClusterPCPHandler::FoundWifiLanServiceProcessor /////////// +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, absl::string_view service_id) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + service_id_(service_id), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnFoundWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanService(Ptr wifi_lan_service) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostWifiLanServiceRunnable( + pcp_handler_, client_proxy_, self_, service_id_, wifi_lan_service))); +} + +template +bool P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + IsRecognizedWifiLanEndpoint(Ptr wifi_lan_service_info) { + if (wifi_lan_service_info.isNull()) { + return false; + } + + if (wifi_lan_service_info->GetPcp() != pcp_handler_->getPCP()) { + return false; + } + + if (*(wifi_lan_service_info->GetServiceIdHash()) != + *(expected_service_id_hash_.get())) { + return false; + } + + return true; +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service), + expected_service_id_hash_(generateHash( + string(service_id), WifiLanServiceInfo::kServiceIdHashLength)) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnFoundWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the discovered endpoint to the client. + pcp_handler_->onEndpointFound( + client_proxy_, + MakePtr(new WifiLanEndpoint( + wifi_lan_service_.release(), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + +template +P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service) + : pcp_handler_(pcp_handler), + client_proxy_(client_proxy), + found_wifi_lan_service_processor_(found_wifi_lan_service_processor), + service_id_(service_id), + wifi_lan_service_(wifi_lan_service.operator->()) {} + +template +void P2PClusterPCPHandler::FoundWifiLanServiceProcessor:: + OnLostWifiLanServiceRunnable::run() { + // Make sure we are still discovering before proceeding. + if (!client_proxy_->isDiscovering()) { + // TODO(b/149806065): Add logging. + return; + } + + // Parse the WifiLan service name. + ScopedPtr> wifi_lan_service_info( + WifiLanServiceInfo::FromString(wifi_lan_service_->GetName())); + + // Make sure the WifiLan service name points to a valid endpoint we're + // discovering. + if (!found_wifi_lan_service_processor_->IsRecognizedWifiLanEndpoint( + wifi_lan_service_info.get())) { + return; + } + + // Report the endpoint as lost to the client. + // TODO(b/149806065): Add logging. + pcp_handler_->onEndpointLost( + client_proxy_, + MakePtr(new WifiLanEndpoint( + Ptr(wifi_lan_service_.release()), + wifi_lan_service_info->GetEndpointId(), + wifi_lan_service_info->GetEndpointName(), service_id_))); +} + //////////////////// END IMPLEMENTATIONS FOR NESTED CLASSES //////////////////// template @@ -724,6 +943,65 @@ proto::connections::Medium P2PClusterPCPHandler::startBleDiscovery( return proto::connections::BLE; } +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + if (!medium_manager_->IsListeningForIncomingWifiLanConnections(service_id)) { + if (!medium_manager_->StartListeningForIncomingWifiLanConnections( + service_id, MakePtr(new IncomingWifiLanConnectionProcessor( + self_, client_proxy, local_endpoint_name)))) { + // TODO(b/149806065): logger.atWarning().log("In + // StartWifiLanAdvertising(%s), client %d failed to start listening for + // incoming WifiLan connections to ServiceId %s", local_endpoint_name, + // clientProxy.getClientId(), service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + + // TODO(b/149806065): Add logging. + } + + // Generate a WifiLanServiceInfo. + const string wifi_lan_service_info = + WifiLanServiceInfo::AsString(kWifiLanServiceInfoVersion, + getPCP(), + local_endpoint_id, + service_id_hash); + if (wifi_lan_service_info.empty()) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } else { + // TODO(b/149806065): Add logging. + } + + // TODO(b/149806065): Add logging + + if (!medium_manager_->StartWifiLanAdvertising( + service_id, wifi_lan_service_info)) { + // TODO(b/149806065): Add logging + medium_manager_->StopWifiLanAdvertising(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + return proto::connections::WIFI_LAN; +} + +template +proto::connections::Medium +P2PClusterPCPHandler::StartWifiLanDiscovery( + Ptr processor, + Ptr > client_proxy, absl::string_view service_id) { + if (!medium_manager_->StartWifiLanDiscovery(service_id, processor)) { + // TODO(b/149806065): Add logging. + return proto::connections::UNKNOWN_MEDIUM; + } + + return proto::connections::WIFI_LAN; +} + template typename BasePCPHandler::ConnectImplResult P2PClusterPCPHandler::bluetoothConnectImpl( @@ -797,6 +1075,38 @@ string P2PClusterPCPHandler::getBlePeripheralId( #endif } +template +typename BasePCPHandler::ConnectImplResult +P2PClusterPCPHandler::WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint) { + Ptr remote_wifi_lan_service = + wifi_lan_endpoint->GetWifiLanService(); + + Ptr wifi_lan_socket = medium_manager_->ConnectToWifiLanService( + remote_wifi_lan_service, wifi_lan_endpoint->getServiceId()); + + if (wifi_lan_socket.isNull()) { + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::BLUETOOTH_ERROR); + } + + ScopedPtr> scoped_wifi_lan_endpoint_channel( + this->endpoint_channel_manager_->CreateOutgoingWifiLanEndpointChannel( + wifi_lan_endpoint->getEndpointId(), wifi_lan_socket)); + + if (scoped_wifi_lan_endpoint_channel.isNull()) { + wifi_lan_socket->Close(); + wifi_lan_socket.destroy(); // Avoid leaks. + return typename BasePCPHandler::ConnectImplResult( + proto::connections::Medium::WIFI_LAN, Status::ERROR); + } + + // TODO(b/149806065): Add logging. + return typename BasePCPHandler::ConnectImplResult( + scoped_wifi_lan_endpoint_channel.release()); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.h b/cpp/core/internal/p2p_cluster_pcp_handler.h index 0be713c0..4aa4e7b9 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -27,6 +27,7 @@ #include "core/internal/endpoint_manager.h" #include "core/internal/medium_manager.h" #include "core/internal/pcp.h" +#include "core/internal/wifi_lan_service_info.h" #include "core/options.h" #include "core/strategy.h" #include "platform/api/bluetooth_classic.h" @@ -49,11 +50,10 @@ namespace connections { template class P2PClusterPCPHandler : public BasePCPHandler { public: - P2PClusterPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PClusterPCPHandler(Ptr> medium_manager, + Ptr> endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PClusterPCPHandler() override; Strategy getStrategy() override; @@ -66,27 +66,29 @@ class P2PClusterPCPHandler : public BasePCPHandler { // @PCPHandlerThread Ptr::StartOperationResult> - startAdvertisingImpl(Ptr > client_proxy, + startAdvertisingImpl(Ptr> client_proxy, const string& service_id, const string& local_endpoint_id, const string& local_endpoint_name, const AdvertisingOptions& options) override; + // @PCPHandlerThread Status::Value stopAdvertisingImpl( - Ptr > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread Ptr::StartOperationResult> - startDiscoveryImpl(Ptr > client_proxy, + startDiscoveryImpl(Ptr> client_proxy, const string& service_id, const DiscoveryOptions& options) override; + // @PCPHandlerThread Status::Value stopDiscoveryImpl( - Ptr > client_proxy) override; + Ptr> client_proxy) override; // @PCPHandlerThread typename BasePCPHandler::ConnectImplResult connectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr::DiscoveredEndpoint> endpoint) override; @@ -96,16 +98,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { template friend class IncomingBleConnectionProcessor; template + friend class IncomingWifiLanConnectionProcessor; + template friend class FoundBluetoothAdvertisementProcessor; template friend class FoundBleAdvertisementProcessor; + template + friend class FoundWifiLanServiceProcessor; class IncomingBluetoothConnectionProcessor : public MediumManager::IncomingBluetoothConnectionProcessor { public: IncomingBluetoothConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBluetoothConnection( @@ -115,20 +121,20 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBluetoothConnectionRunnable : public Runnable { public: OnIncomingBluetoothConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr bluetooth_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr bluetooth_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -136,8 +142,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::IncomingBleConnectionProcessor { public: IncomingBleConnectionProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, const string& local_endpoint_name); void onIncomingBleConnection(Ptr ble_socket, @@ -147,19 +153,51 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnIncomingBleConnectionRunnable : public Runnable { public: OnIncomingBleConnectionRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, Ptr ble_socket); + Ptr> pcp_handler, + Ptr> client_proxy, Ptr ble_socket); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr ble_socket_; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string local_endpoint_name_; + }; + + class IncomingWifiLanConnectionProcessor + : public MediumManager::IncomingWifiLanConnectionProcessor { + public: + IncomingWifiLanConnectionProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view local_endpoint_name); + + void OnIncomingWifiLanConnection( + Ptr wifi_lan_socket) override; + + private: + class OnIncomingWifiLanConnectionRunnable : public Runnable { + public: + OnIncomingWifiLanConnectionRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr wifi_lan_socket); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr wifi_lan_socket_; + }; + + Ptr> pcp_handler_; + Ptr> client_proxy_; const string local_endpoint_name_; }; @@ -167,8 +205,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBluetoothDeviceProcessor { public: FoundBluetoothAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy, const string& service_id); + Ptr> pcp_handler, + Ptr> client_proxy, const string& service_id); void onFoundBluetoothDevice(Ptr bluetooth_device) override; void onLostBluetoothDevice(Ptr bluetooth_device) override; @@ -177,8 +215,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBluetoothDeviceRunnable : public Runnable { public: OnFoundBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -186,19 +224,19 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; class OnLostBluetoothDeviceRunnable : public Runnable { public: OnLostBluetoothDeviceRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_bluetooth_advertisement_processor, const string& service_id, Ptr bluetooth_device); @@ -206,22 +244,22 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_bluetooth_advertisement_processor_; const string service_id_; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; }; bool isRecognizedBluetoothEndpoint( const string& found_bluetooth_device_name, Ptr bluetooth_device_name); - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; const string service_id_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> expected_service_id_hash_; std::shared_ptr self_{this, [](void*) {}}; }; @@ -230,8 +268,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { : public MediumManager::FoundBlePeripheralProcessor { public: FoundBleAdvertisementProcessor( - Ptr > pcp_handler, - Ptr > client_proxy); + Ptr> pcp_handler, + Ptr> client_proxy); void onFoundBlePeripheral(Ptr ble_peripheral, const string& service_id, @@ -243,8 +281,8 @@ class P2PClusterPCPHandler : public BasePCPHandler { class OnFoundBlePeripheralRunnable : public Runnable { public: OnFoundBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral, ConstPtr advertisement_bytes); @@ -252,31 +290,31 @@ class P2PClusterPCPHandler : public BasePCPHandler { void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; - ScopedPtr > advertisement_bytes_; - ScopedPtr > expected_service_id_hash_; + ScopedPtr> ble_peripheral_; + ScopedPtr> advertisement_bytes_; + ScopedPtr> expected_service_id_hash_; }; class OnLostBlePeripheralRunnable : public Runnable { public: OnLostBlePeripheralRunnable( - Ptr > pcp_handler, - Ptr > client_proxy, + Ptr> pcp_handler, + Ptr> client_proxy, Ptr found_ble_advertisement_processor, const string& service_id, Ptr ble_peripheral); void run() override; private: - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; Ptr found_ble_advertisement_processor_; const string service_id_; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; }; // Holds the state required to re-create a BLEEndpoint we see on a @@ -292,14 +330,74 @@ class P2PClusterPCPHandler : public BasePCPHandler { const string endpoint_name; }; - Ptr > pcp_handler_; - Ptr > client_proxy_; + Ptr> pcp_handler_; + Ptr> client_proxy_; + // Maps a BLEPeripheral to its corresponding BLEEndpointState. typedef std::map FoundBLEEndpointsMap; FoundBLEEndpointsMap found_ble_endpoints_; std::shared_ptr self_{this, [](void*) {}}; }; + class FoundWifiLanServiceProcessor + : public MediumManager::FoundWifiLanServiceProcessor { + public: + FoundWifiLanServiceProcessor( + Ptr> pcp_handler, + Ptr> client_proxy, + absl::string_view service_id); + + void OnFoundWifiLanService(Ptr wifi_lan_service) override; + void OnLostWifiLanService(Ptr wifi_lan_service) override; + + private: + class OnFoundWifiLanServiceRunnable : public Runnable { + public: + OnFoundWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + ScopedPtr> expected_service_id_hash_; + }; + + class OnLostWifiLanServiceRunnable : public Runnable { + public: + OnLostWifiLanServiceRunnable( + Ptr> pcp_handler, + Ptr> client_proxy, + Ptr found_wifi_lan_service_processor, + absl::string_view service_id, Ptr wifi_lan_service); + + void run() override; + + private: + Ptr> pcp_handler_; + Ptr> client_proxy_; + Ptr found_wifi_lan_service_processor_; + const string service_id_; + ScopedPtr> wifi_lan_service_; + }; + + bool IsRecognizedWifiLanEndpoint( + Ptr wifi_lan_service_info); + + Ptr> pcp_handler_; + Ptr> client_proxy_; + const string service_id_; + ScopedPtr> expected_service_id_hash_; + std::shared_ptr self_{this, [](void*) {}}; + }; + class BluetoothEndpoint : public BasePCPHandler::DiscoveredEndpoint { public: @@ -324,7 +422,7 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBluetoothAdvertisementProcessor; - ScopedPtr > bluetooth_device_; + ScopedPtr> bluetooth_device_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -350,7 +448,35 @@ class P2PClusterPCPHandler : public BasePCPHandler { friend class FoundBleAdvertisementProcessor; - ScopedPtr > ble_peripheral_; + ScopedPtr> ble_peripheral_; + const string endpoint_id_; + const string endpoint_name_; + const string service_id_; + }; + + class WifiLanEndpoint : public BasePCPHandler::DiscoveredEndpoint { + public: + Ptr GetWifiLanService() { return wifi_lan_service_.get(); } + string getEndpointId() override { return endpoint_id_; } + string getEndpointName() override { return endpoint_name_; } + string getServiceId() override { return service_id_; } + proto::connections::Medium getMedium() override { + return proto::connections::Medium::WIFI_LAN; + } + + private: + WifiLanEndpoint(Ptr wifi_lan_service, + absl::string_view endpoint_id, + absl::string_view endpoint_name, + absl::string_view service_id) + : wifi_lan_service_(wifi_lan_service), + endpoint_id_(endpoint_id), + endpoint_name_(endpoint_name), + service_id_(service_id) {} + + friend class FoundWifiLanServiceProcessor; + + ScopedPtr> wifi_lan_service_; const string endpoint_id_; const string endpoint_name_; const string service_id_; @@ -358,32 +484,44 @@ class P2PClusterPCPHandler : public BasePCPHandler { static const BluetoothDeviceName::Version::Value kBluetoothDeviceNameVersion; static const BLEAdvertisement::Version::Value kBleAdvertisementVersion; + static const WifiLanServiceInfo::Version kWifiLanServiceInfoVersion; static ConstPtr generateHash(const string& source, size_t size); static string getBlePeripheralId(Ptr ble_peripheral); proto::connections::Medium startBluetoothAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBluetoothDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bluetoothConnectImpl( - Ptr > client_proxy, + Ptr> client_proxy, Ptr bluetooth_endpoint); proto::connections::Medium startBleAdvertising( - Ptr > client_proxy, const string& service_id, + Ptr> client_proxy, const string& service_id, ConstPtr service_id_hash, const string& local_endpoint_id, const string& local_endpoint_name); proto::connections::Medium startBleDiscovery( Ptr processor, - Ptr > client_proxy, const string& service_id); + Ptr> client_proxy, const string& service_id); typename BasePCPHandler::ConnectImplResult bleConnectImpl( - Ptr > client_proxy, Ptr ble_endpoint); + Ptr> client_proxy, Ptr ble_endpoint); - Ptr > medium_manager_; + proto::connections::Medium StartWifiLanAdvertising( + Ptr> client_proxy, absl::string_view service_id, + ConstPtr service_id_hash, absl::string_view local_endpoint_id, + absl::string_view local_endpoint_name); + proto::connections::Medium StartWifiLanDiscovery( + Ptr processor, + Ptr> client_proxy, absl::string_view service_id); + typename BasePCPHandler::ConnectImplResult WifiLanConnectImpl( + Ptr> client_proxy, + Ptr wifi_lan_endpoint); + + Ptr> medium_manager_; std::shared_ptr self_{this, [](void*) {}}; }; diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc index 6dd600d0..b4d3caae 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -22,8 +22,8 @@ template P2PPointToPointPCPHandler::P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PStarPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_point_to_point_pcp_handler.h b/cpp/core/internal/p2p_point_to_point_pcp_handler.h index e73c3140..502faf18 100644 --- a/cpp/core/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -41,8 +41,8 @@ class P2PPointToPointPCPHandler : public P2PStarPCPHandler { P2PPointToPointPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); Strategy getStrategy() override; PCP::Value getPCP() override; diff --git a/cpp/core/internal/p2p_star_pcp_handler.cc b/cpp/core/internal/p2p_star_pcp_handler.cc index 0e37e616..5849c643 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.cc +++ b/cpp/core/internal/p2p_star_pcp_handler.cc @@ -24,8 +24,8 @@ template P2PStarPCPHandler::P2PStarPCPHandler( Ptr > medium_manager, Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager) + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager) : P2PClusterPCPHandler(medium_manager, endpoint_manager, endpoint_channel_manager, bandwidth_upgrade_manager), diff --git a/cpp/core/internal/p2p_star_pcp_handler.h b/cpp/core/internal/p2p_star_pcp_handler.h index f7c635ae..afbdbef0 100644 --- a/cpp/core/internal/p2p_star_pcp_handler.h +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -41,11 +41,10 @@ namespace connections { template class P2PStarPCPHandler : public P2PClusterPCPHandler { public: - P2PStarPCPHandler( - Ptr > medium_manager, - Ptr > endpoint_manager, - Ptr > endpoint_channel_manager, - Ptr > bandwidth_upgrade_manager); + P2PStarPCPHandler(Ptr > medium_manager, + Ptr > endpoint_manager, + Ptr endpoint_channel_manager, + Ptr bandwidth_upgrade_manager); ~P2PStarPCPHandler() override; Strategy getStrategy() override; diff --git a/cpp/core/internal/pcp_manager.cc b/cpp/core/internal/pcp_manager.cc index eb618cfe..72817301 100644 --- a/cpp/core/internal/pcp_manager.cc +++ b/cpp/core/internal/pcp_manager.cc @@ -25,9 +25,9 @@ namespace connections { template PCPManager::PCPManager( Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager) + Ptr bandwidth_upgrade_manager) : pcp_handlers_(), current_pcp_handler_() { pcp_handlers_[PCP::P2P_CLUSTER] = MakePtr(new P2PClusterPCPHandler( medium_manager, endpoint_manager, endpoint_channel_manager, diff --git a/cpp/core/internal/pcp_manager.h b/cpp/core/internal/pcp_manager.h index b5695796..01f8bbfc 100644 --- a/cpp/core/internal/pcp_manager.h +++ b/cpp/core/internal/pcp_manager.h @@ -43,9 +43,9 @@ template class PCPManager { public: PCPManager(Ptr > medium_manager, - Ptr > endpoint_channel_manager, + Ptr endpoint_channel_manager, Ptr > endpoint_manager, - Ptr > bandwidth_upgrade_manager); + Ptr bandwidth_upgrade_manager); ~PCPManager(); Status::Value startAdvertising( diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.cc b/cpp/core/internal/wifi_lan_endpoint_channel.cc new file mode 100644 index 00000000..7d559894 --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.cc @@ -0,0 +1,63 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core/internal/wifi_lan_endpoint_channel.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +Ptr +WifiLanEndpointChannel::CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +Ptr +WifiLanEndpointChannel::CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket) { + return MakePtr( + new WifiLanEndpointChannel(channel_name, wifi_lan_socket)); +} + +WifiLanEndpointChannel::WifiLanEndpointChannel( + absl::string_view channel_name, Ptr wifi_lan_socket) + : BaseEndpointChannel(channel_name, + wifi_lan_socket->GetInputStream(), + wifi_lan_socket->GetOutputStream()), + wifi_lan_socket_(wifi_lan_socket) {} + +WifiLanEndpointChannel::~WifiLanEndpointChannel() {} + +proto::connections::Medium WifiLanEndpointChannel::getMedium() { + return proto::connections::Medium::WIFI_LAN; +} + +void WifiLanEndpointChannel::closeImpl() { + Exception::Value exception = wifi_lan_socket_->Close(); + if (exception != Exception::NONE) { + if (exception == Exception::IO) { + // TODO(b/149806065): Add logging. + } + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core/internal/wifi_lan_endpoint_channel.h b/cpp/core/internal/wifi_lan_endpoint_channel.h new file mode 100644 index 00000000..7afd57cb --- /dev/null +++ b/cpp/core/internal/wifi_lan_endpoint_channel.h @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ + +#include "core/internal/base_endpoint_channel.h" +#include "core/internal/medium_manager.h" +#include "platform/api/platform.h" +#include "platform/api/wifi_lan.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +class WifiLanEndpointChannel : public BaseEndpointChannel { + public: + using Platform = platform::ImplementationPlatform; + + static Ptr CreateOutgoing( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + static Ptr CreateIncoming( + Ptr> medium_manager, + absl::string_view channel_name, Ptr wifi_lan_socket); + + ~WifiLanEndpointChannel() override; + + proto::connections::Medium getMedium() override; + + protected: + void closeImpl() override; + + private: + WifiLanEndpointChannel(absl::string_view channel_name, + Ptr wifi_lan_socket); + + ScopedPtr > wifi_lan_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc index 8ddd1ed5..d10b24fd 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.cc +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -32,8 +32,8 @@ class OnIncomingWifiConnectionRunnable : public Runnable { template WifiLanUpgradeHandler::WifiLanUpgradeHandler( Ptr > medium_manager, - Ptr > endpoint_channel_manager) - : BaseBandwidthUpgradeHandler(endpoint_channel_manager), + Ptr endpoint_channel_manager) + : BaseBandwidthUpgradeHandler(endpoint_channel_manager), medium_manager_(medium_manager) {} template diff --git a/cpp/core/internal/wifi_lan_upgrade_handler.h b/cpp/core/internal/wifi_lan_upgrade_handler.h index 54f89804..80554bdc 100644 --- a/cpp/core/internal/wifi_lan_upgrade_handler.h +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -37,36 +37,35 @@ class OnIncomingWifiConnectionRunnable; // Manages the WIFI_LAN-specific methods needed to upgrade an EndpointChannel template -class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { +class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // TODO(ahlee): Uncomment when WIFI_LAN plumbing is done. // public MediumManager::IncomingWifiConnectionProcessor { public: - WifiLanUpgradeHandler( - Ptr > medium_manager_, - Ptr > endpoint_channel_manager); - ~WifiLanUpgradeHandler(); + WifiLanUpgradeHandler(Ptr > medium_manager_, + Ptr endpoint_channel_manager); + ~WifiLanUpgradeHandler() override; void onIncomingWifiConnection(Ptr socket); protected: // @BandwidthUpgradeHandlerThread ConstPtr initializeUpgradedMediumForEndpoint( - const string& endpoint_id); + const string& endpoint_id) override; // @BandwidthUpgradeHandlerThread Ptr createUpgradedEndpointChannel( const string& endpoint_id, ConstPtr - upgrade_path_info); + upgrade_path_info) override; // TODO(ahlee): Change the java counterparts of these methods to private. - proto::connections::Medium getUpgradeMedium(); + proto::connections::Medium getUpgradeMedium() override; // @BandwidthUpgradeHandlerThread - void revertImpl(); + void revertImpl() override; private: class IncomingWifiLanSocketConnection - : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { + : public BaseBandwidthUpgradeHandler::IncomingSocketConnection { public: - IncomingWifiLanSocketConnection(Ptr socket) + explicit IncomingWifiLanSocketConnection(Ptr socket) : new_endpoint_channel_(Ptr()), // TODO(ahlee): Uncomment when plumbing for WIFI_LAN is done. // new_endpoint_channel_(getEndpointChannelManager() @@ -75,15 +74,15 @@ class WifiLanUpgradeHandler : public BaseBandwidthUpgradeHandler { // TODO(ahlee): This is only used for logging which is not currently // implemented. If we want to match the Java code in the future, we'll need // to add toString() to socket.h. - string socketToString() { return string(); } - void closeSocket() { + string socketToString() override { return string(); } + void closeSocket() override { // Ignore the potential Exception returned by close(), as a counterpart // to Java's closeQuietly(). wifi_socket_->close(); } // TODO(ahlee): Double check that the ownership of this is correct when // this is fully implemented. - Ptr getEndpointChannel() { + Ptr getEndpointChannel() override { return new_endpoint_channel_.release(); } diff --git a/cpp/core_v2/BUILD b/cpp/core_v2/BUILD new file mode 100644 index 00000000..f44e694d --- /dev/null +++ b/cpp/core_v2/BUILD @@ -0,0 +1,87 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "core_v2", + srcs = [ + "core.cc", + ], + hdrs = [ + "core.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":core_types", + "//core_v2/internal", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "core_types", + srcs = [ + "strategy.cc", + ], + hdrs = [ + "listeners.h", + "options.h", + "params.h", + "payload.h", + "status.h", + "strategy.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/strings", + "//absl/types:variant", + ], +) + +cc_test( + name = "core_v2_test", + size = "small", + srcs = [ + "core_test.cc", + "listeners_test.cc", + "payload_test.cc", + "status_test.cc", + "strategy_test.cc", + ], + shard_count = 16, + deps = [ + ":core_types", + ":core_v2", + "//core_v2/internal", + "//core_v2/internal:internal_test", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/time", + "//absl/types:variant", + ], +) diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc new file mode 100644 index 00000000..ef149a71 --- /dev/null +++ b/cpp/core_v2/core.cc @@ -0,0 +1,121 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/core.h" + +#include +#include + +#include "core_v2/options.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +Core::~Core() { + CountDownLatch latch(1); + router_.ClientDisconnecting( + &client_, { + .result_cb = [&latch](Status) { latch.CountDown(); }, + }); + if (!latch.Await(kWaitForDisconnect).result()) { + NEARBY_LOG(FATAL, "Unable to shutdown"); + } +} + +void Core::StartAdvertising(absl::string_view service_id, + ConnectionOptions options, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartAdvertising(&client_, service_id, options, info, callback); +} + +void Core::StopAdvertising(const ResultCallback callback) { + router_.StopAdvertising(&client_, callback); +} + +void Core::StartDiscovery(absl::string_view service_id, + ConnectionOptions options, DiscoveryListener listener, + ResultCallback callback) { + assert(!service_id.empty()); + assert(options.strategy.IsValid()); + + router_.StartDiscovery(&client_, service_id, options, listener, callback); +} + +void Core::StopDiscovery(ResultCallback callback) { + router_.StopDiscovery(&client_, callback); +} + +void Core::RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RequestConnection(&client_, endpoint_id, info, callback); +} + +void Core::AcceptConnection(absl::string_view endpoint_id, + PayloadListener listener, ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.AcceptConnection(&client_, endpoint_id, listener, callback); +} + +void Core::RejectConnection(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.RejectConnection(&client_, endpoint_id, callback); +} + +void Core::InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback) { + router_.InitiateBandwidthUpgrade(&client_, endpoint_id, callback); +} + +void Core::SendPayload(absl::Span endpoint_ids, + Payload payload, ResultCallback callback) { + assert(payload.GetType() != Payload::Type::kUnknown); + assert(!endpoint_ids.empty()); + + router_.SendPayload(&client_, endpoint_ids, std::move(payload), callback); +} + +void Core::CancelPayload(std::int64_t payload_id, ResultCallback callback) { + assert(payload_id != 0); + + router_.CancelPayload(&client_, payload_id, callback); +} + +void Core::DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback) { + assert(!endpoint_id.empty()); + + router_.DisconnectFromEndpoint(&client_, endpoint_id, callback); +} + +void Core::StopAllEndpoints(ResultCallback callback) { + router_.StopAllEndpoints(&client_, callback); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/core.h b/cpp/core_v2/core.h new file mode 100644 index 00000000..6f01a231 --- /dev/null +++ b/cpp/core_v2/core.h @@ -0,0 +1,222 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_CORE_H_ +#define CORE_V2_CORE_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/internal/service_controller_router.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// This class defines the API of the Nearby Connections Core library. +class Core { + public: + explicit Core(std::function factory) + : router_(factory) {} + ~Core(); + Core(Core&&) = default; + Core& operator=(Core&&) = default; + + // Starts advertising an endpoint for a local app. + // + // service_id - An identifier to advertise your app to other endpoints. + // This can be an arbitrary string, so long as it uniquely + // identifies your service. A good default is to use your + // app's package name. + // options - The options for advertising. + // info - Connection parameters: + // > name - A human readable name for this endpoint, to appear on + // other devices. + // > listener - A callback notified when remote endpoints request a + // connection to this endpoint. + // callback - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if advertising started successfully. + // Status::STATUS_ALREADY_ADVERTISING if the app is already advertising. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartAdvertising(absl::string_view service_id, ConnectionOptions options, + ConnectionRequestInfo info, ResultCallback callback); + + // Stops advertising a local endpoint. Should be called after calling + // StartAdvertising, as soon as the application no longer needs to advertise + // itself or goes inactive. Payloads can still be sent to connected + // endpoints after advertising ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopAdvertising(ResultCallback callback); + + // Starts discovery for remote endpoints with the specified service ID. + // + // service_id - The ID for the service to be discovered, as specified in + // the corresponding call to StartAdvertising. + // listener - A callback notified when a remote endpoint is discovered. + // options - The options for discovery. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if discovery started successfully. + // Status::STATUS_ALREADY_DISCOVERING if the app is already + // discovering the specified service. + // Status::STATUS_OUT_OF_ORDER_API_CALL if the app is currently + // connected to remote endpoints; call StopAllEndpoints first. + void StartDiscovery(absl::string_view service_id, ConnectionOptions options, + DiscoveryListener listener, ResultCallback callback); + + // Stops discovery for remote endpoints, after a previous call to + // StartDiscovery, when the client no longer needs to discover endpoints or + // goes inactive. Payloads can still be sent to connected endpoints after + // discovery ends. + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void StopDiscovery(ResultCallback callback); + + // Sends a request to connect to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint to which a + // connection request will be sent. Should match the value + // provided in a call to + // DiscoveryListener::endpoint_found_cb() + // info - Connection parameters: + // > name - A human readable name for the local endpoint, to appear on + // the remote endpoint. + // > listener - A callback notified when the remote endpoint sends a + // response to the connection request. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was sent. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already + // has a connection to the specified endpoint. + // Status::STATUS_RADIO_ERROR if we failed to connect because of an + // issue with Bluetooth/WiFi. + // Status::STATUS_ERROR if we failed to connect for any other reason. + void RequestConnection(absl::string_view endpoint_id, + ConnectionRequestInfo info, ResultCallback callback); + + // Accepts a connection to a remote endpoint. This method must be called + // before Payloads can be exchanged with the remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated. + // listener - A callback for payloads exchanged with the remote endpoint. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if the connection request was accepted. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT if the app already. + // has a connection to the specified endpoint. + void AcceptConnection(absl::string_view endpoint_id, PayloadListener listener, + ResultCallback callback); + + // Rejects a connection to a remote endpoint. + // + // endpoint_id - The identifier for the remote endpoint. Should match the + // value provided in a call to + // ConnectionListener::onConnectionInitiated(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK} if the connection request was rejected. + // Status::STATUS_ALREADY_CONNECTED_TO_ENDPOINT} if the app already + // has a connection to the specified endpoint. + void RejectConnection(absl::string_view endpoint_id, ResultCallback callback); + + // Sends a Payload to a remote endpoint. Payloads can only be sent to remote + // endpoints once a notice of connection acceptance has been delivered via + // ConnectionListener::onConnectionResult(). + // + // endpoint_ids - Array of remote endpoint identifiers for the to which the + // payload should be sent. + // payload - The Payload to be sent. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OUT_OF_ORDER_API_CALL if the device has not first + // performed advertisement or discovery (to set the Strategy. + // Status::STATUS_ENDPOINT_UNKNOWN if there's no active (or pending) + // connection to the remote endpoint. + // Status::STATUS_OK if none of the above errors occurred. Note that this + // indicates that Nearby Connections will attempt to send the Payload, + // but not that the send has successfully completed yet. Errors might + // still occur during transmission (and at different times for + // different endpoints), and will be delivered via + // PayloadCallback#onPayloadTransferUpdate. + void SendPayload(absl::Span endpoint_ids, Payload payload, + ResultCallback callback); + + // Cancels a Payload currently in-flight to or from remote endpoint(s). + // + // payload_id - The identifier for the Payload to be canceled. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK if none of the above errors occurred. + void CancelPayload(std::int64_t payload_id, ResultCallback callback); + + // Disconnects from a remote endpoint. {@link Payload}s can no longer be sent + // to or received from the endpoint after this method is called. + // + // endpoint_id - The identifier for the remote endpoint to disconnect from. + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void DisconnectFromEndpoint(absl::string_view endpoint_id, + ResultCallback callback); + + // Disconnects from, and removes all traces of, all connected and/or + // discovered endpoints. This call is expected to be preceded by a call to + // StopAdvertising or StartDiscovery as needed. After calling + // StopAllEndpoints, no further operations with remote endpoints will be + // possible until a new call to one of StartAdvertising() or StartDiscovery(). + // + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void StopAllEndpoints(ResultCallback callback); + + // Sends a request to initiate connection bandwidth upgrade. + // + // endpoint_id - The identifier for the remote endpoint which will be + // switching to a higher connection data rate and possibly + // different wireless protocol. On success, calls + // ConnectionListener::bandwidth_changed_cb(). + // result_cb - to access the status of the operation when available. + // Possible status codes include: + // Status::STATUS_OK - finished successfully. + void InitiateBandwidthUpgrade(absl::string_view endpoint_id, + ResultCallback callback); + + private: + static constexpr absl::Duration kWaitForDisconnect = absl::Milliseconds(5000); + + ClientProxy client_; + ServiceControllerRouter router_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_CORE_H_ diff --git a/cpp/core_v2/core_test.cc b/cpp/core_v2/core_test.cc new file mode 100644 index 00000000..0e7c1fb5 --- /dev/null +++ b/cpp/core_v2/core_test.cc @@ -0,0 +1,58 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/core.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(CoreTest, ConstructorDestructorWorks) { + MockServiceController mock; + Core core{[&mock]() { return &mock; }}; +} + +TEST(CoreTest, DestructorReportsFatalFailure) { + MockServiceController mock; + ON_CALL(mock, StopDiscovery).WillByDefault([](ClientProxy* client) { + NEARBY_LOG(INFO, "Blocking Endpoint disconnect for 10 sec"); + absl::SleepFor(absl::Milliseconds(10000)); + }); + ASSERT_DEATH( + [&mock]() { + Core core{[&mock]() { return &mock; }}; + EXPECT_CALL(mock, StartDiscovery).Times(1); + EXPECT_CALL(mock, StopAdvertising).Times(1); + core.StartDiscovery("service_id", {.strategy = Strategy::kP2pCluster}, + {}, {.result_cb = [](Status status) { + NEARBY_LOG(INFO, "Discovery status: %d", + static_cast(status.value)); + }}); + }(), + "Unable to shutdown"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD new file mode 100644 index 00000000..2b7cbd23 --- /dev/null +++ b/cpp/core_v2/internal/BUILD @@ -0,0 +1,115 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "internal", + srcs = [ + "base_endpoint_channel.cc", + "base_pcp_handler.cc", + "ble_advertisement.cc", + "client_proxy.cc", + "encryption_runner.cc", + "endpoint_channel_manager.cc", + "endpoint_manager.cc", + "offline_frames.cc", + "service_controller_router.cc", + "wifi_lan_service_info.cc", + ], + hdrs = [ + "base_endpoint_channel.h", + "base_pcp_handler.h", + "ble_advertisement.h", + "client_proxy.h", + "encryption_runner.h", + "endpoint_channel.h", + "endpoint_channel_manager.h", + "endpoint_manager.h", + "offline_frames.h", + "pcp.h", + "pcp_handler.h", + "service_controller.h", + "service_controller_router.h", + "wifi_lan_service_info.h", + ], + visibility = [ + "//core_v2:__pkg__", + ], + deps = [ + "//core/internal:message_lite", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "internal_test", + testonly = True, + hdrs = [ + "mock_service_controller.h", + ], + visibility = [ + "//core_v2:__subpackages__", + ], + deps = [ + ":internal", + "//testing/base/public:gunit", + ], +) + +cc_test( + name = "core_v2_internal_test", + size = "small", + srcs = [ + "base_endpoint_channel_test.cc", + "base_pcp_handler_test.cc", + "ble_advertisement_test.cc", + "client_proxy_test.cc", + "encryption_runner_test.cc", + "endpoint_channel_manager_test.cc", + "endpoint_manager_test.cc", + "offline_frames_test.cc", + "service_controller_router_test.cc", + "wifi_lan_service_info_test.cc", + ], + shard_count = 16, + deps = [ + ":internal", + ":internal_test", + "//core_v2:core_types", + "//proto/connections:offline_wire_formats_portable_proto", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//proto:connections_enums_portable_proto", + "//securegcm:ukey2", + "//testing/base/public:gunit", + "//testing/base/public:gunit_main", + "//absl/container:flat_hash_set", + "//absl/synchronization", + "//absl/time", + "//absl/types:span", + ], +) diff --git a/cpp/core_v2/internal/base_endpoint_channel.cc b/cpp/core_v2/internal/base_endpoint_channel.cc new file mode 100644 index 00000000..21470082 --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.cc @@ -0,0 +1,284 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +std::int32_t BytesToInt(const ByteArray& bytes) { + const char* int_bytes = bytes.data(); + + std::int32_t result = 0; + result |= (static_cast(int_bytes[0]) & 0x0FF) << 24; + result |= (static_cast(int_bytes[1]) & 0x0FF) << 16; + result |= (static_cast(int_bytes[2]) & 0x0FF) << 8; + result |= (static_cast(int_bytes[3]) & 0x0FF); + + return result; +} + +ByteArray IntToBytes(std::int32_t value) { + char int_bytes[sizeof(std::int32_t)]; + int_bytes[0] = static_cast((value >> 24) & 0x0FF); + int_bytes[1] = static_cast((value >> 16) & 0x0FF); + int_bytes[2] = static_cast((value >> 8) & 0x0FF); + int_bytes[3] = static_cast((value)&0x0FF); + + return ByteArray(int_bytes, sizeof(int_bytes)); +} + +ExceptionOr ReadExactly(InputStream* reader, std::int64_t size) { + ByteArray buffer(size); + std::int64_t current_pos = 0; + + while (current_pos < size) { + ExceptionOr read_bytes = reader->Read(size - current_pos); + if (!read_bytes.ok()) { + return read_bytes; + } + ByteArray result = read_bytes.result(); + + if (result.Empty()) { + return ExceptionOr(Exception::kIo); + } + + buffer.CopyAt(current_pos, result); + current_pos += result.size(); + } + + return ExceptionOr(std::move(buffer)); +} + +ExceptionOr ReadInt(InputStream* reader) { + ExceptionOr read_bytes = ReadExactly(reader, sizeof(std::int32_t)); + if (!read_bytes.ok()) { + return ExceptionOr(read_bytes.exception()); + } + return ExceptionOr(BytesToInt(std::move(read_bytes.result()))); +} + +Exception WriteInt(OutputStream* writer, std::int32_t value) { + return writer->Write(IntToBytes(value)); +} + +} // namespace + +BaseEndpointChannel::BaseEndpointChannel(const std::string& channel_name, + InputStream* reader, + OutputStream* writer) + : channel_name_(channel_name), reader_(reader), writer_(writer) {} + +ExceptionOr BaseEndpointChannel::Read() { + ByteArray result; + { + MutexLock lock(&reader_mutex_); + + ExceptionOr read_int = ReadInt(reader_); + if (!read_int.ok()) { + return ExceptionOr(read_int.exception()); + } + + if (read_int.result() < 0 || read_int.result() > kMaxAllowedReadBytes) { + return ExceptionOr(Exception::kIo); + } + + ExceptionOr read_bytes = ReadExactly(reader_, read_int.result()); + if (!read_bytes.ok()) { + return read_bytes; + } + result = std::move(read_bytes.result()); + } + + // If encryption is enabled, decode the message. + if (IsEncryptionEnabled()) { + MutexLock crypto_lock(&crypto_mutex_); + result = ByteArray(std::move( + *encryption_context_->DecodeMessageFromPeer(std::string(result)))); + if (result.Empty()) { + return ExceptionOr(Exception::kInvalidProtocolBuffer); + } + } + + { + MutexLock lock(&last_read_mutex_); + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + } + return ExceptionOr(result); +} + +Exception BaseEndpointChannel::Write(const ByteArray& data) { + { + MutexLock pause_lock(&is_paused_mutex_); + if (is_paused_) { + BlockUntilUnpaused(); + } + } + + ByteArray encrypted_data; + const ByteArray* data_to_write = &data; + { + MutexLock crypto_lock(&crypto_mutex_); + // If encryption is enabled, encode the message. + if (IsEncryptionEnabled()) { + encrypted_data = ByteArray(std::move( + *encryption_context_->EncodeMessageToPeer(std::string(data)))); + data_to_write = &encrypted_data; + } + } + + { + MutexLock lock(&writer_mutex_); + Exception write_exception = + WriteInt(writer_, static_cast(data_to_write->size())); + if (!write_exception.Ok()) { + return write_exception; + } + + write_exception = writer_->Write(*data_to_write); + if (write_exception.Ok()) { + return write_exception; + } + + Exception flush_exception = writer_->Flush(); + if (!flush_exception.Ok()) { + return flush_exception; + } + } + + return {Exception::kSuccess}; +} + +void BaseEndpointChannel::Close() { + { + // In case channel is paused, resume it first thing. + MutexLock lock(&is_paused_mutex_); + UnblockPausedWriter(); + } + CloseIo(); + CloseImpl(); +} + +void BaseEndpointChannel::CloseIo() { + // Keep this method dedicated to reader and writer handling an nothing else. + { + // Do not take reader_mutex_ here: read may be in progress, and it will + // deadlock. Calling Close() with Read() in progress will terminate the + // IO and Read() will proceed normally (with Exception::kIo). + Exception exception = reader_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } + { + // Do not take writer_mutex_ here: write may be in progress, and it will + // deadlock. Calling Close() with Write() in progress will terminate the + // IO and Write() will proceed normally (with Exception::kIo). + Exception exception = writer_->Close(); + if (!exception.Ok()) { + // Add logging. + } + } +} + +void BaseEndpointChannel::Close( + proto::connections::DisconnectionReason reason) { + Close(); +} + +std::string BaseEndpointChannel::GetType() const { + std::string subtype = IsEncryptionEnabled() ? "ENCRYPTED_" : ""; + + switch (GetMedium()) { + case proto::connections::Medium::BLUETOOTH: + return absl::StrCat(subtype, "BLUETOOTH"); + case proto::connections::Medium::BLE: + return absl::StrCat(subtype, "BLE"); + case proto::connections::Medium::MDNS: + return absl::StrCat(subtype, "MDNS"); + case proto::connections::Medium::WIFI_HOTSPOT: + return absl::StrCat(subtype, "WIFI_HOTSPOT"); + case proto::connections::Medium::WIFI_LAN: + return absl::StrCat(subtype, "WIFI_LAN"); + default: + return "UNKNOWN"; + } +} + +std::string BaseEndpointChannel::GetName() const { return channel_name_; } + +void BaseEndpointChannel::EnableEncryption( + securegcm::D2DConnectionContextV1* encryption_context) { + MutexLock lock(&crypto_mutex_); + encryption_context_ = encryption_context; +} + +bool BaseEndpointChannel::IsPaused() const { + MutexLock lock(&is_paused_mutex_); + return is_paused_; +} + +void BaseEndpointChannel::Pause() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = true; +} + +void BaseEndpointChannel::Resume() { + MutexLock lock(&is_paused_mutex_); + is_paused_ = false; + is_paused_cond_.Notify(); +} + +absl::Time BaseEndpointChannel::GetLastReadTimestamp() const { + MutexLock lock(&last_read_mutex_); + return last_read_timestamp_; +} + +bool BaseEndpointChannel::IsEncryptionEnabled() const { + return encryption_context_ != nullptr; +} + +void BaseEndpointChannel::BlockUntilUnpaused() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + while (is_paused_) { + Exception wait_succeeded = is_paused_cond_.Wait(); + if (!wait_succeeded.Ok()) { + return; + } + } +} + +void BaseEndpointChannel::UnblockPausedWriter() { + // For more on how this works, see + // https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html + is_paused_ = false; + is_paused_cond_.Notify(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_endpoint_channel.h b/cpp/core_v2/internal/base_endpoint_channel.h new file mode 100644 index 00000000..b325b0b4 --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel.h @@ -0,0 +1,127 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace connections { + +class BaseEndpointChannel : public EndpointChannel { + public: + BaseEndpointChannel(const std::string& channel_name, InputStream* reader, + OutputStream* writer); + ~BaseEndpointChannel() override = default; + + ExceptionOr Read() + ABSL_LOCKS_EXCLUDED(reader_mutex_, crypto_mutex_, + last_read_mutex_) override; + + Exception Write(const ByteArray& data) + ABSL_LOCKS_EXCLUDED(writer_mutex_, crypto_mutex_) override; + + // Closes this EndpointChannel, without tracking the closure in analytics. + void Close() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Closes this EndpointChannel and records the closure with the given reason. + void Close(proto::connections::DisconnectionReason reason) override; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, + // WIFI. + std::string GetType() const override; + + // Returns the name of the EndpointChannel. + std::string GetName() const override; + + // Enables encryption on the EndpointChannel. + // Should be called after connection is accepted by both parties, and + // before entering data phase, where Payloads may be exchanged. + void EnableEncryption(securegcm::D2DConnectionContextV1* context) override; + + // True if the EndpointChannel is currently pausing all writes. + bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Pauses all writes on this EndpointChannel until resume() is called. + void Pause() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + void Resume() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override; + + // Returns the timestamp (returned by ElapsedRealtime) of the last read from + // this endpoint, or -1 if no reads have occurred. + absl::Time GetLastReadTimestamp() const + ABSL_LOCKS_EXCLUDED(last_read_mutex_) override; + + protected: + virtual void CloseImpl() = 0; + + private: + // Used to sanity check that our frame sizes are reasonable. + static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB + + bool IsEncryptionEnabled() const; + void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_); + void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS; + + // We need a separate mutex to pritect read timestamp, because if a read + // blocks on IO, we don't want timestamp read access to block too. + mutable Mutex last_read_mutex_; + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(last_read_mutex_) = + absl::InfinitePast(); + const std::string channel_name_; + + // The reader and writer are synchronized independently since we can't have + // writes waiting on reads that might potentially block forever. + Mutex reader_mutex_; + InputStream* reader_ ABSL_PT_GUARDED_BY(reader_mutex_); + + Mutex writer_mutex_; + OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_); + + // Used by both read and write to protect payload encryption/decryption. + Mutex crypto_mutex_; + // An encryptor/decryptor. May be null. + securegcm::D2DConnectionContextV1* encryption_context_ + ABSL_PT_GUARDED_BY(crypto_mutex_) = nullptr; + + mutable Mutex is_paused_mutex_; + ConditionVariable is_paused_cond_{&is_paused_mutex_}; + // If true, writes should block until this has been set to false. + bool is_paused_ ABSL_GUARDED_BY(is_paused_mutex_) = false; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/base_endpoint_channel_test.cc b/cpp/core_v2/internal/base_endpoint_channel_test.cc new file mode 100644 index 00000000..7f0ac0c1 --- /dev/null +++ b/cpp/core_v2/internal/base_endpoint_channel_test.cc @@ -0,0 +1,356 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/base_endpoint_channel.h" + +#include + +#include "core_v2/internal/encryption_runner.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/single_thread_executor.h" +#include "proto/connections_enums.pb.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; + +class TestEndpointChannel : public BaseEndpointChannel { + public: + explicit TestEndpointChannel(InputStream* input, OutputStream* output) + : BaseEndpointChannel("channel", input, output) {} + + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, CloseImpl, (), (override)); +}; + +std::function MakeDataPump( + std::string label, InputStream* input, OutputStream* output, + std::function monitor = nullptr) { + return [label, input, output, monitor]() { + NEARBY_LOG(INFO, "streaming data thorough '%s'", label.c_str()); + while (true) { + auto read_response = input->Read(Pipe::kChunkSize); + if (!read_response.ok()) { + NEARBY_LOG(INFO, "Peer reader closed on '%s'", label.c_str()); + output->Close(); + break; + } + if (monitor) { + monitor(read_response.result()); + } + auto write_response = output->Write(read_response.result()); + if (write_response.Raised()) { + NEARBY_LOG(INFO, "Peer writer closed on '%s'", label.c_str()); + input->Close(); + break; + } + } + NEARBY_LOG(INFO, "streaming terminated on '%s'", label.c_str()); + }; +} + +std::function MakeDataMonitor(const std::string& label, + std::string* capture, + absl::Mutex* mutex) { + return [label, capture, mutex](const ByteArray& input) mutable { + std::string s = std::string(input); + { + absl::MutexLock lock(mutex); + *capture += s; + } + NEARBY_LOG(INFO, "source='%s'; message='%s'", label.c_str(), s.c_str()); + }; +} + +std::pair, + std::unique_ptr> +DoDhKeyExchange(BaseEndpointChannel* channel_a, + BaseEndpointChannel* channel_b) { + std::unique_ptr context_a; + std::unique_ptr context_b; + EncryptionRunner crypto_a; + EncryptionRunner crypto_b; + ClientProxy proxy_a; + ClientProxy proxy_b; + CountDownLatch latch(2); + crypto_a.StartClient( + &proxy_a, "endpoint_id", channel_a, + { + .on_success_cb = + [&latch, &context_a]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-A side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_a = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-A side key negotiation failed"); + latch.CountDown(); + }, + }); + crypto_b.StartServer( + &proxy_b, "endpoint_id", channel_b, + { + .on_success_cb = + [&latch, &context_b]( + const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, const ByteArray& raw_auth_token) { + NEARBY_LOG(INFO, "client-B side key negotiation done"); + EXPECT_TRUE(ukey2->VerifyHandshake()); + auto context = ukey2->ToConnectionContext(); + EXPECT_NE (context, nullptr); + context_b = std::move(context); + latch.CountDown(); + }, + .on_failure_cb = + [&latch](const string& endpoint_id, EndpointChannel* channel) { + NEARBY_LOG(INFO, "client-B side key negotiation failed"); + latch.CountDown(); + }, + }); + EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); + return std::make_pair(std::move(context_a), std::move(context_b)); +} + +TEST(BaseEndpointChannelTest, ConstructorDestructorWorks) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); +} + +TEST(BaseEndpointChannelTest, ReadWrite) { + // Direct not-encrypted IO. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + EXPECT_EQ(rx_message, tx_message); +} + +TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) { + // Not encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { return Medium::BLE; }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { return Medium::BLE; }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + EXPECT_EQ(channel_a.GetType(), "BLE"); + EXPECT_EQ(channel_b.GetType(), "BLE"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) != std::string::npos || + capture_b.find(message) != std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) { + // Encrypted IO; MITM scenario. + + // Setup test communication environment. + absl::Mutex mutex; + std::string capture_a; + std::string capture_b; + Pipe client_a; // Channel "a" writes to client "a", reads from server "a". + Pipe client_b; // Channel "b" writes to client "b", reads from server "b". + Pipe server_a; // Data pump "a" reads from client "a", writes to server "b". + Pipe server_b; // Data pump "b" reads from client "b", writes to server "a". + TestEndpointChannel channel_a(&server_a.GetInputStream(), + &client_a.GetOutputStream()); + TestEndpointChannel channel_b(&server_b.GetInputStream(), + &client_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::BLUETOOTH; + }); + + MultiThreadExecutor executor(2); + executor.Execute(MakeDataPump( + "pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(), + MakeDataMonitor("monitor_a", &capture_a, &mutex))); + executor.Execute(MakeDataPump( + "pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(), + MakeDataMonitor("monitor_b", &capture_b, &mutex))); + + // Run DH key exchange; setup encryption contexts for channels. + auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b); + ASSERT_NE(context_a, nullptr); + ASSERT_NE(context_b, nullptr); + channel_a.EnableEncryption(context_a.get()); + channel_b.EnableEncryption(context_b.get()); + + EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH"); + EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH"); + + // Start data transfer + ByteArray tx_message{"data message"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Verify expectations. + EXPECT_EQ(rx_message, tx_message); + { + absl::MutexLock lock(&mutex); + std::string message{tx_message}; + EXPECT_TRUE(capture_a.find(message) == std::string::npos && + capture_b.find(message) == std::string::npos); + } + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) { + // Setup test communication environment. + Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b. + Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a. + TestEndpointChannel channel_a(&pipe_b.GetInputStream(), + &pipe_a.GetOutputStream()); + TestEndpointChannel channel_b(&pipe_a.GetInputStream(), + &pipe_b.GetOutputStream()); + + ON_CALL(channel_a, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + ON_CALL(channel_b, GetMedium).WillByDefault([]() { + return Medium::WIFI_LAN; + }); + + EXPECT_EQ(channel_a.GetType(), "WIFI_LAN"); + EXPECT_EQ(channel_b.GetType(), "WIFI_LAN"); + + // Start data transfer + ByteArray tx_message{"data message"}; + ByteArray more_message{"more data"}; + channel_a.Write(tx_message); + ByteArray rx_message = std::move(channel_b.Read().result()); + + // Pause and make sure reader blocks. + MultiThreadExecutor pause_resume_executor(2); + channel_a.Pause(); + pause_resume_executor.Execute([&channel_a, &more_message](){ + // Write will block until channel is resumed, or closed. + EXPECT_TRUE(channel_a.Write(more_message).Ok()); + }); + std::atomic_bool done = false; + ByteArray read_more; + pause_resume_executor.Execute([&channel_b, &read_more, &done](){ + // Read will block until channel is resumed, or closed. + auto response = channel_b.Read(); + EXPECT_TRUE(response.ok()); + read_more = std::move(response.result()); + done = true; + }); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(read_more.Empty()); + + // Resume; verify that data transfer comepleted. + channel_a.Resume(); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_TRUE(done); + EXPECT_EQ(read_more, more_message); + + // Shutdown test environment. + channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION); + channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION); +} + +TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream = pipe.GetInputStream(); + OutputStream& output_stream = pipe.GetOutputStream(); + + TestEndpointChannel test_channel(&input_stream, &output_stream); + + // Close the output stream before trying to read from the input. + output_stream.Close(); + + // Trying to read should fail gracefully with an IO error. + ExceptionOr read_data = test_channel.Read(); + + ASSERT_FALSE(read_data.ok()); + ASSERT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc new file mode 100644 index 00000000..db84c82a --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -0,0 +1,157 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/base_pcp_handler.h" + +#include +#include +#include +#include +#include + +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager) + : endpoint_manager_(endpoint_manager), channel_manager_(channel_manager) {} + +BasePcpHandler::~BasePcpHandler() { + // Unregister ourselves from the FrameProcessors. + endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE, + handle_); + + // Stop all the ongoing Runnables (as gracefully as possible). + serial_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +Status BasePcpHandler::StartAdvertising(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) { + Future response; + RunOnPcpHandlerThread( + [this, client, &service_id, &info, &options, &response]() { + auto result = StartAdvertisingImpl(client, service_id, + client->GenerateLocalEndpointId(), + info.name, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartAdvertising(", info.name, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopAdvertising(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopAdvertisingImpl(client); + client->StoppedAdvertising(); + advertising_options_.Clear(); + latch.CountDown(); + }); + WaitForLatch("StopAdvertising", &latch); +} + +Status BasePcpHandler::StartDiscovery(ClientProxy* client, + const string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) { + Future response; + RunOnPcpHandlerThread( + [this, client, service_id, options, listener, &response]() { + // Ask the implementation to attempt to start discovery. + auto result = StartDiscoveryImpl(client, service_id, options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } + + // Now that we've succeeded, mark the client as discovering and clear + // out any old endpoints we had discovered. + discovery_options_ = options; + discovered_endpoints_.clear(); + client->StartedDiscovery(service_id, GetStrategy(), listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); + return WaitForResult(absl::StrCat("StartDiscovery(", service_id, ")"), + client->GetClientId(), &response); +} + +void BasePcpHandler::StopDiscovery(ClientProxy* client) { + CountDownLatch latch(1); + RunOnPcpHandlerThread([this, client, &latch]() { + StopDiscoveryImpl(client); + client->StoppedDiscovery(); + discovery_options_.Clear(); + latch.CountDown(); + }); + + WaitForLatch("stopDiscovery", &latch); +} + +void BasePcpHandler::WaitForLatch(const string& method_name, + CountDownLatch* latch) { + Exception await_exception = latch->Await(); + if (!await_exception.Ok()) { + if (await_exception.Raised(Exception::kTimeout)) { + NEARBY_LOG(INFO, "Blocked in %s", method_name.c_str()); + } + } +} + +Status BasePcpHandler::WaitForResult(const string& method_name, + std::int64_t client_id, + Future* future) { + if (!future) { + NEARBY_LOG(INFO, "No future to wait for; return with error"); + return {Status::kError}; + } + NEARBY_LOG(INFO, "waiting for future to complete"); + ExceptionOr result = future->Get(); + if (!result.ok()) { + NEARBY_LOG(INFO, "Future completed with exception: %d", result.exception()); + return {Status::kError}; + } + NEARBY_LOG(INFO, "Future completed with status: %d", result.result().value); + return result.result(); +} + +void BasePcpHandler::RunOnPcpHandlerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h new file mode 100644 index 00000000..886e1b73 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -0,0 +1,337 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ + +#include +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/internal/pcp_handler.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/status.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/atomic_reference.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Define a class that supports move operation for pointers using std::swap. +// It replicates std::unique_ptr<> behavior, but it does not own the pointer, +// so it does not attempt destroy it. +// This approach was recommended during code review, as a better alternative to +// reuse of std::unique_ptr<> with custom no-op deleter, for the sake of +// readability. +template +class Swapper { + public: + Swapper(T* pointer) : pointer_(pointer) {} // NOLINT. + Swapper(Swapper&& other) { *this = std::move(other); } + Swapper& operator=(Swapper&& other) { + std::swap(pointer_, other.pointer_); + return *this; + } + T* operator->() const { return pointer_; } + T& operator*() { return *pointer_; } + operator T*() { return pointer_; } // NOLINT. + T* get() const { return pointer_; } + void reset() { pointer_ = nullptr; } + + private: + T* pointer_ = nullptr; +}; + +template +Swapper MakeSwapper(T* value) { + return Swapper(value); +} + +// A base implementation of the PcpHandler interface that takes care of all +// bookkeeping and handshake protocols that are common across all PcpHandler +// implementations -- thus, every concrete PcpHandler implementation must extend +// this class, so that they can focus exclusively on the medium-specific +// operations. +class BasePcpHandler : public PcpHandler, + public EndpointManager::FrameProcessor { + public: + using FrameProcessor = EndpointManager::FrameProcessor; + + // TODO(tracyzhou): Add SecureRandom. + BasePcpHandler(EndpointManager* endpoint_manager, + EndpointChannelManager* channel_manager); + ~BasePcpHandler() override; + BasePcpHandler(BasePcpHandler&&) = delete; + BasePcpHandler& operator=(BasePcpHandler&&) = delete; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See for details + // cpp/core_v2/listeners.h + Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) override; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopAdvertising(ClientProxy* client_proxy) override; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) override; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + void StopDiscovery(ClientProxy* client_proxy) override; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) override { + return Status{Status::kError}; + } + + // Either party may call this to accept connection on their part. + // If either party does call it, connection will terminate. + // Update state in ClientProxy. + Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) override { + return Status{Status::kError}; + } + + // @EndpointManagerReaderThread + void OnIncomingFrame(const OfflineFrame& frame, + const std::string& endpoint_id, ClientProxy* client, + proto::connections::Medium medium) override {} + + // Called when an endpoint disconnects while we're waiting for both sides to + // approve/reject the connection. + // @EndpointManagerThread + void OnEndpointDisconnect(ClientProxy* client_proxy, + const std::string& endpoint_id, + CountDownLatch* barrier) override {} + + protected: + // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). + struct StartOperationResult { + Status status; + // If success, the mediums on which we are now advertising/discovering, for + // analytics. + std::vector mediums; + }; + + // Represents an endpoint that we've discovered. Typically, the implementation + // will know how to connect to this endpoint if asked. (eg. It holds on to a + // BluetoothDevice) + class DiscoveredEndpoint { + public: + virtual ~DiscoveredEndpoint() = default; + + virtual std::string GetEndpointId() const = 0; + virtual std::string GetEndpointName() const = 0; + virtual std::string GetServiceId() const = 0; + virtual proto::connections::Medium GetMedium() const = 0; + }; + + struct ConnectImplResult { + proto::connections::Medium medium = + proto::connections::Medium::UNKNOWN_MEDIUM; + Status status = {Status::kError}; + std::unique_ptr endpoint_channel; + }; + + void RunOnPcpHandlerThread(Runnable runnable); + + ConnectionOptions GetConnectionOptions() const; + + // @PcpHandlerThread + void OnEndpointFound(ClientProxy* client_proxy, + std::unique_ptr endpoint); + + // @PcpHandlerThread + void OnEndpointLost(ClientProxy* client_proxy, + const DiscoveredEndpoint* endpoint); + + Exception OnIncomingConnection( + ClientProxy* client_proxy, const std::string& remote_device_name, + std::unique_ptr endpoint_channel, + proto::connections::Medium medium); // throws Exception::IO + + // @PcpHandlerThread + virtual StartOperationResult StartAdvertisingImpl( + ClientProxy* client_proxy, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopAdvertisingImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual StartOperationResult StartDiscoveryImpl( + ClientProxy* client_proxy, const std::string& service_id, + const ConnectionOptions& options) = 0; + // @PcpHandlerThread + virtual Status StopDiscoveryImpl(ClientProxy* client_proxy) = 0; + + // @PcpHandlerThread + virtual ConnectImplResult ConnectImpl(ClientProxy* client_proxy, + DiscoveredEndpoint* endpoint) = 0; + + virtual std::vector + GetConnectionMediumsByPriority() = 0; + virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0; + + EndpointManager* endpoint_manager_; + EndpointChannelManager* channel_manager_; + + private: + static Exception WriteConnectionRequestFrame( + EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, + const std::string& local_endpoint_name, std::int32_t nonce, + const std::vector& supported_mediums); + + static constexpr absl::Duration kConnectionRequestReadTimeout = + absl::Seconds(2); + static constexpr absl::Duration kRejectedConnectionCloseDelay = + absl::Seconds(2); + + void OnConnectionResponse(ClientProxy* client_proxy, + const std::string& endpoint_id, + const OfflineFrame& frame); + + // Returns true if the new endpoint is preferred over the old endpoint. + bool IsPreferred(const BasePcpHandler::DiscoveredEndpoint& new_endpoint, + const BasePcpHandler::DiscoveredEndpoint& old_endpoint); + + // Called when an incoming connection has been accepted by both sides. + // + // @param client_proxy The client + // @param endpoint_id The id of the remote device + // @param supported_mediums The mediums supported by the remote device. + // Empty + // for outgoing connections and older devices that don't report their + // supported mediums. + void InitiateBandwidthUpgrade( + ClientProxy* client_proxy, const std::string& endpoint_id, + const std::vector& supported_mediums); + + // Returns the optimal medium supported by both devices. + proto::connections::Medium ChooseBestUpgradeMedium( + const std::vector& supported_mediums); + + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, + EndpointChannel* channel, + Status status, + Future* result); + void ProcessPreConnectionResultFailure(ClientProxy* client_proxy, + const std::string& endpoint_id); + DiscoveredEndpoint* GetDiscoveredEndpoint(const std::string& endpoint_id); + + // Called when either side accepts/rejects the connection, but only takes + // effect after both have accepted or one side has rejected. + // + // NOTE: We also take in a 'can_close_immediately' variable. This is because + // any writes in transit are dropped when we close. To avoid having a reject + // write being dropped (which causes the other side to report + // onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our + // close. If the other side behaves properly, we shouldn't even see the + // delay (because they will also close the connection). + void EvaluateConnectionResult(ClientProxy* client_proxy, + const std::string& endpoint_id, + bool can_close_immediately); + + ExceptionOr ReadConnectionRequestFrame( + EndpointChannel* channel); + + void WaitForLatch(const std::string& method_name, CountDownLatch* latch); + Status WaitForResult(const std::string& method_name, std::int64_t client_id, + Future* future); + + AtomicReference bandwidth_upgrade_medium_{ + proto::connections::Medium::UNKNOWN_MEDIUM}; + ScheduledExecutor alarm_executor_; + SingleThreadExecutor serial_executor_; + + // A map of endpoint id -> DiscoveredEndpoint. + absl::flat_hash_map> + discovered_endpoints_; + // A map of endpoint id -> alarm. These alarms delay closing the + // EndpointChannel to give the other side enough time to read the rejection + // message. It's expected that the other side will close the connection + // after reading the message (in which case, this alarm should be cancelled + // as it's no longer needed), but this alarm is the fallback in case that + // doesn't happen. + absl::flat_hash_map pending_alarms_; + + // The active ClientProxy's advertising constraints. Empty() + // returns true if the client hasn't started advertising false otherwise. + // Note: this is not cleared when the client stops advertising because it + // might still be useful downstream of advertising (eg: establishing + // connections, performing bandwidth upgrades, etc.) + ConnectionOptions advertising_options_; + // The active ClientProxy's connection lifecycle listener. Non-null while + // advertising. + ConnectionListener advertising_listener_; + + // The active ClientProxy's discovery constraints. Null if the client + // hasn't started discovering. Note: this is not cleared when the client + // stops discovering because it might still be useful downstream of + // discovery (eg: connection speed, etc.) + ConnectionOptions discovery_options_; + Prng prng_; + EncryptionRunner encryption_runner_; + EndpointManager::FrameProcessor::Handle handle_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc new file mode 100644 index 00000000..c4b8a725 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -0,0 +1,301 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/base_pcp_handler.h" + +#include + +#include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/offline_frames.h" +#include "core_v2/listeners.h" +#include "core_v2/params.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; +using ::testing::_; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public BaseEndpointChannel { + public: + explicit MockEndpointChannel(Pipe* reader, Pipe* writer) + : BaseEndpointChannel("channel", &reader->GetInputStream(), + &writer->GetOutputStream()) {} + + ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } + Exception DoWrite(const ByteArray& data) { + return BaseEndpointChannel::Write(data); + } + absl::Time DoGetLastReadTimestamp() { + return BaseEndpointChannel::GetLastReadTimestamp(); + } + + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, CloseImpl, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); +}; + +class MockPcpHandler : public BasePcpHandler { + public: + MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) + : BasePcpHandler(em, ecm) {} + + // Expose protected inner types of a base type for mocking. + using BasePcpHandler::ConnectImplResult; + using BasePcpHandler::DiscoveredEndpoint; + using BasePcpHandler::StartOperationResult; + + MOCK_METHOD(Strategy, GetStrategy, (), (override)); + MOCK_METHOD(Pcp, GetPcp, (), (override)); + + MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, + (ClientProxy * client, const string& service_id, + const string& local_endpoint_id, + const string& local_endpoint_name, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); + MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, + (ClientProxy * client, const string& service_id, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); + MOCK_METHOD(ConnectImplResult, ConnectImpl, + (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); + MOCK_METHOD(std::vector, + GetConnectionMediumsByPriority, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), + (override)); + + // Mock adapters for protected non-virtual methods of a base class. + void OnEndpointFound(ClientProxy* client, + std::unique_ptr endpoint) { + BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); + } + void OnEndpointLost(ClientProxy* client, DiscoveredEndpoint* endpoint) { + BasePcpHandler::OnEndpointLost(client, endpoint); + } +}; + +class MockDiscoveredEndpoint final : public MockPcpHandler::DiscoveredEndpoint { + public: + MOCK_METHOD(std::string, GetEndpointId, (), (const override)); + MOCK_METHOD(std::string, GetEndpointName, (), (const override)); + MOCK_METHOD(std::string, GetServiceId, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); +}; + +class BasePcpHandlerTest : public ::testing::Test { + protected: + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + StrictMock< + MockFunction> + endpoint_distance_changed_cb; + }; + + void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + ConnectionRequestInfo info{ + .name = "remote_endpoint_name", + .listener = connection_listener_, + }; + EXPECT_CALL(*pcp_handler, + StartAdvertisingImpl(client, service_id, _, info.name, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, + discovery_listener_), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsDiscovering()); + } + + std::pair, + std::unique_ptr> + SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT + auto channel_a = std::make_unique(&pipe_b, &pipe_a); + auto channel_b = std::make_unique(&pipe_a, &pipe_b); + // On initiator (A) side, we drop the first write, since this is a + // connection establishment packet, and we don't have the peer entity, just + // the peer channel. The rest of the exchange must happen for the benefit of + // DH key exchange. + EXPECT_CALL(*channel_a, Read()) + .WillRepeatedly(Invoke( + [channel = channel_a.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_a, Write(_)) + .WillOnce(Return(Exception{Exception::kSuccess})) + .WillRepeatedly( + Invoke([channel = channel_a.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_a, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_a, IsPaused) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_b, Read()) + .WillRepeatedly(Invoke( + [channel = channel_b.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_b, Write(_)) + .WillRepeatedly( + Invoke([channel = channel_b.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_b, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_b, IsPaused) + .WillRepeatedly(Return(false)); + return std::make_pair(std::move(channel_a), std::move(channel_b)); + } + + Pipe pipe_a_; + Pipe pipe_b_; + MockConnectionListener mock_connection_listener_; + MockDiscoveryListener mock_discovery_listener_; + ConnectionListener connection_listener_{ + .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_connection_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = + mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = + mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), + .endpoint_distance_changed_cb = + mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), + }; +}; + +TEST_F(BasePcpHandlerTest, ConstructorDestructorWorks) { + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + SUCCEED(); +} + +TEST_F(BasePcpHandlerTest, StartAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartAdvertising(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopAdvertisingImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsAdvertising()); + pcp_handler->StopAdvertising(client.get()); + EXPECT_FALSE(client->IsAdvertising()); +} + +TEST_F(BasePcpHandlerTest, StartDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); +} + +TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { + auto client = std::make_unique(); + auto ecm = std::make_unique(); + auto em = std::make_unique(ecm.get()); + auto pcp_handler = std::make_unique(em.get(), ecm.get()); + StartDiscovery(client.get(), pcp_handler.get()); + EXPECT_CALL(*pcp_handler, StopDiscoveryImpl(client.get())).Times(1); + EXPECT_TRUE(client->IsDiscovering()); + pcp_handler->StopDiscovery(client.get()); + EXPECT_FALSE(client->IsDiscovering()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc new file mode 100644 index 00000000..adf994a4 --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -0,0 +1,236 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { + +BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, + const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address) { + if (version != Version::kV1 || + service_id_hash.size() != kServiceIdHashLength || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + endpoint_name.length() > kMaxEndpointNameLength) { + return; + } + + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; + endpoint_name_ = endpoint_name; + if (!BluetoothMacAddressHexStringToBytes(bluetooth_mac_address).Empty()) { + bluetooth_mac_address_ = bluetooth_mac_address; + } +} + +BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Start reading the bytes. + auto* ble_advertisement_bytes_read_ptr = ble_advertisement_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_bytes_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV1) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: unsupported Version %d", + version_); + return; + } + + pcp_ = static_cast(*ble_advertisement_bytes_read_ptr & kPcpBitmask); + ble_advertisement_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: { + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength); + ble_advertisement_bytes_read_ptr += kServiceIdHashLength; + + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(ble_advertisement_bytes_read_ptr, kEndpointIdLength); + ble_advertisement_bytes_read_ptr += kEndpointIdLength; + + // The next 8 bits are the length of the endpoint name. + auto expected_endpoint_name_length = static_cast( + *ble_advertisement_bytes_read_ptr & kEndpointNameLengthBitmask); + ble_advertisement_bytes_read_ptr++; + + // The next x bits are the endpoint name. (Max length is 131 bytes). + // Check that the stated endpoint_name_length is the same as what we + // received (based off of the length of ble_advertisement_bytes). + auto actual_endpoint_name_length = + ComputeEndpointNameLength(ble_advertisement_bytes); + if (actual_endpoint_name_length < expected_endpoint_name_length) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisement: expected endpointName to " + "be %d bytes, got %d bytes", + expected_endpoint_name_length, actual_endpoint_name_length); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + endpoint_name_ = std::string(ble_advertisement_bytes_read_ptr, + expected_endpoint_name_length); + ble_advertisement_bytes_read_ptr += expected_endpoint_name_length; + + // The next 48 bits are the bluetooth mac address. + auto bluetooth_mac_address_bytes = ByteArray( + ble_advertisement_bytes_read_ptr, kBluetoothMacAddressLength); + // If the Bluetooth MAC Address bytes are unset or invalid, leave the + // string empty. Otherwise, convert it to the proper colon delimited + // format. + if (!IsBluetoothMacAddressUnset(bluetooth_mac_address_bytes)) { + bluetooth_mac_address_ = + HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); + } + break; + } + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d", + pcp_); + break; + } +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_pcp_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the Pcp. + version_and_pcp_byte |= static_cast(pcp_) & kPcpBitmask; + out.reserve(1 + service_id_hash_.size() + kEndpointIdLength + 1 + + endpoint_name_.size() + kBluetoothMacAddressLength); + out.append(1, version_and_pcp_byte); + out.append(std::string(service_id_hash_)); + out.append(endpoint_id_); + out.append(1, endpoint_name_.size()); + out.append(endpoint_name_); + // The next 48 bits are the bluetooth mac address. If bluetooth_mac_address is + // invalid or empty, we get back a null byte array. + auto bluetooth_mac_address_bytes( + BluetoothMacAddressHexStringToBytes(bluetooth_mac_address_)); + if (!bluetooth_mac_address_bytes.Empty()) { + out.append(bluetooth_mac_address_bytes.data(), kBluetoothMacAddressLength); + } + + return ByteArray(std::move(out)); +} + +std::uint32_t BleAdvertisement::ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const { + std::string bt_mac_address(bluetooth_mac_address); + + // Remove the colon delimiters. + bt_mac_address.erase( + std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'), + bt_mac_address.end()); + + // If the bluetooth mac address is invalid (wrong size), return a null byte + // array. + if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) { + return ByteArray(); + } + + // Convert to bytes. If MAC Address bytes are unset, return a null byte array. + auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address)); + auto bt_mac_address_bytes = + ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size()); + if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) { + return ByteArray(); + } + return bt_mac_address_bytes; +} + +std::string BleAdvertisement::HexBytesToColonDelimitedString( + const ByteArray& hex_bytes) const { + // Convert the hex bytes to a string. + std::string colon_delimited_string( + absl::BytesToHexString(std::string(hex_bytes.data(), hex_bytes.size()))); + absl::AsciiStrToUpper(&colon_delimited_string); + + // Insert the colons. + for (int i = colon_delimited_string.length() - 2; i > 0; i -= 2) { + colon_delimited_string.insert(i, ":"); + } + return colon_delimited_string; +} + +bool BleAdvertisement::IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const { + for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) { + if (bluetooth_mac_address_bytes.data()[i] != 0) { + return false; + } + } + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h new file mode 100644 index 00000000..9209d246 --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -0,0 +1,104 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the Connections Ble Advertisement used in +// Advertising + Discovery. +// +//

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

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; +} From a9f42d7a710802c926530eb6ee1e59ca89aa07ff Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 24 Jun 2020 11:02:33 -0700 Subject: [PATCH 31/52] OSS fix Signed-off-by: Alexey Polyudov Change-Id: I49defdf33502bd2921ef81b8430eb15ed14532a0 --- cpp/core_v2/internal/bluetooth_endpoint_channel.cc | 14 ++++++++++++++ cpp/core_v2/internal/bluetooth_endpoint_channel.h | 14 ++++++++++++++ cpp/core_v2/internal/internal_payload.cc | 14 ++++++++++++++ cpp/core_v2/internal/internal_payload.h | 14 ++++++++++++++ cpp/core_v2/internal/internal_payload_factory.cc | 14 ++++++++++++++ cpp/core_v2/internal/internal_payload_factory.h | 14 ++++++++++++++ .../internal/internal_payload_factory_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/mediums/webrtc.cc | 14 ++++++++++++++ cpp/core_v2/internal/mediums/webrtc.h | 14 ++++++++++++++ .../mediums/webrtc/data_channel_observer_impl.cc | 14 ++++++++++++++ .../mediums/webrtc/data_channel_observer_impl.h | 14 ++++++++++++++ .../mediums/webrtc/session_description_wrapper.h | 14 ++++++++++++++ .../mediums/webrtc/webrtc_socket_wrapper.h | 14 ++++++++++++++ cpp/core_v2/internal/mediums/webrtc_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/mediums/wifi_lan.cc | 14 ++++++++++++++ cpp/core_v2/internal/mediums/wifi_lan.h | 14 ++++++++++++++ cpp/core_v2/internal/mediums/wifi_lan_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/p2p_cluster_pcp_handler.cc | 14 ++++++++++++++ cpp/core_v2/internal/p2p_cluster_pcp_handler.h | 14 ++++++++++++++ .../internal/p2p_cluster_pcp_handler_test.cc | 14 ++++++++++++++ .../internal/p2p_point_to_point_pcp_handler.cc | 14 ++++++++++++++ .../internal/p2p_point_to_point_pcp_handler.h | 14 ++++++++++++++ cpp/core_v2/internal/p2p_star_pcp_handler.cc | 14 ++++++++++++++ cpp/core_v2/internal/p2p_star_pcp_handler.h | 14 ++++++++++++++ cpp/core_v2/internal/payload_manager.cc | 14 ++++++++++++++ cpp/core_v2/internal/payload_manager.h | 14 ++++++++++++++ cpp/core_v2/internal/payload_manager_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/pcp_manager.cc | 14 ++++++++++++++ cpp/core_v2/internal/pcp_manager.h | 14 ++++++++++++++ cpp/core_v2/internal/pcp_manager_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/simulation_user.cc | 14 ++++++++++++++ cpp/core_v2/internal/simulation_user.h | 14 ++++++++++++++ cpp/core_v2/internal/webrtc_endpoint_channel.cc | 14 ++++++++++++++ cpp/core_v2/internal/webrtc_endpoint_channel.h | 14 ++++++++++++++ cpp/core_v2/internal/wifi_lan_endpoint_channel.cc | 14 ++++++++++++++ cpp/core_v2/internal/wifi_lan_endpoint_channel.h | 14 ++++++++++++++ cpp/platform_v2/api/log_message.h | 14 ++++++++++++++ cpp/platform_v2/base/payload_id.h | 14 ++++++++++++++ cpp/platform_v2/base/types.h | 14 ++++++++++++++ cpp/platform_v2/impl/g3/atomic_reference.h | 14 ++++++++++++++ cpp/platform_v2/impl/g3/log_message.cc | 14 ++++++++++++++ cpp/platform_v2/impl/g3/log_message.h | 14 ++++++++++++++ cpp/platform_v2/impl/g3/wifi_lan.cc | 14 ++++++++++++++ cpp/platform_v2/impl/g3/wifi_lan.h | 14 ++++++++++++++ cpp/platform_v2/public/condition_variable_test.cc | 14 ++++++++++++++ cpp/platform_v2/public/settable_future.h | 14 ++++++++++++++ cpp/platform_v2/public/wifi_lan.cc | 14 ++++++++++++++ cpp/platform_v2/public/wifi_lan.h | 14 ++++++++++++++ cpp/platform_v2/public/wifi_lan_test.cc | 14 ++++++++++++++ 49 files changed, 686 insertions(+) diff --git a/cpp/core_v2/internal/bluetooth_endpoint_channel.cc b/cpp/core_v2/internal/bluetooth_endpoint_channel.cc index 1ae5337f..12a83ec4 100644 --- a/cpp/core_v2/internal/bluetooth_endpoint_channel.cc +++ b/cpp/core_v2/internal/bluetooth_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/bluetooth_endpoint_channel.h" #include diff --git a/cpp/core_v2/internal/bluetooth_endpoint_channel.h b/cpp/core_v2/internal/bluetooth_endpoint_channel.h index 64fc0cc0..04a275f2 100644 --- a/cpp/core_v2/internal/bluetooth_endpoint_channel.h +++ b/cpp/core_v2/internal/bluetooth_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ #define CORE_V2_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/internal_payload.cc b/cpp/core_v2/internal/internal_payload.cc index 8e042093..eb847982 100644 --- a/cpp/core_v2/internal/internal_payload.cc +++ b/cpp/core_v2/internal/internal_payload.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/internal_payload.h" namespace location { diff --git a/cpp/core_v2/internal/internal_payload.h b/cpp/core_v2/internal/internal_payload.h index c2bdd868..1f3638cd 100644 --- a/cpp/core_v2/internal/internal_payload.h +++ b/cpp/core_v2/internal/internal_payload.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_H_ #define 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 index 41eb6cca..240882f2 100644 --- a/cpp/core_v2/internal/internal_payload_factory.cc +++ b/cpp/core_v2/internal/internal_payload_factory.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/internal_payload_factory.h" #include diff --git a/cpp/core_v2/internal/internal_payload_factory.h b/cpp/core_v2/internal/internal_payload_factory.h index b4e64174..3d283a28 100644 --- a/cpp/core_v2/internal/internal_payload_factory.h +++ b/cpp/core_v2/internal/internal_payload_factory.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_ #define 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 index b6d34037..be98af4d 100644 --- a/cpp/core_v2/internal/internal_payload_factory_test.cc +++ b/cpp/core_v2/internal/internal_payload_factory_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/internal_payload_factory.h" #include "core_v2/internal/offline_frames.h" diff --git a/cpp/core_v2/internal/mediums/webrtc.cc b/cpp/core_v2/internal/mediums/webrtc.cc index 20e2f1af..94eb891d 100644 --- a/cpp/core_v2/internal/mediums/webrtc.cc +++ b/cpp/core_v2/internal/mediums/webrtc.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/webrtc.h" #include diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h index 0097d2f7..6a4e7d7d 100644 --- a/cpp/core_v2/internal/mediums/webrtc.h +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_ #define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_ 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 index cf048ab7..0781c501 100644 --- a/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/webrtc/data_channel_observer_impl.h" 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 index f7508c1a..9c7ac1b3 100644 --- a/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h +++ b/cpp/core_v2/internal/mediums/webrtc/data_channel_observer_impl.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_ #define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h b/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h index 1c566deb..e68c816e 100644 --- a/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h +++ b/cpp/core_v2/internal/mediums/webrtc/session_description_wrapper.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_ #define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h index e7cc89ee..2aa66d8d 100644 --- a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_ #define 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 index 140571f4..a375f60d 100644 --- a/cpp/core_v2/internal/mediums/webrtc_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/webrtc.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" diff --git a/cpp/core_v2/internal/mediums/wifi_lan.cc b/cpp/core_v2/internal/mediums/wifi_lan.cc index 894c4b9c..cdce6237 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/wifi_lan.h" #include diff --git a/cpp/core_v2/internal/mediums/wifi_lan.h b/cpp/core_v2/internal/mediums/wifi_lan.h index 196cc2cd..6aa48aa3 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.h +++ b/cpp/core_v2/internal/mediums/wifi_lan.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_ #define 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 index 545d6c3b..6a6991c4 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan_test.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/wifi_lan.h" #include diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 126e193b..55ce75e1 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/p2p_cluster_pcp_handler.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index c1c5d19a..25e4757a 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_P2P_CLUSTER_PCP_HANDLER_H_ #define 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 index 9d3ec83d..226076fe 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/p2p_cluster_pcp_handler.h" #include 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 index 60da6883..e51bb324 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/p2p_point_to_point_pcp_handler.h" 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 index e6da2dd9..5b4c009e 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_P2P_POINT_TO_POINT_PCP_HANDLER_H_ #define 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 index 25901ebc..37c7baf8 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/p2p_star_pcp_handler.h" #include diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h index a50bd054..ab22e833 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_P2P_STAR_PCP_HANDLER_H_ #define 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 index 4cb491f0..947bbe37 100644 --- a/cpp/core_v2/internal/payload_manager.cc +++ b/cpp/core_v2/internal/payload_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/payload_manager.h" #include diff --git a/cpp/core_v2/internal/payload_manager.h b/cpp/core_v2/internal/payload_manager.h index 9e9000d1..c7475dfb 100644 --- a/cpp/core_v2/internal/payload_manager.h +++ b/cpp/core_v2/internal/payload_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_PAYLOAD_MANAGER_H_ #define 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 index 826c6172..2b64fe16 100644 --- a/cpp/core_v2/internal/payload_manager_test.cc +++ b/cpp/core_v2/internal/payload_manager_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/payload_manager.h" #include "core_v2/internal/simulation_user.h" diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index caeb6353..296e2333 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/pcp_manager.h" #include "core_v2/internal/p2p_cluster_pcp_handler.h" diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index 811bd08b..5354335f 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_PCP_MANAGER_H_ #define 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 index 15e1d6c0..ef548cae 100644 --- a/cpp/core_v2/internal/pcp_manager_test.cc +++ b/cpp/core_v2/internal/pcp_manager_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/pcp_manager.h" #include diff --git a/cpp/core_v2/internal/simulation_user.cc b/cpp/core_v2/internal/simulation_user.cc index 54dac813..577c5e6f 100644 --- a/cpp/core_v2/internal/simulation_user.cc +++ b/cpp/core_v2/internal/simulation_user.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/simulation_user.h" #include "core_v2/listeners.h" diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 39fa17ee..b039e5fb 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_SIMULATION_USER_H_ #define 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 index 0c22add5..0f5a7e40 100644 --- a/cpp/core_v2/internal/webrtc_endpoint_channel.cc +++ b/cpp/core_v2/internal/webrtc_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/webrtc_endpoint_channel.h" namespace location { diff --git a/cpp/core_v2/internal/webrtc_endpoint_channel.h b/cpp/core_v2/internal/webrtc_endpoint_channel.h index dc5b8512..cb999c94 100644 --- a/cpp/core_v2/internal/webrtc_endpoint_channel.h +++ b/cpp/core_v2/internal/webrtc_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_WEBRTC_ENDPOINT_CHANNEL_H_ #define 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 index a2623a38..262c7e47 100644 --- a/cpp/core_v2/internal/wifi_lan_endpoint_channel.cc +++ b/cpp/core_v2/internal/wifi_lan_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/wifi_lan_endpoint_channel.h" #include diff --git a/cpp/core_v2/internal/wifi_lan_endpoint_channel.h b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h index 6f985fda..846acf8d 100644 --- a/cpp/core_v2/internal/wifi_lan_endpoint_channel.h +++ b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ #define CORE_V2_INTERNAL_WIFI_LAN_ENDPOINT_CHANNEL_H_ diff --git a/cpp/platform_v2/api/log_message.h b/cpp/platform_v2/api/log_message.h index f2e25e48..46f97f5c 100644 --- a/cpp/platform_v2/api/log_message.h +++ b/cpp/platform_v2/api/log_message.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_API_LOG_MESSAGE_H_ #define PLATFORM_V2_API_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/base/payload_id.h b/cpp/platform_v2/base/payload_id.h index 81f2e730..398565d8 100644 --- a/cpp/platform_v2/base/payload_id.h +++ b/cpp/platform_v2/base/payload_id.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_BASE_PAYLOAD_ID_H_ #define PLATFORM_V2_BASE_PAYLOAD_ID_H_ diff --git a/cpp/platform_v2/base/types.h b/cpp/platform_v2/base/types.h index 3ac71f9a..5e9db28c 100644 --- a/cpp/platform_v2/base/types.h +++ b/cpp/platform_v2/base/types.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_BASE_TYPES_H_ #define PLATFORM_V2_BASE_TYPES_H_ diff --git a/cpp/platform_v2/impl/g3/atomic_reference.h b/cpp/platform_v2/impl/g3/atomic_reference.h index 2b33860f..e9e54186 100644 --- a/cpp/platform_v2/impl/g3/atomic_reference.h +++ b/cpp/platform_v2/impl/g3/atomic_reference.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_H_ #define PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/impl/g3/log_message.cc b/cpp/platform_v2/impl/g3/log_message.cc index a9dce4f3..803fee4f 100644 --- a/cpp/platform_v2/impl/g3/log_message.cc +++ b/cpp/platform_v2/impl/g3/log_message.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/impl/g3/log_message.h" #include diff --git a/cpp/platform_v2/impl/g3/log_message.h b/cpp/platform_v2/impl/g3/log_message.h index d9e65076..f86bd47a 100644 --- a/cpp/platform_v2/impl/g3/log_message.h +++ b/cpp/platform_v2/impl/g3/log_message.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_G3_LOG_MESSAGE_H_ #define PLATFORM_V2_IMPL_G3_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/impl/g3/wifi_lan.cc b/cpp/platform_v2/impl/g3/wifi_lan.cc index 2088c8e0..77ec80d8 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.cc +++ b/cpp/platform_v2/impl/g3/wifi_lan.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/impl/g3/wifi_lan.h" #include diff --git a/cpp/platform_v2/impl/g3/wifi_lan.h b/cpp/platform_v2/impl/g3/wifi_lan.h index c8995c02..fb3a8270 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.h +++ b/cpp/platform_v2/impl/g3/wifi_lan.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ #define PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ diff --git a/cpp/platform_v2/public/condition_variable_test.cc b/cpp/platform_v2/public/condition_variable_test.cc index 6a3dd610..d2b3805c 100644 --- a/cpp/platform_v2/public/condition_variable_test.cc +++ b/cpp/platform_v2/public/condition_variable_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/condition_variable.h" #include "platform_v2/public/logging.h" diff --git a/cpp/platform_v2/public/settable_future.h b/cpp/platform_v2/public/settable_future.h index bf0d459c..a62cf263 100644 --- a/cpp/platform_v2/public/settable_future.h +++ b/cpp/platform_v2/public/settable_future.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_PUBLIC_SETTABLE_FUTURE_H_ #define PLATFORM_V2_PUBLIC_SETTABLE_FUTURE_H_ diff --git a/cpp/platform_v2/public/wifi_lan.cc b/cpp/platform_v2/public/wifi_lan.cc index 32eefa18..9fd17af0 100644 --- a/cpp/platform_v2/public/wifi_lan.cc +++ b/cpp/platform_v2/public/wifi_lan.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/wifi_lan.h" #include "platform_v2/public/logging.h" diff --git a/cpp/platform_v2/public/wifi_lan.h b/cpp/platform_v2/public/wifi_lan.h index 7274414f..8b19018f 100644 --- a/cpp/platform_v2/public/wifi_lan.h +++ b/cpp/platform_v2/public/wifi_lan.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_PUBLIC_WIFI_LAN_H_ #define 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 index 398fa242..7b8f762b 100644 --- a/cpp/platform_v2/public/wifi_lan_test.cc +++ b/cpp/platform_v2/public/wifi_lan_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/wifi_lan.h" #include From 0e92da687a6a03e573426e521a9668a66aace517 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 24 Jun 2020 17:17:07 -0700 Subject: [PATCH 32/52] Add mediums/proto to the list of exported paths Signed-off-by: Alexey Polyudov Change-Id: I85d5e9ce51b7fee7df7d0750d5fb9af58af159bc --- script/oss.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/script/oss.py b/script/oss.py index cd0199d8..4b621b0f 100755 --- a/script/oss.py +++ b/script/oss.py @@ -42,6 +42,7 @@ HEADLINE = 1 PARTIAL = 2 FULL = 3 + def has_copyright(lines, max_lookup=3): pos = 0 result = MISSING @@ -65,6 +66,7 @@ def has_copyright(lines, max_lookup=3): return FULL + def add_copyright(lines, prefix, offset): new_lines = lines[0:offset] if offset: @@ -78,6 +80,7 @@ def add_copyright(lines, prefix, offset): new_lines.extend(lines[offset:]) return new_lines + def copy_files_to_oss_project(src_root, dst_root): shutil.rmtree(dst_root + "/cpp", ignore_errors=True) shutil.rmtree(dst_root + "/proto", ignore_errors=True) @@ -87,6 +90,8 @@ def copy_files_to_oss_project(src_root, dst_root): shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") shutil.copytree(src_root + "/connections/core_v2/", dst_root + "/cpp/core_v2/") shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") + shutil.copytree(src_root + "/mediums/proto/", dst_root + "/proto/mediums/") + def detect_file_copy_header_options(fname, lines): if not lines: @@ -102,6 +107,7 @@ def detect_file_copy_header_options(fname, lines): return ("#", 1) return None + def post_process_oss_files(path, args): modified_total = 0 top_level = True @@ -203,6 +209,7 @@ def post_process_oss_files(path, args): return modified_total + def main(): parser = argparse.ArgumentParser('Opensource Nearby Release Tool') parser.add_argument('target', action='store', type=str, nargs="+", default=[]) @@ -232,5 +239,6 @@ def main(): total += post_process_oss_files(dst, args) print("Total modified: {} files".format(total)) + if __name__ == "__main__": sys.exit(main()) From a4ef9ee5643b8372011efe03e77a1372446658fc Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 24 Jun 2020 18:15:07 -0700 Subject: [PATCH 33/52] Roll forward to cl/318180324 Signed-off-by: Alexey Polyudov Change-Id: I71bb49fe74104964088d392b12775ec2a253b1c9 --- cpp/core_v2/internal/base_pcp_handler.h | 9 + cpp/core_v2/internal/base_pcp_handler_test.cc | 12 +- cpp/core_v2/internal/bluetooth_device_name.cc | 4 +- cpp/core_v2/internal/mediums/mediums.cc | 2 + cpp/core_v2/internal/mediums/mediums.h | 6 +- cpp/core_v2/internal/mediums/webrtc.cc | 26 +- cpp/core_v2/internal/mediums/webrtc.h | 4 +- .../mediums/webrtc/connection_flow.cc | 6 +- .../internal/mediums/webrtc/connection_flow.h | 2 +- .../mediums/webrtc/connection_flow_test.cc | 4 +- .../internal/p2p_cluster_pcp_handler.cc | 245 +++++++++++++----- .../internal/p2p_cluster_pcp_handler.h | 27 ++ cpp/core_v2/internal/wifi_lan_service_info.cc | 4 +- cpp/platform_v2/base/types.h | 3 +- cpp/platform_v2/public/atomic_reference.h | 10 +- proto/mediums/BUILD | 95 +++++++ proto/mediums/ble_frames.proto | 40 +++ .../ble_frames_portable_proto_config.asciipb | 7 + proto/mediums/nfc_frames.proto | 29 +++ proto/mediums/web_rtc_signaling_frames.proto | 94 +++++++ proto/mediums/wifi_aware_frames.proto | 48 ++++ script/oss.py | 8 + 22 files changed, 576 insertions(+), 109 deletions(-) create mode 100644 proto/mediums/BUILD create mode 100644 proto/mediums/ble_frames.proto create mode 100644 proto/mediums/ble_frames_portable_proto_config.asciipb create mode 100644 proto/mediums/nfc_frames.proto create mode 100644 proto/mediums/web_rtc_signaling_frames.proto create mode 100644 proto/mediums/wifi_aware_frames.proto diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index a5411612..ec7213ff 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -164,6 +164,15 @@ class BasePcpHandler : public PcpHandler, // instance (but it can if implementation desires to do so). // BasePcpHandler will hold on to the shared_ptr. struct DiscoveredEndpoint { + DiscoveredEndpoint(std::string endpoint_id, std::string endpoint_name, + std::string service_id, + proto::connections::Medium medium) + : endpoint_id(std::move(endpoint_id)), + endpoint_name(std::move(endpoint_name)), + service_id(std::move(service_id)), + medium(medium) {} + virtual ~DiscoveredEndpoint() = default; + std::string endpoint_id; std::string endpoint_name; std::string service_id; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index a5d9f8b6..882dbd5d 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -124,6 +124,10 @@ class MockContext { }; struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { + MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) + : DiscoveredEndpoint(std::move(endpoint)), + context(std::move(context)) {} + MockContext context; }; @@ -262,10 +266,10 @@ class BasePcpHandlerTest : public ::testing::Test { pcp_handler->OnEndpointFound( client, std::make_shared(MockDiscoveredEndpoint{ { - .endpoint_id = endpoint_id, - .endpoint_name = info.name, - .service_id = "service", - .medium = Medium::BLE, + endpoint_id, + info.name, + "service", + Medium::BLE, }, MockContext{flag}, })); diff --git a/cpp/core_v2/internal/bluetooth_device_name.cc b/cpp/core_v2/internal/bluetooth_device_name.cc index 724723db..8afd1737 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.cc +++ b/cpp/core_v2/internal/bluetooth_device_name.cc @@ -34,9 +34,9 @@ BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, version_ = version; pcp_ = pcp; - endpoint_id_ = endpoint_id; + endpoint_id_ = std::string(endpoint_id); service_id_hash_ = service_id_hash; - endpoint_name_ = endpoint_name; + endpoint_name_ = std::string(endpoint_name); } BluetoothDeviceName::BluetoothDeviceName( diff --git a/cpp/core_v2/internal/mediums/mediums.cc b/cpp/core_v2/internal/mediums/mediums.cc index ee2ea3bf..54b3a24f 100644 --- a/cpp/core_v2/internal/mediums/mediums.cc +++ b/cpp/core_v2/internal/mediums/mediums.cc @@ -16,6 +16,8 @@ WifiLan& Mediums::GetWifiLan() { return wifi_lan_; } +mediums::WebRtc& Mediums::GetWebRtc() { return webrtc_; } + } // 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 193bb98b..4e6b07ec 100644 --- a/cpp/core_v2/internal/mediums/mediums.h +++ b/cpp/core_v2/internal/mediums/mediums.h @@ -3,9 +3,9 @@ #include "core_v2/internal/mediums/bluetooth_classic.h" #include "core_v2/internal/mediums/bluetooth_radio.h" +#include "core_v2/internal/mediums/webrtc.h" #include "core_v2/internal/mediums/wifi_lan.h" - namespace location { namespace nearby { namespace connections { @@ -25,6 +25,9 @@ class Mediums { // Returns a handle to the Wifi-Lan medium. WifiLan& GetWifiLan(); + // Returns a handle to the WebRtc medium. + mediums::WebRtc& GetWebRtc(); + private: // The order of declaration is critical for both construction and // destruction. @@ -37,6 +40,7 @@ class Mediums { BluetoothRadio bluetooth_radio_; BluetoothClassic bluetooth_classic_{bluetooth_radio_}; WifiLan wifi_lan_; + mediums::WebRtc webrtc_; }; } // namespace connections diff --git a/cpp/core_v2/internal/mediums/webrtc.cc b/cpp/core_v2/internal/mediums/webrtc.cc index 20e2f1af..32a4ec0e 100644 --- a/cpp/core_v2/internal/mediums/webrtc.cc +++ b/cpp/core_v2/internal/mediums/webrtc.cc @@ -111,14 +111,13 @@ WebRtcSocketWrapper WebRtc::Connect(const PeerId& peer_id) { 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()); + Future 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); + socket_future.Get(kDataChannelTimeout); if (result.ok()) return result.result(); Disconnect(); @@ -149,18 +148,17 @@ void WebRtc::StopAcceptingConnections() { NEARBY_LOG(INFO, "Stopped accepting WebRTC connections"); } -std::shared_ptr> -WebRtc::ListenForWebRtcSocketFuture( - Future>* +Future WebRtc::ListenForWebRtcSocketFuture( + Future> data_channel_future, AcceptedConnectionCallback callback) { - auto socket_future = std::make_shared>(); + Future socket_future; auto data_channel_runnable = [this, socket_future, data_channel_future, - callback{std::move(callback)}]() { + callback{std::move(callback)}]() mutable { // The overall timeout of creating the socket and data channel is controlled // by the caller of this function. ExceptionOr> res = - data_channel_future->Get(); + data_channel_future.Get(); if (res.ok()) { WebRtcSocketWrapper wrapper = CreateWebRtcSocketWrapper(res.result()); callback.accepted_cb(wrapper); @@ -168,15 +166,15 @@ WebRtc::ListenForWebRtcSocketFuture( MutexLock lock(&mutex_); socket_ = wrapper; } - socket_future->Set(wrapper); + socket_future.Set(wrapper); } else { NEARBY_LOG(WARNING, "Failed to get WebRtcSocket."); - socket_future->Set(WebRtcSocketWrapper()); + socket_future.Set(WebRtcSocketWrapper()); } }; - data_channel_future->AddListener(std::move(data_channel_runnable), - &single_thread_executor_); + data_channel_future.AddListener(std::move(data_channel_runnable), + &single_thread_executor_); return socket_future; } diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h index 0097d2f7..9781501f 100644 --- a/cpp/core_v2/internal/mediums/webrtc.h +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -74,8 +74,8 @@ class WebRtc { bool InitWebRtcFlow(Role role, const PeerId& self_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - std::shared_ptr> ListenForWebRtcSocketFuture( - Future>* + Future ListenForWebRtcSocketFuture( + Future> data_channel_future, AcceptedConnectionCallback callback); diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 6da917b5..401cb0dc 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -17,6 +17,8 @@ namespace nearby { namespace connections { namespace mediums { +constexpr absl::Duration ConnectionFlow::kTimeout; + namespace { // This is the same as the nearby data channel name. const char kDataChannelName[] = "dataChannel"; @@ -217,9 +219,9 @@ bool ConnectionFlow::OnRemoteIceCandidatesReceived( return true; } -Future>* +Future> ConnectionFlow::GetDataChannel() { - return &data_channel_future_; + return data_channel_future_; } bool ConnectionFlow::Close() { diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h index 95776ea4..b1e76c41 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h @@ -87,7 +87,7 @@ class ConnectionFlow { std::vector> ice_candidates) ABSL_LOCKS_EXCLUDED(mutex_); // Get a future for the data channel. - Future>* GetDataChannel(); + Future> GetDataChannel(); // Close the peer connection and data channel. bool Close() ABSL_LOCKS_EXCLUDED(mutex_); 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 cca175bc..8087fec5 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc @@ -80,10 +80,10 @@ TEST(ConnectionFlowTest, SuccessfulOfferAnswerFlow) { // Retrieve Data Channels ExceptionOr> - offerer_channel = offerer->GetDataChannel()->Get(absl::Seconds(1)); + offerer_channel = offerer->GetDataChannel().Get(absl::Seconds(1)); EXPECT_TRUE(offerer_channel.ok()); ExceptionOr> - answerer_channel = answerer->GetDataChannel()->Get(absl::Seconds(1)); + answerer_channel = answerer->GetDataChannel().Get(absl::Seconds(1)); EXPECT_TRUE(answerer_channel.ok()); // Send message on data channel diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 126e193b..41d612d7 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -1,7 +1,11 @@ #include "core_v2/internal/p2p_cluster_pcp_handler.h" +#include "core_v2/internal/base_pcp_handler.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" +#include "core_v2/internal/webrtc_endpoint_channel.h" #include "core_v2/internal/wifi_lan_endpoint_channel.h" +#include "platform_v2/base/types.h" #include "platform_v2/public/crypto.h" #include "proto/connections_enums.pb.h" @@ -23,22 +27,24 @@ P2pClusterPcpHandler::P2pClusterPcpHandler( : BasePcpHandler(endpoint_manager, endpoint_channel_manager, pcp), bluetooth_radio_(mediums.GetBluetoothRadio()), bluetooth_medium_(mediums.GetBluetoothClassic()), - wifi_lan_medium_(mediums.GetWifiLan()) {} + wifi_lan_medium_(mediums.GetWifiLan()), + webrtc_medium_(mediums.GetWebRtc()) {} // 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 +// Example: WiFi_LAN, WEB_RTC, 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); } + if (webrtc_medium_.IsAvailable()) { + mediums.push_back(proto::connections::WEB_RTC); + } + if (bluetooth_medium_.IsAvailable()) { + mediums.push_back(proto::connections::BLUETOOTH); + } return mediums; } @@ -52,16 +58,6 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( 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 = @@ -73,6 +69,22 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( mediums_started_successfully.push_back(wifi_lan_medium); } + proto::connections::Medium webrtc_medium = StartListeningForWebRtcConnections( + client, service_id, local_endpoint_id, local_endpoint_name); + if (webrtc_medium != proto::connections::UNKNOWN_MEDIUM) { + mediums_started_successfully.push_back(webrtc_medium); + } + + 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); + } + if (mediums_started_successfully.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: not started"); return { @@ -91,9 +103,13 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( } Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { - wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); bluetooth_medium_.TurnOffDiscoverability(); bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); + + webrtc_medium_.StopAcceptingConnections(); + + wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + return {Status::kSuccess}; } @@ -163,10 +179,10 @@ P2pClusterPcpHandler::MakeBluetoothDeviceDiscoveredHandler( 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_name.GetEndpointId(), + device_name.GetEndpointName(), + service_id, + proto::connections::Medium::BLUETOOTH, }, device, })); @@ -203,16 +219,15 @@ P2pClusterPcpHandler::MakeBluetoothDeviceLostHandler( "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, - }); + OnEndpointLost(client, BluetoothEndpoint{ + { + device_name.GetEndpointId(), + device_name.GetEndpointName(), + service_id, + proto::connections::Medium::BLUETOOTH, + }, + device, + }); }); }; } @@ -282,16 +297,15 @@ P2pClusterPcpHandler::MakeWifiLanServiceDiscoveredHandler( "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, - })); + OnEndpointFound(client, std::make_shared(WifiLanEndpoint{ + { + service_name.GetEndpointId(), + service_name.GetEndpointName(), + service_id, + proto::connections::Medium::WIFI_LAN, + }, + service, + })); }); }; } @@ -328,16 +342,15 @@ P2pClusterPcpHandler::MakeWifiLanServiceLostHandler( "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, - }); + OnEndpointLost(client, WifiLanEndpoint{ + { + service_name.GetEndpointId(), + service_name.GetEndpointName(), + service_id, + proto::connections::Medium::WIFI_LAN, + }, + service, + }); }); }; } @@ -347,18 +360,6 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( 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 = @@ -371,6 +372,18 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( mediums_started_successfully.push_back(wifi_lan_medium); } + 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); + } + if (mediums_started_successfully.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: nothing added"); return { @@ -392,15 +405,35 @@ Status P2pClusterPcpHandler::StopDiscoveryImpl(ClientProxy* client) { BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::ConnectImpl( ClientProxy* client, BasePcpHandler::DiscoveredEndpoint* endpoint) { - BluetoothEndpoint* bluetooth_endpoint = - static_cast(endpoint); - if (bluetooth_endpoint) { - return BluetoothConnectImpl(client, bluetooth_endpoint); + if (!endpoint) { + return BasePcpHandler::ConnectImplResult{ + .status = {Status::kError}, + }; } - - WifiLanEndpoint* wifi_lan_endpoint = static_cast(endpoint); - if (wifi_lan_endpoint) { - return WifiLanConnectImpl(client, wifi_lan_endpoint); + switch (endpoint->medium) { + case proto::connections::Medium::BLUETOOTH: { + auto* bluetooth_endpoint = down_cast(endpoint); + if (bluetooth_endpoint) { + return BluetoothConnectImpl(client, bluetooth_endpoint); + } + break; + } + case proto::connections::Medium::WIFI_LAN: { + auto* wifi_lan_endpoint = down_cast(endpoint); + if (wifi_lan_endpoint) { + return WifiLanConnectImpl(client, wifi_lan_endpoint); + } + break; + } + case proto::connections::Medium::WEB_RTC: { + auto* webrtc_endpoint = down_cast(endpoint); + if (webrtc_endpoint) { + return WebRtcConnectImpl(client, webrtc_endpoint); + } + break; + } + default: + break; } return BasePcpHandler::ConnectImplResult{ @@ -654,6 +687,74 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WifiLanConnectImpl( }; } +proto::connections::Medium +P2pClusterPcpHandler::StartListeningForWebRtcConnections( + ClientProxy* client, const string& service_id, + const string& local_endpoint_id, const string& local_endpoint_name) { + if (!webrtc_medium_.IsAvailable()) { + return proto::connections::UNKNOWN_MEDIUM; + } + + if (!webrtc_medium_.IsAcceptingConnections()) { + mediums::PeerId self_id = CreatePeerIdFromAdvertisement( + service_id, local_endpoint_id, local_endpoint_name); + if (!webrtc_medium_.StartAcceptingConnections( + self_id, {[this, client, local_endpoint_name]( + mediums::WebRtcSocketWrapper socket) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + local_endpoint_name.c_str()); + return; + } + + RunOnPcpHandlerThread( + [this, client, socket = std::move(socket)]() { + string remote_device_name = "WebRtcSocket"; + auto channel = absl::make_unique( + remote_device_name, socket); + + OnIncomingConnection(client, remote_device_name, + std::move(channel), + proto::connections::WEB_RTC); + }); + }})) { + return proto::connections::UNKNOWN_MEDIUM; + } + } + + return proto::connections::WEB_RTC; +} + +BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WebRtcConnectImpl( + ClientProxy* client, WebRtcEndpoint* webrtc_endpoint) { + mediums::WebRtcSocketWrapper socket_wrapper = + webrtc_medium_.Connect(webrtc_endpoint->peer_id); + + if (!socket_wrapper.IsValid()) { + return BasePcpHandler::ConnectImplResult{.status = {Status::kError}}; + } + + auto channel = absl::make_unique( + webrtc_endpoint->endpoint_id, socket_wrapper); + + if (!channel) { + socket_wrapper.Close(); + return BasePcpHandler::ConnectImplResult{.status = {Status::kError}}; + } + + return BasePcpHandler::ConnectImplResult{ + .medium = proto::connections::Medium::WEB_RTC, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel)}; +} + +mediums::PeerId P2pClusterPcpHandler::CreatePeerIdFromAdvertisement( + const std::string& service_id, const std::string& endpoint_id, + const std::string& endpoint_name) { + std::string seed = absl::StrCat(service_id, endpoint_id, endpoint_name); + return mediums::PeerId::FromSeed(ByteArray(seed)); +} + } // 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 index c1c5d19a..fc699aae 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -12,6 +12,8 @@ #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/mediums/webrtc.h" +#include "core_v2/internal/mediums/webrtc/peer_id.h" #include "core_v2/internal/pcp.h" #include "core_v2/internal/wifi_lan_service_info.h" #include "core_v2/options.h" @@ -69,11 +71,24 @@ class P2pClusterPcpHandler : public BasePcpHandler { private: struct BluetoothEndpoint : public BasePcpHandler::DiscoveredEndpoint { + BluetoothEndpoint(DiscoveredEndpoint endpoint, BluetoothDevice device) + : DiscoveredEndpoint(std::move(endpoint)), + bluetooth_device(std::move(device)) {} + BluetoothDevice bluetooth_device; }; struct WifiLanEndpoint : public BasePcpHandler::DiscoveredEndpoint { + WifiLanEndpoint(DiscoveredEndpoint endpoint, WifiLanService service) + : DiscoveredEndpoint(std::move(endpoint)), + wifi_lan_service(std::move(service)) {} WifiLanService wifi_lan_service; }; + struct WebRtcEndpoint : public BasePcpHandler::DiscoveredEndpoint { + WebRtcEndpoint(DiscoveredEndpoint endpoint, mediums::PeerId peer_id) + : DiscoveredEndpoint(std::move(endpoint)), + peer_id(std::move(peer_id)) {} + mediums::PeerId peer_id; + }; using BluetoothDiscoveredDeviceCallback = BluetoothClassic::DiscoveredDeviceCallback; @@ -124,9 +139,21 @@ class P2pClusterPcpHandler : public BasePcpHandler { BasePcpHandler::ConnectImplResult WifiLanConnectImpl( ClientProxy* client, WifiLanEndpoint* endpoint); + // WebRtc + proto::connections::Medium StartListeningForWebRtcConnections( + ClientProxy* client, const std::string& service_id, + const std::string& local_endpoint_id, + const std::string& local_endpoint_name); + BasePcpHandler::ConnectImplResult WebRtcConnectImpl( + ClientProxy* client, WebRtcEndpoint* webrtc_endpoint); + mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, + const string& endpoint_id, + const string& endpoint_name); + BluetoothRadio& bluetooth_radio_; BluetoothClassic& bluetooth_medium_; WifiLan& wifi_lan_medium_; + mediums::WebRtc& webrtc_medium_; }; } // namespace connections diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc index 75fb5463..92496867 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -35,8 +35,8 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, version_ = version; pcp_ = pcp; service_id_hash_ = service_id_hash; - endpoint_id_ = endpoint_id; - endpoint_name_ = endpoint_name; + endpoint_id_ = std::string(endpoint_id); + endpoint_name_ = std::string(endpoint_name); } WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { diff --git a/cpp/platform_v2/base/types.h b/cpp/platform_v2/base/types.h index 3ac71f9a..1cca5f3f 100644 --- a/cpp/platform_v2/base/types.h +++ b/cpp/platform_v2/base/types.h @@ -19,7 +19,8 @@ namespace nearby { template inline Derived down_cast(Base* value) { using DerivedType = typename std::remove_pointer::type; - static_assert(std::is_base_of::value); + static_assert(std::is_base_of::value, + "incompatible casting"); return static_cast(value); } diff --git a/cpp/platform_v2/public/atomic_reference.h b/cpp/platform_v2/public/atomic_reference.h index 049b9f31..66c40e9d 100644 --- a/cpp/platform_v2/public/atomic_reference.h +++ b/cpp/platform_v2/public/atomic_reference.h @@ -8,7 +8,6 @@ #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 { @@ -20,8 +19,7 @@ class AtomicReference; // Platform-based atomic type, for something convertible to std::uint32_t. template class AtomicReference, - void>> + std::is_trivially_copyable::value>> final { public: using Platform = api::ImplementationPlatform; @@ -42,9 +40,9 @@ class AtomicReference -class AtomicReference sizeof(std::uint32_t) || - !std::is_trivially_copyable_v), - void>> +class AtomicReference sizeof(std::uint32_t) || + !std::is_trivially_copyable::value)>> final { public: explicit AtomicReference(T value) { diff --git a/proto/mediums/BUILD b/proto/mediums/BUILD new file mode 100644 index 00000000..dcf12b59 --- /dev/null +++ b/proto/mediums/BUILD @@ -0,0 +1,95 @@ +load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") +load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "nfc_frames_proto", + srcs = [ + "nfc_frames.proto", + ], + cc_api_version = 2, +) + +java_lite_proto_library( + name = "nfc_frames_java_proto_lite", + visibility = ["//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__"], + deps = [":nfc_frames_proto"], +) + +proto_library( + name = "wifi_aware_frames_proto", + srcs = [ + "wifi_aware_frames.proto", + ], + cc_api_version = 2, +) + +java_lite_proto_library( + name = "wifi_aware_frames_java_proto_lite", + visibility = [ + "//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + "//javatests/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + ], + deps = [":wifi_aware_frames_proto"], +) + +proto_library( + name = "ble_frames_proto", + srcs = [ + "ble_frames.proto", + ], + cc_api_version = 2, +) + +java_lite_proto_library( + name = "ble_frames_java_proto_lite", + visibility = [ + "//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + "//javatests/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + ], + deps = [":ble_frames_proto"], +) + +proto_library( + name = "web_rtc_signaling_frames_proto", + srcs = [ + "web_rtc_signaling_frames.proto", + ], + cc_api_version = 2, +) + +cc_proto_library( + name = "web_rtc_signaling_frames_cc_proto", + visibility = ["//location/nearby/connections:__subpackages__"], + deps = [":web_rtc_signaling_frames_proto"], +) + +java_lite_proto_library( + name = "web_rtc_signaling_frames_java_proto_lite", + strict_deps = 0, + visibility = [ + "//java/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + "//javatests/com/google/android/gmscore/integ/modules/nearby/src/com/google/android/gms/nearby/mediums:__subpackages__", + ], + deps = [":web_rtc_signaling_frames_proto"], +) + +portable_proto_library( + name = "ble_frames_portable_proto", + config = ":ble_frames_portable_proto_config", + copts = [ + "-DGOOGLE_PROTOBUF_NO_RTTI=1", + ], + header_outs = [ + "ble_frames.pb.h", + ], + proto_deps = [ + ":ble_frames_proto", + ], +) + +filegroup( + name = "ble_frames_portable_proto_config", + srcs = ["ble_frames_portable_proto_config.asciipb"], +) diff --git a/proto/mediums/ble_frames.proto b/proto/mediums/ble_frames.proto new file mode 100644 index 00000000..d4f6b21c --- /dev/null +++ b/proto/mediums/ble_frames.proto @@ -0,0 +1,40 @@ +syntax = "proto2"; + +package location.nearby.mediums; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "BleFramesProto"; +option java_package = "com.google.location.nearby.mediums.proto"; +option objc_class_prefix = "GNCM"; + +// This should map exactly to BleAdvertisement's socket versions. +// TODO(alexanderkang): Make BleAdvertisement reference this proto. +// https://cs.corp.google.com/piper///depot/google3/java/com/google/android/gmscore/dev/modules/nearby/src/com/google/android/gms/nearby/mediums/bluetoothlowenergy/BleAdvertisement.java?l=57&cl=CS&rcl=214509192 +enum SocketVersion { + UNKNOWN_SOCKET_VERSION = 0; + V1 = 1; + V2 = 2; +} + +message SocketControlFrame { + enum ControlFrameType { + UNKNOWN_CONTROL_FRAME_TYPE = 0; + INTRODUCTION = 1; + DISCONNECTION = 2; + } + + optional ControlFrameType type = 1; + + // Exactly one of the following fields will be set. + optional IntroductionFrame introduction = 2; + optional DisconnectionFrame disconnection = 3; +} + +message IntroductionFrame { + optional bytes service_id_hash = 1; + optional SocketVersion socket_version = 2; +} + +message DisconnectionFrame { + optional bytes service_id_hash = 1; +} diff --git a/proto/mediums/ble_frames_portable_proto_config.asciipb b/proto/mediums/ble_frames_portable_proto_config.asciipb new file mode 100644 index 00000000..73e7564b --- /dev/null +++ b/proto/mediums/ble_frames_portable_proto_config.asciipb @@ -0,0 +1,7 @@ +optimize_mode: LITE_RUNTIME + +allowed_enum: "location.nearby.mediums.proto.SocketVersion" +allowed_message: "location.nearby.mediums.proto.SocketControlFrame" +allowed_enum: "location.nearby.mediums.proto.SocketControlFrame.ControlFrameType" +allowed_message: "location.nearby.mediums.proto.IntroductionFrame" +allowed_message: "location.nearby.mediums.proto.DisconnectionFrame" diff --git a/proto/mediums/nfc_frames.proto b/proto/mediums/nfc_frames.proto new file mode 100644 index 00000000..b3622715 --- /dev/null +++ b/proto/mediums/nfc_frames.proto @@ -0,0 +1,29 @@ +syntax = "proto2"; + +package location.nearby.mediums; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "NfcFramesProto"; +option java_package = "com.google.location.nearby.mediums.proto"; + +// The data to be sent to scanning devices from advertising devices during +// adveritising. +message AdvertisementData { + // The tag in the advertisement. + optional bytes tag = 1; + + // A public key associated with the advertisement. + optional bytes public_key = 2; +} + +// The data to be sent to advertisers from scanning device during discovery. +message AdvertisementRequest { + // Service id of the scanning device. + optional string service_id = 1; + + // Endpoint id of the scanning device. + optional string endpoint_id = 2; + + // Public key from the scanning device. + optional bytes public_key = 3; +} diff --git a/proto/mediums/web_rtc_signaling_frames.proto b/proto/mediums/web_rtc_signaling_frames.proto new file mode 100644 index 00000000..028c474d --- /dev/null +++ b/proto/mediums/web_rtc_signaling_frames.proto @@ -0,0 +1,94 @@ +syntax = "proto2"; + +package location.nearby.mediums; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "WebRtcSignalingFramesProto"; +option java_package = "com.google.location.nearby.mediums.proto"; + +message WebRtcSignalingFrame { + enum FrameType { + UNKNOWN_FRAME_TYPE = 0; + OFFER_TYPE = 1; + ANSWER_TYPE = 2; + ICE_CANDIDATES_TYPE = 3; + READY_FOR_SIGNALING_POKE_TYPE = 4; + } + + optional PeerId sender_id = 1; + + optional FrameType type = 2; + + oneof Frame { + Offer offer = 3; + Answer answer = 4; + IceCandidates ice_candidates = 5; + ReadyForSignalingPoke ready_for_signaling_poke = 6; + } +} + +// The id of the peer who sent the signaling frame. +message PeerId { + optional string id = 1; +} + +// https://en.wikipedia.org/wiki/Session_Description_Protocol +// SDP (Session Description Protocol) is the standard describing a peer-to-peer +// connection. SDP contains the codec, source address, and timing information of +// audio and video. An example message is: +// v=0 +// t=0 0 +// a=group:BUNDLE data +// a=msid-semantic: WMS +// m=application 9 DTLS/SCTP 5000 +// c=IN IP4 0.0.0.0 +// b=AS:30 +// a=ice-ufrag:zaEf +// a=ice-pwd:w9+RrqMj1RbC++15mNcRoRG5 +// a=ice-options:trickle renomination +// a=fingerprint:sha-256 +// B3:FE:B9:E1:F4:58:F6:05:A7:0D:3C:E6:E5:0A:44:A0:88:F4:50:90:41:D6:2E:A3:84:D8:C5:0C:40:2E:DB:6D +// a=setup:active +// a=mid:data +// a=sctpmap:5000 webrtc-datachannel 1024 +// a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host +// a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr +message SessionDescription { + // See the SDP example above. + optional string description = 1; +} + +// https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment +// https://www.slideshare.net/saghul/ice-4414037 +// An example message contains: +// sdp_mid = data +// sdp_m_line_index = 0 +// sdp = candidate:198238137 1 udp 2122262783 +// 620:0:1000:fd1f:1cc5:76e0:78ba:6c54 41539 typ host generation 0 ufrag kq7J +// network-id 4 network-cost 10: +message IceCandidate { + // See the lines beginning with a=candidate in the SDP example above. + optional string sdp = 1; + // For valid values, see https://tools.ietf.org/html/rfc4566 -> Media Types + // This ID uniquely identifies a given media stream with which the candidate + // is associated. Example: data + optional string sdp_mid = 2; + // A zero-based index of the m-line describing the media associated with the + // candidate. Example: 0 + optional int32 sdp_m_line_index = 3; +} + +message IceCandidates { + repeated IceCandidate ice_candidates = 1; +} + +message Offer { + optional SessionDescription session_description = 1; +} + +message Answer { + optional SessionDescription session_description = 1; +} + +// Sent from answerer->offerer once the answerer is ready to receive the offer. +message ReadyForSignalingPoke {} diff --git a/proto/mediums/wifi_aware_frames.proto b/proto/mediums/wifi_aware_frames.proto new file mode 100644 index 00000000..766b8cc0 --- /dev/null +++ b/proto/mediums/wifi_aware_frames.proto @@ -0,0 +1,48 @@ +syntax = "proto2"; + +package location.nearby.mediums; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "WifiAwareFramesProto"; +option java_package = "com.google.location.nearby.mediums.proto"; + +message WifiAwareFrame { + enum FrameType { + UNKNOWN_FRAME_TYPE = 0; + HOST_NETWORK = 1; + NETWORK_AVAILABLE = 2; + IP_AVAILABLE = 3; + CANCELLATION = 4; + } + optional FrameType type = 1; + + // Exactly one of the following fields will be set. + optional HostNetworkFrame host_network = 2; + optional NetworkAvailableFrame network_available = 3; + optional IpAvailableFrame ip_available = 4; + optional CancellationFrame cancellation = 7; + + // The id of each frame. + optional int32 frame_id = 5; + + // A byte array of size 2. It is the id and comparable token of a WifiAware + // endpoint session. + optional bytes session_id = 6; +} + +message HostNetworkFrame {} + +message NetworkAvailableFrame {} + +message IpAvailableFrame { + // NOTE: We use string here, rather than int. This is because the WiFi Aware + // ip address has a network interface appended to the end. It looks like + // 'fe80::a321:2935:9b2d:d7e7%aware_data0', where the %aware_data0 at the end + // lets Android know which type of network this address is associated with. + // If this information is lost, we won't be able to connect to the remote + // device's ServerSocket. + optional string ip_address = 1; + optional int32 port = 2; +} + +message CancellationFrame {} diff --git a/script/oss.py b/script/oss.py index cd0199d8..4b621b0f 100755 --- a/script/oss.py +++ b/script/oss.py @@ -42,6 +42,7 @@ HEADLINE = 1 PARTIAL = 2 FULL = 3 + def has_copyright(lines, max_lookup=3): pos = 0 result = MISSING @@ -65,6 +66,7 @@ def has_copyright(lines, max_lookup=3): return FULL + def add_copyright(lines, prefix, offset): new_lines = lines[0:offset] if offset: @@ -78,6 +80,7 @@ def add_copyright(lines, prefix, offset): new_lines.extend(lines[offset:]) return new_lines + def copy_files_to_oss_project(src_root, dst_root): shutil.rmtree(dst_root + "/cpp", ignore_errors=True) shutil.rmtree(dst_root + "/proto", ignore_errors=True) @@ -87,6 +90,8 @@ def copy_files_to_oss_project(src_root, dst_root): shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") shutil.copytree(src_root + "/connections/core_v2/", dst_root + "/cpp/core_v2/") shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") + shutil.copytree(src_root + "/mediums/proto/", dst_root + "/proto/mediums/") + def detect_file_copy_header_options(fname, lines): if not lines: @@ -102,6 +107,7 @@ def detect_file_copy_header_options(fname, lines): return ("#", 1) return None + def post_process_oss_files(path, args): modified_total = 0 top_level = True @@ -203,6 +209,7 @@ def post_process_oss_files(path, args): return modified_total + def main(): parser = argparse.ArgumentParser('Opensource Nearby Release Tool') parser.add_argument('target', action='store', type=str, nargs="+", default=[]) @@ -232,5 +239,6 @@ def main(): total += post_process_oss_files(dst, args) print("Total modified: {} files".format(total)) + if __name__ == "__main__": sys.exit(main()) From 1c44f4bbdd567c5d61fb95b203a4d3b05bfc3f2a Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 24 Jun 2020 18:21:36 -0700 Subject: [PATCH 34/52] OSS fix Signed-off-by: Alexey Polyudov Change-Id: I7c1c0f3e1f60df706e47a90ed43a6878f665ae85 --- proto/mediums/BUILD | 14 ++++++++++++++ proto/mediums/ble_frames.proto | 14 ++++++++++++++ proto/mediums/nfc_frames.proto | 14 ++++++++++++++ proto/mediums/web_rtc_signaling_frames.proto | 14 ++++++++++++++ proto/mediums/wifi_aware_frames.proto | 14 ++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/proto/mediums/BUILD b/proto/mediums/BUILD index dcf12b59..c3cd9367 100644 --- a/proto/mediums/BUILD +++ b/proto/mediums/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") diff --git a/proto/mediums/ble_frames.proto b/proto/mediums/ble_frames.proto index e6909659..864da316 100644 --- a/proto/mediums/ble_frames.proto +++ b/proto/mediums/ble_frames.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.mediums; diff --git a/proto/mediums/nfc_frames.proto b/proto/mediums/nfc_frames.proto index b3622715..8dcf0183 100644 --- a/proto/mediums/nfc_frames.proto +++ b/proto/mediums/nfc_frames.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.mediums; diff --git a/proto/mediums/web_rtc_signaling_frames.proto b/proto/mediums/web_rtc_signaling_frames.proto index 028c474d..58f8385f 100644 --- a/proto/mediums/web_rtc_signaling_frames.proto +++ b/proto/mediums/web_rtc_signaling_frames.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.mediums; diff --git a/proto/mediums/wifi_aware_frames.proto b/proto/mediums/wifi_aware_frames.proto index 766b8cc0..f4b71235 100644 --- a/proto/mediums/wifi_aware_frames.proto +++ b/proto/mediums/wifi_aware_frames.proto @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + syntax = "proto2"; package location.nearby.mediums; From 5142ef11af8d5e54e9d56a24a856bd1eeac0e48f Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 30 Jun 2020 00:21:02 -0700 Subject: [PATCH 35/52] Roll forward to cl/318932159 Signed-off-by: Alexey Polyudov Change-Id: Ia0cddfc8cf46c66d5739bdb45ab7825422912082 --- cpp/core_v2/core.h | 4 +- cpp/core_v2/internal/BUILD | 6 + cpp/core_v2/internal/base_pcp_handler.cc | 79 +++-- cpp/core_v2/internal/base_pcp_handler.h | 4 + cpp/core_v2/internal/base_pcp_handler_test.cc | 187 +++++----- cpp/core_v2/internal/endpoint_manager.cc | 76 +++- cpp/core_v2/internal/mediums/webrtc.h | 2 +- .../internal/offline_service_controller.cc | 93 +++++ .../internal/offline_service_controller.h | 94 +++++ .../offline_service_controller_test.cc | 335 ++++++++++++++++++ .../internal/offline_simulation_user.cc | 191 ++++++++++ .../internal/offline_simulation_user.h | 175 +++++++++ cpp/core_v2/internal/payload_manager.cc | 26 +- cpp/core_v2/internal/payload_manager.h | 5 +- cpp/core_v2/internal/payload_manager_test.cc | 9 + cpp/core_v2/internal/pcp_manager.cc | 12 + cpp/core_v2/internal/pcp_manager.h | 11 +- cpp/core_v2/internal/pcp_manager_test.cc | 6 + cpp/core_v2/internal/service_controller.h | 24 +- cpp/core_v2/internal/simulation_user.h | 8 +- cpp/platform/impl/shared/BUILD | 1 + cpp/platform/impl/shared/file_impl_test.cc | 3 +- cpp/platform_v2/api/BUILD | 1 - cpp/platform_v2/api/condition_variable.h | 1 - cpp/platform_v2/api/executor.h | 4 + cpp/platform_v2/base/logging.h | 7 +- cpp/platform_v2/impl/g3/BUILD | 1 - cpp/platform_v2/impl/g3/condition_variable.h | 5 +- .../impl/g3/multi_thread_executor.h | 5 + cpp/platform_v2/impl/g3/platform.cc | 5 + cpp/platform_v2/impl/g3/scheduled_executor.h | 3 + cpp/platform_v2/impl/shared/BUILD | 1 + cpp/platform_v2/impl/shared/file_test.cc | 3 +- cpp/platform_v2/public/BUILD | 1 - .../public/condition_variable_test.cc | 8 +- cpp/platform_v2/public/scheduled_executor.h | 8 +- cpp/platform_v2/public/settable_future.h | 4 +- .../public/single_thread_executor.h | 1 + cpp/platform_v2/public/submittable_executor.h | 9 +- 39 files changed, 1229 insertions(+), 189 deletions(-) create mode 100644 cpp/core_v2/internal/offline_service_controller.cc create mode 100644 cpp/core_v2/internal/offline_service_controller.h create mode 100644 cpp/core_v2/internal/offline_service_controller_test.cc create mode 100644 cpp/core_v2/internal/offline_simulation_user.cc create mode 100644 cpp/core_v2/internal/offline_simulation_user.h diff --git a/cpp/core_v2/core.h b/cpp/core_v2/core.h index 60021671..3d4cd6a1 100644 --- a/cpp/core_v2/core.h +++ b/cpp/core_v2/core.h @@ -4,6 +4,7 @@ #include #include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/offline_service_controller.h" #include "core_v2/internal/service_controller.h" #include "core_v2/internal/service_controller_router.h" #include "core_v2/listeners.h" @@ -19,7 +20,8 @@ namespace connections { // This class defines the API of the Nearby Connections Core library. class Core { public: - explicit Core(std::function factory) + explicit Core(std::function factory = + []() { return new OfflineServiceController; }) : router_(factory) {} ~Core(); Core(Core&&) = default; diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index 2df5423b..bcb2aa0e 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -13,6 +13,7 @@ cc_library( "internal_payload.cc", "internal_payload_factory.cc", "offline_frames.cc", + "offline_service_controller.cc", "p2p_cluster_pcp_handler.cc", "p2p_point_to_point_pcp_handler.cc", "p2p_star_pcp_handler.cc", @@ -37,6 +38,7 @@ cc_library( "internal_payload.h", "internal_payload_factory.h", "offline_frames.h", + "offline_service_controller.h", "p2p_cluster_pcp_handler.h", "p2p_point_to_point_pcp_handler.h", "p2p_star_pcp_handler.h", @@ -80,10 +82,12 @@ cc_library( name = "internal_test", testonly = True, srcs = [ + "offline_simulation_user.cc", "simulation_user.cc", ], hdrs = [ "mock_service_controller.h", + "offline_simulation_user.h", "simulation_user.h", ], visibility = [ @@ -96,6 +100,7 @@ cc_library( "//platform_v2/public:types", "//testing/base/public:gunit", "//absl/functional:bind_front", + "//absl/strings", ], ) @@ -113,6 +118,7 @@ cc_test( "endpoint_manager_test.cc", "internal_payload_factory_test.cc", "offline_frames_test.cc", + "offline_service_controller_test.cc", "p2p_cluster_pcp_handler_test.cc", "payload_manager_test.cc", "pcp_manager_test.cc", diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index ec402d7a..ac0da77b 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -32,12 +32,9 @@ BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_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_); - + << strategy_.GetName() << "; handle=" << handle_; + DisconnectFromEndpointManager(); // Stop all the ongoing Runnables (as gracefully as possible). NEARBY_LOGS(INFO) << "BasePcpHandler: bringing down executors; strategy=" << strategy_.GetName(); @@ -47,8 +44,17 @@ BasePcpHandler::~BasePcpHandler() { << strategy_.GetName(); } +void BasePcpHandler::DisconnectFromEndpointManager() { + if (stop_.Set(true)) return; + NEARBY_LOGS(INFO) << "BasePcpHandler: Unregister from EPM; strategy=" + << strategy_.GetName() << "; handle=" << handle_; + // Unregister ourselves from EPM message dispatcher. + endpoint_manager_->UnregisterFrameProcessor(V1Frame::CONNECTION_RESPONSE, + handle_, true); +} + Status BasePcpHandler::StartAdvertising(ClientProxy* client, - const string& service_id, + const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) { Future response; @@ -85,7 +91,7 @@ void BasePcpHandler::StopAdvertising(ClientProxy* client) { } Status BasePcpHandler::StartDiscovery(ClientProxy* client, - const string& service_id, + const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) { Future response; @@ -122,7 +128,7 @@ void BasePcpHandler::StopDiscovery(ClientProxy* client) { WaitForLatch("stopDiscovery", &latch); } -void BasePcpHandler::WaitForLatch(const string& method_name, +void BasePcpHandler::WaitForLatch(const std::string& method_name, CountDownLatch* latch) { Exception await_exception = latch->Await(); if (!await_exception.Ok()) { @@ -132,7 +138,7 @@ void BasePcpHandler::WaitForLatch(const string& method_name, } } -Status BasePcpHandler::WaitForResult(const string& method_name, +Status BasePcpHandler::WaitForResult(const std::string& method_name, std::int64_t client_id, Future* future) { if (!future) { @@ -156,9 +162,10 @@ void BasePcpHandler::RunOnPcpHandlerThread(Runnable runnable) { EncryptionRunner::ResultListener BasePcpHandler::GetResultListener() { return { .on_success_cb = - [this](const string& endpoint_id, + [this](const std::string& endpoint_id, std::unique_ptr ukey2, - const string& auth_token, const ByteArray& raw_auth_token) { + const std::string& auth_token, + const ByteArray& raw_auth_token) { RunOnPcpHandlerThread([this, endpoint_id, raw_ukey2 = ukey2.release(), auth_token, raw_auth_token]() mutable { @@ -168,7 +175,7 @@ EncryptionRunner::ResultListener BasePcpHandler::GetResultListener() { }); }, .on_failure_cb = - [this](const string& endpoint_id, EndpointChannel* channel) { + [this](const std::string& endpoint_id, EndpointChannel* channel) { RunOnPcpHandlerThread([this, endpoint_id, channel]() { OnEncryptionFailureRunnable(endpoint_id, channel); }); @@ -177,8 +184,8 @@ EncryptionRunner::ResultListener BasePcpHandler::GetResultListener() { } void BasePcpHandler::OnEncryptionSuccessRunnable( - const string& endpoint_id, std::unique_ptr ukey2, - const string& auth_token, const ByteArray& raw_auth_token) { + const std::string& endpoint_id, std::unique_ptr ukey2, + const std::string& auth_token, const ByteArray& raw_auth_token) { // Quick fail if we've been removed from pending connections while we were // busy running UKEY2. auto it = pending_connections_.find(endpoint_id); @@ -203,7 +210,8 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( // Set ourselves up so that we receive all acceptance/rejection messages handle_ = endpoint_manager_->RegisterFrameProcessor( - V1Frame::CONNECTION_RESPONSE, this); + V1Frame::CONNECTION_RESPONSE, + static_cast(this)); // Now we register our endpoint so that we can listen for both sides to // accept. @@ -225,7 +233,7 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( } void BasePcpHandler::OnEncryptionFailureRunnable( - const string& endpoint_id, EndpointChannel* endpoint_channel) { + const std::string& endpoint_id, EndpointChannel* endpoint_channel) { auto it = pending_connections_.find(endpoint_id); if (it == pending_connections_.end()) { NEARBY_LOG(INFO, @@ -256,7 +264,7 @@ void BasePcpHandler::OnEncryptionFailureRunnable( } Status BasePcpHandler::RequestConnection(ClientProxy* client, - const string& endpoint_id, + const std::string& endpoint_id, const ConnectionRequestInfo& info) { Future result; RunOnPcpHandlerThread([this, client, &info, endpoint_id, &result]() { @@ -358,7 +366,7 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, } BasePcpHandler::DiscoveredEndpoint* BasePcpHandler::GetDiscoveredEndpoint( - const string& endpoint_id) { + const std::string& endpoint_id) { auto it = discovered_endpoints_.find(endpoint_id); if (it == discovered_endpoints_.end()) { return nullptr; @@ -400,15 +408,15 @@ bool BasePcpHandler::CanReceiveIncomingConnection(ClientProxy* client) const { } Exception BasePcpHandler::WriteConnectionRequestFrame( - EndpointChannel* endpoint_channel, const string& local_endpoint_id, - const string& local_endpoint_name, std::int32_t nonce, + EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, + const std::string& local_endpoint_name, std::int32_t nonce, const std::vector& supported_mediums) { return endpoint_channel->Write(parser::ForConnectionRequest( local_endpoint_id, local_endpoint_name, nonce, supported_mediums)); } void BasePcpHandler::ProcessPreConnectionInitiationFailure( - const string& endpoint_id, EndpointChannel* channel, Status status, + const std::string& endpoint_id, EndpointChannel* channel, Status status, Future* result) { if (channel != nullptr) { channel->Close(); @@ -423,7 +431,7 @@ void BasePcpHandler::ProcessPreConnectionInitiationFailure( } void BasePcpHandler::ProcessPreConnectionResultFailure( - ClientProxy* client, const string& endpoint_id) { + ClientProxy* client, const std::string& endpoint_id) { auto item = pending_connections_.extract(endpoint_id); endpoint_manager_->DiscardEndpoint(client, endpoint_id); client->OnConnectionRejected(endpoint_id, {Status::kError}); @@ -448,7 +456,7 @@ bool BasePcpHandler::AutoUpgradeBandwidth() const { } Status BasePcpHandler::AcceptConnection( - ClientProxy* client, const string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) { Future response; RunOnPcpHandlerThread( @@ -503,7 +511,7 @@ Status BasePcpHandler::AcceptConnection( } Status BasePcpHandler::RejectConnection(ClientProxy* client, - const string& endpoint_id) { + const std::string& endpoint_id) { Future response; RunOnPcpHandlerThread([this, client, endpoint_id, &response]() { NEARBY_LOG(INFO, "RejectConnection: id=%s", endpoint_id.c_str()); @@ -559,7 +567,7 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, //} void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, - const string& endpoint_id, + const std::string& endpoint_id, ClientProxy* client, proto::connections::Medium medium) { CountDownLatch latch(1); @@ -595,8 +603,12 @@ void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, } void BasePcpHandler::OnEndpointDisconnect(ClientProxy* client, - const string& endpoint_id, + const std::string& endpoint_id, CountDownLatch* barrier) { + if (stop_.Get()) { + if (barrier) barrier->CountDown(); + return; + } RunOnPcpHandlerThread([this, client, endpoint_id, barrier]() { auto item = pending_alarms_.find(endpoint_id); if (item != pending_alarms_.end()) { @@ -702,7 +714,7 @@ bool BasePcpHandler::IsPreferred( } Exception BasePcpHandler::OnIncomingConnection( - ClientProxy* client, const string& remote_device_name, + ClientProxy* client, const std::string& remote_device_name, std::unique_ptr channel, proto::connections::Medium medium) { absl::Time start_time = SystemClock::ElapsedRealtime(); @@ -797,7 +809,8 @@ Exception BasePcpHandler::OnIncomingConnection( return {Exception::kSuccess}; } -bool BasePcpHandler::BreakTie(ClientProxy* client, const string& endpoint_id, +bool BasePcpHandler::BreakTie(ClientProxy* client, + const std::string& endpoint_id, std::int32_t incoming_nonce, EndpointChannel* endpoint_channel) { auto it = pending_connections_.find(endpoint_id); @@ -835,7 +848,7 @@ bool BasePcpHandler::BreakTie(ClientProxy* client, const string& endpoint_id, } void BasePcpHandler::ProcessTieBreakLoss( - ClientProxy* client, const string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, BasePcpHandler::PendingConnectionInfo* info) { ProcessPreConnectionInitiationFailure(endpoint_id, info->channel.get(), {Status::kEndpointIoError}, @@ -845,7 +858,7 @@ void BasePcpHandler::ProcessTieBreakLoss( } void BasePcpHandler::InitiateBandwidthUpgrade( - ClientProxy* client, const string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, const std::vector& supported_mediums) { // When we successfully connect to a remote endpoint and a bandwidth upgrade // medium has not yet been decided, we'll pick the highest bandwidth medium @@ -894,7 +907,7 @@ proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( } void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, - const string& endpoint_id, + const std::string& endpoint_id, bool can_close_immediately) { // Short-circuit immediately if we're not in an actionable state yet. We will // be called again once the other side has made their decision. @@ -1032,12 +1045,12 @@ BasePcpHandler::PendingConnectionInfo::~PendingConnectionInfo() { } void BasePcpHandler::PendingConnectionInfo::LocalEndpointAcceptedConnection( - const string& endpoint_id, const PayloadListener& payload_listener) { + const std::string& endpoint_id, const PayloadListener& payload_listener) { client->LocalEndpointAcceptedConnection(endpoint_id, payload_listener); } void BasePcpHandler::PendingConnectionInfo::LocalEndpointRejectedConnection( - const string& endpoint_id) { + const std::string& endpoint_id) { client->LocalEndpointRejectedConnection(endpoint_id); } diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index ec7213ff..437fbf81 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -17,6 +17,7 @@ #include "core_v2/status.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform_v2/base/prng.h" +#include "platform_v2/public/atomic_boolean.h" #include "platform_v2/public/atomic_reference.h" #include "platform_v2/public/cancelable_alarm.h" #include "platform_v2/public/count_down_latch.h" @@ -139,6 +140,7 @@ class BasePcpHandler : public PcpHandler, Pcp GetPcp() const override { return pcp_; } Strategy GetStrategy() const override { return strategy_; } + void DisconnectFromEndpointManager(); protected: // The result of a call to startAdvertisingImpl() or startDiscoveryImpl(). @@ -423,6 +425,8 @@ class BasePcpHandler : public PcpHandler, // stops discovering because it might still be useful downstream of // discovery (eg: connection speed, etc.) ConnectionOptions discovery_options_; + + AtomicBoolean stop_{false}; Pcp pcp_; Strategy strategy_{PcpToStrategy(pcp_)}; Prng prng_; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 882dbd5d..28894559 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -24,6 +24,7 @@ namespace { using ::location::nearby::proto::connections::Medium; using ::testing::_; +using ::testing::AtLeast; using ::testing::Invoke; using ::testing::MockFunction; using ::testing::Return; @@ -125,8 +126,7 @@ class MockContext { struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) - : DiscoveredEndpoint(std::move(endpoint)), - context(std::move(context)) {} + : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} MockContext context; }; @@ -310,148 +310,144 @@ class BasePcpHandlerTest : public ::testing::Test { }; TEST_F(BasePcpHandlerTest, ConstructorDestructorWorks) { - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); SUCCEED(); } TEST_F(BasePcpHandlerTest, StartAdvertisingChangesState) { - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartAdvertising(client.get(), pcp_handler.get()); + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartAdvertising(&client, &pcp_handler); } TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartAdvertising(client.get(), pcp_handler.get()); - EXPECT_CALL(*pcp_handler, StopAdvertisingImpl(client.get())).Times(1); - EXPECT_TRUE(client->IsAdvertising()); - pcp_handler->StopAdvertising(client.get()); - EXPECT_FALSE(client->IsAdvertising()); + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartAdvertising(&client, &pcp_handler); + EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); + EXPECT_TRUE(client.IsAdvertising()); + pcp_handler.StopAdvertising(&client); + EXPECT_FALSE(client.IsAdvertising()); } TEST_F(BasePcpHandlerTest, StartDiscoveryChangesState) { - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartDiscovery(&client, &pcp_handler); } TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); - EXPECT_CALL(*pcp_handler, StopDiscoveryImpl(client.get())).Times(1); - EXPECT_TRUE(client->IsDiscovering()); - pcp_handler->StopDiscovery(client.get()); - EXPECT_FALSE(client->IsDiscovering()); + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartDiscovery(&client, &pcp_handler); + EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); + EXPECT_TRUE(client.IsDiscovering()); + pcp_handler.StopDiscovery(&client); + EXPECT_FALSE(client.IsDiscovering()); } TEST_F(BasePcpHandlerTest, RequestConnectionChangesState) { std::string endpoint_id{"1234"}; - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); + 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_a = channel_pair.first; auto& channel_b = channel_pair.second; - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - client.get(), pcp_handler.get()); + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); NEARBY_LOG(INFO, "RequestConnection complete"); channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); } TEST_F(BasePcpHandlerTest, AcceptConnectionChangesState) { std::string endpoint_id{"1234"}; - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); + 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_a = channel_pair.first; auto& channel_b = channel_pair.second; - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - client.get(), pcp_handler.get()); + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); - EXPECT_EQ(pcp_handler->AcceptConnection(client.get(), endpoint_id, {}), + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); } TEST_F(BasePcpHandlerTest, RejectConnectionChangesState) { std::string endpoint_id{"1234"}; - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); + 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; EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - client.get(), pcp_handler.get()); - NEARBY_LOG(INFO, "Attempting to reject connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler->RejectConnection(client.get(), endpoint_id), + &client, &pcp_handler); + NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; + EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); } TEST_F(BasePcpHandlerTest, OnIncomingFrameChangesState) { std::string endpoint_id{"1234"}; - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); + 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_a = channel_pair.first; auto& channel_b = channel_pair.second; - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - client.get(), pcp_handler.get()); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); + NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); - EXPECT_EQ(pcp_handler->AcceptConnection(client.get(), endpoint_id, {}), + EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) + .Times(AtLeast(0)); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), Status{Status::kSuccess}); NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); auto frame = parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); - pcp_handler->OnIncomingFrame(frame.result(), endpoint_id, client.get(), - Medium::BLE); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, + Medium::BLE); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); -} - -TEST_F(BasePcpHandlerTest, OnEndpointDisconnectChangesState) { - std::string endpoint_id{"1234"}; - auto client = std::make_unique(); - auto ecm = std::make_unique(); - auto em = std::make_unique(ecm.get()); - auto pcp_handler = std::make_unique(em.get(), ecm.get()); - StartDiscovery(client.get(), pcp_handler.get()); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); - auto& channel_b = channel_pair.second; - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - client.get(), pcp_handler.get()); - NEARBY_LOG(INFO, "Simulating disconnect event: id=%s", endpoint_id.c_str()); - CountDownLatch latch(1); - pcp_handler->OnEndpointDisconnect(client.get(), endpoint_id, &latch); - channel_b->Close(); - EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result()); + pcp_handler.DisconnectFromEndpointManager(); } TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { @@ -464,15 +460,20 @@ TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { MockPcpHandler pcp_handler(&em, &ecm); StartDiscovery(&client, &pcp_handler); auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; - RequestConnection(endpoint_id, std::move(channel_pair.first), - channel_b.get(), &client, &pcp_handler, &destroyed_flag); + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), 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}); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); } EXPECT_TRUE(destroyed_flag.load()); } diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 0852b3b4..41e10e12 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -200,6 +200,7 @@ EndpointManager::EndpointManager(EndpointChannelManager* manager) : channel_manager_(manager) {} EndpointManager::~EndpointManager() { + NEARBY_LOG(INFO, "EndpointManager going down"); CountDownLatch latch(1); RunOnEndpointManagerThread([this, &latch]() { NEARBY_LOG(INFO, "Bringing down endpoints"); @@ -208,10 +209,8 @@ EndpointManager::~EndpointManager() { EndpointState& state = item.second; // This will close the channel; all workers will sense that and // terminate. - NEARBY_LOG(INFO, "Bringing down endpoint channels: id=%s", - endpoint_id.c_str()); - WaitForEndpointDisconnectionProcessing(state.client, endpoint_id); channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + state.barrier.Await(); } latch.CountDown(); }); @@ -236,9 +235,12 @@ EndpointManager::RegisterFrameProcessor( RunOnEndpointManagerThread([this, frame_type, &latch, processor]() { auto it = frame_processors_.find(frame_type); if (it != frame_processors_.end()) { - NEARBY_LOG(INFO, "Frame processor found, updated; type=%d", frame_type); + NEARBY_LOGS(INFO) << "Frame processor found: updated; type=" << frame_type + << "; processor=" << processor << "; self=" << this; it->second = processor; } else { + NEARBY_LOGS(INFO) << "Frame processor added; type=" << frame_type + << "; processor=" << processor << "; self=" << this; frame_processors_.emplace(frame_type, processor); } latch.CountDown(); @@ -249,14 +251,22 @@ EndpointManager::RegisterFrameProcessor( void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, const void* handle, bool sync) { + NEARBY_LOGS(INFO) << "UnregisterFrameProcessor [enter]: handle=" << handle; 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 == frame_processors_.end()) { + NEARBY_LOGS(INFO) << "UnregisterFrameProcessor [not found]: handle=" + << handle; + if (sync) latch.CountDown(); + return; + } + NEARBY_LOGS(INFO) << "UnregisterFrameProcessor [found]: handle=" << handle; if (it->second == handle) { frame_processors_.erase(it); - NEARBY_LOG(INFO, "Unregistered: type=%d", frame_type); + NEARBY_LOGS(INFO) << "Unregistered: type=" << frame_type + << "; processor=" << handle << "; self=" << this; } else { NEARBY_LOG(INFO, "Failed to unregister: type=%d; handle mismatch: passed=%p, " @@ -267,7 +277,8 @@ void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, }); if (sync) { latch.Await(); - NEARBY_LOG(INFO, "Unregistered: [sync done] type=%d", frame_type); + NEARBY_LOGS(INFO) << "Unregistered [sync done]: type=" << frame_type + << "; processor=" << handle << "; self=" << this; } } @@ -294,10 +305,13 @@ void EndpointManager::EnsureWorkersTerminated(const std::string& endpoint_id) { // If another instance of data and keep-alive handlers is running, it will // terminate soon; we should block until it happens. EndpointState& endpoint_state = item->second; - NEARBY_LOG(INFO, "Waiting for workers to terminate for endpoint_id='%s'", - endpoint_id.c_str()); + NEARBY_LOGS(INFO) << "Waiting for workers to terminate for id: " + << endpoint_id; endpoint_state.barrier.Await(); endpoints_.erase(item); + NEARBY_LOGS(INFO) << "Workers terminated for id: " << endpoint_id; + } else { + NEARBY_LOGS(INFO) << "EndpointState not found for id: " << endpoint_id; } } @@ -378,8 +392,8 @@ void EndpointManager::UnregisterEndpoint(ClientProxy* client, const std::string& endpoint_id) { CountDownLatch latch(1); RunOnEndpointManagerThread([this, client, endpoint_id, &latch]() { - channel_manager_->UnregisterChannelForEndpoint(endpoint_id); - RemoveEndpoint(client, endpoint_id, /*notify=*/false); + RemoveEndpoint(client, endpoint_id, + client->IsConnectedToEndpoint(endpoint_id)); latch.CountDown(); }); latch.Await(); @@ -391,7 +405,6 @@ void EndpointManager::UnregisterEndpoint(ClientProxy* client, void EndpointManager::DiscardEndpoint(ClientProxy* client, const std::string& endpoint_id) { RunOnEndpointManagerThread([this, client, endpoint_id]() { - channel_manager_->UnregisterChannelForEndpoint(endpoint_id); RemoveEndpoint(client, endpoint_id, /*notify=*/ client->IsConnectedToEndpoint(endpoint_id)); @@ -435,25 +448,50 @@ void EndpointManager::RemoveEndpoint(ClientProxy* client, // should be no further interactions with the endpoint. // (See b/37352254 for history) WaitForEndpointDisconnectionProcessing(client, endpoint_id); - EnsureWorkersTerminated(endpoint_id); client->OnDisconnected(endpoint_id, notify); - NEARBY_LOG(INFO, "Removed endpoint; id=%s", - endpoint_id.c_str()); + NEARBY_LOG(INFO, "Removed endpoint; id=%s", endpoint_id.c_str()); } } // @EndpointManagerThread void EndpointManager::WaitForEndpointDisconnectionProcessing( ClientProxy* client, const std::string& endpoint_id) { - CountDownLatch barrier(frame_processors_.size()); + NEARBY_LOGS(INFO) << "Wait: client=" << client << "; id=" << endpoint_id; + auto total_size = frame_processors_.size(); + NEARBY_LOGS(INFO) << "Total frame processors: " << total_size; + if (!total_size) return; + CountDownLatch barrier(total_size); + int valid = 0; for (auto& item : frame_processors_) { - auto& processor = item.second; - processor->OnEndpointDisconnect(client, endpoint_id, &barrier); + auto* processor = item.second; + NEARBY_LOGS(INFO) << "processor=" << processor << "; type=" << item.first; + if (processor) { + valid++; + processor->OnEndpointDisconnect(client, endpoint_id, &barrier); + } else { + barrier.CountDown(); + } } - barrier.Await(kProcessEndpointDisconnectionTimeout); + if (!valid) { + NEARBY_LOGS(INFO) << "No valid frame processors."; + return; + } else { + NEARBY_LOGS(INFO) << "Valid frame processors: " << valid; + } + + NEARBY_LOGS(INFO) << "Waiting for " << valid + << " frame processors to disconnect from: " << endpoint_id; + if (!barrier.Await(kProcessEndpointDisconnectionTimeout).result()) { + NEARBY_LOGS(INFO) << "Failed to disconnect frame processors from: " + << endpoint_id; + } else { + NEARBY_LOGS(INFO) + << "Finished waiting for frame processors to disconnect from: " + << endpoint_id; + } } std::vector EndpointManager::SendTransferFrameBytes( diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h index 9781501f..27612269 100644 --- a/cpp/core_v2/internal/mediums/webrtc.h +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -138,11 +138,11 @@ class WebRtc { std::vector<::location::nearby::mediums::IceCandidate> pending_local_ice_candidates_ ABSL_GUARDED_BY(mutex_); + WebRtcMedium medium_; 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_; }; diff --git a/cpp/core_v2/internal/offline_service_controller.cc b/cpp/core_v2/internal/offline_service_controller.cc new file mode 100644 index 00000000..7465fc96 --- /dev/null +++ b/cpp/core_v2/internal/offline_service_controller.cc @@ -0,0 +1,93 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/offline_service_controller.h" + +#include + +namespace location { +namespace nearby { +namespace connections { + +OfflineServiceController::~OfflineServiceController() { + Stop(); +} + +void OfflineServiceController::Stop() { + if (stop_.Set(true)) return; + payload_manager_.DisconnectFromEndpointManager(); + pcp_manager_.DisconnectFromEndpointManager(); +} + +Status OfflineServiceController::StartAdvertising( + ClientProxy* client, const std::string& service_id, + const ConnectionOptions& options, const ConnectionRequestInfo& info) { + return pcp_manager_.StartAdvertising(client, service_id, options, info); +} + +void OfflineServiceController::StopAdvertising(ClientProxy* client) { + pcp_manager_.StopAdvertising(client); +} + +Status OfflineServiceController::StartDiscovery( + ClientProxy* client, const std::string& service_id, + const ConnectionOptions& options, const DiscoveryListener& listener) { + return pcp_manager_.StartDiscovery(client, service_id, options, listener); +} + +void OfflineServiceController::StopDiscovery(ClientProxy* client) { + pcp_manager_.StopDiscovery(client); +} + +Status OfflineServiceController::RequestConnection( + ClientProxy* client, const std::string& endpoint_id, + const ConnectionRequestInfo& info) { + return pcp_manager_.RequestConnection(client, endpoint_id, info); +} + +Status OfflineServiceController::AcceptConnection( + ClientProxy* client, const std::string& endpoint_id, + const PayloadListener& listener) { + return pcp_manager_.AcceptConnection(client, endpoint_id, listener); +} + +Status OfflineServiceController::RejectConnection( + ClientProxy* client, const std::string& endpoint_id) { + return pcp_manager_.RejectConnection(client, endpoint_id); +} + +void OfflineServiceController::InitiateBandwidthUpgrade( + ClientProxy* client, const std::string& endpoint_id) { + // TODO(apolyudov): implement. +} + +void OfflineServiceController::SendPayload( + ClientProxy* client, const std::vector& endpoint_ids, + Payload payload) { + payload_manager_.SendPayload(client, endpoint_ids, std::move(payload)); +} + +Status OfflineServiceController::CancelPayload(ClientProxy* client, + std::int64_t payload_id) { + return payload_manager_.CancelPayload(client, payload_id); +} + +void OfflineServiceController::DisconnectFromEndpoint( + ClientProxy* client, const std::string& endpoint_id) { + endpoint_manager_.UnregisterEndpoint(client, endpoint_id); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h new file mode 100644 index 00000000..a4855db2 --- /dev/null +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ + +#include +#include +#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/payload_manager.h" +#include "core_v2/internal/pcp_manager.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" + +namespace location { +namespace nearby { +namespace connections { + +class OfflineServiceController : public ServiceController { + public: + OfflineServiceController() = default; + ~OfflineServiceController() override; + + Status StartAdvertising(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) override; + void StopAdvertising(ClientProxy* client) override; + + Status StartDiscovery(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) override; + void StopDiscovery(ClientProxy* client) override; + + Status RequestConnection(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) override; + Status AcceptConnection(ClientProxy* client, + const std::string& endpoint_id, + const PayloadListener& listener) override; + Status RejectConnection(ClientProxy* client, + const std::string& endpoint_id) override; + + void InitiateBandwidthUpgrade(ClientProxy* client, + const std::string& endpoint_id) override; + + void SendPayload(ClientProxy* client, + const std::vector& endpoint_ids, + Payload payload) override; + Status CancelPayload(ClientProxy* client, + Payload::Id payload_id) override; + + void DisconnectFromEndpoint(ClientProxy* client, + const std::string& endpoint_id) override; + + void Stop(); + + private: + // Note that the order of declaration of these is crucial, because we depend + // on the destructors running (strictly) in the reverse order; a deviation + // from that will lead to crashes at runtime. + AtomicBoolean stop_{false}; + Mediums mediums_; + EndpointChannelManager channel_manager_; + EndpointManager endpoint_manager_{&channel_manager_}; + PayloadManager payload_manager_{endpoint_manager_}; + PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_service_controller_test.cc b/cpp/core_v2/internal/offline_service_controller_test.cc new file mode 100644 index 00000000..2d4487ea --- /dev/null +++ b/cpp/core_v2/internal/offline_service_controller_test.cc @@ -0,0 +1,335 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/offline_service_controller.h" + +#include "core_v2/internal/offline_simulation_user.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/system_clock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::testing::Eq; + +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); +constexpr absl::Duration kDisconnectTimeout = absl::Milliseconds(15000); + +class OfflineServiceControllerTest : public ::testing::Test { + protected: + OfflineServiceControllerTest() { env_.Stop(); } + + bool SetupConnection(OfflineSimulationUser& user_a, + OfflineSimulationUser& user_b) { + user_a.StartAdvertising(std::string(kServiceId), &connect_latch_); + user_b.StartDiscovery(std::string(kServiceId), &discover_latch_); + EXPECT_TRUE(discover_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(&connect_latch_); + EXPECT_TRUE(connect_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 discover_latch_{1}; + CountDownLatch connect_latch_{2}; + CountDownLatch accept_latch_{2}; + CountDownLatch payload_latch_{1}; + MediumEnvironment& env_ = MediumEnvironment::Instance(); +}; + +TEST_F(OfflineServiceControllerTest, CanCreateOne) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanCreateMany) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanStartAdvertising) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_FALSE(user_a.IsAdvertising()); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_a.IsAdvertising()); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanStartDiscoveryBeforeAdvertising) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_FALSE(user_b.IsDiscovering()); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_b.IsDiscovering()); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanStartDiscoveryAfterAdvertising) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_FALSE(user_b.IsDiscovering()); + EXPECT_FALSE(user_b.IsAdvertising()); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), + Eq(Status{Status::kSuccess})); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_a.IsAdvertising()); + EXPECT_TRUE(user_b.IsDiscovering()); + EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanStopAdvertising) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_FALSE(user_a.IsAdvertising()); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_a.IsAdvertising()); + user_a.StopAdvertising(); + EXPECT_FALSE(user_a.IsAdvertising()); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_b.IsDiscovering()); + EXPECT_FALSE(discover_latch_.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanStopDiscovery) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_FALSE(user_b.IsDiscovering()); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(user_b.IsDiscovering()); + user_b.StopDiscovery(); + EXPECT_FALSE(user_b.IsDiscovering()); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), + Eq(Status{Status::kSuccess})); + EXPECT_FALSE(discover_latch_.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanConnect) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); + EXPECT_THAT(user_b.RequestConnection(&connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanAcceptConnection) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); + EXPECT_THAT(user_b.RequestConnection(&connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result()); + EXPECT_THAT(user_a.AcceptConnection(&accept_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_THAT(user_b.AcceptConnection(&accept_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(accept_latch_.Await(kDefaultTimeout).result()); + EXPECT_TRUE(user_a.IsConnected()); + EXPECT_TRUE(user_b.IsConnected()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanRejectConnection) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + CountDownLatch reject_latch(1); + EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); + EXPECT_THAT(user_b.RequestConnection(&connect_latch_), + Eq(Status{Status::kSuccess})); + EXPECT_TRUE(connect_latch_.Await(kDefaultTimeout).result()); + user_a.ExpectRejectedConnection(reject_latch); + EXPECT_THAT(user_b.RejectConnection(nullptr), Eq(Status{Status::kSuccess})); + EXPECT_TRUE(reject_latch.Await(kDefaultTimeout).result()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanSendBytePayload) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + ByteArray message(std::string{kMessage}); + user_a.SendPayload(Payload(message)); + user_b.ExpectPayload(payload_latch_); + EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + EXPECT_EQ(user_b.GetPayload().AsBytes(), message); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanSendStreamPayload) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + ByteArray message(std::string{kMessage}); + auto pipe = std::make_shared(); + OutputStream& tx = pipe->GetOutputStream(); + user_a.SendPayload(Payload([pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + })); + user_b.ExpectPayload(payload_latch_); + tx.Write(message); + EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + EXPECT_NE(user_b.GetPayload().AsStream(), nullptr); + InputStream& rx = *user_b.GetPayload().AsStream(); + ASSERT_TRUE(user_b.WaitForProgress( + [size = message.size()](const PayloadProgressInfo& info) -> bool { + return info.bytes_transferred >= size; + }, + kProgressTimeout)); + EXPECT_EQ(rx.Read(Pipe::kChunkSize).result(), message); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanCancelStreamPayload) { + env_.Start(); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + ByteArray message(std::string{kMessage}); + auto pipe = std::make_shared(); + OutputStream& tx = pipe->GetOutputStream(); + user_a.SendPayload(Payload([pipe]() -> InputStream& { + return pipe->GetInputStream(); // NOLINT + })); + user_b.ExpectPayload(payload_latch_); + tx.Write(message); + EXPECT_TRUE(payload_latch_.Await(kDefaultTimeout).result()); + EXPECT_NE(user_b.GetPayload().AsStream(), nullptr); + InputStream& rx = *user_b.GetPayload().AsStream(); + ASSERT_TRUE(user_b.WaitForProgress( + [size = message.size()](const PayloadProgressInfo& info) -> bool { + return info.bytes_transferred >= size; + }, + kProgressTimeout)); + EXPECT_EQ(rx.Read(Pipe::kChunkSize).result(), message); + user_b.CancelPayload(); + int count = 0; + while (true) { + count++; + if (!tx.Write(message).Ok()) break; + SystemClock::Sleep(kDefaultTimeout); + } + EXPECT_TRUE(user_a.WaitForProgress( + [](const PayloadProgressInfo& info) -> bool { + return info.status == PayloadProgressInfo::Status::kCanceled; + }, + kProgressTimeout)); + EXPECT_LT(count, 10); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +TEST_F(OfflineServiceControllerTest, CanDisconnect) { + env_.Start(); + CountDownLatch disconnect_latch(1); + OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_b(kDeviceB); + ASSERT_TRUE(SetupConnection(user_a, user_b)); + NEARBY_LOGS(INFO) << "Disconnecting"; + user_b.ExpectDisconnect(disconnect_latch); + user_b.Disconnect(); + EXPECT_TRUE(disconnect_latch.Await(kDisconnectTimeout).result()); + NEARBY_LOGS(INFO) << "Disconnected"; + EXPECT_FALSE(user_b.IsConnected()); + user_a.Stop(); + user_b.Stop(); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_simulation_user.cc b/cpp/core_v2/internal/offline_simulation_user.cc new file mode 100644 index 00000000..1a58f117 --- /dev/null +++ b/cpp/core_v2/internal/offline_simulation_user.cc @@ -0,0 +1,191 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/offline_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 OfflineSimulationUser::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 OfflineSimulationUser::OnConnectionAccepted( + const std::string& endpoint_id) { + if (accept_latch_) accept_latch_->CountDown(); +} + +void OfflineSimulationUser::OnConnectionRejected(const std::string& endpoint_id, + Status status) { + if (reject_latch_) reject_latch_->CountDown(); +} + +void OfflineSimulationUser::OnEndpointDisconnect( + const std::string& endpoint_id) { + NEARBY_LOGS(INFO) << "OnEndpointDisconnect: self=" << this + << "; id=" << endpoint_id; + if (disconnect_latch_) disconnect_latch_->CountDown(); +} + +void OfflineSimulationUser::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 OfflineSimulationUser::OnEndpointLost(const std::string& endpoint_id) { + if (lost_latch_) lost_latch_->CountDown(); +} + +void OfflineSimulationUser::OnPayload(const std::string& endpoint_id, + Payload payload) { + payload_ = std::move(payload); + if (payload_latch_) payload_latch_->CountDown(); +} + +void OfflineSimulationUser::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 OfflineSimulationUser::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(); +} + +Status OfflineSimulationUser::StartAdvertising(const std::string& service_id, + CountDownLatch* latch) { + initiated_latch_ = latch; + service_id_ = service_id; + ConnectionListener listener = { + .initiated_cb = + std::bind(&OfflineSimulationUser::OnConnectionInitiated, this, + std::placeholders::_1, std::placeholders::_2, false), + .accepted_cb = + absl::bind_front(&OfflineSimulationUser::OnConnectionAccepted, this), + .rejected_cb = + absl::bind_front(&OfflineSimulationUser::OnConnectionRejected, this), + .disconnected_cb = + absl::bind_front(&OfflineSimulationUser::OnEndpointDisconnect, this), + }; + return ctrl_.StartAdvertising(&client_, service_id_, options_, + { + .name = name_, + .listener = std::move(listener), + }); +} + +void OfflineSimulationUser::StopAdvertising() { + ctrl_.StopAdvertising(&client_); +} + +Status OfflineSimulationUser::StartDiscovery(const std::string& service_id, + CountDownLatch* latch) { + found_latch_ = latch; + DiscoveryListener listener = { + .endpoint_found_cb = + absl::bind_front(&OfflineSimulationUser::OnEndpointFound, this), + .endpoint_lost_cb = + absl::bind_front(&OfflineSimulationUser::OnEndpointLost, this), + }; + return ctrl_.StartDiscovery(&client_, service_id, options_, + std::move(listener)); +} + +void OfflineSimulationUser::StopDiscovery() { ctrl_.StopDiscovery(&client_); } + +Status OfflineSimulationUser::RequestConnection(CountDownLatch* latch) { + initiated_latch_ = latch; + ConnectionListener listener = { + .initiated_cb = + std::bind(&OfflineSimulationUser::OnConnectionInitiated, this, + std::placeholders::_1, std::placeholders::_2, true), + .accepted_cb = + absl::bind_front(&OfflineSimulationUser::OnConnectionAccepted, this), + .rejected_cb = + absl::bind_front(&OfflineSimulationUser::OnConnectionRejected, this), + .disconnected_cb = + absl::bind_front(&OfflineSimulationUser::OnEndpointDisconnect, this), + }; + return ctrl_.RequestConnection(&client_, discovered_.endpoint_id, + { + .name = discovered_.endpoint_name, + .listener = std::move(listener), + }); +} + +Status OfflineSimulationUser::AcceptConnection(CountDownLatch* latch) { + accept_latch_ = latch; + PayloadListener listener = { + .payload_cb = absl::bind_front(&OfflineSimulationUser::OnPayload, this), + .payload_progress_cb = + absl::bind_front(&OfflineSimulationUser::OnPayloadProgress, this), + }; + return ctrl_.AcceptConnection(&client_, discovered_.endpoint_id, + std::move(listener)); +} + +Status OfflineSimulationUser::RejectConnection(CountDownLatch* latch) { + reject_latch_ = latch; + return ctrl_.RejectConnection(&client_, discovered_.endpoint_id); +} + +void OfflineSimulationUser::Disconnect() { + NEARBY_LOGS(INFO) << "Disconnecting from id=" << discovered_.endpoint_id; + ctrl_.DisconnectFromEndpoint(&client_, discovered_.endpoint_id); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_simulation_user.h b/cpp/core_v2/internal/offline_simulation_user.h new file mode 100644 index 00000000..27a41d56 --- /dev/null +++ b/cpp/core_v2/internal/offline_simulation_user.h @@ -0,0 +1,175 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ +#define CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/offline_service_controller.h" +#include "platform_v2/public/atomic_boolean.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" +#include "absl/strings/string_view.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 OfflineSimulationUser { + 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 OfflineSimulationUser(absl::string_view device_name) + : name_(device_name) {} + virtual ~OfflineSimulationUser() = default; + + // Calls PcpManager::StartAdvertising(). + // If latch is provided, will call latch->CountDown() in the initiated_cb + // callback. + Status StartAdvertising(const std::string& service_id, CountDownLatch* latch); + + // Calls PcpManager::StopAdvertising(). + void StopAdvertising(); + + // Calls PcpManager::StartDiscovery(). + // If latch is provided, will call latch->CountDown() in the endpoint_found_cb + // callback. + Status StartDiscovery(const std::string& service_id, CountDownLatch* latch); + + // Calls PcpManager::StopDiscovery(). + void StopDiscovery(); + + // Calls PcpManager::RequestConnection(). + // If latch is provided, latch->CountDown() will be called in the initiated_cb + // callback. + Status RequestConnection(CountDownLatch* latch); + + // Calls PcpManager::AcceptConnection. + // If latch is provided, latch->CountDown() will be called in the accepted_cb + // callback. + Status AcceptConnection(CountDownLatch* latch); + + // Calls PcpManager::RejectConnection. + // If latch is provided, latch->CountDown() will be called in the rejected_cb + // callback. + Status 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; } + void ExpectDisconnect(CountDownLatch& latch) { disconnect_latch_ = &latch; } + + const DiscoveredInfo& GetDiscovered() const { return discovered_; } + std::string GetName() const { return name_; } + + bool WaitForProgress(std::function pred, + absl::Duration timeout); + + Payload& GetPayload() { return payload_; } + void SendPayload(Payload payload) { + sender_payload_id_ = payload.GetId(); + ctrl_.SendPayload(&client_, {discovered_.endpoint_id}, std::move(payload)); + } + + Status CancelPayload() { + if (sender_payload_id_) { + return ctrl_.CancelPayload(&client_, sender_payload_id_); + } else { + return ctrl_.CancelPayload(&client_, payload_.GetId()); + } + } + + void Disconnect(); + + bool IsAdvertising() const { return client_.IsAdvertising(); } + + bool IsDiscovering() const { return client_.IsDiscovering(); } + + bool IsConnected() const { + return client_.IsConnectedToEndpoint(discovered_.endpoint_id); + } + + void Stop() { + ctrl_.Stop(); + } + + 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); + void OnEndpointDisconnect(const std::string& endpoint_id); + + // 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_; + Payload::Id sender_payload_id_ = 0; + CountDownLatch* initiated_latch_ = nullptr; + CountDownLatch* accept_latch_ = nullptr; + CountDownLatch* reject_latch_ = nullptr; + CountDownLatch* found_latch_ = nullptr; + CountDownLatch* lost_latch_ = nullptr; + CountDownLatch* payload_latch_ = nullptr; + CountDownLatch* disconnect_latch_ = nullptr; + Future* future_ = nullptr; + std::function predicate_; + std::string name_; + ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + ClientProxy client_; + OfflineServiceController ctrl_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ diff --git a/cpp/core_v2/internal/payload_manager.cc b/cpp/core_v2/internal/payload_manager.cc index 4cb491f0..27b51a0a 100644 --- a/cpp/core_v2/internal/payload_manager.cc +++ b/cpp/core_v2/internal/payload_manager.cc @@ -44,7 +44,7 @@ bool PayloadManager::SendPayloadLoop( for (const auto& endpoint : unavailable_endpoints) { HandleFinishedOutgoingPayload( client, {endpoint->id}, payload_header, next_chunk_offset, - EndpointInfoStatusToPayloadStatus(endpoint->status)); + EndpointInfoStatusToPayloadStatus(endpoint->status.Get())); } // Update the still-active recipients of this payload. @@ -142,8 +142,8 @@ PayloadManager::GetAvailableAndUnavailableEndpoints( 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 == + endpoint_info->id.c_str(), endpoint_info->status.Get()); + if (endpoint_info->status.Get() == PayloadManager::EndpointInfo::Status::kAvailable) { available.push_back(endpoint_info); } else { @@ -239,12 +239,16 @@ void PayloadManager::CancelAllPayloads() { } } -PayloadManager::~PayloadManager() { - NEARBY_LOG(INFO, "PayloadManager: going down; self=%p", this); - shutdown_.Set(true); +void PayloadManager::DisconnectFromEndpointManager() { + if (shutdown_.Set(true)) return; // Unregister ourselves from the FrameProcessors. endpoint_manager_->UnregisterFrameProcessor(V1Frame::PAYLOAD_TRANSFER, handle_, true); +} + +PayloadManager::~PayloadManager() { + NEARBY_LOG(INFO, "PayloadManager: going down; self=%p", this); + DisconnectFromEndpointManager(); CancelAllPayloads(); NEARBY_LOG(INFO, "PayloadManager: turn down payload executors; self=%p", this); @@ -385,7 +389,11 @@ void PayloadManager::OnIncomingFrame( void PayloadManager::OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) { - RunOnStatusUpdateThread([this, client, endpoint_id, &barrier]() { + if (shutdown_.Get()) { + if (barrier) barrier->CountDown(); + return; + } + RunOnStatusUpdateThread([this, client, endpoint_id, barrier]() { // Iterate through all our payloads and look for payloads associated // with this endpoint. MutexLock lock(&mutex_); @@ -907,7 +915,7 @@ PayloadManager::EndpointInfo::ControlMessageEventToEndpointInfoStatus( void PayloadManager::EndpointInfo::SetStatusFromControlMessage( const PayloadTransferFrame::ControlMessage& control_message) { - status = ControlMessageEventToEndpointInfoStatus(control_message.event()); + status.Set(ControlMessageEventToEndpointInfoStatus(control_message.event())); } //////////////////////////////// PendingPayload //////////////////////////////// @@ -924,7 +932,7 @@ PayloadManager::PendingPayload::PendingPayload( for (const auto& id : endpoint_ids) { endpoints_.emplace(id, EndpointInfo{ .id = id, - .status = EndpointInfo::Status::kAvailable, + .status {EndpointInfo::Status::kAvailable}, }); } } diff --git a/cpp/core_v2/internal/payload_manager.h b/cpp/core_v2/internal/payload_manager.h index 9e9000d1..3df1fe5a 100644 --- a/cpp/core_v2/internal/payload_manager.h +++ b/cpp/core_v2/internal/payload_manager.h @@ -15,6 +15,7 @@ #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/atomic_reference.h" #include "platform_v2/public/count_down_latch.h" #include "platform_v2/public/mutex.h" #include "proto/connections_enums.pb.h" @@ -47,6 +48,8 @@ class PayloadManager : public EndpointManager::FrameProcessor { void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) override; + void DisconnectFromEndpointManager(); + private: // Information about an endpoint for a particular payload. struct EndpointInfo { @@ -65,7 +68,7 @@ class PayloadManager : public EndpointManager::FrameProcessor { PayloadTransferFrame::ControlMessage::EventType event); std::string id; - Status status = Status::kUnknown; + AtomicReference status {Status::kUnknown}; std::int64_t offset = 0; }; diff --git a/cpp/core_v2/internal/payload_manager_test.cc b/cpp/core_v2/internal/payload_manager_test.cc index 826c6172..e88a050c 100644 --- a/cpp/core_v2/internal/payload_manager_test.cc +++ b/cpp/core_v2/internal/payload_manager_test.cc @@ -25,6 +25,7 @@ class PayloadSimulationUser : public SimulationUser { explicit PayloadSimulationUser(absl::string_view name) : SimulationUser(std::string(name)) {} ~PayloadSimulationUser() override { + NEARBY_LOGS(INFO) << "PayloadSimulationUser: [down] name=" << name_; // SystemClock::Sleep(kDefaultTimeout); } @@ -109,6 +110,8 @@ TEST_F(PayloadManagerTest, CanSendBytePayload) { EXPECT_EQ(user_a.GetPayload().AsBytes(), ByteArray(std::string(kMessage))); NEARBY_LOG(INFO, "Test completed."); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } @@ -157,6 +160,8 @@ TEST_F(PayloadManagerTest, CanSendStreamPayload) { rx.Close(); tx.Close(); NEARBY_LOG(INFO, "Test completed."); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } @@ -213,6 +218,8 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { rx.Close(); NEARBY_LOG(INFO, "Test completed."); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } @@ -269,6 +276,8 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) { rx.Close(); NEARBY_LOG(INFO, "Test completed."); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index caeb6353..b6f071c0 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -21,6 +21,18 @@ PcpManager::PcpManager(Mediums& mediums, channel_manager); } +void PcpManager::DisconnectFromEndpointManager() { + if (shutdown_.Set(true)) return; + for (auto& item : handlers_) { + if (!item.second) continue; + item.second->DisconnectFromEndpointManager(); + } +} + +PcpManager::~PcpManager() { + DisconnectFromEndpointManager(); +} + Status PcpManager::StartAdvertising(ClientProxy* client, const string& service_id, const ConnectionOptions& options, diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index ce1d60ed..68228b38 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -3,15 +3,16 @@ #include +#include "core_v2/internal/base_pcp_handler.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/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 "platform_v2/public/atomic_boolean.h" #include "absl/container/flat_hash_map.h" namespace location { @@ -29,7 +30,7 @@ class PcpManager { public: PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, EndpointManager& endpoint_manager); - ~PcpManager() = default; + ~PcpManager(); Status StartAdvertising(ClientProxy* client_proxy, const string& service_id, const ConnectionOptions& options, @@ -48,13 +49,15 @@ class PcpManager { Status RejectConnection(ClientProxy* client_proxy, const string& endpoint_id); proto::connections::Medium GetBandwidthUpgradeMedium(); + void DisconnectFromEndpointManager(); private: bool SetCurrentPcpHandler(Strategy strategy); PcpHandler* GetPcpHandler(Pcp pcp) const; - absl::flat_hash_map> handlers_; - PcpHandler* current_; + AtomicBoolean shutdown_{false}; + absl::flat_hash_map> handlers_; + PcpHandler* current_ = nullptr; }; } // namespace connections diff --git a/cpp/core_v2/internal/pcp_manager_test.cc b/cpp/core_v2/internal/pcp_manager_test.cc index 15e1d6c0..a2404719 100644 --- a/cpp/core_v2/internal/pcp_manager_test.cc +++ b/cpp/core_v2/internal/pcp_manager_test.cc @@ -73,6 +73,8 @@ TEST_F(PcpManagerTest, CanConnect) { 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.Stop(); + user_b.Stop(); env_.Stop(); } @@ -93,6 +95,8 @@ TEST_F(PcpManagerTest, CanAccept) { user_a.AcceptConnection(&accept_latch); user_b.AcceptConnection(&accept_latch); EXPECT_TRUE(accept_latch.Await(absl::Milliseconds(1000)).result()); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } @@ -113,6 +117,8 @@ TEST_F(PcpManagerTest, CanReject) { user_b.ExpectRejectedConnection(reject_latch); user_a.RejectConnection(nullptr); EXPECT_TRUE(reject_latch.Await(absl::Milliseconds(1000)).result()); + user_a.Stop(); + user_b.Stop(); env_.Stop(); } diff --git a/cpp/core_v2/internal/service_controller.h b/cpp/core_v2/internal/service_controller.h index 119a633b..0b6e8c60 100644 --- a/cpp/core_v2/internal/service_controller.h +++ b/cpp/core_v2/internal/service_controller.h @@ -35,38 +35,38 @@ class ServiceController { ServiceController& operator=(const ServiceController&) = delete; // Starts advertising an endpoint for a local app. - virtual Status StartAdvertising(ClientProxy* client_proxy, + virtual Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) = 0; - virtual void StopAdvertising(ClientProxy* client_proxy) = 0; + virtual void StopAdvertising(ClientProxy* client) = 0; - virtual Status StartDiscovery(ClientProxy* client_proxy, + virtual Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) = 0; - virtual void StopDiscovery(ClientProxy* client_proxy) = 0; + virtual void StopDiscovery(ClientProxy* client) = 0; - virtual Status RequestConnection(ClientProxy* client_proxy, + virtual Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, const ConnectionRequestInfo& info) = 0; - virtual Status AcceptConnection(ClientProxy* client_proxy, + virtual Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& listener) = 0; - virtual Status RejectConnection(ClientProxy* client_proxy, + virtual Status RejectConnection(ClientProxy* client, const std::string& endpoint_id) = 0; - virtual void InitiateBandwidthUpgrade(ClientProxy* client_proxy, + virtual void InitiateBandwidthUpgrade(ClientProxy* client, const std::string& endpoint_id) = 0; - virtual void SendPayload(ClientProxy* client_proxy, + virtual void SendPayload(ClientProxy* client, const std::vector& endpoint_ids, Payload payload) = 0; - virtual Status CancelPayload(ClientProxy* client_proxy, - std::int64_t payload_id) = 0; + virtual Status CancelPayload(ClientProxy* client, + Payload::Id payload_id) = 0; - virtual void DisconnectFromEndpoint(ClientProxy* client_proxy, + virtual void DisconnectFromEndpoint(ClientProxy* client, const std::string& endpoint_id) = 0; }; diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 39fa17ee..6d24929c 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -36,7 +36,13 @@ class SimulationUser { explicit SimulationUser(const std::string& device_name) : name_(device_name) {} - virtual ~SimulationUser() = default; + virtual ~SimulationUser() { + Stop(); + } + void Stop() { + pm_.DisconnectFromEndpointManager(); + mgr_.DisconnectFromEndpointManager(); + } // Calls PcpManager::StartAdvertising. // If latch is provided, will call latch->CountDown() in the initiated_cb diff --git a/cpp/platform/impl/shared/BUILD b/cpp/platform/impl/shared/BUILD index c65c458d..cd000b3d 100644 --- a/cpp/platform/impl/shared/BUILD +++ b/cpp/platform/impl/shared/BUILD @@ -69,5 +69,6 @@ cc_test( ":file", "//file/util:temp_path", "//testing/base/public:gunit_main", + "//absl/strings", ], ) diff --git a/cpp/platform/impl/shared/file_impl_test.cc b/cpp/platform/impl/shared/file_impl_test.cc index b2ce22e1..bea472a0 100644 --- a/cpp/platform/impl/shared/file_impl_test.cc +++ b/cpp/platform/impl/shared/file_impl_test.cc @@ -7,6 +7,7 @@ #include "file/util/temp_path.h" #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -20,7 +21,7 @@ class FileImplTest : public ::testing::Test { file_ = std::fstream(path_, std::fstream::in | std::fstream::out); } - void WriteToFile(const std::string& text) { + void WriteToFile(absl::string_view text) { file_ << text; file_.flush(); size_ += text.size(); diff --git a/cpp/platform_v2/api/BUILD b/cpp/platform_v2/api/BUILD index 9a09cb2f..ca6d809f 100644 --- a/cpp/platform_v2/api/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -72,6 +72,5 @@ cc_library( ":types", "//platform_v2/base", "//absl/strings", - "//absl/types:any", ], ) diff --git a/cpp/platform_v2/api/condition_variable.h b/cpp/platform_v2/api/condition_variable.h index 72c113b2..a11b74dc 100644 --- a/cpp/platform_v2/api/condition_variable.h +++ b/cpp/platform_v2/api/condition_variable.h @@ -27,7 +27,6 @@ class ConditionVariable { // 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; }; diff --git a/cpp/platform_v2/api/executor.h b/cpp/platform_v2/api/executor.h index 1b390124..a4a26990 100644 --- a/cpp/platform_v2/api/executor.h +++ b/cpp/platform_v2/api/executor.h @@ -7,6 +7,8 @@ namespace location { namespace nearby { namespace api { +int GetCurrentTid(); + // This abstract class is the superclass of all classes representing an // Executor. class Executor { @@ -19,6 +21,8 @@ class Executor { // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- virtual void Shutdown() = 0; + + virtual int GetTid(int index) const = 0; }; } // namespace api diff --git a/cpp/platform_v2/base/logging.h b/cpp/platform_v2/base/logging.h index ced174e9..3bbf276c 100644 --- a/cpp/platform_v2/base/logging.h +++ b/cpp/platform_v2/base/logging.h @@ -29,7 +29,12 @@ class LogMessageVoidify { location::nearby::api::LogMessage::Severity::kError #define NEARBY_SEVERITY_FATAL \ location::nearby::api::LogMessage::Severity::kFatal - +#if defined(_WIN32) +// wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets substituted +// with 0, and it expands to NEARBY_SEVERITY_0. To allow us to keep using this +// syntax, we define this macro to do the same thing as NEARBY_SEVERITY_ERROR. +#define NEARBY_SEVERITY_0 location::nearby::api::LogMessage::Severity::kError +#endif // defined(_WIN32) #define NEARBY_SEVERITY(severity) NEARBY_SEVERITY_##severity // Log enabling diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index df4ca186..9055a604 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -31,7 +31,6 @@ cc_library( "//absl/base:core_headers", "//absl/synchronization", "//absl/time", - "//absl/types:any", "//thread", ], ) diff --git a/cpp/platform_v2/impl/g3/condition_variable.h b/cpp/platform_v2/impl/g3/condition_variable.h index 82591e97..4fc85689 100644 --- a/cpp/platform_v2/impl/g3/condition_variable.h +++ b/cpp/platform_v2/impl/g3/condition_variable.h @@ -20,9 +20,8 @@ class ConditionVariable : public api::ConditionVariable { return {Exception::kSuccess}; } Exception Wait(absl::Duration timeout) override { - return cond_var_.WaitWithTimeout(mutex_, timeout) - ? Exception{Exception::kTimeout} - : Exception{Exception::kSuccess}; + cond_var_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; } void Notify() override { cond_var_.SignalAll(); } diff --git a/cpp/platform_v2/impl/g3/multi_thread_executor.h b/cpp/platform_v2/impl/g3/multi_thread_executor.h index c8a32233..c2672db1 100644 --- a/cpp/platform_v2/impl/g3/multi_thread_executor.h +++ b/cpp/platform_v2/impl/g3/multi_thread_executor.h @@ -33,6 +33,11 @@ class MultiThreadExecutor : public api::SubmittableExecutor { void Shutdown() override { DoShutdown(); } ~MultiThreadExecutor() override { DoShutdown(); } + int GetTid(int index) const override { + const auto* thread = thread_pool_.thread(index); + return thread ? thread->tid() : 0; + } + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { if (shutdown_) return; thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index 2996b572..cf5c20f9 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -47,6 +47,11 @@ std::string GetPayloadPath(PayloadId payload_id) { } } // namespace +int GetCurrentTid() { + const LiveThread* my = Thread_GetMyLiveThread(); + return LiveThread_Pthread_TID(my); +} + std::unique_ptr ImplementationPlatform::CreateSingleThreadExecutor() { return absl::make_unique(); diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.h b/cpp/platform_v2/impl/g3/scheduled_executor.h index 6c65b009..9ffea951 100644 --- a/cpp/platform_v2/impl/g3/scheduled_executor.h +++ b/cpp/platform_v2/impl/g3/scheduled_executor.h @@ -31,6 +31,9 @@ class ScheduledExecutor final : public api::ScheduledExecutor { absl::Duration delay) override; void Shutdown() override { executor_.Shutdown(); } + int GetTid(int index) const override { + return executor_.GetTid(index); + } private: SingleThreadExecutor executor_; }; diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD index 83d54e78..9787bf31 100644 --- a/cpp/platform_v2/impl/shared/BUILD +++ b/cpp/platform_v2/impl/shared/BUILD @@ -51,5 +51,6 @@ cc_test( "//file/util:temp_path", "//platform_v2/base", "//testing/base/public:gunit_main", + "//absl/strings", ], ) diff --git a/cpp/platform_v2/impl/shared/file_test.cc b/cpp/platform_v2/impl/shared/file_test.cc index 69f7975e..e97b5ad0 100644 --- a/cpp/platform_v2/impl/shared/file_test.cc +++ b/cpp/platform_v2/impl/shared/file_test.cc @@ -8,6 +8,7 @@ #include "file/util/temp_path.h" #include "platform_v2/base/byte_array.h" #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -22,7 +23,7 @@ class FileTest : public ::testing::Test { file_ = std::fstream(path_, std::fstream::in | std::fstream::out); } - void WriteToFile(const std::string& text) { + void WriteToFile(absl::string_view text) { file_ << text; file_.flush(); size_ += text.size(); diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 6abe18d2..66925ef6 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -39,7 +39,6 @@ cc_library( "//absl/base:core_headers", "//absl/container:flat_hash_map", "//absl/time", - "//absl/types:any", ], ) diff --git a/cpp/platform_v2/public/condition_variable_test.cc b/cpp/platform_v2/public/condition_variable_test.cc index 6a3dd610..6e0f7c2b 100644 --- a/cpp/platform_v2/public/condition_variable_test.cc +++ b/cpp/platform_v2/public/condition_variable_test.cc @@ -3,6 +3,7 @@ #include "platform_v2/public/logging.h" #include "platform_v2/public/mutex.h" #include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/time/time.h" @@ -54,7 +55,12 @@ TEST(ConditionVariableTest, WaitTerminatesOnTimeoutWithoutNotify) { Mutex mutex; ConditionVariable cond{&mutex}; MutexLock lock(&mutex); - EXPECT_EQ(cond.Wait(absl::Milliseconds(100)), Exception{Exception::kTimeout}); + + const absl::Duration kWaitTime = absl::Milliseconds(100); + absl::Time start = SystemClock::ElapsedRealtime(); + cond.Wait(kWaitTime); + absl::Duration duration = SystemClock::ElapsedRealtime() - start; + EXPECT_GE(duration, kWaitTime); } } // namespace diff --git a/cpp/platform_v2/public/scheduled_executor.h b/cpp/platform_v2/public/scheduled_executor.h index 3048f16e..ca90e32d 100644 --- a/cpp/platform_v2/public/scheduled_executor.h +++ b/cpp/platform_v2/public/scheduled_executor.h @@ -50,6 +50,12 @@ class ScheduledExecutor final { DoShutdown(); } + int GetTid(int index) const { + MutexLock lock(&mutex_); + return impl_->GetTid(index); + } + int Tid() const { return GetTid(0); } + Cancelable Schedule(Runnable&& runnable, absl::Duration duration) ABSL_LOCKS_EXCLUDED(mutex_) { MutexLock lock(&mutex_); @@ -65,7 +71,7 @@ class ScheduledExecutor final { } } - Mutex mutex_; + mutable Mutex mutex_; std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; }; diff --git a/cpp/platform_v2/public/settable_future.h b/cpp/platform_v2/public/settable_future.h index bf0d459c..7649df07 100644 --- a/cpp/platform_v2/public/settable_future.h +++ b/cpp/platform_v2/public/settable_future.h @@ -57,8 +57,8 @@ class SettableFuture : public api::SettableFuture { MutexLock lock(&mutex_); while (!done_) { absl::Time start_time = SystemClock::ElapsedRealtime(); - if (completed_.Wait(timeout).Raised(Exception::kTimeout)) { - SetExceptionLocked({Exception::kTimeout}); + if (completed_.Wait(timeout).Raised(Exception::kInterrupted)) { + SetExceptionLocked({Exception::kInterrupted}); break; } absl::Duration spent = SystemClock::ElapsedRealtime() - start_time; diff --git a/cpp/platform_v2/public/single_thread_executor.h b/cpp/platform_v2/public/single_thread_executor.h index d9f4e0f9..369af90c 100644 --- a/cpp/platform_v2/public/single_thread_executor.h +++ b/cpp/platform_v2/public/single_thread_executor.h @@ -18,6 +18,7 @@ class SingleThreadExecutor final : public SubmittableExecutor { ~SingleThreadExecutor() override = default; SingleThreadExecutor(SingleThreadExecutor&&) = default; SingleThreadExecutor& operator=(SingleThreadExecutor&&) = default; + int Tid() const { return GetTid(0); } }; } // namespace nearby diff --git a/cpp/platform_v2/public/submittable_executor.h b/cpp/platform_v2/public/submittable_executor.h index 04a0c085..d398dd69 100644 --- a/cpp/platform_v2/public/submittable_executor.h +++ b/cpp/platform_v2/public/submittable_executor.h @@ -17,6 +17,8 @@ namespace location { namespace nearby { +inline int GetCurrentTid() { return api::GetCurrentTid(); } + // Main interface to be used by platform as a base class for // - MultiThreadExecutor // - SingleThreadExecutor @@ -41,6 +43,11 @@ class SubmittableExecutor : public api::SubmittableExecutor { if (impl_) impl_->Execute(std::move(runnable)); } + int GetTid(int index) const ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + return impl_ ? impl_->GetTid(index) : 0; + } + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) override { MutexLock lock(&mutex_); DoShutdown(); @@ -86,7 +93,7 @@ class SubmittableExecutor : public api::SubmittableExecutor { ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) override { return impl_ ? impl_->DoSubmit(std::move(wrapped_callable)) : false; } - Mutex mutex_; + mutable Mutex mutex_; std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; }; From 999cdd99034676cc7f02799d5dc2f16e8d4b9712 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 7 Jul 2020 12:34:48 -0700 Subject: [PATCH 36/52] Roll forward to cl/320013226 Signed-off-by: Alexey Polyudov Change-Id: I8be5378519ca952da43c40fba96141a7f8517748 --- cpp/core_v2/internal/mediums/webrtc.cc | 92 ++++-- cpp/core_v2/internal/mediums/webrtc.h | 15 +- cpp/core_v2/internal/mediums/webrtc_test.cc | 129 +++++++- cpp/core_v2/internal/mediums/wifi_lan.cc | 33 +- cpp/core_v2/internal/mediums/wifi_lan.h | 6 +- cpp/core_v2/internal/mediums/wifi_lan_test.cc | 103 ++++++- .../internal/offline_service_controller.cc | 14 - .../internal/offline_service_controller.h | 14 - .../offline_service_controller_test.cc | 14 - .../internal/offline_simulation_user.cc | 14 - .../internal/offline_simulation_user.h | 14 - .../internal/p2p_cluster_pcp_handler.cc | 2 +- cpp/platform_v2/base/medium_environment.cc | 116 +++++-- cpp/platform_v2/base/medium_environment.h | 41 ++- cpp/platform_v2/impl/g3/wifi_lan.cc | 283 ++++++++++++++++-- cpp/platform_v2/impl/g3/wifi_lan.h | 133 +++++++- cpp/platform_v2/public/BUILD | 1 + cpp/platform_v2/public/cancelable.h | 4 +- cpp/platform_v2/public/cancelable_alarm.h | 5 + .../public/cancelable_alarm_test.cc | 54 ++++ cpp/platform_v2/public/wifi_lan.cc | 6 +- cpp/platform_v2/public/wifi_lan.h | 4 +- cpp/platform_v2/public/wifi_lan_test.cc | 124 ++++++-- proto/error_code_enums.proto | 32 +- 24 files changed, 1026 insertions(+), 227 deletions(-) create mode 100644 cpp/platform_v2/public/cancelable_alarm_test.cc diff --git a/cpp/core_v2/internal/mediums/webrtc.cc b/cpp/core_v2/internal/mediums/webrtc.cc index 32a4ec0e..4b9510c7 100644 --- a/cpp/core_v2/internal/mediums/webrtc.cc +++ b/cpp/core_v2/internal/mediums/webrtc.cc @@ -7,11 +7,13 @@ #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/cancelable_alarm.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 "absl/time/time.h" #include "webrtc/api/jsep.h" namespace location { @@ -22,19 +24,22 @@ 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); +// Delay between restarting signaling messenger to receive messages. +constexpr absl::Duration kRestartReceiveMessagesDuration = absl::Seconds(60); + } // namespace WebRtc::WebRtc() = default; WebRtc::~WebRtc() { + // This ensures that all pending callbacks are run before we reset the medium + // and we are not accepting new runnables. + restart_receive_messages_executor_.Shutdown(); single_thread_executor_.Shutdown(); - { - MutexLock lock(&mutex_); - Disconnect(); - } + + Disconnect(); } bool WebRtc::IsAvailable() { return medium_.IsValid(); } @@ -70,6 +75,11 @@ bool WebRtc::StartAcceptingConnections(const PeerId& self_id, if (!InitWebRtcFlow(Role::kOfferer, self_id)) return false; + restart_receive_messages_alarm_ = CancelableAlarm( + "restart_receiving_messages_webrtc", + std::bind(&WebRtc::RestartReceiveMessages, this), + kRestartReceiveMessagesDuration, &restart_receive_messages_executor_); + SessionDescriptionWrapper offer = connection_flow_->CreateOffer(); pending_local_offer_ = webrtc_frames::EncodeOffer(self_id, offer.GetSdp()); if (!SetLocalSessionDescription(std::move(offer))) { @@ -89,23 +99,25 @@ bool WebRtc::StartAcceptingConnections(const PeerId& self_id, } 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(); - } + { + MutexLock lock(&mutex_); + 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(); + peer_id_ = peer_id; + if (!InitWebRtcFlow(Role::kAnswerer, PeerId::FromRandom())) { + return WebRtcSocketWrapper(); + } } NEARBY_LOG(INFO, "Attempting to make a WebRTC connection to %s.", @@ -116,6 +128,9 @@ WebRtcSocketWrapper WebRtc::Connect(const PeerId& peer_id) { // The two devices have discovered each other, hence we have a timeout for // establishing the transport channel. + // NOTE - We should not hold |mutex_| while waiting for the data channel since + // it would block incoming signaling messages from being processed, resulting + // in a timeout in creating the socket. ExceptionOr result = socket_future.Get(kDataChannelTimeout); if (result.ok()) return result.result(); @@ -186,7 +201,8 @@ WebRtcSocketWrapper WebRtc::CreateWebRtcSocketWrapper( } auto socket = std::make_unique("WebRtcSocket", data_channel); - socket->SetOnSocketClosedListener({std::bind(&WebRtc::Disconnect, this)}); + socket->SetOnSocketClosedListener( + {[this]() { OffloadFromSignalingThread([this]() { Disconnect(); }); }}); return WebRtcSocketWrapper(std::move(socket)); } @@ -217,7 +233,7 @@ bool WebRtc::InitWebRtcFlow(Role role, const PeerId& self_id) { if (!signaling_messenger_->IsValid() || !signaling_messenger_->StartReceivingMessages( signaling_message_callback)) { - Disconnect(); + DisconnectLocked(); return false; } @@ -393,7 +409,7 @@ void WebRtc::SendAnswerToPeer() { void WebRtc::LogAndDisconnect(const std::string& error_message) { NEARBY_LOG(WARNING, "Disconnecting WebRTC : %s", error_message.c_str()); - Disconnect(); + DisconnectLocked(); } void WebRtc::LogAndShutdownSignaling(const std::string& error_message) { @@ -408,6 +424,11 @@ void WebRtc::ShutdownSignaling() { pending_local_offer_ = ByteArray(); pending_local_ice_candidates_.clear(); + if (restart_receive_messages_alarm_.IsValid()) { + restart_receive_messages_alarm_.Cancel(); + restart_receive_messages_alarm_ = CancelableAlarm(); + } + if (signaling_messenger_) { signaling_messenger_->StopReceivingMessages(); signaling_messenger_.reset(); @@ -417,6 +438,11 @@ void WebRtc::ShutdownSignaling() { } void WebRtc::Disconnect() { + MutexLock lock(&mutex_); + DisconnectLocked(); +} + +void WebRtc::DisconnectLocked() { ShutdownSignaling(); ShutdownWebRtcSocket(); ShutdownIceCandidateCollection(); @@ -440,6 +466,34 @@ void WebRtc::OffloadFromSignalingThread(Runnable runnable) { single_thread_executor_.Execute(std::move(runnable)); } +void WebRtc::RestartReceiveMessages() { + if (!IsAcceptingConnections()) { + NEARBY_LOG(INFO, + "Skipping restart since we are not accepting connections."); + return; + } + + NEARBY_LOG(INFO, "Restarting listening for receiving signaling messages."); + { + MutexLock lock(&mutex_); + signaling_messenger_->StopReceivingMessages(); + + 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)) { + DisconnectLocked(); + } + } +} + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h index 27612269..1322b5bb 100644 --- a/cpp/core_v2/internal/mediums/webrtc.h +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -10,9 +10,12 @@ #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/public/cancelable_alarm.h" +#include "platform_v2/public/scheduled_executor.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/base/listeners.h" #include "platform_v2/base/runnable.h" +#include "platform_v2/public/atomic_boolean.h" #include "platform_v2/public/future.h" #include "platform_v2/public/mutex.h" #include "platform_v2/public/single_thread_executor.h" @@ -112,8 +115,11 @@ class WebRtc { void LogAndDisconnect(const std::string& error_message) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Runs on @MainThread. + void Disconnect() ABSL_LOCKS_EXCLUDED(mutex_); + // Runs on @MainThread and |single_thread_executor_|. - void Disconnect() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + void DisconnectLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); void LogAndShutdownSignaling(const std::string& error_message) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -129,6 +135,9 @@ class WebRtc { void OffloadFromSignalingThread(Runnable runnable); + // Runs on |restart_receive_messages_executor_|. + void RestartReceiveMessages() ABSL_LOCKS_EXCLUDED(mutex_); + Mutex mutex_; Role role_ ABSL_GUARDED_BY(mutex_) = Role::kNone; @@ -145,6 +154,10 @@ class WebRtc { WebRtcSocketWrapper socket_ ABSL_GUARDED_BY(mutex_); SingleThreadExecutor single_thread_executor_; + + // Restarts the signaling messenger for receiving messages. + ScheduledExecutor restart_receive_messages_executor_; + CancelableAlarm restart_receive_messages_alarm_; }; } // namespace mediums diff --git a/cpp/core_v2/internal/mediums/webrtc_test.cc b/cpp/core_v2/internal/mediums/webrtc_test.cc index 140571f4..9b4f8399 100644 --- a/cpp/core_v2/internal/mediums/webrtc_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc_test.cc @@ -89,28 +89,139 @@ TEST(WebRtcTest, StartAndStopAcceptingConnections) { 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. + WebRtc receiver, sender, device_c; + WebRtcSocketWrapper receiver_socket, sender_socket; + const PeerId self_id("self_id"), other_id("other_id"); + Future connected; + ByteArray message("message xyz"); + + receiver.StartAcceptingConnections( + self_id, + {[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable { + receiver_socket = wrapper; + connected.Set(receiver_socket.IsValid()); + }}); + + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + device_c.StartAcceptingConnections(other_id, + {mock_accepted_callback_.AsStdFunction()}); + + sender_socket = sender.Connect(self_id); + EXPECT_TRUE(sender_socket.IsValid()); + + ExceptionOr devices_connected = connected.Get(); + ASSERT_TRUE(devices_connected.ok()); + EXPECT_TRUE(devices_connected.result()); + + WebRtcSocketWrapper socket = sender.Connect(other_id); + EXPECT_FALSE(socket.IsValid()); + + EXPECT_TRUE(receiver_socket.IsValid()); + EXPECT_TRUE(sender_socket.IsValid()); + + sender_socket.GetOutputStream().Write(message); + ExceptionOr received_msg = + receiver_socket.GetInputStream().Read(/*size=*/32); + ASSERT_TRUE(received_msg.ok()); + EXPECT_EQ(message, received_msg.result()); + + receiver_socket.Close(); } // 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. + WebRtc receiver, sender; + WebRtcSocketWrapper receiver_socket, sender_socket; + const PeerId self_id("self_id"); + Future connected; + ByteArray message("message xyz"); + + receiver.StartAcceptingConnections( + self_id, + {[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable { + receiver_socket = wrapper; + connected.Set(receiver_socket.IsValid()); + }}); + + sender_socket = sender.Connect(self_id); + EXPECT_TRUE(sender_socket.IsValid()); + + ExceptionOr devices_connected = connected.Get(); + ASSERT_TRUE(devices_connected.ok()); + EXPECT_TRUE(devices_connected.result()); + + receiver_socket.Close(); } // 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. + WebRtc receiver, sender; + WebRtcSocketWrapper receiver_socket, sender_socket; + const PeerId self_id("self_id"); + Future connected; + ByteArray message("message"); + + receiver.StartAcceptingConnections( + self_id, + {[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable { + receiver_socket = wrapper; + connected.Set(receiver_socket.IsValid()); + }}); + + sender_socket = sender.Connect(self_id); + EXPECT_TRUE(sender_socket.IsValid()); + + ExceptionOr devices_connected = connected.Get(); + ASSERT_TRUE(devices_connected.ok()); + EXPECT_TRUE(devices_connected.result()); + + sender_socket.GetOutputStream().Write(message); + ExceptionOr received_msg = + receiver_socket.GetInputStream().Read(/*size=*/32); + ASSERT_TRUE(received_msg.ok()); + EXPECT_EQ(message, received_msg.result()); + + receiver_socket.Close(); +} + +// Tests the flow when the two devices exchange SDP messages and connect to each +// other but the signaling channel is closed before sending the data. +TEST(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) { + WebRtc receiver, sender; + WebRtcSocketWrapper receiver_socket, sender_socket; + const PeerId self_id("self_id"); + Future connected; + ByteArray message("message xyz"); + + receiver.StartAcceptingConnections( + self_id, + {[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable { + receiver_socket = wrapper; + connected.Set(receiver_socket.IsValid()); + }}); + + sender_socket = sender.Connect(self_id); + EXPECT_TRUE(sender_socket.IsValid()); + + ExceptionOr devices_connected = connected.Get(); + ASSERT_TRUE(devices_connected.ok()); + EXPECT_TRUE(devices_connected.result()); + + // Only shuts down signaling channel. + receiver.StopAcceptingConnections(); + + sender_socket.GetOutputStream().Write(message); + ExceptionOr received_msg = + receiver_socket.GetInputStream().Read(/*size=*/32); + ASSERT_TRUE(received_msg.ok()); + EXPECT_EQ(message, received_msg.result()); } } // namespace diff --git a/cpp/core_v2/internal/mediums/wifi_lan.cc b/cpp/core_v2/internal/mediums/wifi_lan.cc index 894c4b9c..1983137f 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan.cc @@ -43,24 +43,28 @@ bool WifiLan::StartAdvertising(const std::string& service_id, return false; } - NEARBY_LOG(INFO, "Turned on WifiLan advertising with service info name=%s", - wifi_lan_service_info_name.c_str()); + NEARBY_LOGS(INFO) << "Turned on WifiLan advertising with service info name=" + << wifi_lan_service_info_name + << ", service id=" << service_id; advertising_info_.service_id = service_id; return true; } -void WifiLan::StopAdvertising(const std::string& service_id) { +bool 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; + return false; } - medium_.StopAdvertising(advertising_info_.service_id); + NEARBY_LOG(INFO, "Turned off WifiLan advertising with service id=%s", + service_id.c_str()); + bool ret = medium_.StopAdvertising(advertising_info_.service_id); // Reset our bundle of advertising state to mark that we're no longer // advertising. advertising_info_.Clear(); + return ret; } bool WifiLan::IsAdvertising() { @@ -103,23 +107,28 @@ bool WifiLan::StartDiscovery(const std::string& service_id, return false; } + NEARBY_LOG(INFO, "Turned on WifiLan discovering with service id=%s", + service_id.c_str()); // 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) { +bool 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; + return false; } - medium_.StopDiscovery(service_id); + NEARBY_LOG(INFO, "Turned off WifiLan discovering with service id=%s", + service_id.c_str()); + bool ret = medium_.StopDiscovery(service_id); discovering_info_.Clear(); + return ret; } bool WifiLan::IsDiscovering(const std::string& service_id) { @@ -169,20 +178,22 @@ bool WifiLan::StartAcceptingConnections(const std::string& service_id, return true; } -void WifiLan::StopAcceptingConnections(const std::string& service_id) { +bool 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; + return false; } - medium_.StopAcceptingConnections(accepting_connections_info_.service_id); + bool ret = + 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(); + return ret; } bool WifiLan::IsAcceptingConnections(const std::string& service_id) { diff --git a/cpp/core_v2/internal/mediums/wifi_lan.h b/cpp/core_v2/internal/mediums/wifi_lan.h index 196cc2cd..16884a5d 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.h +++ b/cpp/core_v2/internal/mediums/wifi_lan.h @@ -30,7 +30,7 @@ class WifiLan { // Disables WifiLan advertising, and restores service info name to // what they were before the call to StartAdvertising(). - void StopAdvertising(const std::string& service_id) + bool StopAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); bool IsAdvertising() ABSL_LOCKS_EXCLUDED(mutex_); @@ -43,7 +43,7 @@ class WifiLan { ABSL_LOCKS_EXCLUDED(mutex_); // Disables WifiLan discovery mode. - void StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + bool StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); bool IsDiscovering(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); @@ -54,7 +54,7 @@ class WifiLan { ABSL_LOCKS_EXCLUDED(mutex_); // Closes socket corresponding to a service id. - void StopAcceptingConnections(const std::string& service_id) + bool StopAcceptingConnections(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); bool IsAcceptingConnections(const std::string& service_id) diff --git a/cpp/core_v2/internal/mediums/wifi_lan_test.cc b/cpp/core_v2/internal/mediums/wifi_lan_test.cc index 545d6c3b..24e64d02 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan_test.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan_test.cc @@ -3,6 +3,8 @@ #include #include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" #include "platform_v2/public/wifi_lan.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -12,11 +14,11 @@ namespace nearby { namespace connections { namespace { +constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); 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; @@ -30,6 +32,8 @@ TEST_F(WifiLanTest, CanConstructValidObject) { env_.Start(); WifiLan wifi_lan_a; WifiLan wifi_lan_b; + std::string service_id(kServiceID); + std::string service_name{kServiceInfoName}; EXPECT_TRUE(wifi_lan_a.IsAvailable()); EXPECT_TRUE(wifi_lan_b.IsAvailable()); @@ -38,9 +42,100 @@ TEST_F(WifiLanTest, CanConstructValidObject) { TEST_F(WifiLanTest, CanStartAdvertising) { env_.Start(); - WifiLan wifi_lan; - EXPECT_TRUE(wifi_lan.StartAdvertising(std::string(kServiceID), - std::string(kServiceInfoName))); + WifiLan wifi_lan_a; + WifiLan wifi_lan_b; + std::string service_id(kServiceID); + std::string service_name{kServiceInfoName}; + CountDownLatch found_latch(1); + + wifi_lan_b.StartDiscovery( + service_id, DiscoveredServiceCallback{ + .service_discovered_cb = + [&found_latch](WifiLanService& service, + const std::string& service_id) { + found_latch.CountDown(); + }, + }); + + EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, service_name)); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id)); + EXPECT_TRUE(wifi_lan_b.StopDiscovery(service_id)); + env_.Stop(); +} + +TEST_F(WifiLanTest, CanStartDiscovery) { + env_.Start(); + WifiLan wifi_lan_a; + WifiLan wifi_lan_b; + std::string service_id(kServiceID); + std::string service_name{kServiceInfoName}; + CountDownLatch accept_latch(1); + CountDownLatch lost_latch(1); + + wifi_lan_b.StartAdvertising(service_id, service_name); + + EXPECT_TRUE(wifi_lan_a.StartDiscovery( + service_id, { + .service_discovered_cb = + [&accept_latch](WifiLanService& service, + const std::string& service_id) { + accept_latch.CountDown(); + }, + .service_lost_cb = + [&lost_latch](WifiLanService& service, + const std::string& service_id) { + lost_latch.CountDown(); + }, + })); + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + wifi_lan_b.StopAdvertising(service_id); + EXPECT_TRUE(lost_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id)); + env_.Stop(); +} + +TEST_F(WifiLanTest, CanStartAcceptingConnectionsAndConnect) { + env_.Start(); + WifiLan wifi_lan_a; + WifiLan wifi_lan_b; + std::string service_id(kServiceID); + std::string service_name{kServiceInfoName}; + CountDownLatch found_latch(1); + CountDownLatch accept_latch(1); + + wifi_lan_a.StartAdvertising(service_id, service_name); + wifi_lan_a.StartAcceptingConnections( + service_id, + { + .accepted_cb = [&accept_latch]( + WifiLanSocket socket, + const std::string&) { accept_latch.CountDown(); }, + }); + WifiLanService discovered_service; + wifi_lan_b.StartDiscovery( + service_id, + { + .service_discovered_cb = + [&found_latch, &discovered_service]( + WifiLanService& service, const std::string& service_id) { + discovered_service = service; + NEARBY_LOG(INFO, "Discovered service=%p [impl=%p]", &service, + &service.GetImpl()); + found_latch.CountDown(); + }, + }); + + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + ASSERT_TRUE(discovered_service.IsValid()); + + WifiLanSocket socket = + wifi_lan_b.Connect(discovered_service, service_id); + + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(socket.IsValid()); + wifi_lan_b.StopDiscovery(service_id); + wifi_lan_a.StopAdvertising(service_id); env_.Stop(); } diff --git a/cpp/core_v2/internal/offline_service_controller.cc b/cpp/core_v2/internal/offline_service_controller.cc index 7465fc96..249c97b8 100644 --- a/cpp/core_v2/internal/offline_service_controller.cc +++ b/cpp/core_v2/internal/offline_service_controller.cc @@ -1,17 +1,3 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #include "core_v2/internal/offline_service_controller.h" #include diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index a4855db2..bcb6e2c7 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -1,17 +1,3 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ #define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_service_controller_test.cc b/cpp/core_v2/internal/offline_service_controller_test.cc index 2d4487ea..260dd527 100644 --- a/cpp/core_v2/internal/offline_service_controller_test.cc +++ b/cpp/core_v2/internal/offline_service_controller_test.cc @@ -1,17 +1,3 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #include "core_v2/internal/offline_service_controller.h" #include "core_v2/internal/offline_simulation_user.h" diff --git a/cpp/core_v2/internal/offline_simulation_user.cc b/cpp/core_v2/internal/offline_simulation_user.cc index 1a58f117..6ed65174 100644 --- a/cpp/core_v2/internal/offline_simulation_user.cc +++ b/cpp/core_v2/internal/offline_simulation_user.cc @@ -1,17 +1,3 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #include "core_v2/internal/offline_simulation_user.h" #include "core_v2/listeners.h" diff --git a/cpp/core_v2/internal/offline_simulation_user.h b/cpp/core_v2/internal/offline_simulation_user.h index 27a41d56..4c00d8eb 100644 --- a/cpp/core_v2/internal/offline_simulation_user.h +++ b/cpp/core_v2/internal/offline_simulation_user.h @@ -1,17 +1,3 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #ifndef CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ #define CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 41d612d7..62ab997f 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -588,7 +588,7 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( service_id.c_str()); if (!wifi_lan_medium_.StartAcceptingConnections( service_id, {.accepted_cb = [this, client, local_endpoint_name]( - WifiLanSocket& socket, + WifiLanSocket socket, const std::string& service_id) { if (!socket.IsValid()) { NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index d2905ba4..164af945 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -152,32 +152,19 @@ 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); - }); - } + NEARBY_LOG(INFO, + "G3 OnWifiLanServiceStateChanged [service impl=%p]; context=%p, " + "notify=%d", + &info, &service, enable_notifications_.load()); + if (!enable_notifications_) return; + 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); - } + RunOnMediumEnvironmentThread([&info, &service, service_id]() { + info.discovery_callback.service_lost_cb(service, service_id); + }); } } @@ -284,14 +271,44 @@ void MediumEnvironment::SendWebRtcSignalingMessage(absl::string_view peer_id, }); } -void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium) { +void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium, + api::WifiLanService& service) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium]() { - wifi_lan_mediums_.insert({&medium, WifiLanMediumContext{}}); + RunOnMediumEnvironmentThread([this, &medium, &service]() { + wifi_lan_mediums_.insert({&medium, WifiLanMediumContext{ + .service = &service, + }}); NEARBY_LOG(INFO, "Registered: medium=%p", &medium); }); } +void MediumEnvironment::UpdateWifiLanMediumForAdvertising( + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, bool enabled) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, &service, service_id, + 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.advertising = enabled; + NEARBY_LOG( + INFO, + "Update WifiLan medium for advertising: this=%p; medium=%p; name=%s; " + "enabled=%d; advertising=%d", + this, &medium, service.GetName().c_str(), enabled, context.advertising); + for (auto& [local_medium, info] : wifi_lan_mediums_) { + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + OnWifiLanServiceStateChanged(info, service, service_id, enabled); + } + }); +} + void MediumEnvironment::UpdateWifiLanMediumForDiscovery( api::WifiLanMedium& medium, api::WifiLanService& service, const std::string& service_id, WifiLanDiscoveredServiceCallback callback, @@ -307,16 +324,29 @@ void MediumEnvironment::UpdateWifiLanMediumForDiscovery( } 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); + NEARBY_LOG( + INFO, + "Update WifiLan medium for discovery: this=%p; medium=%p; name=%s; " + "enabled=%d; advertising=%d", + this, &medium, service.GetName().c_str(), enabled, context.advertising); + for (auto& [local_medium, info] : wifi_lan_mediums_) { + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + // Search advertising mediums and send notification. + if (info.advertising && enabled) { + OnWifiLanServiceStateChanged(context, *(info.service), service_id, + enabled); + } + } }); } void MediumEnvironment::UpdateWifiLanMediumForAcceptedConnection( - api::WifiLanMedium& medium, const std::string& service_id, + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, WifiLanAcceptedConnectionCallback accepted_connection_callback) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, + RunOnMediumEnvironmentThread([this, &medium, &service, service_id, accepted_connection_callback = std::move(accepted_connection_callback)]() { auto item = wifi_lan_mediums_.find(&medium); @@ -328,7 +358,10 @@ void MediumEnvironment::UpdateWifiLanMediumForAcceptedConnection( auto& context = item->second; context.accepted_connection_callback = std::move(accepted_connection_callback); - NEARBY_LOG(INFO, "Updated: this=%p; medium=%p", this, &medium); + NEARBY_LOG(INFO, + "Update WifiLan medium for accepted callback: this=%p; " + "medium=%p; name=%s; ", + this, &medium, service.GetName().c_str()); }); } @@ -341,5 +374,22 @@ void MediumEnvironment::UnregisterWifiLanMedium(api::WifiLanMedium& medium) { }); } +void MediumEnvironment::CallWifiLanAcceptedConnectionCallback( + api::WifiLanMedium& medium, api::WifiLanSocket& socket, + const std::string& service_id) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, &socket, service_id]() { + auto item = wifi_lan_mediums_.find(&medium); + if (item == wifi_lan_mediums_.end()) { + NEARBY_LOG(INFO, + "Call AcceptedConnectionCallback failed.. There is no medium " + "registered."); + return; + } + auto& info = item->second; + info.accepted_connection_callback.accepted_cb(socket, service_id); + }); +} + } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index b34f8cf5..31fab859 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -104,17 +104,48 @@ class MediumEnvironment { // |peer_id|. void SendWebRtcSignalingMessage(absl::string_view peer_id, const ByteArray& message); - // Wifi-Lan medium registration/update calls. - void RegisterWifiLanMedium(api::WifiLanMedium& medium); + // Adds medium-related info to allow for discovery/advertising to work. + // This provides acccess to this medium from other mediums, when protocol + // expects they should communicate. + void RegisterWifiLanMedium(api::WifiLanMedium& medium, + api::WifiLanService& service); + + // Updates advertising info to indicate the current medium is exposing + // advertising event. + void UpdateWifiLanMediumForAdvertising( + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, bool enabled); + + // Updates discovery callback info to allow for dispatch of discovery events. + // + // Invokes callback asynchronously when any changes happen to discoverable + // devices, or if the defice is turned off, whether or not it is discoverable, + // if it was ever reported as discoverable. + // + // This should be called when discoverable state changes. + // with user-specified callback when discovery is enabled, and with default + // (empty) callback otherwise. void UpdateWifiLanMediumForDiscovery( api::WifiLanMedium& medium, api::WifiLanService& service, const std::string& service_id, WifiLanDiscoveredServiceCallback discovery_callback, bool enabled); + + // Updates Accepted connection callback info to allow for dispatch of + // advertising events. void UpdateWifiLanMediumForAcceptedConnection( - api::WifiLanMedium& medium, const std::string& service_id, + api::WifiLanMedium& medium, api::WifiLanService& service, + const std::string& service_id, WifiLanAcceptedConnectionCallback accepted_connection_callback); + + // Removes medium-related info. This should correspond to device power off. void UnregisterWifiLanMedium(api::WifiLanMedium& medium); + // Call back when advertising has created the server socket and is ready for + // connect. + void CallWifiLanAcceptedConnectionCallback(api::WifiLanMedium& medium, + api::WifiLanSocket& socket, + const std::string& service_id); + private: struct BluetoothMediumContext { BluetoothDiscoveryCallback callback; @@ -126,8 +157,8 @@ class MediumEnvironment { struct WifiLanMediumContext { WifiLanDiscoveredServiceCallback discovery_callback; WifiLanAcceptedConnectionCallback accepted_connection_callback; - // discovered service vs service name map. - absl::flat_hash_map services; + api::WifiLanService* service = nullptr; + bool advertising = false; }; // This is a singleton object, for which destructor will never be called. diff --git a/cpp/platform_v2/impl/g3/wifi_lan.cc b/cpp/platform_v2/impl/g3/wifi_lan.cc index 2088c8e0..1b68f30c 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.cc +++ b/cpp/platform_v2/impl/g3/wifi_lan.cc @@ -1,5 +1,6 @@ #include "platform_v2/impl/g3/wifi_lan.h" +#include #include #include @@ -12,20 +13,45 @@ namespace location { namespace nearby { namespace g3 { -InputStream& WifiLanSocket::GetInputStream() { +WifiLanSocket::~WifiLanSocket() { absl::MutexLock lock(&mutex_); - return pipe_.GetInputStream(); + DoClose(); +} + +void WifiLanSocket::Connect(WifiLanSocket& other) { + absl::MutexLock lock(&mutex_); + remote_socket_ = &other; + input_ = other.output_; +} + +InputStream& WifiLanSocket::GetInputStream() { + auto* remote_socket = GetRemoteSocket(); + CHECK(remote_socket != nullptr); + return remote_socket->GetLocalInputStream(); } OutputStream& WifiLanSocket::GetOutputStream() { + return GetLocalOutputStream(); +} + +WifiLanSocket* WifiLanSocket::GetRemoteSocket() { absl::MutexLock lock(&mutex_); - return pipe_.GetOutputStream(); + return remote_socket_; +} + +bool WifiLanSocket::IsConnected() const { + absl::MutexLock lock(&mutex_); + return IsConnectedLocked(); +} + +bool WifiLanSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; } Exception WifiLanSocket::Close() { absl::MutexLock lock(&mutex_); - pipe_.GetOutputStream().Close(); - pipe_.GetInputStream().Close(); + DoClose(); return {Exception::kSuccess}; } @@ -34,45 +60,215 @@ WifiLanService* WifiLanSocket::GetRemoteWifiLanService() { return service_; } +void WifiLanSocket::DoClose() { + if (!closed_) { + remote_socket_ = nullptr; + output_->GetOutputStream().Close(); + output_->GetInputStream().Close(); + if (IsConnectedLocked()) { + input_->GetOutputStream().Close(); + input_->GetInputStream().Close(); + } + closed_ = true; + } +} + +bool WifiLanSocket::IsConnectedLocked() const { return input_ != nullptr; } + +InputStream& WifiLanSocket::GetLocalInputStream() { + absl::MutexLock lock(&mutex_); + return output_->GetInputStream(); +} + +OutputStream& WifiLanSocket::GetLocalOutputStream() { + absl::MutexLock lock(&mutex_); + return output_->GetOutputStream(); +} + +std::unique_ptr WifiLanServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + if (closed_) return {}; + while (pending_sockets_.empty()) { + cond_.Wait(&mutex_); + if (closed_) break; + } + if (closed_) return {}; + auto* remote_socket = + pending_sockets_.extract(pending_sockets_.begin()).value(); + CHECK(remote_socket); + auto local_socket = std::make_unique(); + local_socket->Connect(*remote_socket); + remote_socket->Connect(*local_socket); + cond_.SignalAll(); + return local_socket; +} + +bool WifiLanServerSocket::Connect(WifiLanSocket& socket) { + absl::MutexLock lock(&mutex_); + if (closed_) return false; + if (socket.IsConnected()) { + NEARBY_LOG(ERROR, + "Failed to connect to WifiLan server socket: already connected"); + return true; // already connected. + } + // add client socket to the pending list + pending_sockets_.emplace(&socket); + cond_.SignalAll(); + while (!socket.IsConnected()) { + cond_.Wait(&mutex_); + if (closed_) return false; + } + return true; +} + +void WifiLanServerSocket::SetCloseNotifier(std::function notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +WifiLanServerSocket::~WifiLanServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +Exception WifiLanServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception WifiLanServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + WifiLanMedium::WifiLanMedium() { + service_.SetMedium(this); auto& env = MediumEnvironment::Instance(); - env.RegisterWifiLanMedium(*this); + env.RegisterWifiLanMedium(*this, service_); } WifiLanMedium::~WifiLanMedium() { + service_.SetMedium(nullptr); auto& env = MediumEnvironment::Instance(); env.UnregisterWifiLanMedium(*this); + + StopAdvertising(advertising_info_.service_id); + StopDiscovery(discovering_info_.service_id); + + accept_loops_runner_.Shutdown(); + NEARBY_LOG(INFO, + "WifiLanMedium dtor advertising_accept_thread_running_ = %d", + acceptance_thread_running_.load()); + // If acceptance thread is still running, wait to finish. + if (acceptance_thread_running_) { + while (acceptance_thread_running_) { + CountDownLatch latch(1); + close_accept_loops_runner_.Execute([&latch]() { latch.CountDown(); }); + latch.Await(); + } + } } 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); + NEARBY_LOG(INFO, + "G3 WifiLan StartAdvertising: service_id=%s, service_name=%s", + service_id.c_str(), wifi_lan_service_info_name.c_str()); + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, true); + + absl::MutexLock lock(&mutex_); + if (server_socket_ != nullptr) server_socket_.release(); + server_socket_ = std::make_unique(); + + acceptance_thread_running_.exchange(true); + accept_loops_runner_.Execute([&env, this, service_id]() mutable { + if (!accept_loops_runner_.InShutdown()) { + while (true) { + auto client_socket = server_socket_->Accept(); + if (client_socket == nullptr) break; + env.CallWifiLanAcceptedConnectionCallback(*this, *client_socket, + service_id); + } + } + acceptance_thread_running_.exchange(false); + }); + advertising_info_.service_id = service_id; return true; } bool WifiLanMedium::StopAdvertising(const std::string& service_id) { - // TODO(edwinwu): Integrate medium_environment. + NEARBY_LOG(INFO, "G3 WifiLan StopAdvertising: service_id=%s", + service_id.c_str()); + { + absl::MutexLock lock(&mutex_); + if (advertising_info_.Empty()) { + NEARBY_LOG( + INFO, "Can't stop advertising because we never started advertising."); + return false; + } + advertising_info_.Clear(); + } + + auto& env = MediumEnvironment::Instance(); + env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, false); + accept_loops_runner_.Shutdown(); + if (server_socket_ == nullptr) { + NEARBY_LOG(ERROR, "Failed to find WifiLan Server socket: service_id=%s", + service_id.c_str()); + // Fall through for server socket not found. + return true; + } + + if (!server_socket_->Close().Ok()) { + NEARBY_LOG(INFO, "Failed to close WifiLan server socket for %s.", + service_id.c_str()); + return false; + } + return true; } bool WifiLanMedium::StartDiscovery(const std::string& service_id, DiscoveredServiceCallback callback) { + NEARBY_LOG(INFO, "G3 WifiLan StartDiscovery: service_id=%s", + service_id.c_str()); 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); + { + absl::MutexLock lock(&mutex_); + discovering_info_.service_id = service_id; + } return true; } bool WifiLanMedium::StopDiscovery(const std::string& service_id) { + NEARBY_LOG(INFO, "G3 WifiLan StopDiscovery: service_id=%s", + service_id.c_str()); + { + absl::MutexLock lock(&mutex_); + if (discovering_info_.Empty()) { + NEARBY_LOG( + INFO, "Can't stop discovering because we never started discovering."); + return false; + } + discovering_info_.Clear(); + } + auto& env = MediumEnvironment::Instance(); env.UpdateWifiLanMediumForDiscovery(*this, service_, service_id, {}, false); return true; @@ -80,33 +276,58 @@ bool WifiLanMedium::StopDiscovery(const std::string& service_id) { bool WifiLanMedium::StartAcceptingConnections( const std::string& service_id, AcceptedConnectionCallback callback) { - // TODO(edwinwu): Integrate medium_environment. - // steps: + NEARBY_LOG(INFO, "G3 WifiLan StartAcceptingConnections: service_id=%s", + service_id.c_str()); auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, callback); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_, service_id, + callback); return true; } bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { - // TODO(edwinwu): Integrate medium_environment. + NEARBY_LOG(INFO, "G3 WifiLan StopAcceptingConnections: service_id=%s", + service_id.c_str()); auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, {}); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_, service_id, {}); return true; } std::unique_ptr WifiLanMedium::Connect( - api::WifiLanService& service, const std::string& service_id) { + api::WifiLanService& remote_service, const std::string& service_id) { + NEARBY_LOG(INFO, "G3 WifiLan Connect: medium=%p, service=%p, service_id=%s", + this, &service_, service_id.c_str()); + // First, find an instance of remote medium, that exposed this service. + auto* medium = static_cast(remote_service).GetMedium(); + + if (!medium) return {}; // Can't find medium. Bail out. + + WifiLanServerSocket* server_socket = nullptr; + NEARBY_LOG(INFO, + "G3 WifiLan Connect [peer]: medium=%p, service=%p, service_id=%s", + medium, &remote_service, service_id.c_str()); + // Then, find our server socket context in this medium. + { + absl::MutexLock medium_lock(&medium->mutex_); + server_socket = medium->server_socket_.get(); + if (server_socket == nullptr) { + NEARBY_LOG(ERROR, "Failed to find WifiLan Server socket: service_id=%s", + service_id.c_str()); + return {}; + } + } + auto socket = std::make_unique(); - NEARBY_LOG(INFO, "G3 Connect: medium=%p, service_id=%s", this, - service_id.c_str()); + // Finally, Request to connect to this socket. + if (!server_socket->Connect(*socket)) { + NEARBY_LOG( + ERROR, + "Failed to connect to existing WifiLan Server socket: service_id=%s", + service_id.c_str()); + return {}; + } + + NEARBY_LOG(INFO, "G3 WifiLan Connect: connected: socket=%p", socket.get()); 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 diff --git a/cpp/platform_v2/impl/g3/wifi_lan.h b/cpp/platform_v2/impl/g3/wifi_lan.h index c8995c02..45bdfbfd 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.h +++ b/cpp/platform_v2/impl/g3/wifi_lan.h @@ -1,20 +1,25 @@ #ifndef PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ #define PLATFORM_V2_IMPL_G3_WIFI_LAN_H_ +#include #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/multi_thread_executor.h" #include "platform_v2/impl/g3/pipe.h" #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/synchronization/mutex.h" namespace location { namespace nearby { namespace g3 { +class WifiLanMedium; + // Opaque wrapper over a WifiLan service which contains encoded WifiLan service // info name. class WifiLanService : public api::WifiLanService { @@ -25,19 +30,23 @@ class WifiLanService : public api::WifiLanService { void SetName(std::string name) { name_ = std::move(name); } std::string GetName() const override { return name_; } + void SetMedium(WifiLanMedium* medium) { medium_ = medium; } + WifiLanMedium* GetMedium() { return medium_; } + private: std::string name_; + WifiLanMedium* medium_ = nullptr; }; class WifiLanSocket : public api::WifiLanSocket { public: WifiLanSocket() = default; explicit WifiLanSocket(WifiLanService* service) : service_(service) {} - ~WifiLanSocket() override = default; + ~WifiLanSocket() override; // 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_); + void Connect(WifiLanSocket& other) ABSL_LOCKS_EXCLUDED(mutex_); // Returns the InputStream of this connected WifiLanSocket. InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_); @@ -46,6 +55,15 @@ class WifiLanSocket : public api::WifiLanSocket { // This stream is for local side to write. OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_); + // Returns address of a remote WifiLanSocket or nullptr. + WifiLanSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if socket is closed. + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + // Returns Exception::kIo on error, Exception::kSuccess otherwise. Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); @@ -55,9 +73,75 @@ class WifiLanSocket : public api::WifiLanSocket { ABSL_LOCKS_EXCLUDED(mutex_); private: - Pipe pipe_; - WifiLanService* service_; + 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_); + + // Returns InputStream of our side of a connection. + // This is what the remote side is supposed to read from. + // This is a helper for GetInputStream() method. + InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns OutputStream of our side of a connection. + // This is what the local size is supposed to write to. + // This is a helper for GetOutputStream() method. + OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // 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. + std::shared_ptr output_ {new Pipe}; + std::shared_ptr input_; mutable absl::Mutex mutex_; + WifiLanService* service_; + WifiLanSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +class WifiLanServerSocket { + public: + ~WifiLanServerSocket(); + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + // + // Called by the server side of a connection. + // Returns WifiLanSocket to the server side. + // If not null, returned socket is connected to its remote (client-side) peer. + std::unique_ptr Accept() ABSL_LOCKS_EXCLUDED(mutex_); + + // Blocks until either: + // - connection is available, or + // - server socket is closed, or + // - error happens. + // + // Called by the client side of a connection. + // Returns true, if socket is successfully connected. + bool Connect(WifiLanSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_); + + // Called by the server side of a connection before passing ownership of + // WifiLanServerSocker to user, to track validity of a pointer to this + // server socket, + void SetCloseNotifier(std::function notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + // Calls close_notifier if it was previously set, and marks socket as closed. + Exception Close() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + absl::Mutex mutex_; + absl::CondVar cond_; + absl::flat_hash_set pending_sockets_ ABSL_GUARDED_BY(mutex_); + std::function close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; }; // Container of operations that can be performed over the WifiLan medium. @@ -91,15 +175,48 @@ class WifiLanMedium : public api::WifiLanMedium { bool StopAcceptingConnections(const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); - // Returns a new WifiLanSocket. On Success, WifiLanSocket::IsValid() - // returns true. + // Connects to existing remote WifiLan service. + // + // On success, returns a new WifiLanSocket. + // On error, returns nullptr. std::unique_ptr Connect( - api::WifiLanService& service, const std::string& service_id) override - ABSL_LOCKS_EXCLUDED(mutex_); + api::WifiLanService& remote_service, + const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); private: + static constexpr int kMaxConcurrentAcceptLoops = 5; + + 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; + }; + absl::Mutex mutex_; WifiLanService service_{"wifi_lan_service_info_name"}; + + // A thread pool dedicated to running all the accept loops from + // StartAdvertising(). + MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops}; + std::atomic_bool acceptance_thread_running_ = false; + + // A thread pool dedicated to wait to complete the accept_loops_runner_. + MultiThreadExecutor close_accept_loops_runner_{kMaxConcurrentAcceptLoops}; + + // TODO(edwinwu): Extend it to hashmap to accept multiple sockets for multiple + // entrance. + // A server socket is established when start advertising. + std::unique_ptr server_socket_; + AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); + DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_); }; } // namespace g3 diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 66925ef6..59902e1e 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -93,6 +93,7 @@ cc_test( "atomic_reference_test.cc", "bluetooth_adapter_test.cc", "bluetooth_classic_test.cc", + "cancelable_alarm_test.cc", "condition_variable_test.cc", "count_down_latch_test.cc", "crypto_test.cc", diff --git a/cpp/platform_v2/public/cancelable.h b/cpp/platform_v2/public/cancelable.h index 3105648b..83f10291 100644 --- a/cpp/platform_v2/public/cancelable.h +++ b/cpp/platform_v2/public/cancelable.h @@ -24,7 +24,9 @@ class Cancelable final { explicit Cancelable(std::shared_ptr impl) : impl_(std::move(impl)) {} - bool Cancel() { return impl_->Cancel(); } + bool Cancel() { return impl_ ? impl_->Cancel() : false; } + + bool IsValid() { return impl_ != nullptr; } private: std::shared_ptr impl_; diff --git a/cpp/platform_v2/public/cancelable_alarm.h b/cpp/platform_v2/public/cancelable_alarm.h index 1fc26788..d00d6241 100644 --- a/cpp/platform_v2/public/cancelable_alarm.h +++ b/cpp/platform_v2/public/cancelable_alarm.h @@ -21,6 +21,7 @@ namespace nearby { */ class CancelableAlarm { public: + CancelableAlarm() = default; CancelableAlarm(absl::string_view name, std::function&& runnable, absl::Duration delay, ScheduledExecutor* scheduled_executor) : name_(name), @@ -44,6 +45,10 @@ class CancelableAlarm { return cancelable_.Cancel(); } + bool IsValid() { + return cancelable_.IsValid(); + } + private: Mutex mutex_; std::string name_; diff --git a/cpp/platform_v2/public/cancelable_alarm_test.cc b/cpp/platform_v2/public/cancelable_alarm_test.cc new file mode 100644 index 00000000..5bebf2cb --- /dev/null +++ b/cpp/platform_v2/public/cancelable_alarm_test.cc @@ -0,0 +1,54 @@ +#include "platform_v2/public/cancelable_alarm.h" + +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/scheduled_executor.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +TEST(CancelableAlarmTest, CanCreateDefault) { CancelableAlarm alarm; } + +TEST(CancelableAlarmTest, CancelDefaultFails) { + CancelableAlarm alarm; + EXPECT_FALSE(alarm.Cancel()); +} + +TEST(CancelableAlarmTest, CanCreateAndFireAlarm) { + ScheduledExecutor alarm_executor; + AtomicBoolean done{false}; + CancelableAlarm alarm( + "test_alarm", [&done]() { done.Set(true); }, absl::Milliseconds(100), + &alarm_executor); + SystemClock::Sleep(absl::Milliseconds(1000)); + EXPECT_TRUE(done.Get()); +} + +TEST(CancelableAlarmTest, CanCreateAndCancelAlarm) { + ScheduledExecutor alarm_executor; + AtomicBoolean done{false}; + CancelableAlarm alarm( + "test_alarm", [&done]() { done.Set(true); }, absl::Milliseconds(100), + &alarm_executor); + EXPECT_TRUE(alarm.Cancel()); + SystemClock::Sleep(absl::Milliseconds(1000)); + EXPECT_FALSE(done.Get()); +} + +TEST(CancelableAlarmTest, CancelExpiredAlarmFails) { + ScheduledExecutor alarm_executor; + AtomicBoolean done{false}; + CancelableAlarm alarm( + "test_alarm", [&done]() { done.Set(true); }, absl::Milliseconds(100), + &alarm_executor); + SystemClock::Sleep(absl::Milliseconds(1000)); + EXPECT_TRUE(done.Get()); + EXPECT_FALSE(alarm.Cancel()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/wifi_lan.cc b/cpp/platform_v2/public/wifi_lan.cc index 32eefa18..e1894a17 100644 --- a/cpp/platform_v2/public/wifi_lan.cc +++ b/cpp/platform_v2/public/wifi_lan.cc @@ -48,6 +48,7 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, [this](api::WifiLanService& service, const std::string& service_id) { MutexLock lock(&mutex_); + if (services_.empty()) return; auto item = services_.extract(&service); auto& context = *item.mapped(); NEARBY_LOG(INFO, "Removing service=%p, impl=%p", @@ -87,9 +88,8 @@ bool WifiLanMedium::StartAcceptingConnections( if (!pair.second) { NEARBY_LOG(INFO, "Adding (again) socket=%p, impl=%p", &context.socket, &socket); - return; + context.socket = WifiLanSocket(&socket); } - context.socket = WifiLanSocket(&socket); NEARBY_LOG(INFO, "Adding socket=%p, impl=%p", &context.socket, &socket); accepted_connection_callback_.accepted_cb(context.socket, @@ -106,7 +106,7 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { NEARBY_LOG(INFO, "WifiLan accepted connection disabled: impl=%p", &GetImpl()); } - return impl_->StopDiscovery(service_id); + return impl_->StopAcceptingConnections(service_id); } WifiLanSocket WifiLanMedium::Connect(WifiLanService& service, diff --git a/cpp/platform_v2/public/wifi_lan.h b/cpp/platform_v2/public/wifi_lan.h index 7274414f..f2403f04 100644 --- a/cpp/platform_v2/public/wifi_lan.h +++ b/cpp/platform_v2/public/wifi_lan.h @@ -102,8 +102,8 @@ class WifiLanMedium final { }; struct AcceptedConnectionCallback { - std::function - accepted_cb = DefaultCallback(); + std::function + accepted_cb = DefaultCallback(); }; struct AcceptedConnectionInfo { WifiLanSocket socket; diff --git a/cpp/platform_v2/public/wifi_lan_test.cc b/cpp/platform_v2/public/wifi_lan_test.cc index 398fa242..8a701efa 100644 --- a/cpp/platform_v2/public/wifi_lan_test.cc +++ b/cpp/platform_v2/public/wifi_lan_test.cc @@ -13,10 +13,12 @@ namespace nearby { namespace { constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; +constexpr absl::string_view kServiceName{"service name"}; class WifiLanMediumTest : public ::testing::Test { protected: using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback; + using AcceptedConnectionCallback = WifiLanMedium::AcceptedConnectionCallback; WifiLanMediumTest() { env_.Stop(); } @@ -25,75 +27,149 @@ class WifiLanMediumTest : public ::testing::Test { TEST_F(WifiLanMediumTest, ConstructorDestructorWorks) { env_.Start(); - WifiLanMedium medium_a; - WifiLanMedium medium_b; + WifiLanMedium wifi_a; + WifiLanMedium wifi_b; // Make sure we can create functional mediums. - ASSERT_TRUE(medium_a.IsValid()); - ASSERT_TRUE(medium_b.IsValid()); + ASSERT_TRUE(wifi_a.IsValid()); + ASSERT_TRUE(wifi_b.IsValid()); // Make sure we can create 2 distinct mediums. - EXPECT_NE(&medium_a.GetImpl(), &medium_b.GetImpl()); + EXPECT_NE(&wifi_a.GetImpl(), &wifi_b.GetImpl()); env_.Stop(); } -TEST_F(WifiLanMediumTest, CanStartDiscoveryAndServiceIndeedDiscovered) { +TEST_F(WifiLanMediumTest, CanStartAdvertising) { env_.Start(); - WifiLanMedium medium; + WifiLanMedium wifi_a; + WifiLanMedium wifi_b; + std::string service_id(kServiceID); + std::string service_name{kServiceName}; + CountDownLatch found_latch(1); + + wifi_a.StartAdvertising(service_id, service_name); + + EXPECT_TRUE(wifi_b.StartDiscovery( + service_id, DiscoveredServiceCallback{ + .service_discovered_cb = + [&found_latch](WifiLanService& service, + const std::string& service_id) { + found_latch.CountDown(); + }, + })); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_TRUE(wifi_a.StopAdvertising(service_id)); + EXPECT_TRUE(wifi_b.StopDiscovery(service_id)); + env_.Stop(); +} + +TEST_F(WifiLanMediumTest, CanStartDiscovery) { + env_.Start(); + WifiLanMedium wifi_a; + WifiLanMedium wifi_b; + std::string service_id(kServiceID); + std::string service_name{kServiceName}; CountDownLatch found_latch(1); CountDownLatch lost_latch(1); - medium.StartDiscovery(std::string(kServiceID), + wifi_a.StartDiscovery(service_id, 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(wifi_b.StartAdvertising(service_id, service_name)); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_TRUE(wifi_b.StopAdvertising(service_id)); + EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_TRUE(wifi_a.StopDiscovery(service_id)); env_.Stop(); } TEST_F(WifiLanMediumTest, CanStopDiscovery) { env_.Start(); - WifiLanMedium medium; + WifiLanMedium wifi_a; + WifiLanMedium wifi_b; + std::string service_id(kServiceID); + std::string service_name{kServiceName}; CountDownLatch found_latch(1); CountDownLatch lost_latch(1); - medium.StartDiscovery(std::string(kServiceID), + wifi_a.StartDiscovery(service_id, 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(wifi_b.StartAdvertising(service_id, service_name)); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); - bool stop = medium.StopDiscovery(std::string(kServiceID)); - EXPECT_TRUE(stop); + EXPECT_TRUE(wifi_a.StopDiscovery(service_id)); + EXPECT_TRUE(wifi_b.StopAdvertising(service_id)); + EXPECT_FALSE(lost_latch.Await(absl::Milliseconds(1000)).result()); + env_.Stop(); +} + +TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) { + env_.Start(); + WifiLanMedium wifi_a; + WifiLanMedium wifi_b; + std::string service_id(kServiceID); + std::string service_name{kServiceName}; + CountDownLatch found_latch(1); + CountDownLatch accepted_latch(1); + + WifiLanService* discovered_service = nullptr; + wifi_a.StartDiscovery( + service_id, + DiscoveredServiceCallback{ + .service_discovered_cb = + [&found_latch, &discovered_service]( + WifiLanService& service, const std::string& service_id) { + NEARBY_LOG(INFO, "Service discovered: %s, %p", + service.GetName().c_str(), &service); + discovered_service = &service; + found_latch.CountDown(); + }, + }); + wifi_b.StartAdvertising(service_id, service_name); + wifi_b.StartAcceptingConnections( + service_id, + AcceptedConnectionCallback{ + .accepted_cb = [&accepted_latch](WifiLanSocket socket, + const std::string& service_id) { + NEARBY_LOG(INFO, "Connection accepted: socket=%p, service_id=%s", + &socket, service_id.c_str()); + accepted_latch.CountDown(); + }}); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + + WifiLanSocket socket_a; + EXPECT_FALSE(socket_a.IsValid()); + { + SingleThreadExecutor client_executor; + client_executor.Execute( + [&wifi_a, &socket_a, discovered_service, &service_id]() { + socket_a = wifi_a.Connect(*discovered_service, service_id); + }); + } + EXPECT_TRUE(accepted_latch.Await(absl::Milliseconds(1000)).result()); + EXPECT_TRUE(socket_a.IsValid()); + wifi_b.StopAdvertising(service_id); + wifi_a.StopDiscovery(service_id); env_.Stop(); } diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 62232412..0f463a0b 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -103,9 +103,10 @@ enum StartAdvertisingError { // System error, all advertising slot ran out, can't available for new // regular advertisement. BLE_MAX_GATT_ADVERTISEMENT_SLOT_REACHED = 35; - // System error, failed to start advertising for legacy advertisements + // System error, failed to start advertising for legacy advertisements on BLE START_LEGACY_ADVERTISING_FAILED = 36; - // System error, failed to start advertising for extended advertisements + // System error, failed to start advertising for extended advertisements on + // BLE START_EXTENDED_ADVERTISING_FAILED = 38; // System error, there's already someone advertising on Bluetooth, not allow // to start another one. @@ -128,6 +129,22 @@ enum StartAdvertisingError { // Next ID :46 } +// The error for event START_DISCOVERING. The range between 31 and 99. +enum StartDiscoveringError { + // Developing error, this service ID already requested, should not request it + // again without stop discovering. + DUPLICATE_DISCOVERING_REQUESTED = 31; + // System error, failed to start discovering for legacy advertisements on BLE + START_LEGACY_DISCOVERING_FAILED = 32; + // System error, failed to start discovering for extended advertisements on + // BLE + START_EXTENDED_DISCOVERING_FAILED = 33; + // System error, failed to start discovering. + START_DISCOVERING_FAILED = 34; + + // Next ID :34 +} + enum Description { reserved 28; @@ -171,4 +188,15 @@ enum Description { NULL_WIFI_AWARE_MANAGER = 38; STALE_ANDROID_VERSION = 39; NULL_SERVICE_INFO = 40; + NULL_WORK_SOURCE = 41; + NULL_CALLBACK = 42; + NULL_BLUETOOTH_LE_SCANNER_COMPAT = 43; + EMPTY_WORK_SOURCE_CACHE = 44; + SCAN_FAILED_ALREADY_STARTED = 45; + SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 46; + SCAN_FAILED_INTERNAL_ERROR = 47; + SCAN_FAILED_FEATURE_UNSUPPORTED = 48; + SCAN_FAILED_BLUETOOTH_DISABLED = 49; + SCAN_FILTERS_NOT_ALLOWED_FOR_LOCATION = 50; + BLUETOOTH_SCAN_REJUVENATE_FAILED = 51; } From 59cd56ec820ea2eb2aa3409f95db55c1ea0104c9 Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Thu, 2 Jul 2020 19:54:23 +0100 Subject: [PATCH 37/52] Change smhasher path Changes the smhasher path to "smhasher/src/file" from "smhasher/file" for easier import in Chromium land. Change-Id: Ide6aa844fd8f37c9100e89d6a0bcb945b0860227 --- cmake/CMakeLists-smhasher.txt | 2 +- cmake/local_setup_smhasher.cmake | 8 ++++---- cpp/core/internal/mediums/bloom_filter.cc | 2 +- cpp/core_v2/internal/mediums/bloom_filter.cc | 2 +- script/oss.py | 1 + 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmake/CMakeLists-smhasher.txt b/cmake/CMakeLists-smhasher.txt index 08761ae1..e55c5c0c 100644 --- a/cmake/CMakeLists-smhasher.txt +++ b/cmake/CMakeLists-smhasher.txt @@ -3,7 +3,7 @@ project(smhasher CXX) cmake_minimum_required(VERSION 3.13) add_library(smhasher_murmur3 STATIC - cpp/src/smhasher/MurmurHash3.cpp + cpp/src/smhasher/src/MurmurHash3.cpp ) target_include_directories(smhasher_murmur3 diff --git a/cmake/local_setup_smhasher.cmake b/cmake/local_setup_smhasher.cmake index cb4164dd..26cf9c18 100644 --- a/cmake/local_setup_smhasher.cmake +++ b/cmake/local_setup_smhasher.cmake @@ -2,16 +2,16 @@ set(PKG_STAGE_SRC_ROOT ${TOOLS_ROOT}/src/smhasher) if (NOT EXISTS ${PKG_STAGE_SRC_ROOT}/CMakeLists.txt) set(PKG_SRC_ROOT ${PROJECT_SOURCE_DIR}/third_party/smhasher) execute_process( - COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher + COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher/src ) execute_process( - COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher + COMMAND mkdir -p ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher/src ) execute_process( - COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.cpp ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher + COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.cpp ${PKG_STAGE_SRC_ROOT}/cpp/src/smhasher/src ) execute_process( - COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.h ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher + COMMAND cp ${PKG_SRC_ROOT}/src/MurmurHash3.h ${PKG_STAGE_SRC_ROOT}/cpp/include/smhasher/src ) execute_process( COMMAND cp cmake/CMakeLists-smhasher.txt ${PKG_STAGE_SRC_ROOT}/CMakeLists.txt diff --git a/cpp/core/internal/mediums/bloom_filter.cc b/cpp/core/internal/mediums/bloom_filter.cc index 835ed205..8c05ca66 100644 --- a/cpp/core/internal/mediums/bloom_filter.cc +++ b/cpp/core/internal/mediums/bloom_filter.cc @@ -2,7 +2,7 @@ #include "absl/numeric/int128.h" #include "absl/strings/numbers.h" -#include "smhasher/MurmurHash3.h" +#include "smhasher/src/MurmurHash3.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/bloom_filter.cc b/cpp/core_v2/internal/mediums/bloom_filter.cc index b2f08fc9..f4074347 100644 --- a/cpp/core_v2/internal/mediums/bloom_filter.cc +++ b/cpp/core_v2/internal/mediums/bloom_filter.cc @@ -2,7 +2,7 @@ #include "absl/numeric/int128.h" #include "absl/strings/numbers.h" -#include "smhasher/MurmurHash3.h" +#include "smhasher/src/MurmurHash3.h" namespace location { namespace nearby { diff --git a/script/oss.py b/script/oss.py index 4b621b0f..1892d64e 100755 --- a/script/oss.py +++ b/script/oss.py @@ -135,6 +135,7 @@ def post_process_oss_files(path, args): ("location/nearby/connections/proto", "proto/connections"), ("_portable_proto.pb.h", ".pb.h"), (".proto.h", ".pb.h"), + ("smhasher/MurmurHash3.h", "smhasher/src/MurmurHash3.h"), ) for root, dirs, files in os.walk(path): From 57754ad65be2e454bca91e0a8c98153790c86c8b Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 7 Jul 2020 12:55:46 -0700 Subject: [PATCH 38/52] Oss fix Signed-off-by: Alexey Polyudov Change-Id: I0ddbfaa28d7280197bf5ac4dcf1a407ade5bdc7d --- cpp/core_v2/internal/offline_service_controller.cc | 14 ++++++++++++++ cpp/core_v2/internal/offline_service_controller.h | 14 ++++++++++++++ .../internal/offline_service_controller_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/offline_simulation_user.cc | 14 ++++++++++++++ cpp/core_v2/internal/offline_simulation_user.h | 14 ++++++++++++++ cpp/platform_v2/public/cancelable_alarm_test.cc | 14 ++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/cpp/core_v2/internal/offline_service_controller.cc b/cpp/core_v2/internal/offline_service_controller.cc index 249c97b8..7465fc96 100644 --- a/cpp/core_v2/internal/offline_service_controller.cc +++ b/cpp/core_v2/internal/offline_service_controller.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/offline_service_controller.h" #include diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index bcb6e2c7..a4855db2 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ #define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_service_controller_test.cc b/cpp/core_v2/internal/offline_service_controller_test.cc index 260dd527..2d4487ea 100644 --- a/cpp/core_v2/internal/offline_service_controller_test.cc +++ b/cpp/core_v2/internal/offline_service_controller_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/offline_service_controller.h" #include "core_v2/internal/offline_simulation_user.h" diff --git a/cpp/core_v2/internal/offline_simulation_user.cc b/cpp/core_v2/internal/offline_simulation_user.cc index 6ed65174..1a58f117 100644 --- a/cpp/core_v2/internal/offline_simulation_user.cc +++ b/cpp/core_v2/internal/offline_simulation_user.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/offline_simulation_user.h" #include "core_v2/listeners.h" diff --git a/cpp/core_v2/internal/offline_simulation_user.h b/cpp/core_v2/internal/offline_simulation_user.h index 4c00d8eb..27a41d56 100644 --- a/cpp/core_v2/internal/offline_simulation_user.h +++ b/cpp/core_v2/internal/offline_simulation_user.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ #define CORE_V2_INTERNAL_OFFLINE_SIMULATION_USER_H_ diff --git a/cpp/platform_v2/public/cancelable_alarm_test.cc b/cpp/platform_v2/public/cancelable_alarm_test.cc index 5bebf2cb..dba12453 100644 --- a/cpp/platform_v2/public/cancelable_alarm_test.cc +++ b/cpp/platform_v2/public/cancelable_alarm_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/cancelable_alarm.h" #include "platform_v2/public/atomic_boolean.h" From 6f9228fa6b82816bbd89fad4a7b219fc2faac9c2 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Wed, 15 Jul 2020 11:15:23 -0700 Subject: [PATCH 39/52] Roll forward to cl/321106672 Signed-off-by: Alexey Polyudov Change-Id: Iabb20e9c7f7487268fa3ebbbeb58540092e10e0e --- cpp/core_v2/internal/BUILD | 1 - cpp/core_v2/internal/base_pcp_handler.cc | 36 +++- cpp/core_v2/internal/base_pcp_handler.h | 13 ++ cpp/core_v2/internal/base_pcp_handler_test.cc | 8 +- cpp/core_v2/internal/endpoint_manager.cc | 2 +- cpp/core_v2/internal/endpoint_manager.h | 2 +- cpp/core_v2/internal/mediums/webrtc/BUILD | 1 + .../mediums/webrtc/connection_flow_test.cc | 21 ++- cpp/core_v2/internal/mediums/webrtc_test.cc | 27 ++- cpp/core_v2/internal/mediums/wifi_lan.cc | 39 ++--- cpp/core_v2/internal/mediums/wifi_lan.h | 47 +++-- .../internal/p2p_cluster_pcp_handler.cc | 70 ++++---- .../internal/p2p_cluster_pcp_handler.h | 19 +- .../internal/wifi_lan_endpoint_channel.h | 2 +- cpp/core_v2/options.h | 12 ++ cpp/platform_v2/api/wifi_lan.h | 7 +- cpp/platform_v2/base/medium_environment.cc | 162 +++++++++++------- cpp/platform_v2/base/medium_environment.h | 36 ++-- cpp/platform_v2/impl/g3/BUILD | 1 + cpp/platform_v2/impl/g3/platform.cc | 7 +- cpp/platform_v2/impl/g3/wifi_lan.cc | 63 +++---- cpp/platform_v2/impl/g3/wifi_lan.h | 21 +-- cpp/platform_v2/public/wifi_lan.cc | 24 ++- cpp/platform_v2/public/wifi_lan.h | 5 +- proto/error_code_enums.proto | 74 +++++++- proto/sharing_enums.proto | 12 ++ 26 files changed, 477 insertions(+), 235 deletions(-) diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index bcb2aa0e..8315217b 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -141,7 +141,6 @@ 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", diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index ac0da77b..34d95e72 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -288,6 +288,7 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, return; } + std::vector endpoints; auto endpoint = GetDiscoveredEndpoint(endpoint_id); if (endpoint == nullptr) { NEARBY_LOG(INFO, "Discovered endpoint not found: id=%s", @@ -296,9 +297,31 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, return; } - auto connect_impl_result = ConnectImpl(client, endpoint); - std::unique_ptr channel = - std::move(connect_impl_result.endpoint_channel); + auto webrtc_endpoint = absl::make_unique( + DiscoveredEndpoint{endpoint->endpoint_id, endpoint->endpoint_name, + endpoint->service_id, + proto::connections::Medium::WEB_RTC}, + CreatePeerIdFromAdvertisement(endpoint->service_id, + endpoint->endpoint_id, + endpoint->endpoint_name)); + endpoints.push_back(endpoint); + endpoints.push_back(webrtc_endpoint.get()); + + std::sort(endpoints.begin(), endpoints.end(), + [this](DiscoveredEndpoint* a, DiscoveredEndpoint* b) -> bool { + return IsPreferred(*a, *b); + }); + + std::unique_ptr channel; + ConnectImplResult connect_impl_result; + + for (auto connect_endpoint : endpoints) { + connect_impl_result = ConnectImpl(client, connect_endpoint); + if (connect_impl_result.status.Ok()) { + channel = std::move(connect_impl_result.endpoint_channel); + break; + } + } if (channel == nullptr) { NEARBY_LOG(INFO, "Endpoint channel not available: id=%s", @@ -1054,6 +1077,13 @@ void BasePcpHandler::PendingConnectionInfo::LocalEndpointRejectedConnection( client->LocalEndpointRejectedConnection(endpoint_id); } +mediums::PeerId BasePcpHandler::CreatePeerIdFromAdvertisement( + const std::string& service_id, const std::string& endpoint_id, + const std::string& endpoint_name) { + std::string seed = absl::StrCat(service_id, endpoint_id, endpoint_name); + return mediums::PeerId::FromSeed(ByteArray(std::move(seed))); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index 437fbf81..533ec388 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -10,6 +10,7 @@ #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/webrtc.h" #include "core_v2/internal/pcp.h" #include "core_v2/internal/pcp_handler.h" #include "core_v2/listeners.h" @@ -181,6 +182,14 @@ class BasePcpHandler : public PcpHandler, proto::connections::Medium medium; }; + struct WebRtcEndpoint : public DiscoveredEndpoint { + WebRtcEndpoint(DiscoveredEndpoint endpoint, mediums::PeerId peer_id) + : DiscoveredEndpoint(std::move(endpoint)), + peer_id(std::move(peer_id)) {} + + mediums::PeerId peer_id; + }; + struct ConnectImplResult { proto::connections::Medium medium = proto::connections::Medium::UNKNOWN_MEDIUM; @@ -235,6 +244,10 @@ class BasePcpHandler : public PcpHandler, GetConnectionMediumsByPriority() = 0; virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0; + mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, + const string& endpoint_id, + const string& endpoint_name); + EndpointManager* endpoint_manager_; EndpointChannelManager* channel_manager_; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 28894559..e18ff69c 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -93,11 +93,15 @@ class MockPcpHandler : public BasePcpHandler { MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); MOCK_METHOD(ConnectImplResult, ConnectImpl, (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); - MOCK_METHOD(std::vector, - GetConnectionMediumsByPriority, (), (override)); MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), (override)); + std::vector GetConnectionMediumsByPriority() + override { + return {proto::connections::Medium::BLE, + proto::connections::Medium::WEB_RTC}; + } + // Mock adapters for protected non-virtual methods of a base class. void OnEndpointFound(ClientProxy* client, std::shared_ptr endpoint) { diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 41e10e12..615dd49a 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -227,7 +227,7 @@ EndpointManager::~EndpointManager() { NEARBY_LOG(INFO, "EndpointManager is down"); } -const EndpointManager::FrameProcessor::Handle +EndpointManager::FrameProcessor::Handle EndpointManager::RegisterFrameProcessor( V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) { const FrameProcessor::Handle handle = processor; diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h index 3d761df7..b5ea8194 100644 --- a/cpp/core_v2/internal/endpoint_manager.h +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -81,7 +81,7 @@ class EndpointManager { // FrameProcessor* instances are of dynamic duration and survive all sessions. // returns unique handle to be used for unregistering. // Blocks until registration is complete. - const FrameProcessor::Handle RegisterFrameProcessor( + FrameProcessor::Handle RegisterFrameProcessor( V1Frame::FrameType frame_type, FrameProcessor* processor); void UnregisterFrameProcessor(V1Frame::FrameType frame_type, const void* handle, bool sync = false); diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD index 3e7587d6..b2b278b8 100644 --- a/cpp/core_v2/internal/mediums/webrtc/BUILD +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -49,6 +49,7 @@ cc_test( deps = [ ":webrtc", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # buildcleaner: keep "//platform_v2/public:comm", "//platform_v2/public:types", 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 8087fec5..7a3a859c 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc @@ -5,6 +5,7 @@ #include "core_v2/internal/mediums/webrtc/session_description_wrapper.h" #include "platform_v2/base/byte_array.h" +#include "platform_v2/base/medium_environment.h" #include "platform_v2/public/webrtc.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -20,6 +21,14 @@ namespace connections { namespace mediums { namespace { +class ConnectionFlowTest : public ::testing::Test { + protected: + ConnectionFlowTest() { + MediumEnvironment::Instance().Stop(); + MediumEnvironment::Instance().Start({.webrtc_enabled = true}); + } +}; + std::unique_ptr CopyCandidate( const webrtc::IceCandidateInterface* candidate) { return webrtc::CreateIceCandidate(candidate->sdp_mid(), @@ -29,7 +38,7 @@ std::unique_ptr CopyCandidate( // TODO(bfranz) - Add test that deterministically sends answerer_ice_candidates // before answer is sent. -TEST(ConnectionFlowTest, SuccessfulOfferAnswerFlow) { +TEST_F(ConnectionFlowTest, SuccessfulOfferAnswerFlow) { WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; Future message_received_future; @@ -95,7 +104,7 @@ TEST(ConnectionFlowTest, SuccessfulOfferAnswerFlow) { EXPECT_EQ(received_message.result(), ByteArray{message}); } -TEST(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) { +TEST_F(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) { WebRtcMedium webrtc_medium; std::unique_ptr answerer = ConnectionFlow::Create( @@ -106,7 +115,7 @@ TEST(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) { EXPECT_FALSE(answer.IsValid()); } -TEST(ConnectionFlowTest, SetAnswerBeforeOffer) { +TEST_F(ConnectionFlowTest, SetAnswerBeforeOffer) { WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; std::unique_ptr offerer = @@ -128,7 +137,7 @@ TEST(ConnectionFlowTest, SetAnswerBeforeOffer) { EXPECT_FALSE(offerer->OnAnswerReceived(answer)); } -TEST(ConnectionFlowTest, CannotCreateOfferAfterClose) { +TEST_F(ConnectionFlowTest, CannotCreateOfferAfterClose) { WebRtcMedium webrtc_medium; std::unique_ptr offerer = ConnectionFlow::Create( @@ -140,7 +149,7 @@ TEST(ConnectionFlowTest, CannotCreateOfferAfterClose) { EXPECT_FALSE(offerer->CreateOffer().IsValid()); } -TEST(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) { +TEST_F(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) { WebRtcMedium webrtc_medium; std::unique_ptr offerer = ConnectionFlow::Create( @@ -155,7 +164,7 @@ TEST(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) { EXPECT_FALSE(offerer->SetLocalSessionDescription(offer)); } -TEST(ConnectionFlowTest, CannotReceiveOfferAfterClose) { +TEST_F(ConnectionFlowTest, CannotReceiveOfferAfterClose) { WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer; std::unique_ptr offerer = diff --git a/cpp/core_v2/internal/mediums/webrtc_test.cc b/cpp/core_v2/internal/mediums/webrtc_test.cc index 9b4f8399..6e450e3d 100644 --- a/cpp/core_v2/internal/mediums/webrtc_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc_test.cc @@ -2,6 +2,7 @@ #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "platform_v2/base/listeners.h" +#include "platform_v2/base/medium_environment.h" #include "platform_v2/public/mutex_lock.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -13,8 +14,16 @@ namespace mediums { namespace { +class WebRtcTest : public ::testing::Test { + protected: + WebRtcTest() { + MediumEnvironment::Instance().Stop(); + MediumEnvironment::Instance().Start({.webrtc_enabled = true}); + } +}; + // Basic test to check that device is accepting connections when initialized. -TEST(WebRtcTest, NotAcceptingConnections) { +TEST_F(WebRtcTest, NotAcceptingConnections) { WebRtc webrtc; ASSERT_TRUE(webrtc.IsAvailable()); EXPECT_FALSE(webrtc.IsAcceptingConnections()); @@ -22,7 +31,7 @@ TEST(WebRtcTest, NotAcceptingConnections) { // 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) { +TEST_F(WebRtcTest, StartAcceptingConnectionTwice) { using MockAcceptedCallback = testing::MockFunction; testing::StrictMock mock_accepted_callback_; @@ -40,7 +49,7 @@ TEST(WebRtcTest, StartAcceptingConnectionTwice) { // Tests the flow when the device tries to connect but the data channel times // out. -TEST(WebRtcTest, Connect_DataChannelTimeOut) { +TEST_F(WebRtcTest, Connect_DataChannelTimeOut) { WebRtc webrtc; PeerId peer_id("peer_id"); @@ -54,7 +63,7 @@ TEST(WebRtcTest, Connect_DataChannelTimeOut) { // Tests the flow when the device calls Connect() after calling // StartAcceptingConnections() without StopAcceptingConnections(). -TEST(WebRtcTest, StartAcceptingConnection_ThenConnect) { +TEST_F(WebRtcTest, StartAcceptingConnection_ThenConnect) { using MockAcceptedCallback = testing::MockFunction; testing::StrictMock mock_accepted_callback_; @@ -74,7 +83,7 @@ TEST(WebRtcTest, StartAcceptingConnection_ThenConnect) { // Tests the flow when the device calls StartAcceptingConnections but the medium // is closed before a peer device can connect to it. -TEST(WebRtcTest, StartAndStopAcceptingConnections) { +TEST_F(WebRtcTest, StartAndStopAcceptingConnections) { using MockAcceptedCallback = testing::MockFunction; testing::StrictMock mock_accepted_callback_; @@ -91,7 +100,7 @@ TEST(WebRtcTest, StartAndStopAcceptingConnections) { // Tests the flow when the device tries to connect to two different peers // without disconnecting in between. -TEST(WebRtcTest, ConnectTwice) { +TEST_F(WebRtcTest, ConnectTwice) { WebRtc receiver, sender, device_c; WebRtcSocketWrapper receiver_socket, sender_socket; const PeerId self_id("self_id"), other_id("other_id"); @@ -135,7 +144,7 @@ TEST(WebRtcTest, ConnectTwice) { // 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) { +TEST_F(WebRtcTest, ConnectBothDevicesAndAbort) { WebRtc receiver, sender; WebRtcSocketWrapper receiver_socket, sender_socket; const PeerId self_id("self_id"); @@ -161,7 +170,7 @@ TEST(WebRtcTest, ConnectBothDevicesAndAbort) { // 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) { +TEST_F(WebRtcTest, ConnectBothDevicesAndSendData) { WebRtc receiver, sender; WebRtcSocketWrapper receiver_socket, sender_socket; const PeerId self_id("self_id"); @@ -193,7 +202,7 @@ TEST(WebRtcTest, ConnectBothDevicesAndSendData) { // Tests the flow when the two devices exchange SDP messages and connect to each // other but the signaling channel is closed before sending the data. -TEST(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) { +TEST_F(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) { WebRtc receiver, sender; WebRtcSocketWrapper receiver_socket, sender_socket; const PeerId self_id("self_id"); diff --git a/cpp/core_v2/internal/mediums/wifi_lan.cc b/cpp/core_v2/internal/mediums/wifi_lan.cc index 1983137f..019fc0e6 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan.cc @@ -20,10 +20,10 @@ bool WifiLan::IsAvailable() const { bool WifiLan::IsAvailableLocked() const { return medium_.IsValid(); } bool WifiLan::StartAdvertising(const std::string& service_id, - const std::string& wifi_lan_service_info_name) { + const std::string& service_info_name) { MutexLock lock(&mutex_); - if (wifi_lan_service_info_name.empty()) { + if (service_info_name.empty()) { NEARBY_LOG( INFO, "Refusing to turn on WifiLan advertising. Empty service info name."); @@ -36,45 +36,45 @@ bool WifiLan::StartAdvertising(const std::string& service_id, return false; } - if (!medium_.StartAdvertising(service_id, wifi_lan_service_info_name)) { + if (!medium_.StartAdvertising(service_id, service_info_name)) { NEARBY_LOG( INFO, "Failed to turn on WifiLan advertising with service info name=%s", - wifi_lan_service_info_name.c_str()); + service_info_name.c_str()); return false; } NEARBY_LOGS(INFO) << "Turned on WifiLan advertising with service info name=" - << wifi_lan_service_info_name + << service_info_name << ", service id=" << service_id; - advertising_info_.service_id = service_id; + advertising_info_.Add(service_id); return true; } bool WifiLan::StopAdvertising(const std::string& service_id) { MutexLock lock(&mutex_); - if (!IsAdvertisingLocked()) { + if (!IsAdvertisingLocked(service_id)) { NEARBY_LOG(INFO, "Can't turn off WifiLan advertising; it is already off"); return false; } NEARBY_LOG(INFO, "Turned off WifiLan advertising with service id=%s", service_id.c_str()); - bool ret = medium_.StopAdvertising(advertising_info_.service_id); + bool ret = medium_.StopAdvertising(service_id); // Reset our bundle of advertising state to mark that we're no longer // advertising. - advertising_info_.Clear(); + advertising_info_.Remove(service_id); return ret; } -bool WifiLan::IsAdvertising() { +bool WifiLan::IsAdvertising(const std::string& service_id) { MutexLock lock(&mutex_); - return IsAdvertisingLocked(); + return IsAdvertisingLocked(service_id); } -bool WifiLan::IsAdvertisingLocked() { - return !advertising_info_.Empty(); +bool WifiLan::IsAdvertisingLocked(const std::string& service_id) { + return advertising_info_.Existed(service_id); } bool WifiLan::StartDiscovery(const std::string& service_id, @@ -110,7 +110,7 @@ bool WifiLan::StartDiscovery(const std::string& service_id, NEARBY_LOG(INFO, "Turned on WifiLan discovering with service id=%s", service_id.c_str()); // Mark the fact that we're currently performing a WifiLan discovering. - discovering_info_.service_id = service_id; + discovering_info_.Add(service_id); return true; } @@ -138,7 +138,7 @@ bool WifiLan::IsDiscovering(const std::string& service_id) { } bool WifiLan::IsDiscoveringLocked(const std::string& service_id) { - return !discovering_info_.Empty(); + return discovering_info_.Existed(service_id); } bool WifiLan::StartAcceptingConnections(const std::string& service_id, @@ -174,7 +174,7 @@ bool WifiLan::StartAcceptingConnections(const std::string& service_id, return false; } - accepting_connections_info_.service_id = service_id; + accepting_connections_info_.Add(service_id); return true; } @@ -188,11 +188,10 @@ bool WifiLan::StopAcceptingConnections(const std::string& service_id) { return false; } - bool ret = - medium_.StopAcceptingConnections(accepting_connections_info_.service_id); + bool ret = medium_.StopAcceptingConnections(service_id); // Reset our bundle of accepting connections state to mark that we're no // longer accepting connections. - accepting_connections_info_.Clear(); + accepting_connections_info_.Remove(service_id); return ret; } @@ -203,7 +202,7 @@ bool WifiLan::IsAcceptingConnections(const std::string& service_id) { } bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) { - return !accepting_connections_info_.Empty(); + return accepting_connections_info_.Existed(service_id); } WifiLanSocket WifiLan::Connect(WifiLanService& wifi_lan_service, diff --git a/cpp/core_v2/internal/mediums/wifi_lan.h b/cpp/core_v2/internal/mediums/wifi_lan.h index 16884a5d..890b22e8 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.h +++ b/cpp/core_v2/internal/mediums/wifi_lan.h @@ -9,6 +9,7 @@ #include "platform_v2/public/mutex.h" #include "platform_v2/public/wifi_lan.h" #include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" namespace location { namespace nearby { @@ -25,7 +26,7 @@ class WifiLan { // 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) + const std::string& service_info_name) ABSL_LOCKS_EXCLUDED(mutex_); // Disables WifiLan advertising, and restores service info name to @@ -33,7 +34,7 @@ class WifiLan { bool StopAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); - bool IsAdvertising() ABSL_LOCKS_EXCLUDED(mutex_); + bool IsAdvertising(const std::string& service_id) 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, @@ -70,31 +71,53 @@ class WifiLan { private: struct AdvertisingInfo { - bool Empty() const { return service_id.empty(); } - void Clear() { service_id.clear(); } + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } - std::string service_id; + absl::flat_hash_set service_ids; }; struct DiscoveringInfo { - bool Empty() const { return service_id.empty(); } - void Clear() { service_id.clear(); } + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } - std::string service_id; + absl::flat_hash_set service_ids; }; struct AcceptingConnectionsInfo { - bool Empty() const { return service_id.empty(); } - void Clear() { service_id.clear(); } + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } - std::string service_id; + absl::flat_hash_set service_ids; }; // 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_); + bool IsAdvertisingLocked(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); // Same as IsDiscovering(), but must be called with mutex_ held. bool IsDiscoveringLocked(const std::string& service_id) diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 62ab997f..0ca1ee8c 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -233,32 +233,33 @@ P2pClusterPcpHandler::MakeBluetoothDeviceLostHandler( } bool P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint( - const std::string& name_string, const std::string& service_id, - const WifiLanServiceInfo& name) const { - if (!name.IsValid()) { + const std::string& service_id, + const WifiLanServiceInfo& service_info) const { + if (!service_info.IsValid()) { NEARBY_LOG( INFO, "P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: name is invalid"); return false; } - if (name.GetPcp() != GetPcp()) { + if (service_info.GetPcp() != GetPcp()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint: Pcp is " "not matched; name.Pcp=%d, Pcp=%d", - name.GetPcp(), GetPcp()); + service_info.GetPcp(), GetPcp()); return false; } ByteArray expected_service_id_hash = GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); - if (name.GetServiceIdHash() != expected_service_id_hash) { + if (service_info.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()); + service_info.GetServiceIdHash().data(), + expected_service_id_hash.data()); return false; } @@ -282,25 +283,23 @@ P2pClusterPcpHandler::MakeWifiLanServiceDiscoveredHandler( } // Parse the WifiLan service name. - const std::string& service_name_string = service.GetName(); - WifiLanServiceInfo service_name(service_name_string); + const std::string& service_info_name = service.GetName(); + WifiLanServiceInfo service_info(service_info_name); // Make sure the WifiLan service name points to a valid // endpoint we're discovering. - if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id, - service_name)) - return; + if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) 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()); + service_id.c_str(), service_info.GetEndpointId().c_str(), + service_info.GetEndpointName().c_str()); OnEndpointFound(client, std::make_shared(WifiLanEndpoint{ { - service_name.GetEndpointId(), - service_name.GetEndpointName(), + service_info.GetEndpointId(), + service_info.GetEndpointName(), service_id, proto::connections::Medium::WIFI_LAN, }, @@ -327,14 +326,12 @@ P2pClusterPcpHandler::MakeWifiLanServiceLostHandler( } // Parse the WifiLan service name. - const std::string& service_name_string = service.GetName(); - WifiLanServiceInfo service_name(service_name_string); + const std::string& service_info_name = service.GetName(); + WifiLanServiceInfo service_info(service_info_name); // Make sure the WifiLan service name points to a valid // endpoint we're discovering. - if (!IsRecognizedWifiLanEndpoint(service_name_string, service_id, - service_name)) - return; + if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) return; // Report the discovered endpoint to the client. NEARBY_LOG( @@ -344,8 +341,8 @@ P2pClusterPcpHandler::MakeWifiLanServiceLostHandler( client, service_id.c_str()); OnEndpointLost(client, WifiLanEndpoint{ { - service_name.GetEndpointId(), - service_name.GetEndpointName(), + service_info.GetEndpointId(), + service_info.GetEndpointName(), service_id, proto::connections::Medium::WIFI_LAN, }, @@ -597,11 +594,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( } RunOnPcpHandlerThread([this, client, local_endpoint_name, socket = std::move(socket)]() mutable { - std::string remote_service_name = + std::string remote_service_info_name = socket.GetRemoteWifiLanService().GetName(); auto channel = absl::make_unique( - remote_service_name, socket); - OnIncomingConnection(client, remote_service_name, + remote_service_info_name, socket); + OnIncomingConnection(client, remote_service_info_name, std::move(channel), proto::connections::Medium::WIFI_LAN); }); @@ -618,10 +615,10 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( 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( + std::string service_info_name(WifiLanServiceInfo( WifiLanServiceInfo::Version::kV1, GetPcp(), local_endpoint_id, service_id_hash, local_endpoint_name)); - if (service_name.empty()) { + if (service_info_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " "WifiLanServiceInfo failed"); @@ -630,8 +627,8 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( } else { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " - "WifiLanServiceInfo succeeded; service_name=%s", - service_name.c_str()); + "WifiLanServiceInfo succeeded; service_info_name=%s", + service_info_name.c_str()); } NEARBY_LOG( @@ -639,11 +636,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: come up", service_id.c_str()); - if (!wifi_lan_medium_.StartAdvertising(service_id, service_name)) { + if (!wifi_lan_medium_.StartAdvertising(service_id, service_info_name)) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: failed to " - "start advertising, service_name=%s", - service_name.c_str()); + "start advertising, service_info_name=%s", + service_info_name.c_str()); wifi_lan_medium_.StopAcceptingConnections(service_id); return proto::connections::UNKNOWN_MEDIUM; } @@ -748,13 +745,6 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WebRtcConnectImpl( .endpoint_channel = std::move(channel)}; } -mediums::PeerId P2pClusterPcpHandler::CreatePeerIdFromAdvertisement( - const std::string& service_id, const std::string& endpoint_id, - const std::string& endpoint_name) { - std::string seed = absl::StrCat(service_id, endpoint_id, endpoint_name); - return mediums::PeerId::FromSeed(ByteArray(seed)); -} - } // 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 index fc699aae..7b5c4172 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -83,12 +83,6 @@ class P2pClusterPcpHandler : public BasePcpHandler { wifi_lan_service(std::move(service)) {} WifiLanService wifi_lan_service; }; - struct WebRtcEndpoint : public BasePcpHandler::DiscoveredEndpoint { - WebRtcEndpoint(DiscoveredEndpoint endpoint, mediums::PeerId peer_id) - : DiscoveredEndpoint(std::move(endpoint)), - peer_id(std::move(peer_id)) {} - mediums::PeerId peer_id; - }; using BluetoothDiscoveredDeviceCallback = BluetoothClassic::DiscoveredDeviceCallback; @@ -101,7 +95,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { static ByteArray GenerateHash(const std::string& source, size_t size); - // Bluetooth. + // Bluetooth bool IsRecognizedBluetoothEndpoint(const std::string& name_string, const std::string& service_id, const BluetoothDeviceName& name) const; @@ -119,10 +113,10 @@ class P2pClusterPcpHandler : public BasePcpHandler { BasePcpHandler::ConnectImplResult BluetoothConnectImpl( ClientProxy* client, BluetoothEndpoint* endpoint); - // WifiLan. - bool IsRecognizedWifiLanEndpoint(const std::string& name_string, - const std::string& service_id, - const WifiLanServiceInfo& name) const; + // WifiLan + bool IsRecognizedWifiLanEndpoint( + const std::string& service_id, + const WifiLanServiceInfo& service_info) const; std::function MakeWifiLanServiceDiscoveredHandler(ClientProxy* client, const std::string& service_id); @@ -146,9 +140,6 @@ class P2pClusterPcpHandler : public BasePcpHandler { const std::string& local_endpoint_name); BasePcpHandler::ConnectImplResult WebRtcConnectImpl( ClientProxy* client, WebRtcEndpoint* webrtc_endpoint); - mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, - const string& endpoint_id, - const string& endpoint_name); BluetoothRadio& bluetooth_radio_; BluetoothClassic& bluetooth_medium_; diff --git a/cpp/core_v2/internal/wifi_lan_endpoint_channel.h b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h index 6f985fda..52cb6564 100644 --- a/cpp/core_v2/internal/wifi_lan_endpoint_channel.h +++ b/cpp/core_v2/internal/wifi_lan_endpoint_channel.h @@ -13,7 +13,7 @@ class WifiLanEndpointChannel final : public BaseEndpointChannel { public: // Creates both outgoing and incoming WifiLan channels. WifiLanEndpointChannel(const std::string& channel_name, - WifiLanSocket bluetooth_socket); + WifiLanSocket socket); proto::connections::Medium GetMedium() const override; diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index d55e41d5..86fe59dc 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -7,10 +7,22 @@ namespace location { namespace nearby { namespace connections { +// Generic type: allows definition of a feature T for every Medium. +template +struct MediumSelector { + T bluetooth; + T web_rtc; + T wifi_lan; +}; + +// Feature On/Off switch for mediums. +using BooleanMediumSelector = MediumSelector; + // Connection Options: used for both Advertising and Discovery. // All fields are mutable, to make the type copy-assignable. struct ConnectionOptions { Strategy strategy; + BooleanMediumSelector allowed; bool auto_upgrade_bandwidth; bool enforce_topology_constraints; // Verify if ConnectionOptions is in a not-initialized (Empty) state. diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h index 49b979a8..10e6cdb2 100644 --- a/cpp/platform_v2/api/wifi_lan.h +++ b/cpp/platform_v2/api/wifi_lan.h @@ -13,7 +13,8 @@ namespace location { namespace nearby { namespace api { -// Opaque wrapper over a WifiLan service which contains encoded service name. +// Opaque wrapper over a WifiLan service which contains packed +// |WifiLanServiceInfo| string name. class WifiLanService { public: virtual ~WifiLanService() = default; @@ -57,10 +58,8 @@ class WifiLanMedium { const std::string& wifi_lan_service_info_name) = 0; virtual bool StopAdvertising(const std::string& service_id) = 0; + // Callback that is invoked when a discovered service is found or lost. 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 = diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 164af945..8d1cbece 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -22,9 +22,10 @@ MediumEnvironment& MediumEnvironment::Instance() { return *env; } -void MediumEnvironment::Start() { +void MediumEnvironment::Start(EnvironmentConfig config) { if (!enabled_.exchange(true)) { NEARBY_LOG(INFO, "MediumEnvironment::Start()"); + config_ = std::move(config); Reset(); } } @@ -65,6 +66,10 @@ void MediumEnvironment::Sync(bool enable_notifications) { NEARBY_LOG(INFO, "MediumEnvironment::Sync(): done [count=%d]", count); } +const EnvironmentConfig& MediumEnvironment::GetEnvironmentConfig() { + return config_; +} + void MediumEnvironment::OnBluetoothAdapterChangedState( api::BluetoothAdapter& adapter, api::BluetoothDevice& adapter_device, std::string name, bool enabled, api::BluetoothAdapter::ScanMode mode) { @@ -74,7 +79,8 @@ void MediumEnvironment::OnBluetoothAdapterChangedState( NEARBY_LOG(INFO, "[adapter=%p, device=%p] update: name=%s, enabled=%d, mode=%d", &adapter, &adapter_device, name.c_str(), enabled, mode); - for (auto& [medium, info] : bluetooth_mediums_) { + for (auto& medium_info : bluetooth_mediums_) { + auto& info = medium_info.second; // Do not send notification to medium that owns this adapter. if (info.adapter == &adapter) continue; NEARBY_LOG(INFO, "[adapter=%p, device=%p] notify: adapter=%p", &adapter, @@ -153,19 +159,22 @@ void MediumEnvironment::OnWifiLanServiceStateChanged( const std::string& service_id, bool enabled) { if (!enabled_) return; NEARBY_LOG(INFO, - "G3 OnWifiLanServiceStateChanged [service impl=%p]; context=%p, " - "notify=%d", - &info, &service, enable_notifications_.load()); + "G3 OnWifiLanServiceStateChanged [service impl=%p]; context=%p; " + "service_id=%s; notify=%d", + &service, &info, service_id.c_str(), enable_notifications_.load()); if (!enable_notifications_) return; - 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); - }); - } + RunOnMediumEnvironmentThread([&info, enabled, &service, service_id]() { + auto service_id_context = info.services.find(service_id); + if (service_id_context == info.services.end()) return; + + if (enabled) { + service_id_context->second.discovery_callback.service_discovered_cb( + service, service_id); + } else { + service_id_context->second.discovery_callback.service_lost_cb(service, + service_id); + } + }); } void MediumEnvironment::RunOnMediumEnvironmentThread( @@ -188,7 +197,9 @@ void MediumEnvironment::RegisterBluetoothMedium( auto* owned_adapter = context.adapter; NEARBY_LOG(INFO, "Registered: medium=%p; adapter=%p", &medium, owned_adapter); - for (auto& [adapter, device] : bluetooth_adapters_) { + for (auto& adapter_device : bluetooth_adapters_) { + auto& adapter = adapter_device.first; + auto& device = adapter_device.second; if (adapter == nullptr) continue; OnBluetoothDeviceStateChanged(context, *device, adapter->GetName(), adapter->GetScanMode(), @@ -212,7 +223,9 @@ void MediumEnvironment::UpdateBluetoothMedium( "Updated: this=%p; medium=%p; adapter=%p; name=%s; enabled=%d; mode=%d", this, &medium, owned_adapter, owned_adapter->GetName().c_str(), owned_adapter->IsEnabled(), owned_adapter->GetScanMode()); - for (auto& [adapter, device] : bluetooth_adapters_) { + for (auto& adapter_device : bluetooth_adapters_) { + auto& adapter = adapter_device.first; + auto& device = adapter_device.second; if (adapter == nullptr) continue; OnBluetoothDeviceStateChanged(context, *device, adapter->GetName(), adapter->GetScanMode(), @@ -271,13 +284,10 @@ void MediumEnvironment::SendWebRtcSignalingMessage(absl::string_view peer_id, }); } -void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium, - api::WifiLanService& service) { +void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, &service]() { - wifi_lan_mediums_.insert({&medium, WifiLanMediumContext{ - .service = &service, - }}); + RunOnMediumEnvironmentThread([this, &medium]() { + wifi_lan_mediums_.insert({&medium, WifiLanMediumContext{}}); NEARBY_LOG(INFO, "Registered: medium=%p", &medium); }); } @@ -290,18 +300,31 @@ void MediumEnvironment::UpdateWifiLanMediumForAdvertising( 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."); + NEARBY_LOG(INFO, + "UpdateWifiLanMediumForAdvertising failed. There is no medium " + "registered."); return; } auto& context = item->second; - context.advertising = enabled; - NEARBY_LOG( - INFO, - "Update WifiLan medium for advertising: this=%p; medium=%p; name=%s; " - "enabled=%d; advertising=%d", - this, &medium, service.GetName().c_str(), enabled, context.advertising); - for (auto& [local_medium, info] : wifi_lan_mediums_) { + context.wifi_lan_service = &service; + auto service_id_context = context.services.find(service_id); + if (service_id_context == context.services.end()) { + WifiLanServiceIdContext id_context{ + .advertising = enabled, + }; + context.services.emplace(service_id, std::move(id_context)); + } else { + service_id_context->second.advertising = enabled; + } + NEARBY_LOG(INFO, + "Update WifiLan medium for advertising: this=%p; medium=%p; " + "service_id=%s; name=%s; " + "enabled=%d", + this, &medium, service_id.c_str(), service.GetName().c_str(), + enabled); + for (auto& medium_info : wifi_lan_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; // Do not send notification to the same medium. if (local_medium == &medium) continue; OnWifiLanServiceStateChanged(info, service, service_id, enabled); @@ -310,45 +333,56 @@ void MediumEnvironment::UpdateWifiLanMediumForAdvertising( } void MediumEnvironment::UpdateWifiLanMediumForDiscovery( - api::WifiLanMedium& medium, api::WifiLanService& service, - const std::string& service_id, WifiLanDiscoveredServiceCallback callback, - bool enabled) { + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanDiscoveredServiceCallback callback, bool enabled) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, &service, service_id, + RunOnMediumEnvironmentThread([this, &medium, 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."); + NEARBY_LOG(INFO, + "UpdateWifiLanMediumForDiscovery failed. There is no medium " + "registered."); return; } auto& context = item->second; - context.discovery_callback = std::move(callback); - NEARBY_LOG( - INFO, - "Update WifiLan medium for discovery: this=%p; medium=%p; name=%s; " - "enabled=%d; advertising=%d", - this, &medium, service.GetName().c_str(), enabled, context.advertising); - for (auto& [local_medium, info] : wifi_lan_mediums_) { + auto service_id_context = context.services.find(service_id); + if (service_id_context == context.services.end()) { + WifiLanServiceIdContext id_context{ + .discovery_callback = std::move(callback), + }; + context.services.emplace(service_id, std::move(id_context)); + } else { + service_id_context->second.discovery_callback = std::move(callback); + } + NEARBY_LOG(INFO, + "Update WifiLan medium for discovery: this=%p; medium=%p; " + "service_id=%s; enabled=%d; ", + this, &medium, service_id.c_str(), enabled); + for (auto& medium_info : wifi_lan_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; // Do not send notification to the same medium. if (local_medium == &medium) continue; // Search advertising mediums and send notification. - if (info.advertising && enabled) { - OnWifiLanServiceStateChanged(context, *(info.service), service_id, - enabled); + for (auto& service_id_context : info.services) { + auto& service_id = service_id_context.first; + auto& id_context = service_id_context.second; + if (id_context.advertising && enabled) { + OnWifiLanServiceStateChanged(context, *(info.wifi_lan_service), + service_id, enabled); + } } } }); } void MediumEnvironment::UpdateWifiLanMediumForAcceptedConnection( - api::WifiLanMedium& medium, api::WifiLanService& service, - const std::string& service_id, - WifiLanAcceptedConnectionCallback accepted_connection_callback) { + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanAcceptedConnectionCallback callback) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, &service, service_id, - accepted_connection_callback = - std::move(accepted_connection_callback)]() { + RunOnMediumEnvironmentThread([this, &medium, service_id, + callback = std::move(callback)]() { auto item = wifi_lan_mediums_.find(&medium); if (item == wifi_lan_mediums_.end()) { NEARBY_LOG( @@ -356,12 +390,20 @@ void MediumEnvironment::UpdateWifiLanMediumForAcceptedConnection( return; } auto& context = item->second; - context.accepted_connection_callback = - std::move(accepted_connection_callback); + auto service_id_context = context.services.find(service_id); + if (service_id_context == context.services.end()) { + WifiLanServiceIdContext id_context{ + .accepted_connection_callback = std::move(callback), + }; + context.services.emplace(service_id, std::move(id_context)); + } else { + service_id_context->second.accepted_connection_callback = + std::move(callback); + } NEARBY_LOG(INFO, "Update WifiLan medium for accepted callback: this=%p; " - "medium=%p; name=%s; ", - this, &medium, service.GetName().c_str()); + "medium=%p; service_id=%s; ", + this, &medium, service_id.c_str()); }); } @@ -387,7 +429,11 @@ void MediumEnvironment::CallWifiLanAcceptedConnectionCallback( return; } auto& info = item->second; - info.accepted_connection_callback.accepted_cb(socket, service_id); + auto service_id_context = info.services.find(service_id); + if (service_id_context != info.services.end()) { + service_id_context->second.accepted_connection_callback.accepted_cb( + socket, service_id); + } }); } diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 31fab859..0464a598 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -15,6 +15,15 @@ namespace location { namespace nearby { +// Environment config that can control availability of certain mediums for +// testing. +struct EnvironmentConfig { + // Control whether WEB_RTC medium is enabled in the environment. + // This is currently set to false, due to http://b/139734036 that would lead + // to flaky tests. + bool webrtc_enabled = false; +}; + // MediumEnvironment is a simulated environment which allows multiple instances // of simulated HW devices to "work" together as if they are physical. // For each medium type it provides necessary methods to implement @@ -30,6 +39,7 @@ class MediumEnvironment { api::WifiLanMedium::DiscoveredServiceCallback; using WifiLanAcceptedConnectionCallback = api::WifiLanMedium::AcceptedConnectionCallback; + MediumEnvironment(const MediumEnvironment&) = delete; MediumEnvironment& operator=(const MediumEnvironment&) = delete; @@ -42,7 +52,7 @@ class MediumEnvironment { // tests that are already using it and relying on it being ON. // Enables Medium environment. - void Start(); + void Start(EnvironmentConfig config = EnvironmentConfig()); // Disables Medium environment. void Stop(); @@ -93,6 +103,8 @@ class MediumEnvironment { // Removes medium-related info. This should correspond to device power off. void UnregisterBluetoothMedium(api::BluetoothClassicMedium& medium); + const EnvironmentConfig& GetEnvironmentConfig(); + // Registers |callback| to receive messages sent to device with id |self_id|. void RegisterWebRtcSignalingMessenger(absl::string_view self_id, OnSignalingMessageCallback callback); @@ -107,8 +119,7 @@ class MediumEnvironment { // Adds medium-related info to allow for discovery/advertising to work. // This provides acccess to this medium from other mediums, when protocol // expects they should communicate. - void RegisterWifiLanMedium(api::WifiLanMedium& medium, - api::WifiLanService& service); + void RegisterWifiLanMedium(api::WifiLanMedium& medium); // Updates advertising info to indicate the current medium is exposing // advertising event. @@ -126,16 +137,14 @@ class MediumEnvironment { // with user-specified callback when discovery is enabled, and with default // (empty) callback otherwise. void UpdateWifiLanMediumForDiscovery( - api::WifiLanMedium& medium, api::WifiLanService& service, - const std::string& service_id, - WifiLanDiscoveredServiceCallback discovery_callback, bool enabled); + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanDiscoveredServiceCallback callback, bool enabled); // Updates Accepted connection callback info to allow for dispatch of // advertising events. void UpdateWifiLanMediumForAcceptedConnection( - api::WifiLanMedium& medium, api::WifiLanService& service, - const std::string& service_id, - WifiLanAcceptedConnectionCallback accepted_connection_callback); + api::WifiLanMedium& medium, const std::string& service_id, + WifiLanAcceptedConnectionCallback callback); // Removes medium-related info. This should correspond to device power off. void UnregisterWifiLanMedium(api::WifiLanMedium& medium); @@ -154,13 +163,17 @@ class MediumEnvironment { absl::flat_hash_map devices; }; - struct WifiLanMediumContext { + struct WifiLanServiceIdContext { WifiLanDiscoveredServiceCallback discovery_callback; WifiLanAcceptedConnectionCallback accepted_connection_callback; - api::WifiLanService* service = nullptr; bool advertising = false; }; + struct WifiLanMediumContext { + api::WifiLanService* wifi_lan_service = nullptr; + 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 @@ -185,6 +198,7 @@ class MediumEnvironment { std::atomic_int job_count_ = 0; std::atomic_bool enable_notifications_ = false; SingleThreadExecutor executor_; + EnvironmentConfig config_; // The following data members are accessed in the context of a private // executor_ thread. diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 9055a604..ae64a943 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -105,6 +105,7 @@ cc_library( "//platform_v2/api:comm", "//platform_v2/api:platform", "//platform_v2/api:types", + "//platform_v2/base:test_util", "//platform_v2/impl/shared:file", "//absl/base:core_headers", "//absl/memory", diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index cf5c20f9..31d17c6a 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -18,6 +18,7 @@ #include "platform_v2/api/submittable_executor.h" #include "platform_v2/api/webrtc.h" #include "platform_v2/api/wifi.h" +#include "platform_v2/base/medium_environment.h" #include "platform_v2/impl/g3/atomic_boolean.h" #include "platform_v2/impl/g3/atomic_reference.h" #include "platform_v2/impl/g3/bluetooth_adapter.h" @@ -133,7 +134,11 @@ std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { } std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { - return absl::make_unique(); + if (MediumEnvironment::Instance().GetEnvironmentConfig().webrtc_enabled) { + return absl::make_unique(); + } else { + return nullptr; + } } std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { diff --git a/cpp/platform_v2/impl/g3/wifi_lan.cc b/cpp/platform_v2/impl/g3/wifi_lan.cc index 1b68f30c..e310c76d 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.cc +++ b/cpp/platform_v2/impl/g3/wifi_lan.cc @@ -156,7 +156,7 @@ Exception WifiLanServerSocket::DoClose() { WifiLanMedium::WifiLanMedium() { service_.SetMedium(this); auto& env = MediumEnvironment::Instance(); - env.RegisterWifiLanMedium(*this, service_); + env.RegisterWifiLanMedium(*this); } WifiLanMedium::~WifiLanMedium() { @@ -167,7 +167,6 @@ WifiLanMedium::~WifiLanMedium() { StopAdvertising(advertising_info_.service_id); StopDiscovery(discovering_info_.service_id); - accept_loops_runner_.Shutdown(); NEARBY_LOG(INFO, "WifiLanMedium dtor advertising_accept_thread_running_ = %d", acceptance_thread_running_.load()); @@ -181,12 +180,11 @@ WifiLanMedium::~WifiLanMedium() { } } -bool WifiLanMedium::StartAdvertising( - const std::string& service_id, - const std::string& wifi_lan_service_info_name) { +bool WifiLanMedium::StartAdvertising(const std::string& service_id, + const std::string& service_info_name) { NEARBY_LOG(INFO, - "G3 WifiLan StartAdvertising: service_id=%s, service_name=%s", - service_id.c_str(), wifi_lan_service_info_name.c_str()); + "G3 WifiLan StartAdvertising: service_id=%s, service_info_name=%s", + service_id.c_str(), service_info_name.c_str()); auto& env = MediumEnvironment::Instance(); env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, true); @@ -216,8 +214,9 @@ bool WifiLanMedium::StopAdvertising(const std::string& service_id) { { absl::MutexLock lock(&mutex_); if (advertising_info_.Empty()) { - NEARBY_LOG( - INFO, "Can't stop advertising because we never started advertising."); + NEARBY_LOG(INFO, + "G3 WifiLan StopAdvertising: Can't stop advertising because " + "we never started advertising."); return false; } advertising_info_.Clear(); @@ -227,14 +226,17 @@ bool WifiLanMedium::StopAdvertising(const std::string& service_id) { env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, false); accept_loops_runner_.Shutdown(); if (server_socket_ == nullptr) { - NEARBY_LOG(ERROR, "Failed to find WifiLan Server socket: service_id=%s", - service_id.c_str()); + NEARBY_LOGS(ERROR) << "G3 WifiLan StopAdvertising: failed to find WifiLan " + "Server socket: service_id=" + << service_id; // Fall through for server socket not found. return true; } if (!server_socket_->Close().Ok()) { - NEARBY_LOG(INFO, "Failed to close WifiLan server socket for %s.", + NEARBY_LOG(INFO, + "G3 WifiLan StopAdvertising: Failed to close WifiLan server " + "socket for %s.", service_id.c_str()); return false; } @@ -247,8 +249,8 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, NEARBY_LOG(INFO, "G3 WifiLan StartDiscovery: service_id=%s", service_id.c_str()); auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForDiscovery(*this, service_, service_id, - std::move(callback), true); + env.UpdateWifiLanMediumForDiscovery(*this, service_id, std::move(callback), + true); { absl::MutexLock lock(&mutex_); discovering_info_.service_id = service_id; @@ -262,15 +264,16 @@ bool WifiLanMedium::StopDiscovery(const std::string& service_id) { { absl::MutexLock lock(&mutex_); if (discovering_info_.Empty()) { - NEARBY_LOG( - INFO, "Can't stop discovering because we never started discovering."); + NEARBY_LOG(INFO, + "G3 WifiLan StopDiscovery: Can't stop discovering because we " + "never started discovering."); return false; } discovering_info_.Clear(); } auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForDiscovery(*this, service_, service_id, {}, false); + env.UpdateWifiLanMediumForDiscovery(*this, service_id, {}, false); return true; } @@ -279,8 +282,7 @@ bool WifiLanMedium::StartAcceptingConnections( NEARBY_LOG(INFO, "G3 WifiLan StartAcceptingConnections: service_id=%s", service_id.c_str()); auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForAcceptedConnection(*this, service_, service_id, - callback); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, callback); return true; } @@ -288,7 +290,7 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { NEARBY_LOG(INFO, "G3 WifiLan StopAcceptingConnections: service_id=%s", service_id.c_str()); auto& env = MediumEnvironment::Instance(); - env.UpdateWifiLanMediumForAcceptedConnection(*this, service_, service_id, {}); + env.UpdateWifiLanMediumForAcceptedConnection(*this, service_id, {}); return true; } @@ -301,28 +303,31 @@ std::unique_ptr WifiLanMedium::Connect( if (!medium) return {}; // Can't find medium. Bail out. - WifiLanServerSocket* server_socket = nullptr; + WifiLanServerSocket* remote_server_socket = nullptr; NEARBY_LOG(INFO, "G3 WifiLan Connect [peer]: medium=%p, service=%p, service_id=%s", medium, &remote_service, service_id.c_str()); // Then, find our server socket context in this medium. { absl::MutexLock medium_lock(&medium->mutex_); - server_socket = medium->server_socket_.get(); - if (server_socket == nullptr) { - NEARBY_LOG(ERROR, "Failed to find WifiLan Server socket: service_id=%s", + remote_server_socket = medium->server_socket_.get(); + if (remote_server_socket == nullptr) { + NEARBY_LOG(ERROR, + "G3 WifiLan Connect: Failed to find WifiLan Server socket: " + "service_id=%s", service_id.c_str()); + // Fall through for server socket not found. return {}; } } auto socket = std::make_unique(); // Finally, Request to connect to this socket. - if (!server_socket->Connect(*socket)) { - NEARBY_LOG( - ERROR, - "Failed to connect to existing WifiLan Server socket: service_id=%s", - service_id.c_str()); + if (!remote_server_socket->Connect(*socket)) { + NEARBY_LOG(ERROR, + "G3 WifiLan Connect: Failed to connect to existing WifiLan " + "Server socket: service_id=%s", + service_id.c_str()); return {}; } diff --git a/cpp/platform_v2/impl/g3/wifi_lan.h b/cpp/platform_v2/impl/g3/wifi_lan.h index 45bdfbfd..7bc0e0dd 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.h +++ b/cpp/platform_v2/impl/g3/wifi_lan.h @@ -20,21 +20,24 @@ namespace g3 { class WifiLanMedium; -// Opaque wrapper over a WifiLan service which contains encoded WifiLan service -// info name. +// Opaque wrapper over a WifiLan service which contains packed +// |WifiLanServiceInfo| string name. class WifiLanService : public api::WifiLanService { public: - explicit WifiLanService(std::string name) : name_(std::move(name)) {} + explicit WifiLanService(std::string service_info_name) + : service_info_name_(std::move(service_info_name)) {} ~WifiLanService() override = default; - void SetName(std::string name) { name_ = std::move(name); } - std::string GetName() const override { return name_; } + void SetName(std::string service_info_name) { + service_info_name_ = std::move(service_info_name); + } + std::string GetName() const override { return service_info_name_; } void SetMedium(WifiLanMedium* medium) { medium_ = medium; } WifiLanMedium* GetMedium() { return medium_; } private: - std::string name_; + std::string service_info_name_; WifiLanMedium* medium_ = nullptr; }; @@ -151,7 +154,7 @@ class WifiLanMedium : public api::WifiLanMedium { ~WifiLanMedium() override; bool StartAdvertising(const std::string& service_id, - const std::string& wifi_lan_service_info_name) override + const std::string& service_info_name) override ABSL_LOCKS_EXCLUDED(mutex_); bool StopAdvertising(const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); @@ -201,7 +204,7 @@ class WifiLanMedium : public api::WifiLanMedium { }; absl::Mutex mutex_; - WifiLanService service_{"wifi_lan_service_info_name"}; + WifiLanService service_{"unknown G3 WifiLan service"}; // A thread pool dedicated to running all the accept loops from // StartAdvertising(). @@ -211,8 +214,6 @@ class WifiLanMedium : public api::WifiLanMedium { // A thread pool dedicated to wait to complete the accept_loops_runner_. MultiThreadExecutor close_accept_loops_runner_{kMaxConcurrentAcceptLoops}; - // TODO(edwinwu): Extend it to hashmap to accept multiple sockets for multiple - // entrance. // A server socket is established when start advertising. std::unique_ptr server_socket_; AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); diff --git a/cpp/platform_v2/public/wifi_lan.cc b/cpp/platform_v2/public/wifi_lan.cc index e1894a17..f5882f7e 100644 --- a/cpp/platform_v2/public/wifi_lan.cc +++ b/cpp/platform_v2/public/wifi_lan.cc @@ -8,8 +8,8 @@ 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); + const std::string& service_info_name) { + return impl_->StartAdvertising(service_id, service_info_name); } bool WifiLanMedium::StopAdvertising(const std::string& service_id) { @@ -34,13 +34,18 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, &service, absl::make_unique()); auto& context = *pair.first->second; if (!pair.second) { - NEARBY_LOG(INFO, "Adding (again) service=%p, impl=%p", - &context.service, &service); + NEARBY_LOG(INFO, + "Discovering (again) service=%p, impl=%p, " + "service_info_name=%s", + &context.service, &service, + service.GetName().c_str()); return; } context.service = WifiLanService(&service); - NEARBY_LOG(INFO, "Adding service=%p, impl=%p", &context.service, - &service); + NEARBY_LOG( + INFO, + "Discovering service=%p, impl=%p, service_info_name=%s", + &context.service, &service, service.GetName().c_str()); discovered_service_callback_.service_discovered_cb( context.service, service_id); }, @@ -86,12 +91,13 @@ bool WifiLanMedium::StartAcceptingConnections( &socket, absl::make_unique()); auto& context = *pair.first->second; if (!pair.second) { - NEARBY_LOG(INFO, "Adding (again) socket=%p, impl=%p", + NEARBY_LOG(INFO, "Accepting (again) socket=%p, impl=%p", &context.socket, &socket); context.socket = WifiLanSocket(&socket); + } else { + NEARBY_LOG(INFO, "Accepting socket=%p, impl=%p", + &context.socket, &socket); } - NEARBY_LOG(INFO, "Adding socket=%p, impl=%p", &context.socket, - &socket); accepted_connection_callback_.accepted_cb(context.socket, service_id); }, diff --git a/cpp/platform_v2/public/wifi_lan.h b/cpp/platform_v2/public/wifi_lan.h index f2403f04..c94ac1b8 100644 --- a/cpp/platform_v2/public/wifi_lan.h +++ b/cpp/platform_v2/public/wifi_lan.h @@ -12,7 +12,8 @@ namespace location { namespace nearby { -// Opaque wrapper over a WifiLan service which contains encoded service name. +// Opaque wrapper over a WifiLan service which contains packed +// |WifiLanServiceInfo| string name. class WifiLanService final { public: WifiLanService() = default; @@ -113,7 +114,7 @@ class WifiLanMedium final { ~WifiLanMedium() = default; bool StartAdvertising(const std::string& service_id, - const std::string& wifi_lan_service_info_name); + const std::string& service_info_name); bool StopAdvertising(const std::string& service_id); // Returns true once the WifiLan discovery has been initiated. diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 0f463a0b..0464f602 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -80,6 +80,9 @@ enum CommonError { // 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; + // Others error, the error happens when user cancel the flow, it's not a + // real failure. + FLOW_CANCELED = 5; // Reserved 5 to 30 } @@ -141,8 +144,56 @@ enum StartDiscoveringError { START_EXTENDED_DISCOVERING_FAILED = 33; // System error, failed to start discovering. START_DISCOVERING_FAILED = 34; + // Network error, invalid remote target info, discover the nearby devices but + // the information not valid. + INVALID_TARGET_INFO = 35; + // Network error, failed to fetch the advertisement from the remote devices. + FETCH_ADVERTISEMENT_FAILED = 36; + // Network error, failed to fetch the advertisement via GATT from the remote + // devices. + GATT_FETCH_ADVERTISEMENT_FAILED = 37; + // Network error, failed to fetch the advertisement via L2CAP from the remote + // devices. + L2CAP_FETCH_ADVERTISEMENT_FAILED = 38; + // System error, the medium not available when trying to fetch advertisements. + // e.g. fetch advertisements but BT disabled unexpectedly. + NOT_AVAILABLE_TO_FETCH_ADVERTISEMENT = 39; + // System error, failed to acquire WifiAwareSession + ACQUIRE_WIFI_AWARE_SESSION_FOR_DISCOVERING_FAILED = 40; - // Next ID :34 + // Next ID :40 +} + +// The error for event CONNECT. The range between 31 and 99. +enum ConnectError { + // Network error, failed to connect to remote device because we lost the + // target without MAC address to connect to. e.g. BLE cache MAC address in + // medium, it may lost when just try to connect. + UNEXPECT_TARGET_LOST = 31; + // System error, failed to establish connection on GATT + ESTABLISH_GATT_CONNECTION_FAILED = 32; + // System error, failed to establish connection on L2CAP + ESTABLISH_L2CAP_CONNECTION_FAILED = 33; + // Developing error, the MAC address not valid for connecting + INVALID_MAC_ADDRESS = 34; + // Others error, unexpected interrupt when sleep before connect GATT for + // waiting GATT server ready. Should not hapepen, it may be the process be + // killed. + SLEEP_BEFORE_CONNECT_GATT_INTERRUPTED = 35; + // Others error, unexpected interrupt when sleep after GATT connect to wait + // GATT connection ready to transfer data. Should not hapepen, it may be + // the process be killed. + SLEEP_AFTER_GATT_CONNECTED_INTERRUPTED = 36; + // Network error, failed to configure the GATT connection priority, it may + // failed when the connection still wait for the status update from network or + // just a RemoteException. + REQUEST_GATT_CONNECTION_PRIORITY_FAILED = 37; + // Network error, failed to change connection for data transferring on L2CAP + // connection. + L2CAP_SWITCH_TO_DATA_TRANSFERRING_FAILED = 38; + // Network error, failed to change connection for data transferring on GATT + // connection. + GATT_SWITCH_TO_DATA_TRANSFERRING_FAILED = 39; } enum Description { @@ -199,4 +250,25 @@ enum Description { SCAN_FAILED_BLUETOOTH_DISABLED = 49; SCAN_FILTERS_NOT_ALLOWED_FOR_LOCATION = 50; BLUETOOTH_SCAN_REJUVENATE_FAILED = 51; + NULL_BLE_PERIPHERAL = 52; + NULL_BLUETOOTH_GATT = 53; + UNEXPECTED_BLUETOOTH_STATE = 54; + REMOTE_EXCEPTION = 55; + INVALID_BLUETOOTH_SOCKET_STATE_BEFORE_CONNECT = 56; + BLUETOOTH_SOCKET_CLOSED_AFTER_CONNECTED = 57; + INVALID_BLUETOOTH_CHANNEL = 58; + NULL_BLUETOOTH_DEVICE = 59; + NULL_BLUETOOTH_PROXY = 60; + INVALID_PACKET_LENGTH = 61; + INVALID_PACKET_BYTES = 62; + UNEXPECTED_EOF_EXCEPTION = 63; + SOCKET_CLOSED_OR_TIMEOUT = 64; + INVALID_IPV4_ADDRESS = 65; + INVALID_IPV6_ADDRESS = 66; + NULL_ADDRESS = 67; + INVALID_VERSION = 68; + SET_CONNECTION_PRIORITY_FAILED = 69; + SET_CONNECTION_PRIORITY_INTERRUPTED = 70; + UNKNOWN_IO_EXCEPTION = 71; + READ_CHARACTERISTIC_FAILED = 72; } diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index 32aeee98..962d8e82 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -126,6 +126,18 @@ enum EventType { // Receiver taps a privacy notification. TAP_PRIVACY_NOTIFICATION = 33; + + // Receiver taps a help page. + TAP_HELP = 34; + + // Receiver taps a feedback. + TAP_FEEDBACK = 35; + + // Receiver adds quick settings tile. + ADD_QUICK_SETTINGS_TILE = 36; + + // Receiver removes quick settings tile. + REMOVE_QUICK_SETTINGS_TILE = 37; } // Event category to differentiate whether this comes from sender or receiver, From c673bf6ac005ca6b362048b56218445e52d33de2 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 25 Aug 2020 11:15:38 -0700 Subject: [PATCH 40/52] Roll forward to cl/328359974 Change-Id: If2b57ecc852aecf7dea454648f485fd7c08e72a9 --- cpp/core_v2/BUILD | 5 +- cpp/core_v2/core.cc | 3 +- cpp/core_v2/core.h | 3 +- cpp/core_v2/internal/BUILD | 6 + cpp/core_v2/internal/base_endpoint_channel.cc | 3 +- cpp/core_v2/internal/base_pcp_handler.cc | 229 +++--- cpp/core_v2/internal/base_pcp_handler.h | 106 ++- cpp/core_v2/internal/base_pcp_handler_test.cc | 278 +++++-- cpp/core_v2/internal/ble_advertisement.cc | 180 ++--- cpp/core_v2/internal/ble_advertisement.h | 59 +- .../internal/ble_advertisement_test.cc | 356 ++++++--- cpp/core_v2/internal/ble_endpoint_channel.cc | 45 ++ cpp/core_v2/internal/ble_endpoint_channel.h | 29 + cpp/core_v2/internal/bluetooth_device_name.cc | 37 +- cpp/core_v2/internal/bluetooth_device_name.h | 10 +- .../internal/bluetooth_device_name_test.cc | 37 +- cpp/core_v2/internal/client_proxy.cc | 60 +- cpp/core_v2/internal/client_proxy.h | 10 +- cpp/core_v2/internal/client_proxy_test.cc | 24 +- cpp/core_v2/internal/encryption_runner.cc | 24 +- cpp/core_v2/internal/encryption_runner.h | 4 +- .../internal/endpoint_channel_manager.cc | 5 + .../internal/endpoint_channel_manager.h | 9 +- cpp/core_v2/internal/endpoint_manager.cc | 9 +- cpp/core_v2/internal/endpoint_manager.h | 8 +- cpp/core_v2/internal/endpoint_manager_test.cc | 19 +- cpp/core_v2/internal/mediums/BUILD | 5 + cpp/core_v2/internal/mediums/ble.cc | 269 +++++++ cpp/core_v2/internal/mediums/ble.h | 162 ++++ cpp/core_v2/internal/mediums/ble_test.cc | 162 ++++ .../internal/mediums/bluetooth_classic.cc | 6 + .../internal/mediums/bluetooth_classic.h | 3 + cpp/core_v2/internal/mediums/mediums.cc | 2 + cpp/core_v2/internal/mediums/mediums.h | 5 + cpp/core_v2/internal/mediums/wifi_lan.cc | 14 +- cpp/core_v2/internal/mediums/wifi_lan.h | 5 +- cpp/core_v2/internal/mediums/wifi_lan_test.cc | 21 +- .../internal/mock_service_controller.h | 3 +- cpp/core_v2/internal/offline_frames.cc | 133 +++- cpp/core_v2/internal/offline_frames.h | 38 +- cpp/core_v2/internal/offline_frames_test.cc | 64 +- .../internal/offline_service_controller.cc | 8 +- .../internal/offline_service_controller.h | 7 +- .../offline_service_controller_test.cc | 119 +-- .../internal/offline_simulation_user.cc | 27 +- .../internal/offline_simulation_user.h | 35 +- .../internal/p2p_cluster_pcp_handler.cc | 753 ++++++++++++------ .../internal/p2p_cluster_pcp_handler.h | 73 +- .../internal/p2p_cluster_pcp_handler_test.cc | 93 ++- .../p2p_point_to_point_pcp_handler.cc | 3 +- .../internal/p2p_point_to_point_pcp_handler.h | 6 +- cpp/core_v2/internal/p2p_star_pcp_handler.cc | 4 +- cpp/core_v2/internal/p2p_star_pcp_handler.h | 6 +- cpp/core_v2/internal/payload_manager_test.cc | 67 +- cpp/core_v2/internal/pcp_handler.h | 5 +- cpp/core_v2/internal/pcp_manager.cc | 7 +- cpp/core_v2/internal/pcp_manager.h | 17 +- cpp/core_v2/internal/pcp_manager_test.cc | 67 +- cpp/core_v2/internal/service_controller.h | 6 +- .../internal/service_controller_router.cc | 34 +- .../internal/service_controller_router.h | 1 + .../service_controller_router_test.cc | 12 +- cpp/core_v2/internal/simulation_user.cc | 22 +- cpp/core_v2/internal/simulation_user.h | 26 +- cpp/core_v2/internal/wifi_lan_service_info.cc | 41 +- cpp/core_v2/internal/wifi_lan_service_info.h | 10 +- .../internal/wifi_lan_service_info_test.cc | 30 +- cpp/core_v2/listeners.h | 9 +- cpp/core_v2/options.h | 65 +- cpp/core_v2/params.h | 11 +- cpp/core_v2/status.h | 1 + cpp/platform/impl/g3/BUILD | 1 - cpp/platform/impl/sample/BUILD | 6 +- cpp/platform/impl/shared/sample/BUILD | 1 - cpp/platform_v2/api/ble.h | 77 +- cpp/platform_v2/api/bluetooth_adapter.h | 3 + cpp/platform_v2/api/bluetooth_classic.h | 5 + cpp/platform_v2/api/wifi_lan.h | 10 +- cpp/platform_v2/base/BUILD | 4 + cpp/platform_v2/base/bluetooth_utils.cc | 61 ++ cpp/platform_v2/base/bluetooth_utils.h | 32 + cpp/platform_v2/base/bluetooth_utils_test.cc | 75 ++ cpp/platform_v2/base/byte_array.h | 2 +- cpp/platform_v2/base/medium_environment.cc | 188 +++++ cpp/platform_v2/base/medium_environment.h | 73 +- cpp/platform_v2/impl/g3/BUILD | 7 +- cpp/platform_v2/impl/g3/ble.cc | 341 ++++++++ cpp/platform_v2/impl/g3/ble.h | 213 +++++ cpp/platform_v2/impl/g3/bluetooth_adapter.cc | 40 +- cpp/platform_v2/impl/g3/bluetooth_adapter.h | 47 +- cpp/platform_v2/impl/g3/bluetooth_classic.cc | 17 +- cpp/platform_v2/impl/g3/bluetooth_classic.h | 5 +- cpp/platform_v2/impl/g3/platform.cc | 4 +- cpp/platform_v2/impl/g3/wifi_lan.cc | 51 +- cpp/platform_v2/impl/g3/wifi_lan.h | 19 +- cpp/platform_v2/impl/shared/BUILD | 4 +- cpp/platform_v2/public/BUILD | 4 + cpp/platform_v2/public/ble.cc | 127 +++ cpp/platform_v2/public/ble.h | 146 ++++ cpp/platform_v2/public/ble_test.cc | 189 +++++ cpp/platform_v2/public/bluetooth_adapter.h | 23 + cpp/platform_v2/public/bluetooth_classic.h | 3 + cpp/platform_v2/public/wifi_lan.cc | 40 +- cpp/platform_v2/public/wifi_lan.h | 2 + cpp/platform_v2/public/wifi_lan_test.cc | 28 +- proto/connections/offline_wire_formats.proto | 6 + proto/connections_enums.proto | 9 +- proto/error_code_enums.proto | 103 ++- proto/magic_pair_enums.proto | 3 + proto/sharing_enums.proto | 19 + 110 files changed, 4758 insertions(+), 1245 deletions(-) create mode 100644 cpp/core_v2/internal/ble_endpoint_channel.cc create mode 100644 cpp/core_v2/internal/ble_endpoint_channel.h create mode 100644 cpp/core_v2/internal/mediums/ble.cc create mode 100644 cpp/core_v2/internal/mediums/ble.h create mode 100644 cpp/core_v2/internal/mediums/ble_test.cc create mode 100644 cpp/platform_v2/base/bluetooth_utils.cc create mode 100644 cpp/platform_v2/base/bluetooth_utils.h create mode 100644 cpp/platform_v2/base/bluetooth_utils_test.cc create mode 100644 cpp/platform_v2/impl/g3/ble.cc create mode 100644 cpp/platform_v2/impl/g3/ble.h create mode 100644 cpp/platform_v2/public/ble.cc create mode 100644 cpp/platform_v2/public/ble.h create mode 100644 cpp/platform_v2/public/ble_test.cc diff --git a/cpp/core_v2/BUILD b/cpp/core_v2/BUILD index eed3c011..993686cc 100644 --- a/cpp/core_v2/BUILD +++ b/cpp/core_v2/BUILD @@ -6,9 +6,7 @@ cc_library( hdrs = [ "core.h", ], - visibility = [ - "//core_v2:__subpackages__", - ], + visibility = ["//visibility:private"], deps = [ ":core_types", "//core_v2/internal", @@ -42,6 +40,7 @@ cc_library( "//platform_v2/public:comm", "//platform_v2/public:logging", "//platform_v2/public:types", + "//proto:connections_enums_portable_proto", "//absl/strings", "//absl/types:variant", ], diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc index 412f987c..3b410f48 100644 --- a/cpp/core_v2/core.cc +++ b/cpp/core_v2/core.cc @@ -54,10 +54,11 @@ void Core::StopDiscovery(ResultCallback callback) { void Core::RequestConnection(absl::string_view endpoint_id, ConnectionRequestInfo info, + ConnectionOptions options, ResultCallback callback) { assert(!endpoint_id.empty()); - router_.RequestConnection(&client_, endpoint_id, info, callback); + router_.RequestConnection(&client_, endpoint_id, info, options, callback); } void Core::AcceptConnection(absl::string_view endpoint_id, diff --git a/cpp/core_v2/core.h b/cpp/core_v2/core.h index 3d4cd6a1..37509763 100644 --- a/cpp/core_v2/core.h +++ b/cpp/core_v2/core.h @@ -104,7 +104,8 @@ class Core { // issue with Bluetooth/WiFi. // Status::STATUS_ERROR if we failed to connect for any other reason. void RequestConnection(absl::string_view endpoint_id, - ConnectionRequestInfo info, ResultCallback callback); + ConnectionRequestInfo info, ConnectionOptions options, + ResultCallback callback); // Accepts a connection to a remote endpoint. This method must be called // before Payloads can be exchanged with the remote endpoint. diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index 8315217b..e68a6c28 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -4,6 +4,7 @@ cc_library( "base_endpoint_channel.cc", "base_pcp_handler.cc", "ble_advertisement.cc", + "ble_endpoint_channel.cc", "bluetooth_device_name.cc", "bluetooth_endpoint_channel.cc", "client_proxy.cc", @@ -28,6 +29,7 @@ cc_library( "base_endpoint_channel.h", "base_pcp_handler.h", "ble_advertisement.h", + "ble_endpoint_channel.h", "bluetooth_device_name.h", "bluetooth_endpoint_channel.h", "client_proxy.h", @@ -69,8 +71,10 @@ cc_library( "//proto:connections_enums_portable_proto", "//securegcm:ukey2", "//absl/base:core_headers", + "//absl/container:btree", "//absl/container:flat_hash_map", "//absl/container:flat_hash_set", + "//absl/functional:bind_front", "//absl/memory", "//absl/strings", "//absl/time", @@ -96,6 +100,7 @@ cc_library( deps = [ ":internal", "//core_v2:core_types", + "//platform_v2/base", "//platform_v2/base:test_util", "//platform_v2/public:types", "//testing/base/public:gunit", @@ -107,6 +112,7 @@ cc_library( cc_test( name = "core_v2_internal_test", size = "small", + timeout = "moderate", srcs = [ "base_endpoint_channel_test.cc", "base_pcp_handler_test.cc", diff --git a/cpp/core_v2/internal/base_endpoint_channel.cc b/cpp/core_v2/internal/base_endpoint_channel.cc index 569135f5..b90c19cf 100644 --- a/cpp/core_v2/internal/base_endpoint_channel.cc +++ b/cpp/core_v2/internal/base_endpoint_channel.cc @@ -108,8 +108,7 @@ ExceptionOr BaseEndpointChannel::Read() { // 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))); + crypto_context_->DecodeMessageFromPeer(input); if (decrypted_data) { result = ByteArray(std::move(*decrypted_data)); } else { diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 34d95e72..44f6779d 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -8,11 +8,13 @@ #include "core_v2/internal/offline_frames.h" #include "core_v2/internal/pcp_handler.h" +#include "core_v2/options.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/system_clock.h" #include "securegcm/d2d_connection_context_v1.h" #include "securegcm/ukey2_handshake.h" #include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" #include "absl/types/span.h" namespace location { @@ -25,9 +27,11 @@ using ::securegcm::UKey2Handshake; constexpr absl::Duration BasePcpHandler::kConnectionRequestReadTimeout; constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; -BasePcpHandler::BasePcpHandler(EndpointManager* endpoint_manager, +BasePcpHandler::BasePcpHandler(Mediums* mediums, + EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, Pcp pcp) - : endpoint_manager_(endpoint_manager), + : mediums_(mediums), + endpoint_manager_(endpoint_manager), channel_manager_(channel_manager), pcp_(pcp) {} @@ -58,25 +62,27 @@ Status BasePcpHandler::StartAdvertising(ClientProxy* client, const ConnectionOptions& options, const ConnectionRequestInfo& info) { Future response; + ConnectionOptions advertising_options = options.CompatibleOptions(); RunOnPcpHandlerThread( - [this, client, &service_id, &info, &options, &response]() { - auto result = StartAdvertisingImpl(client, service_id, - client->GenerateLocalEndpointId(), - info.name, options); + [this, client, &service_id, &info, &advertising_options, &response]() { + auto result = StartAdvertisingImpl( + client, service_id, client->GetLocalEndpointId(), + info.endpoint_info, advertising_options); if (!result.status.Ok()) { response.Set(result.status); return; } // Now that we've succeeded, mark the client as advertising. - advertising_options_ = options; + advertising_options_ = advertising_options; advertising_listener_ = info.listener; client->StartedAdvertising(service_id, GetStrategy(), info.listener, absl::MakeSpan(result.mediums)); response.Set({Status::kSuccess}); }); - return WaitForResult(absl::StrCat("StartAdvertising(", info.name, ")"), - client->GetClientId(), &response); + return WaitForResult( + absl::StrCat("StartAdvertising(", std::string(info.endpoint_info), ")"), + client->GetClientId(), &response); } void BasePcpHandler::StopAdvertising(ClientProxy* client) { @@ -95,10 +101,11 @@ Status BasePcpHandler::StartDiscovery(ClientProxy* client, const ConnectionOptions& options, const DiscoveryListener& listener) { Future response; + ConnectionOptions discovery_options = options.CompatibleOptions(); RunOnPcpHandlerThread( - [this, client, service_id, options, &listener, &response]() { + [this, client, service_id, discovery_options, &listener, &response]() { // Ask the implementation to attempt to start discovery. - auto result = StartDiscoveryImpl(client, service_id, options); + auto result = StartDiscoveryImpl(client, service_id, discovery_options); if (!result.status.Ok()) { response.Set(result.status); return; @@ -106,7 +113,7 @@ Status BasePcpHandler::StartDiscovery(ClientProxy* client, // Now that we've succeeded, mark the client as discovering and clear // out any old endpoints we had discovered. - discovery_options_ = options; + discovery_options_ = discovery_options; discovered_endpoints_.clear(); client->StartedDiscovery(service_id, GetStrategy(), listener, absl::MakeSpan(result.mediums)); @@ -125,7 +132,7 @@ void BasePcpHandler::StopDiscovery(ClientProxy* client) { latch.CountDown(); }); - WaitForLatch("stopDiscovery", &latch); + WaitForLatch("StopDiscovery", &latch); } void BasePcpHandler::WaitForLatch(const std::string& method_name, @@ -148,10 +155,12 @@ Status BasePcpHandler::WaitForResult(const std::string& method_name, NEARBY_LOG(INFO, "waiting for future to complete"); ExceptionOr result = future->Get(); if (!result.ok()) { - NEARBY_LOG(INFO, "Future completed with exception: %d", result.exception()); + NEARBY_LOG(INFO, "Future:[%s] completed with exception: %d", + method_name.c_str(), result.exception()); return {Status::kError}; } - NEARBY_LOG(INFO, "Future completed with status: %d", result.result().value); + NEARBY_LOG(INFO, "Future:[%s] completed with status: %d", method_name.c_str(), + result.result().value); return result.result(); } @@ -218,11 +227,12 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( endpoint_manager_->RegisterEndpoint( connection_info.client, endpoint_id, { - .remote_endpoint_name = connection_info.remote_endpoint_name, + .remote_endpoint_info = connection_info.remote_endpoint_info, .authentication_token = auth_token, .raw_authentication_token = raw_auth_token, .is_incoming_connection = connection_info.is_incoming, }, + connection_info.options, std::move(connection_info.channel), connection_info.listener); if (connection_info.result != nullptr) { @@ -265,9 +275,10 @@ void BasePcpHandler::OnEncryptionFailureRunnable( Status BasePcpHandler::RequestConnection(ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) { + const ConnectionRequestInfo& info, + const ConnectionOptions& options) { Future result; - RunOnPcpHandlerThread([this, client, &info, endpoint_id, &result]() { + RunOnPcpHandlerThread([this, client, &info, options, endpoint_id, &result]() { absl::Time start_time = SystemClock::ElapsedRealtime(); // If we already have a pending connection, then we shouldn't allow any more @@ -288,8 +299,7 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, return; } - std::vector endpoints; - auto endpoint = GetDiscoveredEndpoint(endpoint_id); + DiscoveredEndpoint* endpoint = GetDiscoveredEndpoint(endpoint_id); if (endpoint == nullptr) { NEARBY_LOG(INFO, "Discovered endpoint not found: id=%s", endpoint_id.c_str()); @@ -297,24 +307,24 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, return; } - auto webrtc_endpoint = absl::make_unique( - DiscoveredEndpoint{endpoint->endpoint_id, endpoint->endpoint_name, - endpoint->service_id, - proto::connections::Medium::WEB_RTC}, - CreatePeerIdFromAdvertisement(endpoint->service_id, - endpoint->endpoint_id, - endpoint->endpoint_name)); - endpoints.push_back(endpoint); - endpoints.push_back(webrtc_endpoint.get()); - - std::sort(endpoints.begin(), endpoints.end(), - [this](DiscoveredEndpoint* a, DiscoveredEndpoint* b) -> bool { - return IsPreferred(*a, *b); - }); + if (discovery_options_.allowed.web_rtc) { + auto webrtc_endpoint = std::make_shared( + DiscoveredEndpoint{endpoint->endpoint_id, endpoint->endpoint_info, + endpoint->service_id, + proto::connections::Medium::WEB_RTC}, + CreatePeerIdFromAdvertisement(endpoint->service_id, + endpoint->endpoint_id, + endpoint->endpoint_info)); + OnEndpointFound(client, webrtc_endpoint); + } + auto endpoints = GetDiscoveredEndpoints(endpoint_id); std::unique_ptr channel; ConnectImplResult connect_impl_result; + // TODO(b/156634369): add GetRemoteBluetoothMacAddressEndpoint here for + // valid remote mac address. + for (auto connect_endpoint : endpoints) { connect_impl_result = ConnectImpl(client, connect_endpoint); if (connect_impl_result.status.Ok()) { @@ -338,7 +348,7 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, // The first message we have to send, after connecting, is to tell the // endpoint about ourselves. Exception write_exception = WriteConnectionRequestFrame( - channel.get(), client->GenerateLocalEndpointId(), info.name, nonce, + channel.get(), client->GetLocalEndpointId(), info.endpoint_info, nonce, GetConnectionMediumsByPriority()); if (!write_exception.Ok()) { NEARBY_LOG(INFO, "Failed to send connection request: id=%s", @@ -354,18 +364,19 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, // We've successfully connected to the device, and are now about to jump on // to the EncryptionRunner thread to start running our encryption protocol. // We'll mark ourselves as pending in case we get another call to - // requestConnection or OnIncomingConnection, so that we can cancel the + // RequestConnection or OnIncomingConnection, so that we can cancel the // connection if needed. EndpointChannel* endpoint_channel = pending_connections_ .emplace(endpoint_id, PendingConnectionInfo{ .client = client, - .remote_endpoint_name = endpoint->endpoint_name, + .remote_endpoint_info = endpoint->endpoint_info, .nonce = nonce, .is_incoming = false, .start_time = start_time, .listener = info.listener, + .options = options, .result = MakeSwapper(&result), .channel = std::move(channel), }) @@ -374,20 +385,21 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, NEARBY_LOG(INFO, "Initiating secure connection: id=%s", endpoint_id.c_str()); // Next, we'll set up encryption. When it's done, our future will return and - // requestConnection() will finish. + // RequestConnection() will finish. encryption_runner_.StartClient(client, endpoint_id, endpoint_channel, GetResultListener()); }); NEARBY_LOG(INFO, "Waiting for connection to complete: id=%s", endpoint_id.c_str()); auto status = - WaitForResult(absl::StrCat("requestConnection(", endpoint_id, ")"), + WaitForResult(absl::StrCat("RequestConnection(", endpoint_id, ")"), client->GetClientId(), &result); NEARBY_LOG(INFO, "Wait is complete: id=%s; status=%d", endpoint_id.c_str(), status.value); return status; } +// Get any single discovered endpoint for a given endpoint_id. BasePcpHandler::DiscoveredEndpoint* BasePcpHandler::GetDiscoveredEndpoint( const std::string& endpoint_id) { auto it = discovered_endpoints_.find(endpoint_id); @@ -397,6 +409,20 @@ BasePcpHandler::DiscoveredEndpoint* BasePcpHandler::GetDiscoveredEndpoint( return it->second.get(); } +std::vector +BasePcpHandler::GetDiscoveredEndpoints(const std::string& endpoint_id) { + std::vector result; + auto it = discovered_endpoints_.equal_range(endpoint_id); + for (auto item = it.first; item != it.second; item++) { + result.push_back(item->second.get()); + } + std::sort(result.begin(), result.end(), + [this](DiscoveredEndpoint* a, DiscoveredEndpoint* b) -> bool { + return IsPreferred(*a, *b); + }); + return result; +} + void BasePcpHandler::PendingConnectionInfo::SetCryptoContext( std::unique_ptr ukey2) { this->ukey2 = std::move(ukey2); @@ -432,10 +458,10 @@ bool BasePcpHandler::CanReceiveIncomingConnection(ClientProxy* client) const { Exception BasePcpHandler::WriteConnectionRequestFrame( EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, - const std::string& local_endpoint_name, std::int32_t nonce, + const ByteArray& local_endpoint_info, std::int32_t nonce, const std::vector& supported_mediums) { return endpoint_channel->Write(parser::ForConnectionRequest( - local_endpoint_id, local_endpoint_name, nonce, supported_mediums)); + local_endpoint_id, local_endpoint_info, nonce, supported_mediums)); } void BasePcpHandler::ProcessPreConnectionInitiationFailure( @@ -529,7 +555,7 @@ Status BasePcpHandler::AcceptConnection( response.Set({Status::kSuccess}); }); - return WaitForResult(absl::StrCat("acceptConnection(", endpoint_id, ")"), + return WaitForResult(absl::StrCat("AcceptConnection(", endpoint_id, ")"), client->GetClientId(), &response); } @@ -581,7 +607,7 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, response.Set({Status::kSuccess}); }); - return WaitForResult(absl::StrCat("rejectConnection(", endpoint_id, ")"), + return WaitForResult(absl::StrCat("RejectConnection(", endpoint_id, ")"), client->GetClientId(), &response); } @@ -648,44 +674,52 @@ ConnectionOptions BasePcpHandler::GetConnectionOptions() const { return advertising_options_; } +ConnectionOptions BasePcpHandler::GetDiscoveryOptions() const { + return discovery_options_; +} + void BasePcpHandler::OnEndpointFound( - ClientProxy* client, - std::shared_ptr endpoint) { + ClientProxy* client, 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 = - GetDiscoveredEndpoint(endpoint_id); - NEARBY_LOG(INFO, "OnEndpointFound: id='%s' [enter]", endpoint_id.c_str()); - if (previously_discovered_endpoint == nullptr) { - // 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)) - .first->second; + auto range = discovered_endpoints_.equal_range(endpoint->endpoint_id); + + DiscoveredEndpoint* owned_endpoint = nullptr; + for (auto& item = range.first; item != range.second; ++item) { + auto& discovered_endpoint = item->second; + if (discovered_endpoint->medium != endpoint->medium) continue; + // Check if there was a info change. If there was, report the previous + // endpoint as lost. + if (discovered_endpoint->endpoint_info != endpoint->endpoint_info) { + OnEndpointLost(client, *discovered_endpoint); + discovered_endpoint = endpoint; // Replace endpoint. + OnEndpointFound(client, std::move(endpoint)); + return; + } else { + owned_endpoint = endpoint.get(); + break; + } + } + + if (!owned_endpoint) { + owned_endpoint = + discovered_endpoints_.emplace(endpoint_id, std::move(endpoint)) + ->second.get(); + } + + // Range is empty: this is the first endpoint we discovered so far. + // Report this endpoint_id to client. + if (range.first == range.second) { NEARBY_LOG(INFO, "Adding new endpoint: id=%s", endpoint_id.c_str()); // And, as it's the first time, report it to the client. client->OnEndpointFound( owned_endpoint->service_id, owned_endpoint->endpoint_id, - owned_endpoint->endpoint_name, owned_endpoint->medium); - } else if (previously_discovered_endpoint->endpoint_name != - endpoint->endpoint_name) { - // If we've already seen this endpoint before, check if there was a name - // change. If there was, report the previous endpoint as lost. - NEARBY_LOG(INFO, "Switch to new endpoint: id=%s", endpoint_id.c_str()); - - OnEndpointLost(client, *previously_discovered_endpoint); - OnEndpointFound(client, std::move(endpoint)); + owned_endpoint->endpoint_info, owned_endpoint->medium); } else { - // Otherwise, we need to see if the medium we discovered the endpoint over - // this time is better than the medium we originally discovered the endpoint - // over. - 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)); - } + NEARBY_LOGS(INFO) << "Adding new medium for endpoint: id=" << endpoint_id + << "; medium=" << owned_endpoint->medium; } } @@ -699,19 +733,22 @@ void BasePcpHandler::OnEndpointLost( return; } - // Validate that the cached endpoint has the same name as the one reported as - // onLost. If the name differs, then no-op. This likely means that the remote - // device changed their name. We reported onFound for the new name and are - // just now figuring out that we lost the old name. - if (discovered_endpoint->endpoint_name != endpoint.endpoint_name) { + // Validate that the cached endpoint has the same info as the one reported as + // onLost. If the info differs, then no-op. This likely means that the remote + // device changed their info. We reported onFound for the new info and are + // just now figuring out that we lost the old info. + if (discovered_endpoint->endpoint_info != endpoint.endpoint_info) { NEARBY_LOG(INFO, "Previous endpoint name mismatch; passed=%s; expected=%s", - endpoint.endpoint_name.c_str(), - discovered_endpoint->endpoint_name.c_str()); + absl::BytesToHexString(endpoint.endpoint_info.data()).c_str(), + absl::BytesToHexString(discovered_endpoint->endpoint_info.data()) + .c_str()); return; } auto item = discovered_endpoints_.extract(endpoint.endpoint_id); - client->OnEndpointLost(endpoint.service_id, endpoint.endpoint_id); + if (!discovered_endpoints_.count(endpoint.endpoint_id)) { + client->OnEndpointLost(endpoint.service_id, endpoint.endpoint_id); + } } bool BasePcpHandler::IsPreferred( @@ -732,17 +769,24 @@ bool BasePcpHandler::IsPreferred( return false; } } - NEARBY_LOG(FATAL, "Failed to determine preferred medium; bailing out"); + std::string medium_string; + for (const auto& medium : mediums) { + absl::StrAppend(&medium_string, medium, "; "); + } + NEARBY_LOG(FATAL, + "Failed to determine preferred medium; bailing out; mediums=%s; " + "new=%d; old=%d", + medium_string.c_str(), new_endpoint.medium, old_endpoint.medium); return false; } Exception BasePcpHandler::OnIncomingConnection( - ClientProxy* client, const std::string& remote_device_name, + ClientProxy* client, const ByteArray& remote_endpoint_info, std::unique_ptr channel, proto::connections::Medium medium) { absl::Time start_time = SystemClock::ElapsedRealtime(); - // Fixes an NPE in ClientProxy.OnConnectionResult. The crash happened when + // Fixes an NPE in ClientProxy.OnConnectionAccepted. The crash happened when // the client stopped advertising and we nulled out state, followed by an // incoming connection where we attempted to check that state. if (!client->IsAdvertising()) { @@ -763,7 +807,8 @@ Exception BasePcpHandler::OnIncomingConnection( ERROR, "Failed to parse incoming connection request; client_id=0x%" PRIX64 "; device=%s", - client->GetClientId(), remote_device_name.c_str()); + client->GetClientId(), + absl::BytesToHexString(remote_endpoint_info.data()).c_str()); ProcessPreConnectionInitiationFailure("", channel.get(), {Status::kError}, nullptr); return {Exception::kSuccess}; @@ -777,7 +822,8 @@ Exception BasePcpHandler::OnIncomingConnection( NEARBY_LOG(INFO, "Incoming connection request; client_id=0x%" PRIX64 "; device=%s; id=%s", - client->GetClientId(), remote_device_name.c_str(), + client->GetClientId(), + absl::BytesToHexString(remote_endpoint_info.data()).c_str(), connection_request.endpoint_id().c_str()); if (client->IsConnectedToEndpoint(connection_request.endpoint_id())) { return {Exception::kIo}; @@ -801,20 +847,20 @@ Exception BasePcpHandler::OnIncomingConnection( // EndpointInfo. The legacy field stores it as a string while the newer field // stores it as a byte array. We'll attempt to grab from the newer field, but // will accept the older string if it's all that exists. - const std::string endpoint_name = connection_request.has_endpoint_info() - ? connection_request.endpoint_info() - : connection_request.endpoint_name(); + const ByteArray endpoint_info{connection_request.has_endpoint_info() + ? connection_request.endpoint_info() + : connection_request.endpoint_name()}; // We've successfully connected to the device, and are now about to jump on to // the EncryptionRunner thread to start running our encryption protocol. We'll - // mark ourselves as pending in case we get another call to requestConnection + // mark ourselves as pending in case we get another call to RequestConnection // or OnIncomingConnection, so that we can cancel the connection if needed. auto* owned_channel = pending_connections_ .emplace(connection_request.endpoint_id(), PendingConnectionInfo{ .client = client, - .remote_endpoint_name = endpoint_name, + .remote_endpoint_info = endpoint_info, .nonce = connection_request.nonce(), .is_incoming = true, .start_time = start_time, @@ -1079,8 +1125,9 @@ void BasePcpHandler::PendingConnectionInfo::LocalEndpointRejectedConnection( mediums::PeerId BasePcpHandler::CreatePeerIdFromAdvertisement( const std::string& service_id, const std::string& endpoint_id, - const std::string& endpoint_name) { - std::string seed = absl::StrCat(service_id, endpoint_id, endpoint_name); + const ByteArray& endpoint_info) { + std::string seed = + absl::StrCat(service_id, endpoint_id, std::string(endpoint_info)); return mediums::PeerId::FromSeed(ByteArray(std::move(seed))); } diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index 533ec388..d9ee3f92 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -10,6 +10,7 @@ #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/mediums.h" #include "core_v2/internal/mediums/webrtc.h" #include "core_v2/internal/pcp.h" #include "core_v2/internal/pcp_handler.h" @@ -17,6 +18,7 @@ #include "core_v2/options.h" #include "core_v2/status.h" #include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" #include "platform_v2/base/prng.h" #include "platform_v2/public/atomic_boolean.h" #include "platform_v2/public/atomic_reference.h" @@ -29,6 +31,7 @@ #include "proto/connections_enums.pb.h" #include "securegcm/d2d_connection_context_v1.h" #include "securegcm/ukey2_handshake.h" +#include "absl/container/btree_map.h" #include "absl/container/flat_hash_map.h" #include "absl/time/time.h" @@ -77,7 +80,7 @@ class BasePcpHandler : public PcpHandler, using FrameProcessor = EndpointManager::FrameProcessor; // TODO(apolyudov): Add SecureRandom. - BasePcpHandler(EndpointManager* endpoint_manager, + BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, Pcp pcp); ~BasePcpHandler() override; BasePcpHandler(BasePcpHandler&&) = delete; @@ -87,44 +90,45 @@ class BasePcpHandler : public PcpHandler, // Notifies ConnectionListener (info.listener) in case of any event. // See // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 - Status StartAdvertising(ClientProxy* client_proxy, + Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; // Stops Advertising is active, and changes CLientProxy state, // otherwise does nothing. - void StopAdvertising(ClientProxy* client_proxy) override; + void StopAdvertising(ClientProxy* client) override; // Starts discovery of endpoints that may be advertising. // Updates ClientProxy state once discovery started. // DiscoveryListener will get called in case of any event. - Status StartDiscovery(ClientProxy* client_proxy, + Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; // Stops Discovery if it is active, and changes CLientProxy state, // otherwise does nothing. - void StopDiscovery(ClientProxy* client_proxy) override; + void StopDiscovery(ClientProxy* client) override; // Requests a newly discovered remote endpoint it to form a connection. // Updates state on ClientProxy. - Status RequestConnection(ClientProxy* client_proxy, + Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) override; + const ConnectionRequestInfo& info, + const ConnectionOptions& options) override; // Called by either party to accept connection on their part. // Until both parties call it, connection will not reach a data phase. // Updates state in ClientProxy. - Status AcceptConnection(ClientProxy* client_proxy, + Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) override; // Called by either party to reject connection on their part. // If either party does call it, connection will terminate. // Updates state in ClientProxy. - Status RejectConnection(ClientProxy* client_proxy, + Status RejectConnection(ClientProxy* client, const std::string& endpoint_id) override; // @EndpointManagerReaderThread @@ -135,7 +139,7 @@ class BasePcpHandler : public PcpHandler, // Called when an endpoint disconnects while we're waiting for both sides to // approve/reject the connection. // @EndpointManagerThread - void OnEndpointDisconnect(ClientProxy* client_proxy, + void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) override; @@ -167,21 +171,37 @@ class BasePcpHandler : public PcpHandler, // instance (but it can if implementation desires to do so). // BasePcpHandler will hold on to the shared_ptr. struct DiscoveredEndpoint { - DiscoveredEndpoint(std::string endpoint_id, std::string endpoint_name, + DiscoveredEndpoint(std::string endpoint_id, ByteArray endpoint_info, std::string service_id, proto::connections::Medium medium) : endpoint_id(std::move(endpoint_id)), - endpoint_name(std::move(endpoint_name)), + endpoint_info(std::move(endpoint_info)), service_id(std::move(service_id)), medium(medium) {} virtual ~DiscoveredEndpoint() = default; std::string endpoint_id; - std::string endpoint_name; + ByteArray endpoint_info; std::string service_id; proto::connections::Medium medium; }; + struct BluetoothEndpoint : public DiscoveredEndpoint { + BluetoothEndpoint(DiscoveredEndpoint endpoint, BluetoothDevice device) + : DiscoveredEndpoint(std::move(endpoint)), + bluetooth_device(std::move(device)) {} + + BluetoothDevice bluetooth_device; + }; + + struct WifiLanEndpoint : public DiscoveredEndpoint { + WifiLanEndpoint(DiscoveredEndpoint endpoint, WifiLanService service) + : DiscoveredEndpoint(std::move(endpoint)), + wifi_lan_service(std::move(service)) {} + + WifiLanService wifi_lan_service; + }; + struct WebRtcEndpoint : public DiscoveredEndpoint { WebRtcEndpoint(DiscoveredEndpoint endpoint, mediums::PeerId peer_id) : DiscoveredEndpoint(std::move(endpoint)), @@ -200,54 +220,64 @@ class BasePcpHandler : public PcpHandler, void RunOnPcpHandlerThread(Runnable runnable); ConnectionOptions GetConnectionOptions() const; + ConnectionOptions GetDiscoveryOptions() const; // @PcpHandlerThread - void OnEndpointFound(ClientProxy* client_proxy, + void OnEndpointFound(ClientProxy* client, std::shared_ptr endpoint); // @PcpHandlerThread - void OnEndpointLost(ClientProxy* client_proxy, + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint); Exception OnIncomingConnection( - ClientProxy* client_proxy, const std::string& remote_device_name, + ClientProxy* client, const ByteArray& remote_endpoint_info, std::unique_ptr endpoint_channel, proto::connections::Medium medium); // throws Exception::IO - virtual bool HasOutgoingConnections(ClientProxy* client_proxy) const; - virtual bool HasIncomingConnections(ClientProxy* client_proxy) const; + virtual bool HasOutgoingConnections(ClientProxy* client) const; + virtual bool HasIncomingConnections(ClientProxy* client) const; - virtual bool CanSendOutgoingConnection(ClientProxy* client_proxy) const; - virtual bool CanReceiveIncomingConnection(ClientProxy* client_proxy) const; + virtual bool CanSendOutgoingConnection(ClientProxy* client) const; + virtual bool CanReceiveIncomingConnection(ClientProxy* client) const; // @PcpHandlerThread virtual StartOperationResult StartAdvertisingImpl( - ClientProxy* client_proxy, const std::string& service_id, + ClientProxy* client, const std::string& service_id, const std::string& local_endpoint_id, - const std::string& local_endpoint_name, + const ByteArray& local_endpoint_info, const ConnectionOptions& options) = 0; // @PcpHandlerThread - virtual Status StopAdvertisingImpl(ClientProxy* client_proxy) = 0; + virtual Status StopAdvertisingImpl(ClientProxy* client) = 0; // @PcpHandlerThread virtual StartOperationResult StartDiscoveryImpl( - ClientProxy* client_proxy, const std::string& service_id, + ClientProxy* client, const std::string& service_id, const ConnectionOptions& options) = 0; // @PcpHandlerThread - virtual Status StopDiscoveryImpl(ClientProxy* client_proxy) = 0; + virtual Status StopDiscoveryImpl(ClientProxy* client) = 0; // @PcpHandlerThread - virtual ConnectImplResult ConnectImpl(ClientProxy* client_proxy, + virtual ConnectImplResult ConnectImpl(ClientProxy* client, DiscoveredEndpoint* endpoint) = 0; virtual std::vector GetConnectionMediumsByPriority() = 0; virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0; + // Returns the first discovered endpoint for the given endpoint_id. + DiscoveredEndpoint* GetDiscoveredEndpoint(const std::string& endpoint_id); + + // Returns a vector of discovered endpoints, sorted in order of decreasing + // preference. + std::vector + GetDiscoveredEndpoints(const std::string& endpoint_id); + mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, const string& endpoint_id, - const string& endpoint_name); + const ByteArray& endpoint_info); + Mediums* mediums_; EndpointManager* endpoint_manager_; EndpointChannelManager* channel_manager_; @@ -272,13 +302,14 @@ class BasePcpHandler : public PcpHandler, // Client state tracker to report events to. Never changes. Always valid. ClientProxy* client = nullptr; - // Peer endpoint name, or empty, if not discovered yet. May change. - std::string remote_endpoint_name; + // Peer endpoint info, or empty, if not discovered yet. May change. + ByteArray remote_endpoint_info; std::int32_t nonce = 0; bool is_incoming = false; absl::Time start_time{absl::InfinitePast()}; // Client callbacks. Always valid. ConnectionListener listener; + ConnectionOptions options; // Only set for outgoing connections. If set, we must call // result->Set() when connection is established, or rejected. @@ -322,7 +353,7 @@ class BasePcpHandler : public PcpHandler, static Exception WriteConnectionRequestFrame( EndpointChannel* endpoint_channel, const std::string& local_endpoint_id, - const std::string& local_endpoint_name, std::int32_t nonce, + const ByteArray& local_endpoint_info, std::int32_t nonce, const std::vector& supported_mediums); static constexpr absl::Duration kConnectionRequestReadTimeout = @@ -330,8 +361,7 @@ class BasePcpHandler : public PcpHandler, static constexpr absl::Duration kRejectedConnectionCloseDelay = absl::Seconds(2); - void OnConnectionResponse(ClientProxy* client_proxy, - const std::string& endpoint_id, + void OnConnectionResponse(ClientProxy* client, const std::string& endpoint_id, const OfflineFrame& frame); // Returns true if the new endpoint is preferred over the old endpoint. @@ -353,8 +383,7 @@ class BasePcpHandler : public PcpHandler, // We're not sure how far our outgoing connection has gotten. We may (or may // not) have called ClientProxy::OnConnectionInitiated. Therefore, we'll // call both preInit and preResult failures. - void ProcessTieBreakLoss(ClientProxy* client_proxy, - const std::string& endpoint_id, + void ProcessTieBreakLoss(ClientProxy* client, const std::string& endpoint_id, PendingConnectionInfo* info); // Called when an incoming connection has been accepted by both sides. @@ -366,7 +395,7 @@ class BasePcpHandler : public PcpHandler, // for outgoing connections and older devices that don't report their // supported mediums. void InitiateBandwidthUpgrade( - ClientProxy* client_proxy, const std::string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, const std::vector& supported_mediums); // Returns the optimal medium supported by both devices. @@ -377,9 +406,8 @@ class BasePcpHandler : public PcpHandler, EndpointChannel* channel, Status status, Future* result); - void ProcessPreConnectionResultFailure(ClientProxy* client_proxy, + void ProcessPreConnectionResultFailure(ClientProxy* client, const std::string& endpoint_id); - DiscoveredEndpoint* GetDiscoveredEndpoint(const std::string& endpoint_id); // Called when either side accepts/rejects the connection, but only takes // effect after both have accepted or one side has rejected. @@ -390,7 +418,7 @@ class BasePcpHandler : public PcpHandler, // onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our // close. If the other side behaves properly, we shouldn't even see the // delay (because they will also close the connection). - void EvaluateConnectionResult(ClientProxy* client_proxy, + void EvaluateConnectionResult(ClientProxy* client, const std::string& endpoint_id, bool can_close_immediately); @@ -413,7 +441,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::btree_multimap> 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 diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index e18ff69c..1a580067 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -8,11 +8,13 @@ #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/offline_frames.h" #include "core_v2/listeners.h" +#include "core_v2/options.h" #include "core_v2/params.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/public/count_down_latch.h" #include "platform_v2/public/pipe.h" +#include "proto/connections_enums.pb.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/time/time.h" @@ -30,6 +32,20 @@ using ::testing::MockFunction; using ::testing::Return; using ::testing::StrictMock; +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{}, + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + class MockEndpointChannel : public BaseEndpointChannel { public: explicit MockEndpointChannel(Pipe* reader, Pipe* writer) @@ -58,8 +74,10 @@ class MockEndpointChannel : public BaseEndpointChannel { class MockPcpHandler : public BasePcpHandler { public: - MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} + using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; + + MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm) + : BasePcpHandler(m, em, ecm, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; @@ -80,9 +98,9 @@ class MockPcpHandler : public BasePcpHandler { (const, override)); MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, - (ClientProxy * client, const string& service_id, - const string& local_endpoint_id, - const string& local_endpoint_name, + (ClientProxy * client, const std::string& service_id, + const std::string& local_endpoint_id, + const ByteArray& local_endpoint_info, const ConnectionOptions& options), (override)); MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); @@ -98,8 +116,7 @@ class MockPcpHandler : public BasePcpHandler { std::vector GetConnectionMediumsByPriority() override { - return {proto::connections::Medium::BLE, - proto::connections::Medium::WEB_RTC}; + return GetDiscoveryMediums(); } // Mock adapters for protected non-virtual methods of a base class. @@ -110,22 +127,37 @@ class MockPcpHandler : public BasePcpHandler { void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { BasePcpHandler::OnEndpointLost(client, endpoint); } + std::vector GetDiscoveredEndpoints( + const std::string& endpoint_id) { + return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); + } + + std::vector GetDiscoveryMediums() { + auto allowed = + BasePcpHandler::GetDiscoveryOptions().CompatibleOptions().allowed; + return GetMediumsFromSelector(allowed); + } + + std::vector GetMediumsFromSelector( + BooleanMediumSelector allowed) { + return allowed.GetMediums(true); + } }; class MockContext { public: - explicit MockContext(std::atomic_bool* destroyed = nullptr) { + explicit MockContext(std::atomic_int* destroyed = nullptr) { destroyed_ = destroyed; } MockContext(MockContext&&) = default; MockContext& operator=(MockContext&&) = default; ~MockContext() { - if (destroyed_) *destroyed_ = true; + if (destroyed_) (*destroyed_)++; } private: - Swapper destroyed_{nullptr}; + Swapper destroyed_{nullptr}; }; struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { @@ -135,7 +167,8 @@ struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { MockContext context; }; -class BasePcpHandlerTest : public ::testing::Test { +class BasePcpHandlerTest + : public ::testing::TestWithParam { protected: struct MockConnectionListener { StrictMock> endpoint_found_cb; StrictMock> @@ -163,39 +196,43 @@ class BasePcpHandlerTest : public ::testing::Test { endpoint_distance_changed_cb; }; - void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler) { + void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, + BooleanMediumSelector allowed = GetParam()) { std::string service_id{"service"}; ConnectionOptions options{ .strategy = Strategy::kP2pCluster, + .allowed = allowed, .auto_upgrade_bandwidth = true, .enforce_topology_constraints = true, }; ConnectionRequestInfo info{ - .name = "remote_endpoint_name", + .endpoint_info = ByteArray{"remote_endpoint_name"}, .listener = connection_listener_, }; - EXPECT_CALL(*pcp_handler, - StartAdvertisingImpl(client, service_id, _, info.name, _)) + EXPECT_CALL(*pcp_handler, StartAdvertisingImpl(client, service_id, _, + info.endpoint_info, _)) .WillOnce(Return(MockPcpHandler::StartOperationResult{ .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, + .mediums = pcp_handler->GetMediumsFromSelector(allowed), })); EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), Status{Status::kSuccess}); EXPECT_TRUE(client->IsAdvertising()); } - void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler) { + void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, + BooleanMediumSelector allowed = GetParam()) { std::string service_id{"service"}; ConnectionOptions options{ .strategy = Strategy::kP2pCluster, + .allowed = allowed, .auto_upgrade_bandwidth = true, .enforce_topology_constraints = true, }; EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) .WillOnce(Return(MockPcpHandler::StartOperationResult{ .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, + .mediums = pcp_handler->GetMediumsFromSelector(allowed), })); EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, discovery_listener_), @@ -205,7 +242,8 @@ class BasePcpHandlerTest : public ::testing::Test { std::pair, std::unique_ptr> - SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT + SetupConnection(Pipe& pipe_a, Pipe& pipe_b, + proto::connections::Medium medium) { // NOLINT auto channel_a = std::make_unique(&pipe_b, &pipe_a); auto channel_b = std::make_unique(&pipe_a, &pipe_b); // On initiator (A) side, we drop the first write, since this is a @@ -221,7 +259,7 @@ class BasePcpHandlerTest : public ::testing::Test { Invoke([channel = channel_a.get()](const ByteArray& data) { return channel->DoWrite(data); })); - EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_a, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); @@ -233,7 +271,7 @@ class BasePcpHandlerTest : public ::testing::Test { Invoke([channel = channel_b.get()](const ByteArray& data) { return channel->DoWrite(data); })); - EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(medium)); EXPECT_CALL(*channel_b, GetLastReadTimestamp) .WillRepeatedly(Return(absl::Now())); EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); @@ -244,39 +282,50 @@ class BasePcpHandlerTest : public ::testing::Test { std::unique_ptr channel_a, MockEndpointChannel* channel_b, ClientProxy* client, MockPcpHandler* pcp_handler, - std::atomic_bool* flag = nullptr) { + proto::connections::Medium connect_medium, + std::atomic_int* flag = nullptr) { ConnectionRequestInfo info{ - .name = "ABCD", + .endpoint_info = ByteArray{"ABCD"}, .listener = connection_listener_, }; + ConnectionOptions options{ + .remote_bluetooth_mac_address = + ByteArray{std::string("\x12\x34\x56\x78\x9a\xbc")}, + }; EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) .WillRepeatedly(Return(true)); EXPECT_CALL(*pcp_handler, GetStrategy) .WillRepeatedly(Return(Strategy::kP2pCluster)); EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); - EXPECT_CALL(*pcp_handler, ConnectImpl) - .WillOnce( - Invoke([&channel_a](ClientProxy* client, - MockPcpHandler::DiscoveredEndpoint* endpoint) { - return MockPcpHandler::ConnectImplResult{ - .medium = Medium::BLE, - .status = {Status::kSuccess}, - .endpoint_channel = std::move(channel_a), - }; - })); // Simulate successful discovery. auto encryption_runner = std::make_unique(); - pcp_handler->OnEndpointFound( - client, std::make_shared(MockDiscoveredEndpoint{ - { - endpoint_id, - info.name, - "service", - Medium::BLE, - }, - MockContext{flag}, - })); + auto allowed_mediums = pcp_handler->GetDiscoveryMediums(); + + EXPECT_CALL(*pcp_handler, ConnectImpl) + .WillOnce(Invoke([&channel_a, connect_medium]( + ClientProxy* client, + MockPcpHandler::DiscoveredEndpoint* endpoint) { + return MockPcpHandler::ConnectImplResult{ + .medium = connect_medium, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel_a), + }; + })); + + for (const auto& discovered_medium : allowed_mediums) { + pcp_handler->OnEndpointFound( + client, + std::make_shared(MockDiscoveredEndpoint{ + { + endpoint_id, + info.endpoint_info, + "service", + discovered_medium, + }, + MockContext{flag}, + })); + } auto other_client = std::make_unique(); // Run peer crypto in advance, if channel_b is provided. @@ -285,8 +334,9 @@ class BasePcpHandlerTest : public ::testing::Test { encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, {}); } - EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), - Status{Status::kSuccess}); + EXPECT_EQ( + pcp_handler->RequestConnection(client, endpoint_id, info, options), + Status{Status::kSuccess}); NEARBY_LOG(INFO, "Stopping Encryption Runner"); } @@ -313,26 +363,29 @@ class BasePcpHandlerTest : public ::testing::Test { }; }; -TEST_F(BasePcpHandlerTest, ConstructorDestructorWorks) { +TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); SUCCEED(); } -TEST_F(BasePcpHandlerTest, StartAdvertisingChangesState) { +TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartAdvertising(&client, &pcp_handler); } -TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { +TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartAdvertising(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); EXPECT_TRUE(client.IsAdvertising()); @@ -340,19 +393,21 @@ TEST_F(BasePcpHandlerTest, StopAdvertisingChangesState) { EXPECT_FALSE(client.IsAdvertising()); } -TEST_F(BasePcpHandlerTest, StartDiscoveryChangesState) { +TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); } -TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { +TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); EXPECT_TRUE(client.IsDiscovering()); @@ -360,40 +415,46 @@ TEST_F(BasePcpHandlerTest, StopDiscoveryChangesState) { EXPECT_FALSE(client.IsDiscovering()); } -TEST_F(BasePcpHandlerTest, RequestConnectionChangesState) { +TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { std::string endpoint_id{"1234"}; ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); + &pcp_handler, connect_medium); NEARBY_LOG(INFO, "RequestConnection complete"); channel_b->Close(); pcp_handler.DisconnectFromEndpointManager(); } -TEST_F(BasePcpHandlerTest, AcceptConnectionChangesState) { +TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { std::string endpoint_id{"1234"}; ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); + &pcp_handler, connect_medium); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), @@ -404,18 +465,21 @@ TEST_F(BasePcpHandlerTest, AcceptConnectionChangesState) { pcp_handler.DisconnectFromEndpointManager(); } -TEST_F(BasePcpHandlerTest, RejectConnectionChangesState) { +TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { std::string endpoint_id{"1234"}; ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_b = channel_pair.second; EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - &client, &pcp_handler); + &client, &pcp_handler, connect_medium); NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), Status{Status::kSuccess}); @@ -424,20 +488,23 @@ TEST_F(BasePcpHandlerTest, RejectConnectionChangesState) { pcp_handler.DisconnectFromEndpointManager(); } -TEST_F(BasePcpHandlerTest, OnIncomingFrameChangesState) { +TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { std::string endpoint_id{"1234"}; ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); + &pcp_handler, connect_medium); NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) @@ -448,28 +515,33 @@ TEST_F(BasePcpHandlerTest, OnIncomingFrameChangesState) { auto frame = parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, - Medium::BLE); + connect_medium); NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; channel_b->Close(); pcp_handler.DisconnectFromEndpointManager(); } -TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { - std::atomic_bool destroyed_flag = false; +TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { + std::atomic_int destroyed_flag = 0; + int mediums_count = 0; { std::string endpoint_id{"1234"}; ClientProxy client; + Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); StartDiscovery(&client, &pcp_handler); - auto channel_pair = SetupConnection(pipe_a_, pipe_b_); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); auto& channel_a = channel_pair.first; auto& channel_b = channel_pair.second; EXPECT_CALL(*channel_a, CloseImpl).Times(1); EXPECT_CALL(*channel_b, CloseImpl).Times(1); RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), - &client, &pcp_handler, &destroyed_flag); + &client, &pcp_handler, connect_medium, &destroyed_flag); + mediums_count = mediums.size(); NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", endpoint_id.c_str()); EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), @@ -479,9 +551,57 @@ TEST_F(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { channel_b->Close(); pcp_handler.DisconnectFromEndpointManager(); } - EXPECT_TRUE(destroyed_flag.load()); + EXPECT_EQ(destroyed_flag.load(), mediums_count); } +TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) { + BooleanMediumSelector allowed = GetParam(); + if (allowed.Count(true) < 2) { + // Ignore single-medium test cases, and implicit "all mediums" case. + SUCCEED(); + return; + } + std::atomic_int destroyed_flag = 0; + int mediums_count = 0; + { + std::string endpoint_id{"1234"}; + ClientProxy client; + Mediums m; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&m, &em, &ecm); + StartDiscovery(&client, &pcp_handler); + auto mediums = pcp_handler.GetDiscoveryMediums(); + auto connect_medium = mediums[mediums.size() - 1]; + auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium); + auto& channel_a = channel_pair.first; + auto& channel_b = channel_pair.second; + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + EXPECT_CALL(mock_discovery_listener_.endpoint_lost_cb, Call).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), + &client, &pcp_handler, connect_medium, &destroyed_flag); + auto allowed_mediums = pcp_handler.GetDiscoveryMediums(); + mediums_count = allowed_mediums.size(); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), + Status{Status::kSuccess}); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + for (const auto* endpoint : + pcp_handler.GetDiscoveredEndpoints(endpoint_id)) { + pcp_handler.OnEndpointLost(&client, *endpoint); + } + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); + } + EXPECT_EQ(destroyed_flag.load(), mediums_count); +} + +INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, + ::testing::ValuesIn(kTestCases)); + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc index 0443a03f..1ad5df12 100644 --- a/cpp/core_v2/internal/ble_advertisement.cc +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -13,12 +13,33 @@ namespace connections { BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, const std::string& bluetooth_mac_address) { - if (version != Version::kV1 || - service_id_hash.size() != kServiceIdHashLength || endpoint_id.empty() || + DoInitialize(/*fast_advertisement=*/false, version, pcp, service_id_hash, + endpoint_id, endpoint_info, bluetooth_mac_address); +} + +BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, + const std::string& endpoint_id, + const ByteArray& endpoint_info) { + DoInitialize(/*fast_advertisement=*/true, version, pcp, {}, endpoint_id, + endpoint_info, {}); +} + +void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version, + Pcp pcp, const ByteArray& service_id_hash, + const std::string& endpoint_id, + const ByteArray& endpoint_info, + const std::string& bluetooth_mac_address) { + fast_advertisement_ = fast_advertisement; + if (!fast_advertisement_) { + if (service_id_hash.size() != kServiceIdHashLength) return; + } + int max_endpoint_info_length = + fast_advertisement_ ? kMaxFastEndpointInfoLength : kMaxEndpointInfoLength; + if (version != Version::kV1 || endpoint_id.empty() || endpoint_id.length() != kEndpointIdLength || - endpoint_name.length() > kMaxEndpointNameLength) { + endpoint_info.size() > max_endpoint_info_length) { return; } @@ -35,20 +56,29 @@ BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, pcp_ = pcp; service_id_hash_ = service_id_hash; endpoint_id_ = endpoint_id; - endpoint_name_ = endpoint_name; - if (!BluetoothMacAddressHexStringToBytes(bluetooth_mac_address).Empty()) { - bluetooth_mac_address_ = bluetooth_mac_address; + endpoint_info_ = endpoint_info; + if (!fast_advertisement_) { + if (!BluetoothUtils::FromString(bluetooth_mac_address).Empty()) { + bluetooth_mac_address_ = bluetooth_mac_address; + } } } -BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { +BleAdvertisement::BleAdvertisement(bool fast_advertisement, + const ByteArray& ble_advertisement_bytes) { + fast_advertisement_ = fast_advertisement; + if (ble_advertisement_bytes.Empty()) { NEARBY_LOG(ERROR, "Cannot deserialize BleAdvertisement: null bytes passed in."); return; } - if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + int min_advertisement_length = fast_advertisement_ + ? kMinFastAdvertisementLength + : kMinAdvertisementLength; + + if (ble_advertisement_bytes.size() < min_advertisement_length) { NEARBY_LOG(ERROR, "Cannot deserialize BleAdvertisement: expecting min %d raw " "bytes, got %" PRIu64, @@ -82,43 +112,44 @@ BleAdvertisement::BleAdvertisement(const ByteArray& ble_advertisement_bytes) { pcp_); } - // The next 3 bytes are supposed to be the service_id_hash. - service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); + // The next 3 bytes are supposed to be the service_id_hash if not fast + // advertisment. + if (!fast_advertisement_) + 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 1 byte are supposed to be the length of the endpoint_info. + std::uint32_t expected_endpoint_info_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) { + // The next x bytes are the endpoint info. (Max length is 131 bytes or 17 + // bytes as fast_advertisement being true). + endpoint_info_ = base_input_stream.ReadBytes(expected_endpoint_info_length); + const int max_endpoint_info_length = + fast_advertisement_ ? kMaxFastEndpointInfoLength : kMaxEndpointInfoLength; + if (endpoint_info_.Empty() || + endpoint_info_.size() != expected_endpoint_info_length || + endpoint_info_.size() > max_endpoint_info_length) { NEARBY_LOG(INFO, - "Cannot deserialize BleAdvertisement: expected " - "endpointName to be %d bytes, got %" PRIu64, - expected_endpoint_name_length, endpoint_name_bytes.size()); + "Cannot deserialize BleAdvertisement(fast advertisement=%d): " + "expected endpointInfo to be %d bytes, got %" PRIu64, + fast_advertisement_, expected_endpoint_info_length, + endpoint_info_.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)) { + // The next 6 bytes are the bluetooth mac address if not fast advertisment. + if (!fast_advertisement_) { + auto bluetooth_mac_address_bytes = + base_input_stream.ReadBytes(BluetoothUtils::kBluetoothMacAddressLength); bluetooth_mac_address_ = - HexBytesToColonDelimitedString(bluetooth_mac_address_bytes); + BluetoothUtils::ToString(bluetooth_mac_address_bytes); } + base_input_stream.Close(); } @@ -133,74 +164,35 @@ BleAdvertisement::operator ByteArray() const { // The next 5 bits are the Pcp. version_and_pcp_byte |= static_cast(pcp_) & kPcpBitmask; - // 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 + std::string out; + if (fast_advertisement_) { + // clang-format off + out = absl::StrCat(std::string(1, version_and_pcp_byte), + endpoint_id_, + std::string(1, endpoint_info_.size()), + std::string(endpoint_info_)); + // clang-format on + } else { + // clang-format off + out = absl::StrCat(std::string(1, version_and_pcp_byte), + std::string(service_id_hash_), + endpoint_id_, + std::string(1, endpoint_info_.size()), + std::string(endpoint_info_)); + // 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()) { - absl::StrAppend(&out, std::string(bluetooth_mac_address_bytes)); + // 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{ + BluetoothUtils::FromString(bluetooth_mac_address_)}; + if (!bluetooth_mac_address_bytes.Empty()) { + absl::StrAppend(&out, std::string(bluetooth_mac_address_bytes)); + } } return ByteArray(std::move(out)); } -ByteArray BleAdvertisement::BluetoothMacAddressHexStringToBytes( - const std::string& bluetooth_mac_address) const { - std::string bt_mac_address(bluetooth_mac_address); - - // Remove the colon delimiters. - bt_mac_address.erase( - std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'), - bt_mac_address.end()); - - // If the bluetooth mac address is invalid (wrong size), return a null byte - // array. - if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) { - return ByteArray(); - } - - // Convert to bytes. If MAC Address bytes are unset, return a null byte array. - auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address)); - auto bt_mac_address_bytes = - ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size()); - if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) { - return ByteArray(); - } - return bt_mac_address_bytes; -} - -std::string BleAdvertisement::HexBytesToColonDelimitedString( - const ByteArray& hex_bytes) const { - // Convert the hex bytes to a string. - std::string colon_delimited_string( - absl::BytesToHexString(std::string(hex_bytes.data(), hex_bytes.size()))); - absl::AsciiStrToUpper(&colon_delimited_string); - - // Insert the colons. - for (int i = colon_delimited_string.length() - 2; i > 0; i -= 2) { - colon_delimited_string.insert(i, ":"); - } - return colon_delimited_string; -} - -bool BleAdvertisement::IsBluetoothMacAddressUnset( - const ByteArray& bluetooth_mac_address_bytes) const { - for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) { - if (bluetooth_mac_address_bytes.data()[i] != 0) { - return false; - } - } - return true; -} - } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h index 5523e17d..3f1d04f3 100644 --- a/cpp/core_v2/internal/ble_advertisement.h +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -2,6 +2,7 @@ #define CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ #include "core_v2/internal/pcp.h" +#include "platform_v2/base/bluetooth_utils.h" #include "platform_v2/base/byte_array.h" namespace location { @@ -11,8 +12,11 @@ namespace connections { // Represents the format of the Connections Ble Advertisement used in // Advertising + Discovery. // -//

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

[VERSION][PCP][SERVICE_ID_HASH][ENDPOINT_ID][ENDPOINT_INFO_SIZE] +// [ENDPOINT_INFO][BLUETOOTH_MAC] +// +//

The fast version of this advertisement simply omits SERVICE_ID_HASH and +// the Bluetooth MAC address. // //

See go/connections-ble-advertisement for more information. class BleAdvertisement { @@ -25,28 +29,35 @@ class BleAdvertisement { // can never go beyond V7. }; - static constexpr int kServiceIdHashLength = 3; static constexpr int kVersionAndPcpLength = 1; - // Should be defined as EndpointManager::kEndpointIdLength, but that - // involves making BleAdvertisement templatized on Platform just for - // that one little thing, so forget it (at least for now). - static constexpr int kEndpointIdLength = 4; - static constexpr int kEndpointNameSizeLength = 1; - static constexpr int kBluetoothMacAddressLength = 6; - static constexpr int kMinAdvertisementLength = - kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + - kEndpointNameSizeLength + kBluetoothMacAddressLength; - static constexpr int kMaxEndpointNameLength = 131; static constexpr int kVersionBitmask = 0x0E0; static constexpr int kPcpBitmask = 0x01F; - static constexpr int kEndpointNameLengthBitmask = 0x0FF; + static constexpr int kServiceIdHashLength = 3; + static constexpr int kEndpointIdLength = 4; + static constexpr int kEndpointInfoSizeLength = 1; + static constexpr int kEndpointInfoLengthBitmask = 0x0FF; + static constexpr int kMinAdvertisementLength = + kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + + kEndpointInfoSizeLength + BluetoothUtils::kBluetoothMacAddressLength; + + // The difference between normal and fast advertisements is that the fast one + // omits the SERVICE_ID_HASH and Bluetooth MAC address. This is done to save + // space. + static constexpr int kMinFastAdvertisementLength = + kMinAdvertisementLength - kServiceIdHashLength - + BluetoothUtils::kBluetoothMacAddressLength; + static constexpr int kMaxEndpointInfoLength = 131; + static constexpr int kMaxFastEndpointInfoLength = 17; BleAdvertisement() = default; + BleAdvertisement(Version version, Pcp pcp, const std::string& endpoint_id, + const ByteArray& endpoint_info); BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, const std::string& bluetooth_mac_address); - explicit BleAdvertisement(const ByteArray& ble_advertisement_bytes); + BleAdvertisement(bool fast_advertisement, + const ByteArray& ble_advertisement_bytes); BleAdvertisement(const BleAdvertisement&) = default; BleAdvertisement& operator=(const BleAdvertisement&) = default; BleAdvertisement(BleAdvertisement&&) = default; @@ -56,25 +67,27 @@ class BleAdvertisement { explicit operator ByteArray() const; bool IsValid() const { return !endpoint_id_.empty(); } + bool IsFastAdvertisement() const { return fast_advertisement_; } Version GetVersion() const { return version_; } Pcp GetPcp() const { return pcp_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } std::string GetEndpointId() const { return endpoint_id_; } - std::string GetEndpointName() const { return endpoint_name_; } + ByteArray GetEndpointInfo() const { return endpoint_info_; } std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; } private: - ByteArray BluetoothMacAddressHexStringToBytes( - const std::string& bluetooth_mac_address) const; - std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const; - bool IsBluetoothMacAddressUnset( - const ByteArray& bluetooth_mac_address_bytes) const; + void DoInitialize(bool fast_advertisement, Version version, Pcp pcp, + const ByteArray& service_id_hash, + const std::string& endpoint_id, + const ByteArray& endpoint_info, + const std::string& bluetooth_mac_address); + bool fast_advertisement_ = false; Version version_ = Version::kUndefined; Pcp pcp_ = Pcp::kUnknown; ByteArray service_id_hash_; std::string endpoint_id_; - std::string endpoint_name_; + ByteArray endpoint_info_; std::string bluetooth_mac_address_; }; diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc index b0621d68..7ad1d374 100644 --- a/cpp/core_v2/internal/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -9,81 +9,138 @@ namespace { 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 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 kFastAdvertisementEndpointName{"Fast Advertise"}; constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"}; TEST(BleAdvertisementTest, ConstructionWorks) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - BleAdvertisement ble_advertisement{kVersion, - kPcp, - service_id_hash, - std::string(kEndPointID), - std::string(kEndpointName), - std::string(kBluetoothMacAddress)}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); - EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); } -TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { - std::string empty_endpoint_name; +TEST(BleAdvertisementTest, ConstructionWorksForFastAdvertisement) { + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + fast_endpoint_info}; - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfo) { + ByteArray empty_endpoint_info; + + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - std::string(kEndPointID), - empty_endpoint_name, + std::string(kEndpointId), + empty_endpoint_info, std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); - EXPECT_EQ(empty_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); } -TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { - std::string emoji_endpoint_name{"\u0001F450 \u0001F450"}; +TEST(BleAdvertisementTest, + ConstructionWorksWithEmptyEndpointInfoForFastAdvertisement) { + ByteArray empty_endpoint_info; - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + empty_endpoint_info}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfo) { + ByteArray emoji_endpoint_info{std::string("\u0001F450 \u0001F450")}; + + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; BleAdvertisement ble_advertisement{kVersion, kPcp, service_id_hash, - std::string(kEndPointID), - emoji_endpoint_name, + std::string(kEndpointId), + emoji_endpoint_info, std::string(kBluetoothMacAddress)}; EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); - EXPECT_EQ(emoji_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); } -TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { - std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, +TEST(BleAdvertisementTest, + ConstructionWorksWithEmojiEndpointInfoForFastAdvertisement) { + ByteArray emoji_endpoint_info{std::string("\u0001F450 \u0001F450")}; + + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + emoji_endpoint_info}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointInfo) { + std::string long_endpoint_name(BleAdvertisement::kMaxEndpointInfoLength + 1, 'x'); + ByteArray long_endpoint_info{long_endpoint_name}; - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - BleAdvertisement ble_advertisement{kVersion, - kPcp, - service_id_hash, - std::string(kEndPointID), - long_endpoint_name, - std::string(kBluetoothMacAddress)}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + long_endpoint_info, std::string(kBluetoothMacAddress)}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFailsWithLongEndpointInfoForFastAdvertisement) { + std::string long_endpoint_name( + BleAdvertisement::kMaxFastEndpointInfoLength + 1, 'x'); + ByteArray long_endpoint_info{long_endpoint_name}; + + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + long_endpoint_info}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -91,13 +148,23 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); - 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)}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + bad_version, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFailsWithBadVersionForFastAdvertisement) { + auto bad_version = static_cast(666); + + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement ble_advertisement{ + bad_version, kPcp, std::string(kEndpointId), fast_endpoint_info}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -105,13 +172,22 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); - 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)}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, bad_pcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadPCPForFastAdvertisement) { + auto bad_pcp = static_cast(666); + + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, bad_pcp, std::string(kEndpointId), fast_endpoint_info}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -119,13 +195,12 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { std::string empty_bluetooth_mac_address = ""; - 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}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, empty_bluetooth_mac_address}; EXPECT_TRUE(ble_advertisement.IsValid()); } @@ -133,125 +208,180 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { std::string bad_bluetooth_mac_address = "022:00"; - 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}; + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, bad_bluetooth_mac_address}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); - EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty()); } TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // Serialize good data into a good Ble Advertisement. - 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); + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement org_ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + ByteArray ble_advertisement_bytes(org_ble_advertisement); - BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + BleAdvertisement ble_advertisement{false, ble_advertisement_bytes}; EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); - EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); } +TEST(BleAdvertisementTest, ConstructionFromBytesWorksForFastAdvertisement) { + // Serialize good data into a good Ble Advertisement. + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement org_ble_advertisement{ + kVersion, kPcp, std::string(kEndpointId), fast_endpoint_info}; + ByteArray ble_advertisement_bytes(org_ble_advertisement); + + BleAdvertisement ble_advertisement{true, ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); + EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo()); +} + // Bytes at the end should be ignored so that they can be used as reserve bytes // in the future. TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { // Serialize good data into a good Ble Advertisement. - 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); + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + ByteArray ble_advertisement_bytes(ble_advertisement); // Add bytes to the end of the valid Ble advertisement. - auto long_ble_advertisement_bytes = - ByteArray(BleAdvertisement::kMinAdvertisementLength + 1000); + ByteArray long_ble_advertisement_bytes( + BleAdvertisement::kMinAdvertisementLength + 1000); ASSERT_LE(ble_advertisement_bytes.size(), long_ble_advertisement_bytes.size()); - memcpy(long_ble_advertisement_bytes.data(), - ble_advertisement_bytes.data(), + memcpy(long_ble_advertisement_bytes.data(), ble_advertisement_bytes.data(), ble_advertisement_bytes.size()); - BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + BleAdvertisement long_ble_advertisement{false, long_ble_advertisement_bytes}; EXPECT_TRUE(long_ble_advertisement.IsValid()); EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp()); EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(kEndPointID, long_ble_advertisement.GetEndpointId()); - EXPECT_EQ(kEndpointName, long_ble_advertisement.GetEndpointName()); + EXPECT_EQ(kEndpointId, long_ble_advertisement.GetEndpointId()); + EXPECT_EQ(endpoint_info, long_ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, long_ble_advertisement.GetBluetoothMacAddress()); } TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { - BleAdvertisement ble_advertisement{ByteArray{}}; + BleAdvertisement ble_advertisement{false, ByteArray{}}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFailsForFastAdvertisement) { + BleAdvertisement ble_advertisement{true, ByteArray{}}; EXPECT_FALSE(ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { // Serialize good data into a good Ble Advertisement. - 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); + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + ByteArray ble_advertisement_bytes(ble_advertisement); // Shorten the valid Ble Advertisement. ByteArray short_ble_advertisement_bytes{ ble_advertisement_bytes.data(), BleAdvertisement::kMinAdvertisementLength - 1}; - BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + BleAdvertisement short_ble_advertisement{false, + short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} +TEST(BleAdvertisementTest, + ConstructionFromShortLengthBytesFailsForFastAdvertisement) { + // Serialize good data into a good Ble Advertisement. + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + fast_endpoint_info}; + ByteArray ble_advertisement_bytes(ble_advertisement); + + // Shorten the valid Ble Advertisement. + ByteArray short_ble_advertisement_bytes{ + ble_advertisement_bytes.data(), + BleAdvertisement::kMinAdvertisementLength - 1}; + + BleAdvertisement short_ble_advertisement{true, short_ble_advertisement_bytes}; EXPECT_FALSE(short_ble_advertisement.IsValid()); } TEST(BleAdvertisementTest, - ConstructionFromByesWithWrongEndpointNameLengthFails) { + ConstructionFromByesWithWrongEndpointInfoLengthFails) { // Serialize good data into a good Ble Advertisement. - 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); + ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; + ByteArray endpoint_info{std::string(kEndpointName)}; + BleAdvertisement ble_advertisement{ + kVersion, kPcp, + service_id_hash, std::string(kEndpointId), + endpoint_info, std::string(kBluetoothMacAddress)}; + ByteArray ble_advertisement_bytes(ble_advertisement); // Corrupt the EndpointNameLength bits. - auto corrupt_ble_advertisement_string = std::string(ble_advertisement_bytes); + std::string corrupt_ble_advertisement_string(ble_advertisement_bytes); corrupt_ble_advertisement_string[8] ^= 0x0FF; - auto corrupt_ble_advertisement_bytes = - ByteArray(corrupt_ble_advertisement_string); + ByteArray corrupt_ble_advertisement_bytes(corrupt_ble_advertisement_string); - BleAdvertisement corrupt_ble_advertisement{corrupt_ble_advertisement_bytes}; + BleAdvertisement corrupt_ble_advertisement{false, + corrupt_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupt_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromByesWithWrongEndpointInfoLengthFailsForFastAdvertisement) { + // Serialize good data into a good Ble Advertisement. + ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; + BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), + fast_endpoint_info}; + ByteArray ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Corrupt the EndpointInfoLength bits. + std::string corrupt_ble_advertisement_string(ble_advertisement_bytes); + corrupt_ble_advertisement_string[5] ^= 0x0FF; + ByteArray corrupt_ble_advertisement_bytes(corrupt_ble_advertisement_string); + + BleAdvertisement corrupt_ble_advertisement{true, + corrupt_ble_advertisement_bytes}; EXPECT_FALSE(corrupt_ble_advertisement.IsValid()); } diff --git a/cpp/core_v2/internal/ble_endpoint_channel.cc b/cpp/core_v2/internal/ble_endpoint_channel.cc new file mode 100644 index 00000000..aba332d0 --- /dev/null +++ b/cpp/core_v2/internal/ble_endpoint_channel.cc @@ -0,0 +1,45 @@ +#include "core_v2/internal/ble_endpoint_channel.h" + +#include + +#include "platform_v2/public/ble.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { + +OutputStream* GetOutputStreamOrNull(BleSocket& socket) { + if (socket.GetRemotePeripheral().IsValid()) return &socket.GetOutputStream(); + return nullptr; +} + +InputStream* GetInputStreamOrNull(BleSocket& socket) { + if (socket.GetRemotePeripheral().IsValid()) return &socket.GetInputStream(); + return nullptr; +} + +} // namespace + +BleEndpointChannel::BleEndpointChannel(const std::string& channel_name, + BleSocket socket) + : BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket), + GetOutputStreamOrNull(socket)), + ble_socket_(std::move(socket)) {} + +proto::connections::Medium BleEndpointChannel::GetMedium() const { + return proto::connections::Medium::BLE; +} + +void BleEndpointChannel::CloseImpl() { + auto status = ble_socket_.Close(); + if (!status.Ok()) { + NEARBY_LOG(INFO, "Failed to close Ble socket: exception=%d", status.value); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/ble_endpoint_channel.h b/cpp/core_v2/internal/ble_endpoint_channel.h new file mode 100644 index 00000000..74d68993 --- /dev/null +++ b/cpp/core_v2/internal/ble_endpoint_channel.h @@ -0,0 +1,29 @@ +#ifndef CORE_V2_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ + +#include "core_v2/internal/base_endpoint_channel.h" +#include "platform_v2/public/ble.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +class BleEndpointChannel final : public BaseEndpointChannel { + public: + // Creates both outgoing and incoming Ble channels. + BleEndpointChannel(const std::string& channel_name, BleSocket socket); + + proto::connections::Medium GetMedium() const override; + + private: + void CloseImpl() override; + + BleSocket ble_socket_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/bluetooth_device_name.cc b/cpp/core_v2/internal/bluetooth_device_name.cc index 8afd1737..48897dc9 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.cc +++ b/cpp/core_v2/internal/bluetooth_device_name.cc @@ -8,6 +8,7 @@ #include "platform_v2/base/base64_utils.h" #include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" namespace location { @@ -17,7 +18,7 @@ namespace connections { BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - absl::string_view endpoint_name) { + const ByteArray& endpoint_info) { if (version != Version::kV1 || endpoint_id.empty() || endpoint_id.length() != kEndpointIdLength || service_id_hash.size() != kServiceIdHashLength) { @@ -36,7 +37,7 @@ BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, pcp_ = pcp; endpoint_id_ = std::string(endpoint_id); service_id_hash_ = service_id_hash; - endpoint_name_ = std::string(endpoint_name); + endpoint_info_ = endpoint_info; } BluetoothDeviceName::BluetoothDeviceName( @@ -106,24 +107,22 @@ BluetoothDeviceName::BluetoothDeviceName( // 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 next 1 byte are supposed to be the length of the endpoint_info. + std::uint32_t expected_endpoint_info_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) { + // The rest bytes are supposed to be the endpoint_info + endpoint_info_ = base_input_stream.ReadBytes(expected_endpoint_info_length); + if (endpoint_info_.Empty() || + endpoint_info_.size() != expected_endpoint_info_length) { NEARBY_LOG(INFO, "Cannot deserialize BluetoothDeviceName: expected " - "endpointName to be %d bytes, got %" PRIu64, - expected_endpoint_name_length, endpoint_name_bytes.size()); + "endpoint info to be %d bytes, got %" PRIu64, + expected_endpoint_info_length, endpoint_info_.size()); // Clear enpoint_id for validadity. endpoint_id_.clear(); return; } - endpoint_name_ = std::string{endpoint_name_bytes}; } BluetoothDeviceName::operator std::string() const { @@ -140,14 +139,14 @@ BluetoothDeviceName::operator std::string() const { ByteArray reserved_bytes{kReservedLength}; - std::string usable_endpoint_name(endpoint_name_); - if (endpoint_name_.size() > kMaxEndpointNameLength) { + ByteArray usable_endpoint_info(endpoint_info_); + if (endpoint_info_.size() > kMaxEndpointInfoLength) { NEARBY_LOG(INFO, "While serializing Advertisement, truncating Endpoint Name %s " "(%lu bytes) down to %d bytes", - endpoint_name_.c_str(), endpoint_name_.size(), - kMaxEndpointNameLength); - usable_endpoint_name.erase(kMaxEndpointNameLength); + absl::BytesToHexString(endpoint_info_.data()).c_str(), + endpoint_info_.size(), kMaxEndpointInfoLength); + usable_endpoint_info.SetData(endpoint_info_.data(), kMaxEndpointInfoLength); } // clang-format off @@ -155,8 +154,8 @@ BluetoothDeviceName::operator std::string() const { endpoint_id_, std::string(service_id_hash_), std::string(reserved_bytes), - std::string(1, usable_endpoint_name.size()), - usable_endpoint_name); + std::string(1, usable_endpoint_info.size()), + std::string(usable_endpoint_info)); // clang-format on return Base64Utils::Encode(ByteArray{std::move(out)}); diff --git a/cpp/core_v2/internal/bluetooth_device_name.h b/cpp/core_v2/internal/bluetooth_device_name.h index b92d433a..c5c3f652 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.h +++ b/cpp/core_v2/internal/bluetooth_device_name.h @@ -30,7 +30,7 @@ class BluetoothDeviceName { BluetoothDeviceName() = default; BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - absl::string_view endpoint_name); + const ByteArray& endpoint_info); explicit BluetoothDeviceName(absl::string_view bluetooth_device_name_string); BluetoothDeviceName(const BluetoothDeviceName&) = default; BluetoothDeviceName& operator=(const BluetoothDeviceName&) = default; @@ -45,15 +45,15 @@ class BluetoothDeviceName { Pcp GetPcp() const { return pcp_; } std::string GetEndpointId() const { return endpoint_id_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } - std::string GetEndpointName() const { return endpoint_name_; } + ByteArray GetEndpointInfo() const { return endpoint_info_; } private: static constexpr int kMaxBluetoothDeviceNameLength = 147; static constexpr int kEndpointIdLength = 4; static constexpr int kReservedLength = 7; - static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kMaxEndpointInfoLength = 131; static constexpr int kMinBluetoothDeviceNameLength = - kMaxBluetoothDeviceNameLength - kMaxEndpointNameLength; + kMaxBluetoothDeviceNameLength - kMaxEndpointInfoLength; static constexpr int kVersionBitmask = 0x0E0; static constexpr int kPcpBitmask = 0x01F; @@ -63,7 +63,7 @@ class BluetoothDeviceName { Pcp pcp_{Pcp::kUnknown}; std::string endpoint_id_; ByteArray service_id_hash_; - std::string endpoint_name_; + ByteArray endpoint_info_; }; } // namespace connections diff --git a/cpp/core_v2/internal/bluetooth_device_name_test.cc b/cpp/core_v2/internal/bluetooth_device_name_test.cc index f92c5468..d957bb63 100644 --- a/cpp/core_v2/internal/bluetooth_device_name_test.cc +++ b/cpp/core_v2/internal/bluetooth_device_name_test.cc @@ -20,38 +20,40 @@ constexpr absl::string_view kEndPointName{"RAWK + ROWL!"}; TEST(BluetoothDeviceNameTest, ConstructionWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_TRUE(bluetooth_device_name.IsValid()); EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp()); EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); - EXPECT_EQ(kEndPointName, bluetooth_device_name.GetEndpointName()); + EXPECT_EQ(endpoint_info, bluetooth_device_name.GetEndpointInfo()); } TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { - std::string empty_endpoint_name; + ByteArray empty_endpoint_info; ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_name}; + kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_info}; EXPECT_TRUE(bluetooth_device_name.IsValid()); EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp()); EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); - EXPECT_EQ(empty_endpoint_name, bluetooth_device_name.GetEndpointName()); + EXPECT_EQ(empty_endpoint_info, bluetooth_device_name.GetEndpointInfo()); } TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{bad_version, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -60,8 +62,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) { auto bad_pcp = static_cast(666); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{kVersion, bad_pcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -70,8 +73,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) { std::string short_endpoint_id("AB1"); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, short_endpoint_id, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -80,8 +84,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) { std::string long_endpoint_id("AB12X"); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, long_endpoint_id, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -90,8 +95,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortServiceIdHash) { char short_service_id_hash_bytes[] = "\x0a\x0b"; ByteArray short_service_id_hash{short_service_id_hash_bytes}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName}; + kVersion, kPcp, kEndPointID, short_service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -100,8 +106,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongServiceIdHash) { char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; ByteArray long_service_id_hash{long_service_id_hash_bytes}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName}; + kVersion, kPcp, kEndPointID, long_service_id_hash, endpoint_info}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -119,8 +126,9 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortStringLength) { TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { // Serialize good data into a good Bluetooth Device Name. ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; auto bluetooth_device_name_string = std::string(bluetooth_device_name); // Base64-decode the good Bluetooth Device Name. @@ -145,9 +153,10 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { TEST(BluetoothDeviceNameTest, CanParseGeneratedName) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; // Build name1 from scratch. BluetoothDeviceName name1{kVersion, kPcp, kEndPointID, service_id_hash, - kEndPointName}; + endpoint_info}; // Build name2 from string composed from name1. BluetoothDeviceName name2{std::string(name1)}; EXPECT_TRUE(name1.IsValid()); @@ -156,7 +165,7 @@ TEST(BluetoothDeviceNameTest, CanParseGeneratedName) { EXPECT_EQ(name1.GetPcp(), name2.GetPcp()); EXPECT_EQ(name1.GetEndpointId(), name2.GetEndpointId()); EXPECT_EQ(name1.GetServiceIdHash(), name2.GetServiceIdHash()); - EXPECT_EQ(name1.GetEndpointName(), name2.GetEndpointName()); + EXPECT_EQ(name1.GetEndpointInfo(), name2.GetEndpointInfo()); } } // namespace diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc index 3ee2d6c3..bd5a36a8 100644 --- a/cpp/core_v2/internal/client_proxy.cc +++ b/cpp/core_v2/internal/client_proxy.cc @@ -12,6 +12,7 @@ #include "proto/connections_enums.pb.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" #include "absl/strings/str_cat.h" namespace location { @@ -24,21 +25,22 @@ ClientProxy::~ClientProxy() { Reset(); } std::int64_t ClientProxy::GetClientId() const { return client_id_; } -std::string ClientProxy::GenerateLocalEndpointId() { - // 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 kEndpointIdLength bytes to make ID. - ByteArray id_hash = Crypto::Sha256( - absl::StrCat("client", prng_.NextInt64())); - - 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; +std::string ClientProxy::GetLocalEndpointId() { + if (local_endpoint_id_.empty()) { + // 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 kEndpointIdLength bytes to make ID. + ByteArray id_hash = + Crypto::Sha256(absl::StrCat("client", prng_.NextInt64())); + 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()); + local_endpoint_id_ = id; + } + return local_endpoint_id_; } void ClientProxy::Reset() { @@ -55,6 +57,7 @@ void ClientProxy::StartedAdvertising( absl::Span mediums) { MutexLock lock(&mutex_); + if (connections_.empty()) local_endpoint_id_.clear(); advertising_info_ = {service_id, listener}; } @@ -64,6 +67,7 @@ void ClientProxy::StoppedAdvertising() { if (IsAdvertising()) { advertising_info_.Clear(); } + if (connections_.empty()) local_endpoint_id_.clear(); } bool ClientProxy::IsAdvertising() const { @@ -83,6 +87,7 @@ void ClientProxy::StartedDiscovery( absl::Span mediums) { MutexLock lock(&mutex_); + if (connections_.empty()) local_endpoint_id_.clear(); discovery_info_ = DiscoveryInfo{service_id, listener}; } @@ -93,6 +98,7 @@ void ClientProxy::StoppedDiscovery() { discovered_endpoint_ids_.clear(); discovery_info_.Clear(); } + if (connections_.empty()) local_endpoint_id_.clear(); } bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const { @@ -115,13 +121,14 @@ std::string ClientProxy::GetDiscoveryServiceId() const { void ClientProxy::OnEndpointFound(const std::string& service_id, const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, proto::connections::Medium medium) { MutexLock lock(&mutex_); 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()); + "ClientProxy [Endpoint Found]: [enter] id=%s; service=%s; info=%s", + endpoint_id.c_str(), service_id.c_str(), + absl::BytesToHexString(endpoint_info.data()).c_str()); if (!IsDiscoveringServiceId(service_id)) { NEARBY_LOG(INFO, "ClientProxy [Endpoint Found]: [no discovery] id=%s", endpoint_id.c_str()); @@ -133,7 +140,7 @@ void ClientProxy::OnEndpointFound(const std::string& service_id, return; } discovered_endpoint_ids_.insert(endpoint_id); - discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_name, + discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_info, service_id); } @@ -150,6 +157,7 @@ void ClientProxy::OnEndpointLost(const std::string& service_id, void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, const ConnectionResponseInfo& info, + const ConnectionOptions& options, const ConnectionListener& listener) { MutexLock lock(&mutex_); @@ -160,6 +168,7 @@ void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, endpoint_id, Connection{ .is_incoming = info.is_incoming_connection, .connection_listener = listener, + .connection_options = options, }); // Instead of using structured binding which is nice, but banned // (can not use c++17 features, until chromium does) we unpack manually. @@ -234,6 +243,7 @@ void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) { item->connection_listener.disconnected_cb({endpoint_id}); } connections_.erase(endpoint_id); + if (connections_.empty()) local_endpoint_id_.clear(); } } @@ -248,6 +258,17 @@ bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id, return false; } +BooleanMediumSelector ClientProxy::GetUpgradeMediums( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->connection_options.allowed; + } + return {}; +} + bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const { return ConnectionStatusMatches(endpoint_id, Connection::kConnected); } @@ -469,6 +490,7 @@ void ClientProxy::RemoveAllEndpoints() { // endpoint, in the case when this is called from stopAllEndpoints(). For now, // just remove without notifying. connections_.clear(); + local_endpoint_id_.clear(); } bool ClientProxy::ConnectionStatusesContains( diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h index 67ada3ef..3185f785 100644 --- a/cpp/core_v2/internal/client_proxy.h +++ b/cpp/core_v2/internal/client_proxy.h @@ -6,6 +6,7 @@ #include #include "core_v2/listeners.h" +#include "core_v2/options.h" #include "core_v2/status.h" #include "core_v2/strategy.h" #include "platform_v2/base/byte_array.h" @@ -35,7 +36,7 @@ class ClientProxy final { std::int64_t GetClientId() const; - std::string GenerateLocalEndpointId(); + std::string GetLocalEndpointId(); // Clears all the runtime state of this client. void Reset(); @@ -64,7 +65,7 @@ class ClientProxy final { // Proxies to the client's DiscoveryListener::OnEndpointFound() callback. void OnEndpointFound(const std::string& service_id, const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, proto::connections::Medium medium); // Proxies to the client's DiscoveryListener::OnEndpointLost() callback. void OnEndpointLost(const std::string& service_id, @@ -73,6 +74,7 @@ class ClientProxy final { // Proxies to the client's ConnectionListener::OnInitiated() callback. void OnConnectionInitiated(const std::string& endpoint_id, const ConnectionResponseInfo& info, + const ConnectionOptions& options, const ConnectionListener& listener); // Proxies to the client's ConnectionListener::OnAccepted() callback. @@ -88,6 +90,8 @@ class ClientProxy final { // ConnectionListener.disconnected_cb() callback. void OnDisconnected(const std::string& endpoint_id, bool notify); + // Returns all mediums eligible for upgrade. + BooleanMediumSelector GetUpgradeMediums(const std::string& endpoint_id) const; // Returns true if it's safe to send payloads to this endpoint. bool IsConnectedToEndpoint(const std::string& endpoint_id) const; // Returns all endpoints that can safely be sent payloads. @@ -157,6 +161,7 @@ class ClientProxy final { Status status{kPending}; ConnectionListener connection_listener; PayloadListener payload_listener; + ConnectionOptions connection_options; }; struct AdvertisingInfo { @@ -188,6 +193,7 @@ class ClientProxy final { mutable RecursiveMutex mutex_; std::int64_t client_id_; + std::string local_endpoint_id_; Prng prng_; // If not empty, we are currently advertising and accepting connection diff --git a/cpp/core_v2/internal/client_proxy_test.cc b/cpp/core_v2/internal/client_proxy_test.cc index 88a3e93e..5c091852 100644 --- a/cpp/core_v2/internal/client_proxy_test.cc +++ b/cpp/core_v2/internal/client_proxy_test.cc @@ -3,6 +3,7 @@ #include #include "core_v2/listeners.h" +#include "core_v2/options.h" #include "core_v2/strategy.h" #include "platform_v2/base/byte_array.h" #include "gmock/gmock.h" @@ -22,7 +23,7 @@ class ClientProxyTest : public testing::Test { protected: struct MockDiscoveryListener { StrictMock> endpoint_found_cb; StrictMock> @@ -52,14 +53,14 @@ class ClientProxyTest : public testing::Test { }; struct Endpoint { - std::string name; + ByteArray info; std::string id; }; Endpoint StartAdvertising(ClientProxy* client, ConnectionListener listener) { Endpoint endpoint{ - .name = "advertising endpoint name", - .id = client->GenerateLocalEndpointId(), + .info = ByteArray{"advertising endpoint name"}, + .id = client->GetLocalEndpointId(), }; client->StartedAdvertising(service_id_, strategy_, listener, absl::MakeSpan(mediums_)); @@ -68,8 +69,8 @@ class ClientProxyTest : public testing::Test { Endpoint StartDiscovery(ClientProxy* client, DiscoveryListener listener) { Endpoint endpoint{ - .name = "discovery endpoint name", - .id = client->GenerateLocalEndpointId(), + .info = ByteArray{"discovery endpoint name"}, + .id = client->GetLocalEndpointId(), }; client->StartedDiscovery(service_id_, strategy_, listener, absl::MakeSpan(mediums_)); @@ -78,7 +79,8 @@ class ClientProxyTest : public testing::Test { void OnDiscoveryEndpointFound(ClientProxy* client, const Endpoint& endpoint) { EXPECT_CALL(mock_discovery_.endpoint_found_cb, Call).Times(1); - client->OnEndpointFound(service_id_, endpoint.id, endpoint.name, medium_); + client->OnEndpointFound(service_id_, endpoint.id, endpoint.info, + medium_); } void OnDiscoveryEndpointLost(ClientProxy* client, const Endpoint& endpoint) { @@ -91,8 +93,9 @@ class ClientProxyTest : public testing::Test { EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1); const std::string auth_token{"auth_token"}; const ByteArray raw_auth_token{auth_token}; - advertising_connection_info_.remote_endpoint_name = endpoint.name; + advertising_connection_info_.remote_endpoint_info = endpoint.info; client->OnConnectionInitiated(endpoint.id, advertising_connection_info_, + connection_options_, discovery_connection_listener_); EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); } @@ -208,6 +211,7 @@ class ClientProxyTest : public testing::Test { .payload_progress_cb = mock_discovery_payload_.payload_progress_cb.AsStdFunction(), }; + ConnectionOptions connection_options_; }; TEST_F(ClientProxyTest, ConstructorDestructorWorks) { SUCCEED(); } @@ -217,8 +221,8 @@ TEST_F(ClientProxyTest, ClientIdIsUnique) { } TEST_F(ClientProxyTest, GeneratedEndpointIdIsUnique) { - EXPECT_NE(client1_.GenerateLocalEndpointId(), - client2_.GenerateLocalEndpointId()); + EXPECT_NE(client1_.GetLocalEndpointId(), + client2_.GetLocalEndpointId()); } TEST_F(ClientProxyTest, ResetClearsState) { diff --git a/cpp/core_v2/internal/encryption_runner.cc b/cpp/core_v2/internal/encryption_runner.cc index 226c0695..ddddd887 100644 --- a/cpp/core_v2/internal/encryption_runner.cc +++ b/cpp/core_v2/internal/encryption_runner.cc @@ -52,13 +52,13 @@ bool HandleEncryptionSuccess(const std::string& endpoint_id, return true; } -void CancelableAlarmRunnable(ClientProxy* client_proxy, +void CancelableAlarmRunnable(ClientProxy* client, const std::string& endpoint_id, EndpointChannel* endpoint_channel) { NEARBY_LOG(INFO, "Timing out encryption for client %" PRId64 " to endpoint %s after %" PRId64 " ms", - client_proxy->GetClientId(), endpoint_id.c_str(), + client->GetClientId(), endpoint_id.c_str(), static_cast(absl::ToInt64Milliseconds(kTimeout))); endpoint_channel->Close(); } @@ -76,7 +76,7 @@ class ServerRunnable final { void operator()() const { CancelableAlarm timeout_alarm( - "EncryptionRunner.startServer() timeout", + "EncryptionRunner.StartServer() timeout", [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, kTimeout, alarm_executor_); @@ -109,7 +109,7 @@ class ServerRunnable final { return; } - NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 1 from endpoint %s", + NEARBY_LOG(INFO, "In StartServer(), read UKEY2 Message 1 from endpoint %s", endpoint_id_.c_str()); // Message 2 (Server Init) @@ -131,7 +131,7 @@ class ServerRunnable final { return; } - NEARBY_LOG(INFO, "In startServer(), wrote UKEY2 Message 2 to endpoint %s", + NEARBY_LOG(INFO, "In StartServer(), wrote UKEY2 Message 2 to endpoint %s", endpoint_id_.c_str()); // Message 3 (Client Finish) @@ -156,7 +156,7 @@ class ServerRunnable final { return; } - NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 3 from endpoint %s", + NEARBY_LOG(INFO, "In StartServer(), read UKEY2 Message 3 from endpoint %s", endpoint_id_.c_str()); timeout_alarm.Cancel(); @@ -170,7 +170,7 @@ class ServerRunnable final { private: void LogException() const { - NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s", + NEARBY_LOG(ERROR, "In StartServer(), UKEY2 failed with endpoint %s", endpoint_id_.c_str()); } @@ -185,7 +185,7 @@ class ServerRunnable final { channel_->Write(ByteArray(*parse_result.alert_to_send)); if (!write_exception.Ok()) { NEARBY_LOG(WARNING, - "In startServer(), client %" PRId64 + "In StartServer(), client %" PRId64 " failed to pass the alert error message to endpoint %s", client_->GetClientId(), endpoint_id_.c_str()); } @@ -342,22 +342,22 @@ EncryptionRunner::~EncryptionRunner() { } void EncryptionRunner::StartServer( - ClientProxy* client_proxy, const std::string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, EndpointChannel* endpoint_channel, EncryptionRunner::ResultListener&& listener) { server_executor_.Execute( - [runnable{ServerRunnable(client_proxy, &alarm_executor_, endpoint_id, + [runnable{ServerRunnable(client, &alarm_executor_, endpoint_id, endpoint_channel, std::move(listener))}]() { runnable(); }); } void EncryptionRunner::StartClient( - ClientProxy* client_proxy, const std::string& endpoint_id, + ClientProxy* client, const std::string& endpoint_id, EndpointChannel* endpoint_channel, EncryptionRunner::ResultListener&& listener) { client_executor_.Execute( - [runnable{ClientRunnable(client_proxy, &alarm_executor_, endpoint_id, + [runnable{ClientRunnable(client, &alarm_executor_, endpoint_id, endpoint_channel, std::move(listener))}]() { runnable(); }); diff --git a/cpp/core_v2/internal/encryption_runner.h b/cpp/core_v2/internal/encryption_runner.h index 399fb0b5..a3cd73ea 100644 --- a/cpp/core_v2/internal/encryption_runner.h +++ b/cpp/core_v2/internal/encryption_runner.h @@ -51,11 +51,11 @@ class EncryptionRunner { }; // @AnyThread - void StartServer(ClientProxy* client_proxy, const std::string& endpoint_id, + void StartServer(ClientProxy* client, const std::string& endpoint_id, EndpointChannel* endpoint_channel, ResultListener&& result_listener); // @AnyThread - void StartClient(ClientProxy* client_proxy, const std::string& endpoint_id, + void StartClient(ClientProxy* client, const std::string& endpoint_id, EndpointChannel* endpoint_channel, ResultListener&& result_listener); diff --git a/cpp/core_v2/internal/endpoint_channel_manager.cc b/cpp/core_v2/internal/endpoint_channel_manager.cc index f214c845..d3d0354c 100644 --- a/cpp/core_v2/internal/endpoint_channel_manager.cc +++ b/cpp/core_v2/internal/endpoint_channel_manager.cc @@ -74,6 +74,11 @@ void EndpointChannelManager::SetActiveEndpointChannel( if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint); } +int EndpointChannelManager::GetConnectedEndpointsCount() const { + MutexLock lock(&mutex_); + return channel_state_.GetConnectedEndpointsCount(); +} + ///////////////////////////////// ChannelState ///////////////////////////////// // endpoint - channel endpoint to encrypt diff --git a/cpp/core_v2/internal/endpoint_channel_manager.h b/cpp/core_v2/internal/endpoint_channel_manager.h index 14f8e718..b9f82dc3 100644 --- a/cpp/core_v2/internal/endpoint_channel_manager.h +++ b/cpp/core_v2/internal/endpoint_channel_manager.h @@ -80,6 +80,8 @@ class EndpointChannelManager final { bool UnregisterChannelForEndpoint(const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + int GetConnectedEndpointsCount() const ABSL_LOCKS_EXCLUDED(mutex_); + private: // Tracks channel state for all endpoints. This includes what EndpointChannel // the endpoint is currently using and whether or not the EndpointChannel has @@ -97,9 +99,7 @@ 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::shared_ptr context; @@ -134,6 +134,7 @@ class EndpointChannelManager final { proto::connections::DisconnectionReason reason); bool EncryptChannel(EndpointData* endpoint); + int GetConnectedEndpointsCount() const { return endpoints_.size(); } private: // Endpoint ID -> EndpointData. Contains everything we know about the @@ -146,7 +147,7 @@ class EndpointChannelManager final { std::unique_ptr channel) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - Mutex mutex_; + mutable Mutex mutex_; ChannelState channel_state_ ABSL_GUARDED_BY(mutex_); }; diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 615dd49a..4e3e5d7a 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -227,8 +227,7 @@ EndpointManager::~EndpointManager() { NEARBY_LOG(INFO, "EndpointManager is down"); } -EndpointManager::FrameProcessor::Handle -EndpointManager::RegisterFrameProcessor( +EndpointManager::FrameProcessor::Handle EndpointManager::RegisterFrameProcessor( V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) { const FrameProcessor::Handle handle = processor; CountDownLatch latch(1); @@ -318,6 +317,7 @@ void EndpointManager::EnsureWorkersTerminated(const std::string& endpoint_id) { void EndpointManager::RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id, const ConnectionResponseInfo& info, + const ConnectionOptions& options, std::unique_ptr channel, const ConnectionListener& listener) { CountDownLatch latch(1); @@ -329,7 +329,8 @@ void EndpointManager::RegisterEndpoint(ClientProxy* client, // We ignore the risk of job not scheduled (and an associated risk of memory // leak), because this may only happen during service shutdown. RunOnEndpointManagerThread([this, client, channel = channel.release(), - &endpoint_id, &info, &listener, &latch]() { + &endpoint_id, &info, &options, &listener, + &latch]() { // Pass ownership of channel to EndpointChannelManager NEARBY_LOG(INFO, "Registering endpoint with channel manager: id=%s", endpoint_id.c_str()); @@ -382,7 +383,7 @@ void EndpointManager::RegisterEndpoint(ClientProxy* client, // It's now time to let the client know of this new connection so that // they can accept or reject it. - client->OnConnectionInitiated(endpoint_id, info, listener); + client->OnConnectionInitiated(endpoint_id, info, options, listener); latch.CountDown(); }); latch.Await(); diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h index b5ea8194..898a6e86 100644 --- a/cpp/core_v2/internal/endpoint_manager.h +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -81,8 +81,8 @@ class EndpointManager { // FrameProcessor* instances are of dynamic duration and survive all sessions. // returns unique handle to be used for unregistering. // Blocks until registration is complete. - FrameProcessor::Handle RegisterFrameProcessor( - V1Frame::FrameType frame_type, FrameProcessor* processor); + FrameProcessor::Handle RegisterFrameProcessor(V1Frame::FrameType frame_type, + FrameProcessor* processor); void UnregisterFrameProcessor(V1Frame::FrameType frame_type, const void* handle, bool sync = false); @@ -91,6 +91,7 @@ class EndpointManager { // Blocks until registration is complete. void RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id, const ConnectionResponseInfo& info, + const ConnectionOptions& options, std::unique_ptr channel, const ConnectionListener& listener); // Called when a client explicitly asks to disconnect from this endpoint. In @@ -201,8 +202,7 @@ class EndpointManager { EndpointChannelManager* channel_manager_; - absl::flat_hash_map - frame_processors_; + absl::flat_hash_map frame_processors_; // We keep track of all registered channel endpoints here. absl::flat_hash_map endpoints_; diff --git a/cpp/core_v2/internal/endpoint_manager_test.cc b/cpp/core_v2/internal/endpoint_manager_test.cc index fa9b485a..7842454e 100644 --- a/cpp/core_v2/internal/endpoint_manager_test.cc +++ b/cpp/core_v2/internal/endpoint_manager_test.cc @@ -6,6 +6,7 @@ #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/offline_frames.h" +#include "core_v2/options.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" #include "platform_v2/public/count_down_latch.h" @@ -40,8 +41,7 @@ class MockEndpointChannel : public EndpointChannel { MOCK_METHOD(std::string, GetName, (), (const override)); MOCK_METHOD(Medium, GetMedium, (), (const override)); MOCK_METHOD(void, EnableEncryption, - (std::shared_ptr context), - (override)); + (std::shared_ptr context), (override)); MOCK_METHOD(bool, IsPaused, (), (const override)); MOCK_METHOD(void, Pause, (), (override)); MOCK_METHOD(void, Resume, (), (override)); @@ -89,22 +89,23 @@ class EndpointManagerTest : public ::testing::Test { EXPECT_CALL(*channel, GetLastReadTimestamp()) .WillRepeatedly(Return(start_time_)); EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1); - em_.RegisterEndpoint(&client_, endpoint_id_, info_, std::move(channel), - listener_); + em_.RegisterEndpoint(&client_, endpoint_id_, info_, options_, + std::move(channel), listener_); if (should_close) { EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); } } ClientProxy client_; + ConnectionOptions options_; std::vector> processors_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; std::string endpoint_id_ = "endpoint_id"; ConnectionResponseInfo info_ = { - .remote_endpoint_name = "name", + .remote_endpoint_info = ByteArray{"info"}, .authentication_token = "auth_token", - .raw_authentication_token = ByteArray("auth_token"), + .raw_authentication_token = ByteArray{"auth_token"}, .is_incoming_connection = true, }; struct MockConnectionListener { @@ -158,8 +159,10 @@ TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) { TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { auto endpoint_channel = std::make_unique(); auto connect_request = std::make_unique(); - auto read_data = parser::ForConnectionRequest("endpoint_id", "endpoint_name", - 1234, std::vector{Medium::BLE}); + ByteArray endpoint_info{"endpoint_name"}; + auto read_data = + parser::ForConnectionRequest("endpoint_id", endpoint_info, + 1234, std::vector{Medium::BLE}); EXPECT_CALL(*connect_request, OnIncomingFrame); EXPECT_CALL(*connect_request, OnEndpointDisconnect); EXPECT_CALL(*endpoint_channel, Read()) diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index 1681a94f..02190bcc 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -2,6 +2,7 @@ cc_library( name = "mediums", srcs = [ "advertisement_read_result.cc", + "ble.cc", "ble_advertisement.cc", "ble_advertisement_header.cc", "ble_packet.cc", @@ -15,6 +16,7 @@ cc_library( ], hdrs = [ "advertisement_read_result.h", + "ble.h", "ble_advertisement.h", "ble_advertisement_header.h", "ble_packet.h", @@ -56,6 +58,7 @@ cc_library( srcs = ["utils.cc"], hdrs = ["utils.h"], visibility = [ + "//core_v2/internal:__pkg__", "//core_v2/internal/mediums/webrtc:__pkg__", ], deps = [ @@ -74,6 +77,7 @@ cc_test( "ble_advertisement_test.cc", "ble_packet_test.cc", "ble_peripheral_test.cc", + "ble_test.cc", "bloom_filter_test.cc", "bluetooth_classic_test.cc", "bluetooth_radio_test.cc", @@ -93,6 +97,7 @@ cc_test( "//platform_v2/public:logging", "//platform_v2/public:types", "//testing/base/public:gunit_main", + "//absl/strings", "//absl/time", ], ) diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc new file mode 100644 index 00000000..ae1efae9 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -0,0 +1,269 @@ +#include "core_v2/internal/mediums/ble.h" + +#include +#include +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +Ble::Ble(BluetoothRadio& radio) : radio_(radio) {} + +bool Ble::IsAvailable() const { + MutexLock lock(&mutex_); + + return IsAvailableLocked(); +} + +bool Ble::IsAvailableLocked() const { return medium_.IsValid(); } + +bool Ble::StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) { + MutexLock lock(&mutex_); + + if (advertisement_bytes.Empty()) { + NEARBY_LOGS(INFO) + << "Refusing to turn on BLE advertising. Empty advertisement data."; + return false; + } + + if (advertisement_bytes.size() > kMaxAdvertisementLength) { + NEARBY_LOG(INFO, + "Refusing to start BLE advertising because the advertisement " + "was too long. Expected at most %d bytes but received %d.", + kMaxAdvertisementLength, advertisement_bytes.size()); + return false; + } + + if (IsAdvertisingLocked(service_id)) { + NEARBY_LOGS(INFO) + << "Failed to BLE advertise because we're already advertising."; + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOGS(INFO) + << "Can't start BLE scanning because Bluetooth was never turned on"; + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOGS(INFO) << "Can't turn on BLE advertising. BLE is not available."; + return false; + } + + NEARBY_LOGS(INFO) << "Turning on BLE advertising with advertisement bytes=" + << advertisement_bytes.data() << "(" + << advertisement_bytes.size() << ")" + << ", service id=" << service_id; + if (!medium_.StartAdvertising(service_id, advertisement_bytes)) { + NEARBY_LOGS(INFO) + << "Failed to turn on BLE advertising with advertisement bytes=" + << advertisement_bytes.data() << "(" << advertisement_bytes.size() + << ")"; + return false; + } + + advertising_info_.Add(service_id); + return true; +} + +bool Ble::StopAdvertising(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsAdvertisingLocked(service_id)) { + NEARBY_LOGS(INFO) << "Can't turn off BLE advertising; it is already off"; + return false; + } + + NEARBY_LOGS(INFO) << "Turned off BLE advertising with service id=" + << service_id; + bool ret = medium_.StopAdvertising(service_id); + // Reset our bundle of advertising state to mark that we're no longer + // advertising. + advertising_info_.Remove(service_id); + return ret; +} + +bool Ble::IsAdvertising(const std::string& service_id) { + MutexLock lock(&mutex_); + + return IsAdvertisingLocked(service_id); +} + +bool Ble::IsAdvertisingLocked(const std::string& service_id) { + return advertising_info_.Existed(service_id); +} + +bool Ble::StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOGS(INFO) + << "Refusing to start BLE scanning with empty service id."; + return false; + } + + if (IsScanningLocked(service_id)) { + NEARBY_LOGS(INFO) << "Refusing to start scan of BLE peripherals because " + "another scanning is already in-progress."; + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOGS(INFO) + << "Can't start BLE scanning because Bluetooth was never turned on"; + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOGS(INFO) + << "Can't scan BLE peripherals because BLE isn't available."; + return false; + } + + if (!medium_.StartScanning(service_id, callback)) { + NEARBY_LOGS(INFO) << "Failed to start scan of BLE services."; + return false; + } + + NEARBY_LOGS(INFO) << "Turned on BLE scanning with service id=" << service_id; + // Mark the fact that we're currently performing a BLE discovering. + scanning_info_.Add(service_id); + return true; +} + +bool Ble::StopScanning(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsScanningLocked(service_id)) { + NEARBY_LOGS(INFO) << "Can't turn off BLE sacanning because we never " + "started scanning."; + return false; + } + + NEARBY_LOG(INFO, "Turned off BLE scanning with service id=%s", + service_id.c_str()); + bool ret = medium_.StopScanning(service_id); + scanning_info_.Clear(); + return ret; +} + +bool Ble::IsScanning(const std::string& service_id) { + MutexLock lock(&mutex_); + + return IsScanningLocked(service_id); +} + +bool Ble::IsScanningLocked(const std::string& service_id) { + return scanning_info_.Existed(service_id); +} + +bool Ble::StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) { + MutexLock lock(&mutex_); + + if (service_id.empty()) { + NEARBY_LOGS(INFO) + << "Refusing to start accepting BLE connections with empty service id."; + return false; + } + + if (IsAcceptingConnectionsLocked(service_id)) { + NEARBY_LOGS(INFO) + << "Refusing to start accepting BLE connections for " + << service_id + << " because another BLE peripheral socket is already in-progress."; + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for " + << service_id + << " because Bluetooth isn't enabled."; + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for " + << service_id << " because BLE isn't available."; + return false; + } + + if (!medium_.StartAcceptingConnections(service_id, callback)) { + NEARBY_LOGS(INFO) << "Failed to accept connections callback for " + << service_id << " ."; + return false; + } + + accepting_connections_info_.Add(service_id); + return true; +} + +bool Ble::StopAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + if (!IsAcceptingConnectionsLocked(service_id)) { + NEARBY_LOGS(INFO) + << "Can't stop accepting BLE connections because it was never started."; + return false; + } + + bool ret = medium_.StopAcceptingConnections(service_id); + // Reset our bundle of accepting connections state to mark that we're no + // longer accepting connections. + accepting_connections_info_.Remove(service_id); + return ret; +} + +bool Ble::IsAcceptingConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + return IsAcceptingConnectionsLocked(service_id); +} + +bool Ble::IsAcceptingConnectionsLocked(const std::string& service_id) { + return accepting_connections_info_.Existed(service_id); +} + +BleSocket Ble::Connect(BlePeripheral& peripheral, + const std::string& service_id) { + MutexLock lock(&mutex_); + NEARBY_LOGS(INFO) << "BLE::Connect: service=" << &peripheral; + // Socket to return. To allow for NRVO to work, it has to be a single object. + BleSocket socket; + + if (service_id.empty()) { + NEARBY_LOGS(INFO) << "Refusing to create BLE socket with empty service_id."; + return socket; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOGS(INFO) << "Can't create client BLE socket to " + << &peripheral << " because Bluetooth isn't enabled."; + return socket; + } + + if (!IsAvailableLocked()) { + NEARBY_LOGS(INFO) << "Can't create client BLE socket [service_id=" + << service_id << "]; BLE isn't available."; + return socket; + } + + socket = medium_.Connect(peripheral, service_id); + if (!socket.IsValid()) { + NEARBY_LOGS(INFO) << "Failed to Connect via BLE [service=" << service_id + << "]"; + } + + return socket; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h new file mode 100644 index 00000000..7880f837 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble.h @@ -0,0 +1,162 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_H_ + +#include +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/ble.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" + +namespace location { +namespace nearby { +namespace connections { + +class Ble { + public: + using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback; + using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback; + + explicit Ble(BluetoothRadio& bluetooth_radio); + ~Ble() = default; + + // Returns true, if Ble communications are supported by a platform. + bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets custom advertisement data, and then enables Ble advertising. + // Returns true, if data is successfully set, and false otherwise. + bool StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables Ble advertising. + bool StopAdvertising(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Enables Ble scanning mode. Will report any discoverable peripherals in + // range through a callback. Returns true, if scanning mode was enabled, + // false otherwise. + bool StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables Ble discovery mode. + bool StopScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Starts a worker thread, creates a Ble 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. + bool StopAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsAcceptingConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if this object owns a valid platform implementation. + bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return medium_.IsValid(); + } + + // Returns true if this object has a valid BluetoothAdapter reference. + bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return adapter_.IsValid(); + } + + // Establishes connection to Ble peripheral that was might be started on + // another peripheral with StartAcceptingConnections() using the same + // service_id. Blocks until connection is established, or server-side is + // terminated. Returns socket instance. On success, BleSocket.IsValid() return + // true. + BleSocket Connect(BlePeripheral& peripheral, const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + static constexpr int kMaxAdvertisementLength = 512; + + struct AdvertisingInfo { + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } + + absl::flat_hash_set service_ids; + }; + + struct ScanningInfo { + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } + + absl::flat_hash_set service_ids; + }; + + struct AcceptingConnectionsInfo { + bool Empty() const { return service_ids.empty(); } + void Clear() { service_ids.clear(); } + void Add(const std::string& service_id) { service_ids.emplace(service_id); } + void Remove(const std::string& service_id) { + service_ids.erase(service_id); + } + bool Existed(const std::string& service_id) const { + return service_ids.contains(service_id); + } + + absl::flat_hash_set service_ids; + }; + + // 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(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsDiscovering(), but must be called with mutex_ held. + bool IsScanningLocked(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_; + BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_); + BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){ + radio_.GetBluetoothAdapter()}; + BleMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_}; + AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); + ScanningInfo scanning_info_ ABSL_GUARDED_BY(mutex_); + AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_H_ diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc new file mode 100644 index 00000000..5ce85562 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -0,0 +1,162 @@ +#include "core_v2/internal/mediums/ble.h" + +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/ble.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 connections { +namespace { + +constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); +constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; +constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; + +class BleTest : public ::testing::Test { + protected: + using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback; + + BleTest() { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(BleTest, CanConstructValidObject) { + env_.Start(); + BluetoothRadio radio_a; + BluetoothRadio radio_b; + Ble ble_a{radio_a}; + Ble ble_b{radio_b}; + + EXPECT_TRUE(ble_a.IsMediumValid()); + EXPECT_TRUE(ble_a.IsAdapterValid()); + EXPECT_TRUE(ble_a.IsAvailable()); + EXPECT_TRUE(ble_b.IsMediumValid()); + EXPECT_TRUE(ble_b.IsAdapterValid()); + EXPECT_TRUE(ble_b.IsAvailable()); + EXPECT_NE(&radio_a.GetBluetoothAdapter(), &radio_b.GetBluetoothAdapter()); + env_.Stop(); +} + +TEST_F(BleTest, CanStartAdvertising) { + env_.Start(); + BluetoothRadio radio_a; + BluetoothRadio radio_b; + Ble ble_a{radio_a}; + Ble ble_b{radio_b}; + radio_a.Enable(); + radio_b.Enable(); + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + + ble_b.StartScanning(service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch](BlePeripheral& peripheral, + const std::string& service_id) { + found_latch.CountDown(); + }, + }); + + EXPECT_TRUE(ble_a.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_a.StopAdvertising(service_id)); + EXPECT_TRUE(ble_b.StopScanning(service_id)); + env_.Stop(); +} + +TEST_F(BleTest, CanStartDiscovery) { + env_.Start(); + BluetoothRadio radio_a; + BluetoothRadio radio_b; + Ble ble_a{radio_a}; + Ble ble_b{radio_b}; + radio_a.Enable(); + radio_b.Enable(); + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch accept_latch(1); + CountDownLatch lost_latch(1); + + ble_b.StartAdvertising(service_id, advertisement_bytes); + + EXPECT_TRUE(ble_a.StartScanning( + service_id, DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&accept_latch](BlePeripheral& peripheral, + const std::string& service_id) { + accept_latch.CountDown(); + }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + })); + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + ble_b.StopAdvertising(service_id); + EXPECT_TRUE(lost_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_a.StopScanning(service_id)); + env_.Stop(); +} + +TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { + env_.Start(); + BluetoothRadio radio_a; + BluetoothRadio radio_b; + Ble ble_a{radio_a}; + Ble ble_b{radio_b}; + radio_a.Enable(); + radio_b.Enable(); + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + CountDownLatch accept_latch(1); + + ble_a.StartAdvertising(service_id, advertisement_bytes); + ble_a.StartAcceptingConnections( + service_id, + { + .accepted_cb = [&accept_latch]( + BleSocket socket, + const std::string&) { accept_latch.CountDown(); }, + }); + BlePeripheral discovered_peripheral; + ble_b.StartScanning( + service_id, + { + .peripheral_discovered_cb = + [&found_latch, &discovered_peripheral]( + BlePeripheral& peripheral, const std::string& service_id) { + discovered_peripheral = peripheral; + NEARBY_LOG(INFO, "Discovered peripheral=%p [impl=%p]", + &peripheral, &peripheral.GetImpl()); + found_latch.CountDown(); + }, + }); + + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + ASSERT_TRUE(discovered_peripheral.IsValid()); + + BleSocket socket = + ble_b.Connect(discovered_peripheral, service_id); + + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(socket.IsValid()); + ble_b.StopScanning(service_id); + ble_a.StopAdvertising(service_id); + env_.Stop(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc index 39ebda64..97da7811 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -368,6 +368,12 @@ BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, return socket; } +BluetoothDevice BluetoothClassic::FindRemoteDevice( + const std::string& mac_address) { + MutexLock lock(&mutex_); + return medium_.FindRemoteDevice(mac_address); +} + std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) { return std::string(Uuid(data)); } diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h index 69308309..f45ab79d 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.h +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -100,6 +100,9 @@ class BluetoothClassic { const std::string& service_name) ABSL_LOCKS_EXCLUDED(mutex_); + BluetoothDevice FindRemoteDevice(const std::string& mac_address) + ABSL_LOCKS_EXCLUDED(mutex_); + private: struct ScanInfo { bool valid = false; diff --git a/cpp/core_v2/internal/mediums/mediums.cc b/cpp/core_v2/internal/mediums/mediums.cc index 54b3a24f..2a9c58b2 100644 --- a/cpp/core_v2/internal/mediums/mediums.cc +++ b/cpp/core_v2/internal/mediums/mediums.cc @@ -12,6 +12,8 @@ BluetoothClassic& Mediums::GetBluetoothClassic() { return bluetooth_classic_; } +Ble& Mediums::GetBle() { return ble_; } + WifiLan& Mediums::GetWifiLan() { return wifi_lan_; } diff --git a/cpp/core_v2/internal/mediums/mediums.h b/cpp/core_v2/internal/mediums/mediums.h index 4e6b07ec..367365ea 100644 --- a/cpp/core_v2/internal/mediums/mediums.h +++ b/cpp/core_v2/internal/mediums/mediums.h @@ -1,6 +1,7 @@ #ifndef CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ #define CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ +#include "core_v2/internal/mediums/ble.h" #include "core_v2/internal/mediums/bluetooth_classic.h" #include "core_v2/internal/mediums/bluetooth_radio.h" #include "core_v2/internal/mediums/webrtc.h" @@ -22,6 +23,9 @@ class Mediums { // Returns a handle to the Bluetooth Classic medium. BluetoothClassic& GetBluetoothClassic(); + // Returns a handle to the Ble medium. + Ble& GetBle(); + // Returns a handle to the Wifi-Lan medium. WifiLan& GetWifiLan(); @@ -39,6 +43,7 @@ class Mediums { // corresponding radio. BluetoothRadio bluetooth_radio_; BluetoothClassic bluetooth_classic_{bluetooth_radio_}; + Ble ble_{bluetooth_radio_}; WifiLan wifi_lan_; mediums::WebRtc webrtc_; }; diff --git a/cpp/core_v2/internal/mediums/wifi_lan.cc b/cpp/core_v2/internal/mediums/wifi_lan.cc index 019fc0e6..531b3941 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan.cc @@ -44,8 +44,7 @@ bool WifiLan::StartAdvertising(const std::string& service_id, } NEARBY_LOGS(INFO) << "Turned on WifiLan advertising with service info name=" - << service_info_name - << ", service id=" << service_id; + << service_info_name << ", service id=" << service_id; advertising_info_.Add(service_id); return true; } @@ -208,7 +207,8 @@ bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) { 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); + NEARBY_LOG(INFO, "WifiLan::Connect: service=%p, service_info_name=%s", + &wifi_lan_service, wifi_lan_service.GetName().c_str()); // Socket to return. To allow for NRVO to work, it has to be a single object. WifiLanSocket socket; @@ -228,13 +228,19 @@ WifiLanSocket WifiLan::Connect(WifiLanService& wifi_lan_service, socket = medium_.Connect(wifi_lan_service, service_id); if (!socket.IsValid()) { - NEARBY_LOG(INFO, "Failed to Connect via WifiLan [service=%s]", + NEARBY_LOG(INFO, "Failed to Connect via WifiLan [service_id=%s]", service_id.c_str()); } return socket; } +WifiLanService WifiLan::GetRemoteWifiLanService(const std::string& ip_address, + int port) { + MutexLock lock(&mutex_); + return medium_.FindRemoteService(ip_address, port); +} + } // 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 index 890b22e8..1aeabb2d 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan.h +++ b/cpp/core_v2/internal/mediums/wifi_lan.h @@ -69,6 +69,9 @@ class WifiLan { const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + WifiLanService GetRemoteWifiLanService(const std::string& ip_address, + int port) ABSL_LOCKS_EXCLUDED(mutex_); + private: struct AdvertisingInfo { bool Empty() const { return service_ids.empty(); } @@ -115,7 +118,7 @@ class WifiLan { // 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. + // Same as IsAdvertising(), but must be called with mutex_ held. bool IsAdvertisingLocked(const std::string& service_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); diff --git a/cpp/core_v2/internal/mediums/wifi_lan_test.cc b/cpp/core_v2/internal/mediums/wifi_lan_test.cc index 24e64d02..c0586b94 100644 --- a/cpp/core_v2/internal/mediums/wifi_lan_test.cc +++ b/cpp/core_v2/internal/mediums/wifi_lan_test.cc @@ -8,6 +8,7 @@ #include "platform_v2/public/wifi_lan.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -33,7 +34,6 @@ TEST_F(WifiLanTest, CanConstructValidObject) { WifiLan wifi_lan_a; WifiLan wifi_lan_b; std::string service_id(kServiceID); - std::string service_name{kServiceInfoName}; EXPECT_TRUE(wifi_lan_a.IsAvailable()); EXPECT_TRUE(wifi_lan_b.IsAvailable()); @@ -45,19 +45,19 @@ TEST_F(WifiLanTest, CanStartAdvertising) { WifiLan wifi_lan_a; WifiLan wifi_lan_b; std::string service_id(kServiceID); - std::string service_name{kServiceInfoName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); wifi_lan_b.StartDiscovery( service_id, DiscoveredServiceCallback{ .service_discovered_cb = [&found_latch](WifiLanService& service, - const std::string& service_id) { + absl::string_view service_id) { found_latch.CountDown(); }, }); - EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, service_name)); + EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, service_info_name)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id)); EXPECT_TRUE(wifi_lan_b.StopDiscovery(service_id)); @@ -69,11 +69,11 @@ TEST_F(WifiLanTest, CanStartDiscovery) { WifiLan wifi_lan_a; WifiLan wifi_lan_b; std::string service_id(kServiceID); - std::string service_name{kServiceInfoName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch accept_latch(1); CountDownLatch lost_latch(1); - wifi_lan_b.StartAdvertising(service_id, service_name); + wifi_lan_b.StartAdvertising(service_id, service_info_name); EXPECT_TRUE(wifi_lan_a.StartDiscovery( service_id, { @@ -100,17 +100,17 @@ TEST_F(WifiLanTest, CanStartAcceptingConnectionsAndConnect) { WifiLan wifi_lan_a; WifiLan wifi_lan_b; std::string service_id(kServiceID); - std::string service_name{kServiceInfoName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); CountDownLatch accept_latch(1); - wifi_lan_a.StartAdvertising(service_id, service_name); + wifi_lan_a.StartAdvertising(service_id, service_info_name); wifi_lan_a.StartAcceptingConnections( service_id, { .accepted_cb = [&accept_latch]( WifiLanSocket socket, - const std::string&) { accept_latch.CountDown(); }, + absl::string_view) { accept_latch.CountDown(); }, }); WifiLanService discovered_service; wifi_lan_b.StartDiscovery( @@ -118,7 +118,7 @@ TEST_F(WifiLanTest, CanStartAcceptingConnectionsAndConnect) { { .service_discovered_cb = [&found_latch, &discovered_service]( - WifiLanService& service, const std::string& service_id) { + WifiLanService& service, absl::string_view service_id) { discovered_service = service; NEARBY_LOG(INFO, "Discovered service=%p [impl=%p]", &service, &service.GetImpl()); @@ -135,6 +135,7 @@ TEST_F(WifiLanTest, CanStartAcceptingConnectionsAndConnect) { EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); EXPECT_TRUE(socket.IsValid()); wifi_lan_b.StopDiscovery(service_id); + wifi_lan_a.StopAcceptingConnections(service_id); wifi_lan_a.StopAdvertising(service_id); env_.Stop(); } diff --git a/cpp/core_v2/internal/mock_service_controller.h b/cpp/core_v2/internal/mock_service_controller.h index f2668139..d6029bbd 100644 --- a/cpp/core_v2/internal/mock_service_controller.h +++ b/cpp/core_v2/internal/mock_service_controller.h @@ -35,7 +35,8 @@ class MockServiceController : public ServiceController { MOCK_METHOD(Status, RequestConnection, (ClientProxy * client, const std::string& endpoint_id, - const ConnectionRequestInfo& info), + const ConnectionRequestInfo& info, + const ConnectionOptions& options), (override)); MOCK_METHOD(Status, AcceptConnection, diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 6ccb6d9e..636334ff 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -4,6 +4,7 @@ #include #include "core/internal/message_lite.h" +#include "proto/connections/offline_wire_formats.pb.h" #include "platform_v2/base/byte_array.h" namespace location { @@ -13,7 +14,6 @@ namespace parser { namespace { using ExceptionOrOfflineFrame = ExceptionOr; -using Medium = proto::connections::Medium; using MessageLite = ::google::protobuf::MessageLite; ByteArray ToBytes(OfflineFrame&& frame) { @@ -44,7 +44,7 @@ V1Frame::FrameType GetFrameType(const OfflineFrame& frame) { } ByteArray ForConnectionRequest(const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, std::int32_t nonce, const std::vector& mediums) { OfflineFrame frame; @@ -54,8 +54,8 @@ ByteArray ForConnectionRequest(const std::string& endpoint_id, v1_frame->set_type(V1Frame::CONNECTION_REQUEST); auto* connection_request = v1_frame->mutable_connection_request(); connection_request->set_endpoint_id(endpoint_id); - connection_request->set_endpoint_name(endpoint_name); - connection_request->set_endpoint_info(endpoint_name); + connection_request->set_endpoint_name(std::string(endpoint_info)); + connection_request->set_endpoint_info(std::string(endpoint_info)); connection_request->set_nonce(nonce); for (const auto& medium : mediums) { connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); @@ -108,7 +108,7 @@ ByteArray ForControlPayloadTransfer( return ToBytes(std::move(frame)); } -ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, +ByteArray ForBwuWifiHotspotPathAvailable(const std::string& ssid, const std::string& password, std::int32_t port) { OfflineFrame frame; @@ -120,8 +120,7 @@ ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, sub_frame->set_event_type( BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); - upgrade_path_info->set_medium( - BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_HOTSPOT); auto* wifi_hotspot_credentials = upgrade_path_info->mutable_wifi_hotspot_credentials(); wifi_hotspot_credentials->set_ssid(ssid); @@ -131,7 +130,46 @@ ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, return ToBytes(std::move(frame)); } -ByteArray ForBandwidthUpgradeLastWrite() { +ByteArray ForBwuWifiLanPathAvailable(const std::string& ip_address, + std::int32_t port) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WIFI_LAN); + auto* wifi_lan_socket = upgrade_path_info->mutable_wifi_lan_socket(); + wifi_lan_socket->set_ip_address(ip_address); + wifi_lan_socket->set_wifi_port(port); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, + const std::string& mac_address) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::BLUETOOTH); + auto* bluetooth_credentials = + upgrade_path_info->mutable_bluetooth_credentials(); + bluetooth_credentials->set_mac_address(mac_address); + bluetooth_credentials->set_service_name(service_id); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBwuLastWrite() { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -144,7 +182,7 @@ ByteArray ForBandwidthUpgradeLastWrite() { return ToBytes(std::move(frame)); } -ByteArray ForBandwidthUpgradeSafeToClose() { +ByteArray ForBwuSafeToClose() { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -157,7 +195,7 @@ ByteArray ForBandwidthUpgradeSafeToClose() { return ToBytes(std::move(frame)); } -ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id) { +ByteArray ForBwuIntroduction(const std::string& endpoint_id) { OfflineFrame frame; frame.set_version(OfflineFrame::V1); @@ -172,6 +210,21 @@ ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id) { return ToBytes(std::move(frame)); } +ByteArray ForBwuFailure(const UpgradePathInfo& info) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_FAILURE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + *upgrade_path_info = info; + + return ToBytes(std::move(frame)); +} + ByteArray ForKeepAlive() { OfflineFrame frame; @@ -183,8 +236,57 @@ ByteArray ForKeepAlive() { return ToBytes(std::move(frame)); } -ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( - proto::connections::Medium medium) { +UpgradePathInfo::Medium MediumToUpgradePathInfoMedium(Medium medium) { + switch (medium) { + case Medium::MDNS: + return UpgradePathInfo::MDNS; + case Medium::BLUETOOTH: + return UpgradePathInfo::BLUETOOTH; + case Medium::WIFI_HOTSPOT: + return UpgradePathInfo::WIFI_HOTSPOT; + case Medium::BLE: + return UpgradePathInfo::BLE; + case Medium::WIFI_LAN: + return UpgradePathInfo::WIFI_LAN; + case Medium::WIFI_AWARE: + return UpgradePathInfo::WIFI_AWARE; + case Medium::NFC: + return UpgradePathInfo::NFC; + case Medium::WIFI_DIRECT: + return UpgradePathInfo::WIFI_DIRECT; + case Medium::WEB_RTC: + return UpgradePathInfo::WEB_RTC; + default: + return UpgradePathInfo::UNKNOWN_MEDIUM; + } +} + +Medium UpgradePathInfoMediumToMedium(UpgradePathInfo::Medium medium) { + switch (medium) { + case UpgradePathInfo::MDNS: + return Medium::MDNS; + case UpgradePathInfo::BLUETOOTH: + return Medium::BLUETOOTH; + case UpgradePathInfo::WIFI_HOTSPOT: + return Medium::WIFI_HOTSPOT; + case UpgradePathInfo::BLE: + return Medium::BLE; + case UpgradePathInfo::WIFI_LAN: + return Medium::WIFI_LAN; + case UpgradePathInfo::WIFI_AWARE: + return Medium::WIFI_AWARE; + case UpgradePathInfo::NFC: + return Medium::NFC; + case UpgradePathInfo::WIFI_DIRECT: + return Medium::WIFI_DIRECT; + case UpgradePathInfo::WEB_RTC: + return Medium::WEB_RTC; + default: + return Medium::UNKNOWN_MEDIUM; + } +} + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium(Medium medium) { switch (medium) { case Medium::MDNS: return ConnectionRequestFrame::MDNS; @@ -209,8 +311,7 @@ ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( } } -proto::connections::Medium ConnectionRequestMediumToMedium( - ConnectionRequestFrame::Medium medium) { +Medium ConnectionRequestMediumToMedium(ConnectionRequestFrame::Medium medium) { switch (medium) { case ConnectionRequestFrame::MDNS: return Medium::MDNS; @@ -235,9 +336,9 @@ proto::connections::Medium ConnectionRequestMediumToMedium( } } -std::vector ConnectionRequestMediumsToMediums( +std::vector ConnectionRequestMediumsToMediums( const ConnectionRequestFrame& frame) { - std::vector result; + std::vector result; for (const auto& int_medium : frame.mediums()) { result.push_back(ConnectionRequestMediumToMedium( static_cast(int_medium))); diff --git a/cpp/core_v2/internal/offline_frames.h b/cpp/core_v2/internal/offline_frames.h index 81bf8aca..339c543a 100644 --- a/cpp/core_v2/internal/offline_frames.h +++ b/cpp/core_v2/internal/offline_frames.h @@ -4,6 +4,7 @@ #include #include +#include "core_v2/options.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/base/exception.h" @@ -14,6 +15,8 @@ namespace nearby { namespace connections { namespace parser { +using UpgradePathInfo = BandwidthUpgradeNegotiationFrame::UpgradePathInfo; + // Serialize/Deserialize Nearby Connections Protocol messages. // Parses incoming message. @@ -25,12 +28,13 @@ ExceptionOr FromBytes(const ByteArray& offline_frame_bytes); // V1Frame::UNKNOWN_FRAME_TYPE, if frame contents is not recognized. V1Frame::FrameType GetFrameType(const OfflineFrame& offline_frame); -// Build ConnectionRequest message. +// Builds Connection Request / Response messages. ByteArray ForConnectionRequest( - const std::string& endpoint_id, const std::string& endpoint_name, - std::int32_t nonce, const std::vector& mediums); + const std::string& endpoint_id, const ByteArray& endpoint_info, + std::int32_t nonce, const std::vector& mediums); ByteArray ForConnectionResponse(std::int32_t status); +// Builds Payload transfer messages. ByteArray ForDataPayloadTransfer( const PayloadTransferFrame::PayloadHeader& header, const PayloadTransferFrame::PayloadChunk& chunk); @@ -38,19 +42,27 @@ ByteArray ForControlPayloadTransfer( const PayloadTransferFrame::PayloadHeader& header, const PayloadTransferFrame::ControlMessage& control); -ByteArray ForBandwidthUpgradeWifiHotspot( - const std::string& ssid, const std::string& password, std::int32_t port); -ByteArray ForBandwidthUpgradeLastWrite(); -ByteArray ForBandwidthUpgradeSafeToClose(); -ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id); +// Builds Bandwidth Upgrade [BWU] messages. +ByteArray ForBwuIntroduction(const std::string& endpoint_id); +ByteArray ForBwuWifiHotspotPathAvailable(const std::string& ssid, + const std::string& password, + std::int32_t port); +ByteArray ForBwuWifiLanPathAvailable(const std::string& ip_address, + std::int32_t port); +ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, + const std::string& mac_address); +ByteArray ForBwuFailure(const UpgradePathInfo& info); +ByteArray ForBwuLastWrite(); +ByteArray ForBwuSafeToClose(); ByteArray ForKeepAlive(); -ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( - proto::connections::Medium medium); -proto::connections::Medium ConnectionRequestMediumToMedium( - ConnectionRequestFrame::Medium medium); -std::vector ConnectionRequestMediumsToMediums( +UpgradePathInfo::Medium MediumToUpgradePathInfoMedium(Medium medium); +Medium UpgradePathInfoMediumToMedium(UpgradePathInfo::Medium medium); + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium(Medium medium); +Medium ConnectionRequestMediumToMedium(ConnectionRequestFrame::Medium medium); +std::vector ConnectionRequestMediumsToMediums( const ConnectionRequestFrame& connection_request_frame); } // namespace parser diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc index d5ba067b..42dce1f3 100644 --- a/cpp/core_v2/internal/offline_frames_test.cc +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -79,7 +79,7 @@ TEST(OfflineFramesTest, CanGenerateConnectionRequest) { > >)pb"; ByteArray bytes = ForConnectionRequest( - std::string(kEndpointId), std::string(kEndpointName), kNonce, + std::string(kEndpointId), ByteArray{std::string(kEndpointName)}, kNonce, std::vector(kMediums.begin(), kMediums.end())); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); @@ -157,7 +157,7 @@ TEST(OfflineFramesTest, CanGenerateDataPayloadTransfer) { EXPECT_THAT(message, EqualsProto(kExpected)); } -TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeWifiHotspot) { +TEST(OfflineFramesTest, CanGenerateBwuWifiHotspotPathAvailable) { constexpr char kExpected[] = R"pb( version: V1 @@ -175,14 +175,60 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeWifiHotspot) { > > >)pb"; - ByteArray bytes = ForBandwidthUpgradeWifiHotspot("ssid", "password", 1234); + ByteArray bytes = ForBwuWifiHotspotPathAvailable("ssid", "password", 1234); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); EXPECT_THAT(message, EqualsProto(kExpected)); } -TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeLastWrite) { +TEST(OfflineFramesTest, CanGenerateBwuWifiLanPathAvailable) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_LAN + wifi_lan_socket: < ip_address: "\x01\x02\x03\x04" wifi_port: 1234 > + > + > + >)pb"; + ByteArray bytes = ForBwuWifiLanPathAvailable("\x01\x02\x03\x04", 1234); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBwuBluetoothPathAvailable) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: BLUETOOTH + bluetooth_credentials: < + service_name: "service" + mac_address: "\x11\x22\x33\x44\x55\x66" + > + > + > + >)pb"; + ByteArray bytes = + ForBwuBluetoothPathAvailable("service", "\x11\x22\x33\x44\x55\x66"); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBwuLastWrite) { constexpr char kExpected[] = R"pb( version: V1 @@ -190,14 +236,14 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeLastWrite) { type: BANDWIDTH_UPGRADE_NEGOTIATION bandwidth_upgrade_negotiation: < event_type: LAST_WRITE_TO_PRIOR_CHANNEL > >)pb"; - ByteArray bytes = ForBandwidthUpgradeLastWrite(); + ByteArray bytes = ForBwuLastWrite(); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); EXPECT_THAT(message, EqualsProto(kExpected)); } -TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeSafeToClose) { +TEST(OfflineFramesTest, CanGenerateBwuSafeToClose) { constexpr char kExpected[] = R"pb( version: V1 @@ -205,14 +251,14 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeSafeToClose) { type: BANDWIDTH_UPGRADE_NEGOTIATION bandwidth_upgrade_negotiation: < event_type: SAFE_TO_CLOSE_PRIOR_CHANNEL > >)pb"; - ByteArray bytes = ForBandwidthUpgradeSafeToClose(); + ByteArray bytes = ForBwuSafeToClose(); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); EXPECT_THAT(message, EqualsProto(kExpected)); } -TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { +TEST(OfflineFramesTest, CanGenerateBwuIntroduction) { constexpr char kExpected[] = R"pb( version: V1 @@ -223,7 +269,7 @@ TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { client_introduction: < endpoint_id: "ABC" > > >)pb"; - ByteArray bytes = ForBandwidthUpgradeIntroduction(std::string(kEndpointId)); + ByteArray bytes = ForBwuIntroduction(std::string(kEndpointId)); auto response = FromBytes(bytes); ASSERT_TRUE(response.ok()); OfflineFrame message = FromBytes(bytes).result(); diff --git a/cpp/core_v2/internal/offline_service_controller.cc b/cpp/core_v2/internal/offline_service_controller.cc index 249c97b8..3c1de259 100644 --- a/cpp/core_v2/internal/offline_service_controller.cc +++ b/cpp/core_v2/internal/offline_service_controller.cc @@ -6,9 +6,7 @@ namespace location { namespace nearby { namespace connections { -OfflineServiceController::~OfflineServiceController() { - Stop(); -} +OfflineServiceController::~OfflineServiceController() { Stop(); } void OfflineServiceController::Stop() { if (stop_.Set(true)) return; @@ -38,8 +36,8 @@ void OfflineServiceController::StopDiscovery(ClientProxy* client) { Status OfflineServiceController::RequestConnection( ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) { - return pcp_manager_.RequestConnection(client, endpoint_id, info); + const ConnectionRequestInfo& info, const ConnectionOptions& options) { + return pcp_manager_.RequestConnection(client, endpoint_id, info, options); } Status OfflineServiceController::AcceptConnection( diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index bcb6e2c7..97517fa7 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -40,7 +40,8 @@ class OfflineServiceController : public ServiceController { Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) override; + const ConnectionRequestInfo& info, + const ConnectionOptions& options) override; Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& listener) override; @@ -51,8 +52,8 @@ class OfflineServiceController : public ServiceController { const std::string& endpoint_id) override; void SendPayload(ClientProxy* client, - const std::vector& endpoint_ids, - Payload payload) override; + const std::vector& endpoint_ids, + Payload payload) override; Status CancelPayload(ClientProxy* client, Payload::Id payload_id) override; diff --git a/cpp/core_v2/internal/offline_service_controller_test.cc b/cpp/core_v2/internal/offline_service_controller_test.cc index 260dd527..b7286cdf 100644 --- a/cpp/core_v2/internal/offline_service_controller_test.cc +++ b/cpp/core_v2/internal/offline_service_controller_test.cc @@ -25,7 +25,21 @@ constexpr absl::Duration kProgressTimeout = absl::Milliseconds(1000); constexpr absl::Duration kDefaultTimeout = absl::Milliseconds(1000); constexpr absl::Duration kDisconnectTimeout = absl::Milliseconds(15000); -class OfflineServiceControllerTest : public ::testing::Test { +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + +class OfflineServiceControllerTest + : public ::testing::TestWithParam { protected: OfflineServiceControllerTest() { env_.Stop(); } @@ -35,7 +49,7 @@ class OfflineServiceControllerTest : public ::testing::Test { user_b.StartDiscovery(std::string(kServiceId), &discover_latch_); EXPECT_TRUE(discover_latch_.Await(kDefaultTimeout).result()); EXPECT_EQ(user_b.GetDiscovered().service_id, kServiceId); - EXPECT_EQ(user_b.GetDiscovered().endpoint_name, user_a.GetName()); + EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); EXPECT_FALSE(user_b.GetDiscovered().endpoint_id.empty()); NEARBY_LOG(INFO, "EP-B: [discovered] %s", user_b.GetDiscovered().endpoint_id.c_str()); @@ -53,29 +67,30 @@ class OfflineServiceControllerTest : public ::testing::Test { } CountDownLatch discover_latch_{1}; + CountDownLatch lost_latch_{1}; CountDownLatch connect_latch_{2}; CountDownLatch accept_latch_{2}; CountDownLatch payload_latch_{1}; MediumEnvironment& env_ = MediumEnvironment::Instance(); }; -TEST_F(OfflineServiceControllerTest, CanCreateOne) { +TEST_P(OfflineServiceControllerTest, CanCreateOne) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); + OfflineSimulationUser user_a(kDeviceA, GetParam()); env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanCreateMany) { +TEST_P(OfflineServiceControllerTest, CanCreateMany) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanStartAdvertising) { +TEST_P(OfflineServiceControllerTest, CanStartAdvertising) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_FALSE(user_a.IsAdvertising()); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), Eq(Status{Status::kSuccess})); @@ -83,10 +98,10 @@ TEST_F(OfflineServiceControllerTest, CanStartAdvertising) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanStartDiscoveryBeforeAdvertising) { +TEST_P(OfflineServiceControllerTest, CanStartDiscoveryBeforeAdvertising) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_FALSE(user_b.IsDiscovering()); EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), Eq(Status{Status::kSuccess})); @@ -99,10 +114,10 @@ TEST_F(OfflineServiceControllerTest, CanStartDiscoveryBeforeAdvertising) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanStartDiscoveryAfterAdvertising) { +TEST_P(OfflineServiceControllerTest, CanStartDiscoveryAfterAdvertising) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_FALSE(user_b.IsDiscovering()); EXPECT_FALSE(user_b.IsAdvertising()); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), @@ -117,29 +132,39 @@ TEST_F(OfflineServiceControllerTest, CanStartDiscoveryAfterAdvertising) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanStopAdvertising) { +TEST_P(OfflineServiceControllerTest, CanStopAdvertising) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_FALSE(user_a.IsAdvertising()); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), nullptr), Eq(Status{Status::kSuccess})); EXPECT_TRUE(user_a.IsAdvertising()); user_a.StopAdvertising(); EXPECT_FALSE(user_a.IsAdvertising()); - EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), + EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_, + &lost_latch_), Eq(Status{Status::kSuccess})); EXPECT_TRUE(user_b.IsDiscovering()); - EXPECT_FALSE(discover_latch_.Await(kDefaultTimeout).result()); + auto discover_none = discover_latch_.Await(kDefaultTimeout).GetResult(); + if (!discover_none) { + EXPECT_TRUE(true); + } else { + // There are rare cases (1/1000) that advertisment data has been captured by + // discovery device before advertising is stopped. So we need to check if + // lost_cb has grabbed the event in the end to prove the advertising service + // is stopped. + EXPECT_TRUE(lost_latch_.Await(kDefaultTimeout).result()); + } user_a.Stop(); user_b.Stop(); env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanStopDiscovery) { +TEST_P(OfflineServiceControllerTest, CanStopDiscovery) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_FALSE(user_b.IsDiscovering()); EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), Eq(Status{Status::kSuccess})); @@ -154,10 +179,10 @@ TEST_F(OfflineServiceControllerTest, CanStopDiscovery) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanConnect) { +TEST_P(OfflineServiceControllerTest, CanConnect) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), Eq(Status{Status::kSuccess})); EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), @@ -171,10 +196,10 @@ TEST_F(OfflineServiceControllerTest, CanConnect) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanAcceptConnection) { +TEST_P(OfflineServiceControllerTest, CanAcceptConnection) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), Eq(Status{Status::kSuccess})); EXPECT_THAT(user_b.StartDiscovery(std::string(kServiceId), &discover_latch_), @@ -195,10 +220,10 @@ TEST_F(OfflineServiceControllerTest, CanAcceptConnection) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanRejectConnection) { +TEST_P(OfflineServiceControllerTest, CanRejectConnection) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); CountDownLatch reject_latch(1); EXPECT_THAT(user_a.StartAdvertising(std::string(kServiceId), &connect_latch_), Eq(Status{Status::kSuccess})); @@ -216,10 +241,10 @@ TEST_F(OfflineServiceControllerTest, CanRejectConnection) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanSendBytePayload) { +TEST_P(OfflineServiceControllerTest, CanSendBytePayload) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); ByteArray message(std::string{kMessage}); user_a.SendPayload(Payload(message)); @@ -231,10 +256,10 @@ TEST_F(OfflineServiceControllerTest, CanSendBytePayload) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanSendStreamPayload) { +TEST_P(OfflineServiceControllerTest, CanSendStreamPayload) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); ByteArray message(std::string{kMessage}); auto pipe = std::make_shared(); @@ -258,10 +283,10 @@ TEST_F(OfflineServiceControllerTest, CanSendStreamPayload) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanCancelStreamPayload) { +TEST_P(OfflineServiceControllerTest, CanCancelStreamPayload) { env_.Start(); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); ByteArray message(std::string{kMessage}); auto pipe = std::make_shared(); @@ -298,11 +323,11 @@ TEST_F(OfflineServiceControllerTest, CanCancelStreamPayload) { env_.Stop(); } -TEST_F(OfflineServiceControllerTest, CanDisconnect) { +TEST_P(OfflineServiceControllerTest, CanDisconnect) { env_.Start(); CountDownLatch disconnect_latch(1); - OfflineSimulationUser user_a(kDeviceA); - OfflineSimulationUser user_b(kDeviceB); + OfflineSimulationUser user_a(kDeviceA, GetParam()); + OfflineSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); NEARBY_LOGS(INFO) << "Disconnecting"; user_b.ExpectDisconnect(disconnect_latch); @@ -315,6 +340,10 @@ TEST_F(OfflineServiceControllerTest, CanDisconnect) { env_.Stop(); } +INSTANTIATE_TEST_SUITE_P(ParametrisedOfflineServiceControllerTest, + OfflineServiceControllerTest, + ::testing::ValuesIn(kTestCases)); + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/offline_simulation_user.cc b/cpp/core_v2/internal/offline_simulation_user.cc index 6ed65174..4f79ac99 100644 --- a/cpp/core_v2/internal/offline_simulation_user.cc +++ b/cpp/core_v2/internal/offline_simulation_user.cc @@ -1,6 +1,7 @@ #include "core_v2/internal/offline_simulation_user.h" #include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" #include "platform_v2/public/count_down_latch.h" #include "platform_v2/public/system_clock.h" #include "absl/functional/bind_front.h" @@ -18,7 +19,7 @@ void OfflineSimulationUser::OnConnectionInitiated( NEARBY_LOG(INFO, "StartAdvertising: initiated_cb called"); discovered_ = DiscoveredInfo{ .endpoint_id = endpoint_id, - .endpoint_name = name_, + .endpoint_info = GetInfo(), .service_id = service_id_, }; } @@ -43,12 +44,12 @@ void OfflineSimulationUser::OnEndpointDisconnect( } void OfflineSimulationUser::OnEndpointFound(const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, 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, + .endpoint_info = endpoint_info, .service_id = service_id, }; if (found_latch_) found_latch_->CountDown(); @@ -107,7 +108,7 @@ Status OfflineSimulationUser::StartAdvertising(const std::string& service_id, }; return ctrl_.StartAdvertising(&client_, service_id_, options_, { - .name = name_, + .endpoint_info = info_, .listener = std::move(listener), }); } @@ -117,8 +118,10 @@ void OfflineSimulationUser::StopAdvertising() { } Status OfflineSimulationUser::StartDiscovery(const std::string& service_id, - CountDownLatch* latch) { - found_latch_ = latch; + CountDownLatch* found_latch, + CountDownLatch* lost_latch) { + found_latch_ = found_latch; + lost_latch_ = lost_latch; DiscoveryListener listener = { .endpoint_found_cb = absl::bind_front(&OfflineSimulationUser::OnEndpointFound, this), @@ -144,11 +147,13 @@ Status OfflineSimulationUser::RequestConnection(CountDownLatch* latch) { .disconnected_cb = absl::bind_front(&OfflineSimulationUser::OnEndpointDisconnect, this), }; - return ctrl_.RequestConnection(&client_, discovered_.endpoint_id, - { - .name = discovered_.endpoint_name, - .listener = std::move(listener), - }); + return ctrl_.RequestConnection( + &client_, discovered_.endpoint_id, + { + .endpoint_info = discovered_.endpoint_info, + .listener = std::move(listener), + }, + connection_options_); } Status OfflineSimulationUser::AcceptConnection(CountDownLatch* latch) { diff --git a/cpp/core_v2/internal/offline_simulation_user.h b/cpp/core_v2/internal/offline_simulation_user.h index 4c00d8eb..5b103b85 100644 --- a/cpp/core_v2/internal/offline_simulation_user.h +++ b/cpp/core_v2/internal/offline_simulation_user.h @@ -5,6 +5,7 @@ #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/offline_service_controller.h" +#include "core_v2/options.h" #include "platform_v2/public/atomic_boolean.h" #include "platform_v2/public/condition_variable.h" #include "platform_v2/public/count_down_latch.h" @@ -25,15 +26,21 @@ class OfflineSimulationUser { public: struct DiscoveredInfo { std::string endpoint_id; - std::string endpoint_name; + ByteArray endpoint_info; std::string service_id; bool Empty() const { return endpoint_id.empty(); } void Clear() { endpoint_id.clear(); } }; - explicit OfflineSimulationUser(absl::string_view device_name) - : name_(device_name) {} + explicit OfflineSimulationUser( + absl::string_view device_name, + BooleanMediumSelector allowed = BooleanMediumSelector()) + : info_{ByteArray{std::string(device_name)}}, + options_{ + .strategy = Strategy::kP2pCluster, + .allowed = allowed, + } {} virtual ~OfflineSimulationUser() = default; // Calls PcpManager::StartAdvertising(). @@ -45,9 +52,13 @@ class OfflineSimulationUser { void StopAdvertising(); // Calls PcpManager::StartDiscovery(). - // If latch is provided, will call latch->CountDown() in the endpoint_found_cb - // callback. - Status StartDiscovery(const std::string& service_id, CountDownLatch* latch); + // If found_latch is provided, will call found_latch->CountDown() in the + // endpoint_found_cb callback. + // If lost_latch is provided, will call lost_latch->CountDown() in the + // endpoint_lost_cb callback. + Status StartDiscovery(const std::string& service_id, + CountDownLatch* found_latch, + CountDownLatch* lost_latch = nullptr); // Calls PcpManager::StopDiscovery(). void StopDiscovery(); @@ -79,7 +90,7 @@ class OfflineSimulationUser { void ExpectDisconnect(CountDownLatch& latch) { disconnect_latch_ = &latch; } const DiscoveredInfo& GetDiscovered() const { return discovered_; } - std::string GetName() const { return name_; } + ByteArray GetInfo() const { return info_; } bool WaitForProgress(std::function pred, absl::Duration timeout); @@ -109,6 +120,8 @@ class OfflineSimulationUser { } void Stop() { + StopAdvertising(); + StopDiscovery(); ctrl_.Stop(); } @@ -123,7 +136,7 @@ class OfflineSimulationUser { // DiscoveryListener callbacks void OnEndpointFound(const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, const std::string& service_id); void OnEndpointLost(const std::string& endpoint_id); @@ -134,6 +147,8 @@ class OfflineSimulationUser { std::string service_id_; DiscoveredInfo discovered_; + ConnectionOptions connection_options_; + Mutex progress_mutex_; ConditionVariable progress_sync_{&progress_mutex_}; PayloadProgressInfo progress_info_; @@ -148,8 +163,8 @@ class OfflineSimulationUser { CountDownLatch* disconnect_latch_ = nullptr; Future* future_ = nullptr; std::function predicate_; - std::string name_; - ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + ByteArray info_; + ConnectionOptions options_; ClientProxy client_; OfflineServiceController ctrl_; }; diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 0ca1ee8c..ad7c8bc3 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -1,6 +1,8 @@ #include "core_v2/internal/p2p_cluster_pcp_handler.h" #include "core_v2/internal/base_pcp_handler.h" +#include "core_v2/internal/ble_advertisement.h" +#include "core_v2/internal/ble_endpoint_channel.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "core_v2/internal/webrtc_endpoint_channel.h" @@ -8,6 +10,8 @@ #include "platform_v2/base/types.h" #include "platform_v2/public/crypto.h" #include "proto/connections_enums.pb.h" +#include "absl/functional/bind_front.h" +#include "absl/strings/escaping.h" namespace location { namespace nearby { @@ -22,13 +26,14 @@ ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source, } P2pClusterPcpHandler::P2pClusterPcpHandler( - Mediums& mediums, EndpointManager* endpoint_manager, + 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()), - webrtc_medium_(mediums.GetWebRtc()) {} + : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, pcp), + bluetooth_radio_(mediums->GetBluetoothRadio()), + bluetooth_medium_(mediums->GetBluetoothClassic()), + ble_medium_(mediums->GetBle()), + wifi_lan_medium_(mediums->GetWifiLan()), + webrtc_medium_(mediums->GetWebRtc()) {} // Returns a vector or mediums sorted in order or decreasing priority for // all the supported mediums. @@ -45,6 +50,9 @@ P2pClusterPcpHandler::GetConnectionMediumsByPriority() { if (bluetooth_medium_.IsAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } + if (ble_medium_.IsAvailable()) { + mediums.push_back(proto::connections::BLE); + } return mediums; } @@ -54,35 +62,55 @@ proto::connections::Medium P2pClusterPcpHandler::GetDefaultUpgradeMedium() { 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) { + const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, + const ConnectionOptions& options) { std::vector mediums_started_successfully; - 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 (options.allowed.wifi_lan) { + 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_info); + if (wifi_lan_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartAdvertisingImpl: WifiLan added"); + mediums_started_successfully.push_back(wifi_lan_medium); + } } - proto::connections::Medium webrtc_medium = StartListeningForWebRtcConnections( - client, service_id, local_endpoint_id, local_endpoint_name); - if (webrtc_medium != proto::connections::UNKNOWN_MEDIUM) { - mediums_started_successfully.push_back(webrtc_medium); + if (options.allowed.web_rtc) { + proto::connections::Medium webrtc_medium = + StartListeningForWebRtcConnections( + client, service_id, local_endpoint_id, local_endpoint_info); + if (webrtc_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartAdvertisingImpl: WebRtc added"); + mediums_started_successfully.push_back(webrtc_medium); + } } - 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); + if (options.allowed.bluetooth) { + 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_info); + if (bluetooth_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: BT added"); + mediums_started_successfully.push_back(bluetooth_medium); + } + } + + if (options.allowed.ble) { + const ByteArray ble_hash = + GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); + proto::connections::Medium ble_medium = StartBleAdvertising( + client, service_id, ble_hash, local_endpoint_id, local_endpoint_info); + if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: Ble added"); + mediums_started_successfully.push_back(ble_medium); + } } if (mediums_started_successfully.empty()) { @@ -106,6 +134,8 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { bluetooth_medium_.TurnOffDiscoverability(); bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); + ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + webrtc_medium_.StopAcceptingConnections(); wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); @@ -146,90 +176,210 @@ bool P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint( 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. +void P2pClusterPcpHandler::BluetoothDeviceDiscoveredHandler( + ClientProxy* client, const std::string& service_id, + BluetoothDevice& device) { + RunOnPcpHandlerThread([this, client, service_id, &device]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { 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{ - { - device_name.GetEndpointId(), - device_name.GetEndpointName(), - service_id, - proto::connections::Medium::BLUETOOTH, - }, - device, - })); - }); - }; + "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_LOGS(INFO) + << "Invoking BasePcpHandler::OnEndpointFound() for BT service=" + << service_id << "; id=" << device_name.GetEndpointId() << "; name=" + << absl::BytesToHexString(device_name.GetEndpointInfo().data()); + OnEndpointFound(client, + std::make_shared(BluetoothEndpoint{ + { + device_name.GetEndpointId(), + device_name.GetEndpointInfo(), + service_id, + 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; - } +void P2pClusterPcpHandler::BluetoothDeviceLostHandler( + ClientProxy* client, const std::string& service_id, + BluetoothDevice& device) { + const std::string& device_name_string = device.GetName(); + RunOnPcpHandlerThread([this, client, service_id, device_name_string]() { + // 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); + // Parse the Bluetooth device name. + 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; + // 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, DiscoveredEndpoint{ + device_name.GetEndpointId(), + device_name.GetEndpointInfo(), + service_id, + proto::connections::Medium::BLUETOOTH, + }); + }); +} + +bool P2pClusterPcpHandler::IsRecognizedBleEndpoint( + const std::string& service_id, + const BleAdvertisement& advertisement) const { + if (!advertisement.IsValid()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBleEndpoint: advertisement " + "is invalid"); + return false; + } + + if (advertisement.GetVersion() != BleAdvertisement::Version::kV1) { + NEARBY_LOG( + INFO, + "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: Version is " + "not matched; advertisement.Version=%d, Version=%d", + advertisement.GetVersion(), BleAdvertisement::Version::kV1); + return false; + } + + if (advertisement.GetPcp() != GetPcp()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: Pcp is " + "not matched; advertisement.Pcp=%d, Pcp=%d", + advertisement.GetPcp(), GetPcp()); + return false; + } + + ByteArray expected_service_id_hash = + GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); + + if (advertisement.GetServiceIdHash() != expected_service_id_hash) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBleEndpoint: service " + "id hash is " + "not matched; advertisement.service_id_hash=%s, expected=%s", + advertisement.GetServiceIdHash().data(), + expected_service_id_hash.data()); + return false; + } + + return true; +} + +void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( + ClientProxy* client, BlePeripheral& peripheral, + const std::string& service_id) { + RunOnPcpHandlerThread([this, client, service_id, &peripheral]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG(INFO, + "Ble scanning handler (FOUND) [client=%p, service_id=%s]: not " + "in discovery mode", + client, service_id.c_str()); + return; + } + + // Parse the Ble advertisement bytes. + BleAdvertisement advertisement( + /*fast_advertisement=*/false, + peripheral.GetAdvertisementBytes(service_id)); + + // Make sure the Ble advertisement points to a valid + // endpoint we're discovering. + if (!IsRecognizedBleEndpoint(service_id, advertisement)) return; + + // Store all the state we need to be able to re-create a BleEndpoint + // in BlePeripheralLostHandler, since that isn't privy to + // the bytes of the ble advertisement itself. + found_ble_endpoints_.emplace( + peripheral.GetName(), + BleEndpointState(advertisement.GetEndpointId(), + advertisement.GetEndpointInfo())); + + // Report the discovered endpoint to the client. + NEARBY_LOGS(INFO) + << "Invoking BasePcpHandler::OnEndpointFound() for Ble service=" + << service_id << "; id=" << advertisement.GetEndpointId() << "; name=" + << absl::BytesToHexString(advertisement.GetEndpointInfo().data()); + OnEndpointFound(client, std::make_shared(BleEndpoint{ + { + advertisement.GetEndpointId(), + advertisement.GetEndpointInfo(), + service_id, + proto::connections::Medium::BLE, + }, + peripheral, + })); + }); +} + +void P2pClusterPcpHandler::BlePeripheralLostHandler( + ClientProxy* client, BlePeripheral& peripheral, + const std::string& service_id) { + std::string peripheral_name = peripheral.GetName(); + NEARBY_LOG(INFO, "Ble: [LOST, SCHED] peripheral_name=%s", + peripheral_name.c_str()); + RunOnPcpHandlerThread([this, client, service_id, &peripheral]() { + // Make sure we are still discovering before proceeding. + if (!client->IsDiscovering()) { + NEARBY_LOG(INFO, + "Ble scanning handler (LOST) [client=%p, service_id=%s]: not " + "in scanning mode", + client, service_id.c_str()); + return; + } + + // Remove this BlePeripheral from found_ble_endpoints_, and + // report the endpoint as lost to the client. + auto item = found_ble_endpoints_.find(peripheral.GetName()); + if (item != found_ble_endpoints_.end()) { + BleEndpointState ble_endpoint_state(item->second); + found_ble_endpoints_.erase(item); // Report the discovered endpoint to the client. NEARBY_LOG(INFO, - "BT discovery handler (LOST) [client=%p, service=%s]: report " - "to client", + "Ble scanning handler (LOST) [client=%p, " + "service_id=%s]: report to client", client, service_id.c_str()); - OnEndpointLost(client, BluetoothEndpoint{ - { - device_name.GetEndpointId(), - device_name.GetEndpointName(), - service_id, - proto::connections::Medium::BLUETOOTH, - }, - device, + OnEndpointLost(client, DiscoveredEndpoint{ + ble_endpoint_state.endpoint_id, + ble_endpoint_state.endpoint_info, + service_id, + proto::connections::Medium::BLE, }); - }); - }; + } + }); } bool P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint( @@ -266,90 +416,84 @@ bool P2pClusterPcpHandler::IsRecognizedWifiLanEndpoint( 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_info_name = service.GetName(); - WifiLanServiceInfo service_info(service_info_name); - - // Make sure the WifiLan service name points to a valid - // endpoint we're discovering. - if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) 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_info.GetEndpointId().c_str(), - service_info.GetEndpointName().c_str()); - OnEndpointFound(client, std::make_shared(WifiLanEndpoint{ - { - service_info.GetEndpointId(), - service_info.GetEndpointName(), - service_id, - 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_info_name = service.GetName(); - WifiLanServiceInfo service_info(service_info_name); - - // Make sure the WifiLan service name points to a valid - // endpoint we're discovering. - if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) return; - - // Report the discovered endpoint to the client. +void P2pClusterPcpHandler::WifiLanServiceDiscoveredHandler( + ClientProxy* 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]: report " - "to client", + "WifiLan discovery handler (FOUND) [client=%p, service=%s]: not " + "in discovery mode", client, service_id.c_str()); - OnEndpointLost(client, WifiLanEndpoint{ - { - service_info.GetEndpointId(), - service_info.GetEndpointName(), - service_id, - proto::connections::Medium::WIFI_LAN, - }, - service, - }); - }); - }; + return; + } + + // Parse the WifiLan service name. + const std::string& service_info_name = service.GetName(); + WifiLanServiceInfo service_info(service_info_name); + + // Make sure the WifiLan service name points to a valid + // endpoint we're discovering. + if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) 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_info.GetEndpointId().c_str(), + absl::BytesToHexString(service_info.GetEndpointInfo().data()).c_str()); + OnEndpointFound(client, std::make_shared(WifiLanEndpoint{ + { + service_info.GetEndpointId(), + service_info.GetEndpointInfo(), + service_id, + proto::connections::Medium::WIFI_LAN, + }, + service, + })); + }); +} + +void P2pClusterPcpHandler::WifiLanServiceLostHandler( + ClientProxy* client, WifiLanService& service, + const std::string& service_id) { + std::string service_info_name = service.GetName(); + NEARBY_LOG(INFO, "WifiLAN: [LOST, SCHED] service_info_name=%s", + service_info_name.c_str()); + RunOnPcpHandlerThread([this, client, service_id, service_info_name]() { + // 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. + WifiLanServiceInfo service_info(service_info_name); + + // Make sure the WifiLan service name points to a valid + // endpoint we're discovering. + if (!IsRecognizedWifiLanEndpoint(service_id, service_info)) return; + + // Report the discovered endpoint to the client. + NEARBY_LOG( + INFO, + "WifiLan discovery handler (LOST) [client=%p, service_id=%s]: report " + "to client", + client, service_id.c_str()); + OnEndpointLost(client, DiscoveredEndpoint{ + service_info.GetEndpointId(), + service_info.GetEndpointInfo(), + service_id, + proto::connections::Medium::WIFI_LAN, + }); + }); } BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( @@ -357,28 +501,54 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( const ConnectionOptions& options) { std::vector mediums_started_successfully; - 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 (options.allowed.wifi_lan) { + proto::connections::Medium wifi_lan_medium = StartWifiLanDiscovery( + { + .service_discovered_cb = absl::bind_front( + &P2pClusterPcpHandler::WifiLanServiceDiscoveredHandler, this, + client), + .service_lost_cb = absl::bind_front( + &P2pClusterPcpHandler::WifiLanServiceLostHandler, this, client), + }, + 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); + } } - 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); + if (options.allowed.bluetooth) { + proto::connections::Medium bluetooth_medium = StartBluetoothDiscovery( + { + .device_discovered_cb = absl::bind_front( + &P2pClusterPcpHandler::BluetoothDeviceDiscoveredHandler, this, + client, service_id), + .device_lost_cb = absl::bind_front( + &P2pClusterPcpHandler::BluetoothDeviceLostHandler, this, 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); + } + } + + if (options.allowed.ble) { + proto::connections::Medium ble_medium = StartBleScanning( + { + .peripheral_discovered_cb = absl::bind_front( + &P2pClusterPcpHandler::BlePeripheralDiscoveredHandler, this, + client), + .peripheral_lost_cb = absl::bind_front( + &P2pClusterPcpHandler::BlePeripheralLostHandler, this, client), + }, + client, service_id); + if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { + NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: Ble added"); + mediums_started_successfully.push_back(ble_medium); + } } if (mediums_started_successfully.empty()) { @@ -397,6 +567,7 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( Status P2pClusterPcpHandler::StopDiscoveryImpl(ClientProxy* client) { wifi_lan_medium_.StopDiscovery(client->GetDiscoveryServiceId()); bluetooth_medium_.StopDiscovery(); + ble_medium_.StopScanning(client->GetDiscoveryServiceId()); return {Status::kSuccess}; } @@ -415,6 +586,13 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::ConnectImpl( } break; } + case proto::connections::Medium::BLE: { + auto* ble_endpoint = down_cast(endpoint); + if (ble_endpoint) { + return BleConnectImpl(client, ble_endpoint); + } + break; + } case proto::connections::Medium::WIFI_LAN: { auto* wifi_lan_endpoint = down_cast(endpoint); if (wifi_lan_endpoint) { @@ -441,7 +619,7 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::ConnectImpl( 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) { + const ByteArray& local_endpoint_info) { // Start listening for connections before advertising in case a connection // request comes in very quickly. NEARBY_LOG( @@ -460,20 +638,22 @@ proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising( service_id.c_str()); if (!bluetooth_radio_.Enable() || !bluetooth_medium_.StartAcceptingConnections( - service_id, {.accepted_cb = [this, client, local_endpoint_name]( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( BluetoothSocket socket) { if (!socket.IsValid()) { NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", - local_endpoint_name.c_str()); + std::string(local_endpoint_info).c_str()); return; } - RunOnPcpHandlerThread([this, client, local_endpoint_name, + RunOnPcpHandlerThread([this, client, local_endpoint_info, 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, + ByteArray remote_device_info{remote_device_name}; + + OnIncomingConnection(client, remote_device_info, std::move(channel), proto::connections::Medium::BLUETOOTH); }); @@ -487,11 +667,12 @@ proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising( "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()); + absl::BytesToHexString(service_id_hash.data()).c_str(), + absl::BytesToHexString(local_endpoint_info.data()).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)); + service_id_hash, local_endpoint_info)); if (device_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothAdvertising: generate " @@ -564,10 +745,132 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BluetoothConnectImpl( }; } +proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const ByteArray& local_endpoint_info) { + // Start listening for connections before advertising in case a connection + // request comes in very quickly. + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" + << service_id << ": start"; + if (ble_medium_.IsAcceptingConnections(service_id)) { + NEARBY_LOGS(ERROR) << "Ble is already accepting connections for service_id=" + << service_id; + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" + << service_id << ": invoking"; + if (!bluetooth_radio_.Enable() || + !ble_medium_.StartAcceptingConnections( + service_id, + {.accepted_cb = [this, client, local_endpoint_info]( + BleSocket socket, const std::string& service_id) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + service_id, + socket = std::move(socket)]() mutable { + std::string remote_peripheral_name = + socket.GetRemotePeripheral().GetName(); + auto channel = absl::make_unique( + remote_peripheral_name, socket); + ByteArray remote_peripheral_info = + socket.GetRemotePeripheral().GetAdvertisementBytes( + service_id); + + OnIncomingConnection(client, remote_peripheral_info, + std::move(channel), + proto::connections::Medium::BLE); + }); + }})) { + NEARBY_LOGS(ERROR) + << "Ble failed to start accepting connections for service_id=" + << service_id; + return proto::connections::UNKNOWN_MEDIUM; + } + + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBleAdvertising: service=%s: " + "make advertisement; id=%s, hash=%s, name=%s", + service_id.c_str(), local_endpoint_id.c_str(), + std::string(service_id_hash).c_str(), + std::string(local_endpoint_info).c_str()); + // Generate a BleAdvertisement with which to become Ble discoverable. + // TODO(edwinwu): Add a bluetooth_adapter method to get the mac address. + std::string bluetooth_mac_address; + ByteArray advertisement_bytes(BleAdvertisement( + BleAdvertisement::Version::kV1, GetPcp(), service_id_hash, + local_endpoint_id, local_endpoint_info, bluetooth_mac_address)); + if (advertisement_bytes.Empty()) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::StartBleAdvertising: generate " + "BleAdvertisement failed"); + ble_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } else { + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: generate " + "BleAdvertisement succeeded; advertisement_bytes=" + << advertisement_bytes.data(); + } + + NEARBY_LOG( + INFO, "P2pClusterPcpHandler::StartBleAdvertising: service_id=%s: come up", + service_id.c_str()); + + if (!ble_medium_.StartAdvertising(service_id, advertisement_bytes)) { + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: failed to " + "start advertising, advertisement_bytes=%p" + << advertisement_bytes.data(); + ble_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" + << service_id << ": done"; + return proto::connections::BLE; +} + +proto::connections::Medium P2pClusterPcpHandler::StartBleScanning( + BleDiscoveredPeripheralCallback callback, ClientProxy* client, + const std::string& service_id) { + if (bluetooth_radio_.Enable() && + ble_medium_.StartScanning(service_id, std::move(callback))) { + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleScanning: ok"; + return proto::connections::BLE; + } else { + NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleScanning: failed"; + return proto::connections::UNKNOWN_MEDIUM; + } +} + +BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BleConnectImpl( + ClientProxy* client, BleEndpoint* endpoint) { + BlePeripheral& peripheral = endpoint->ble_peripheral; + + BleSocket ble_socket = ble_medium_.Connect(peripheral, endpoint->service_id); + if (!ble_socket.IsValid()) { + return BasePcpHandler::ConnectImplResult{ + .status = {Status::kBleError}, + }; + } + + auto channel = + absl::make_unique(endpoint->endpoint_id, ble_socket); + + return BasePcpHandler::ConnectImplResult{ + .medium = proto::connections::Medium::BLE, + .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) { + const ByteArray& local_endpoint_info) { // Start listening for connections before advertising in case a connection // request comes in very quickly. NEARBY_LOG(INFO, @@ -584,21 +887,23 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( "P2pClusterPcpHandler::StartWifiLanAdvertising: service=%s: invoking", service_id.c_str()); if (!wifi_lan_medium_.StartAcceptingConnections( - service_id, {.accepted_cb = [this, client, local_endpoint_name]( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( 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()); + std::string(local_endpoint_info).c_str()); return; } - RunOnPcpHandlerThread([this, client, local_endpoint_name, + RunOnPcpHandlerThread([this, client, local_endpoint_info, socket = std::move(socket)]() mutable { std::string remote_service_info_name = socket.GetRemoteWifiLanService().GetName(); auto channel = absl::make_unique( remote_service_info_name, socket); - OnIncomingConnection(client, remote_service_info_name, + ByteArray remote_service_info{remote_service_info_name}; + + OnIncomingConnection(client, remote_service_info, std::move(channel), proto::connections::Medium::WIFI_LAN); }); @@ -613,11 +918,12 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( "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()); + absl::BytesToHexString(service_id_hash.data()).c_str(), + absl::BytesToHexString(local_endpoint_info.data()).c_str()); // Generate a WifiLanServiceInfo with which to become WifiLan discoverable. std::string service_info_name(WifiLanServiceInfo( WifiLanServiceInfo::Version::kV1, GetPcp(), local_endpoint_id, - service_id_hash, local_endpoint_name)); + service_id_hash, local_endpoint_info)); if (service_info_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " @@ -687,20 +993,20 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::WifiLanConnectImpl( proto::connections::Medium P2pClusterPcpHandler::StartListeningForWebRtcConnections( ClientProxy* client, const string& service_id, - const string& local_endpoint_id, const string& local_endpoint_name) { + const string& local_endpoint_id, const ByteArray& local_endpoint_info) { if (!webrtc_medium_.IsAvailable()) { return proto::connections::UNKNOWN_MEDIUM; } if (!webrtc_medium_.IsAcceptingConnections()) { mediums::PeerId self_id = CreatePeerIdFromAdvertisement( - service_id, local_endpoint_id, local_endpoint_name); + service_id, local_endpoint_id, local_endpoint_info); if (!webrtc_medium_.StartAcceptingConnections( - self_id, {[this, client, local_endpoint_name]( + self_id, {[this, client, local_endpoint_info]( mediums::WebRtcSocketWrapper socket) { if (!socket.IsValid()) { NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", - local_endpoint_name.c_str()); + std::string(local_endpoint_info).c_str()); return; } @@ -709,8 +1015,9 @@ P2pClusterPcpHandler::StartListeningForWebRtcConnections( string remote_device_name = "WebRtcSocket"; auto channel = absl::make_unique( remote_device_name, socket); + ByteArray remote_device_info{remote_device_name}; - OnIncomingConnection(client, remote_device_name, + OnIncomingConnection(client, remote_device_info, std::move(channel), proto::connections::WEB_RTC); }); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index 7b5c4172..a18be31f 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -36,7 +36,7 @@ namespace connections { // connects over Bluetooth. class P2pClusterPcpHandler : public BasePcpHandler { public: - P2pClusterPcpHandler(Mediums& mediums, EndpointManager* endpoint_manager, + P2pClusterPcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, Pcp pcp = Pcp::kP2pCluster); ~P2pClusterPcpHandler() override = default; @@ -50,7 +50,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { BasePcpHandler::StartOperationResult StartAdvertisingImpl( ClientProxy* client, const std::string& service_id, const std::string& local_endpoint_id, - const std::string& local_endpoint_name, + const ByteArray& local_endpoint_info, const ConnectionOptions& options) override; // @PCPHandlerThread @@ -77,15 +77,36 @@ class P2pClusterPcpHandler : public BasePcpHandler { BluetoothDevice bluetooth_device; }; + struct BleEndpoint : public BasePcpHandler::DiscoveredEndpoint { + BleEndpoint(DiscoveredEndpoint endpoint, BlePeripheral peripheral) + : DiscoveredEndpoint(std::move(endpoint)), + ble_peripheral(std::move(peripheral)) {} + BlePeripheral ble_peripheral; + }; + + // Holds the state required to re-create a BleEndpoint we see on a + // BlePeripheral, so BlePeripheralLostHandler can call + // BasePcpHandler::OnEndpointLost() with the same information as was passed + // in to BasePCPHandler::onEndpointFound(). + struct BleEndpointState { + public: + BleEndpointState(const string& endpoint_id, const ByteArray& endpoint_info) + : endpoint_id(endpoint_id), endpoint_info(endpoint_info) {} + + std::string endpoint_id; + ByteArray endpoint_info; + }; struct WifiLanEndpoint : public BasePcpHandler::DiscoveredEndpoint { WifiLanEndpoint(DiscoveredEndpoint endpoint, WifiLanService service) : DiscoveredEndpoint(std::move(endpoint)), wifi_lan_service(std::move(service)) {} + WifiLanService wifi_lan_service; }; using BluetoothDiscoveredDeviceCallback = BluetoothClassic::DiscoveredDeviceCallback; + using BleDiscoveredPeripheralCallback = Ble::DiscoveredPeripheralCallback; using WifiLanDiscoveredServiceCallback = WifiLan::DiscoveredServiceCallback; static constexpr BluetoothDeviceName::Version kBluetoothDeviceNameVersion = @@ -99,34 +120,55 @@ class P2pClusterPcpHandler : public BasePcpHandler { 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); + void BluetoothDeviceDiscoveredHandler(ClientProxy* client, + const std::string& service_id, + BluetoothDevice& device); + void BluetoothDeviceLostHandler(ClientProxy* client, + const std::string& service_id, + BluetoothDevice& device); 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); + const ByteArray& local_endpoint_info); proto::connections::Medium StartBluetoothDiscovery( BluetoothDiscoveredDeviceCallback callback, ClientProxy* client, const std::string& service_id); BasePcpHandler::ConnectImplResult BluetoothConnectImpl( ClientProxy* client, BluetoothEndpoint* endpoint); + // Ble + // Maps a BlePeripheral to its corresponding BleEndpointState. + absl::flat_hash_map found_ble_endpoints_; + bool IsRecognizedBleEndpoint(const std::string& service_id, + const BleAdvertisement& advertisement) const; + void BlePeripheralDiscoveredHandler(ClientProxy* client, + BlePeripheral& peripheral, + const std::string& service_id); + void BlePeripheralLostHandler(ClientProxy* client, BlePeripheral& peripheral, + const std::string& service_id); + proto::connections::Medium StartBleAdvertising( + ClientProxy* client, const std::string& service_id, + const ByteArray& service_id_hash, const std::string& local_endpoint_id, + const ByteArray& local_endpoint_info); + proto::connections::Medium StartBleScanning( + BleDiscoveredPeripheralCallback callback, ClientProxy* client, + const std::string& service_id); + BasePcpHandler::ConnectImplResult BleConnectImpl(ClientProxy* client, + BleEndpoint* endpoint); + // WifiLan bool IsRecognizedWifiLanEndpoint( const std::string& service_id, const WifiLanServiceInfo& service_info) const; - std::function - MakeWifiLanServiceDiscoveredHandler(ClientProxy* client, - const std::string& service_id); - std::function - MakeWifiLanServiceLostHandler(ClientProxy* client, - const std::string& service_id); + void WifiLanServiceDiscoveredHandler(ClientProxy* client, + WifiLanService& service, + const std::string& service_id); + void WifiLanServiceLostHandler(ClientProxy* client, WifiLanService& service, + 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); + const ByteArray& local_endpoint_info); proto::connections::Medium StartWifiLanDiscovery( WifiLanDiscoveredServiceCallback callback, ClientProxy* client, const std::string& service_id); @@ -137,12 +179,13 @@ class P2pClusterPcpHandler : public BasePcpHandler { proto::connections::Medium StartListeningForWebRtcConnections( ClientProxy* client, const std::string& service_id, const std::string& local_endpoint_id, - const std::string& local_endpoint_name); + const ByteArray& local_endpoint_info); BasePcpHandler::ConnectImplResult WebRtcConnectImpl( ClientProxy* client, WebRtcEndpoint* webrtc_endpoint); BluetoothRadio& bluetooth_radio_; BluetoothClassic& bluetooth_medium_; + Ble& ble_medium_; WifiLan& wifi_lan_medium_; mediums::WebRtc& webrtc_medium_; }; diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc index 9d3ec83d..51bce6df 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -15,31 +15,57 @@ namespace nearby { namespace connections { namespace { -class P2pClusterPcpHandlerTest : public ::testing::Test { +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + +class P2pClusterPcpHandlerTest + : public ::testing::TestWithParam { protected: void SetUp() override { NEARBY_LOG(INFO, "SetUp: begin"); env_.Stop(); + if (options_.allowed.bluetooth) { + NEARBY_LOG(INFO, "SetUp: BT enabled"); + } + if (options_.allowed.wifi_lan) { + NEARBY_LOG(INFO, "SetUp: Wifi LAN enabled"); + } + if (options_.allowed.web_rtc) { + NEARBY_LOG(INFO, "SetUp: WebRTC enabled"); + } NEARBY_LOG(INFO, "SetUp: end"); } ClientProxy client_a_; ClientProxy client_b_; std::string service_id_{"service"}; - ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + ConnectionOptions options_{ + .strategy = Strategy::kP2pCluster, + .allowed = GetParam(), + }; MediumEnvironment& env_{MediumEnvironment::Instance()}; }; -TEST_F(P2pClusterPcpHandlerTest, CanConstructOne) { +TEST_P(P2pClusterPcpHandlerTest, CanConstructOne) { env_.Start(); Mediums mediums; EndpointChannelManager ecm; EndpointManager em(&ecm); - P2pClusterPcpHandler handler(mediums, &em, &ecm); + P2pClusterPcpHandler handler(&mediums, &em, &ecm); env_.Stop(); } -TEST_F(P2pClusterPcpHandlerTest, CanConstructMultiple) { +TEST_P(P2pClusterPcpHandlerTest, CanConstructMultiple) { env_.Start(); Mediums mediums_a; Mediums mediums_b; @@ -47,25 +73,26 @@ TEST_F(P2pClusterPcpHandlerTest, CanConstructMultiple) { 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); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b); env_.Stop(); } -TEST_F(P2pClusterPcpHandlerTest, CanAdvertise) { +TEST_P(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}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a); + EXPECT_EQ( + handler_a.StartAdvertising(&client_a_, service_id_, options_, + {.endpoint_info = ByteArray{endpoint_name}}), + Status{Status::kSuccess}); env_.Stop(); } -TEST_F(P2pClusterPcpHandlerTest, CanDiscover) { +TEST_P(P2pClusterPcpHandlerTest, CanDiscover) { env_.Start(); std::string endpoint_name{"endpoint_name"}; Mediums mediums_a; @@ -74,18 +101,19 @@ TEST_F(P2pClusterPcpHandlerTest, CanDiscover) { 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); + 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_a.StartAdvertising(&client_a_, service_id_, options_, + {.endpoint_info = ByteArray{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 ByteArray& endpoint_info, const std::string& service_id) { NEARBY_LOG(INFO, "Device discovered: id=%s", endpoint_id.c_str()); @@ -94,10 +122,13 @@ TEST_F(P2pClusterPcpHandlerTest, CanDiscover) { }), Status{Status::kSuccess}); EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result()); + // We discovered endpoint over one medium. Before we finish the test, we have + // to stop discovery for other mediums that may be still ongoing. + handler_b.StopDiscovery(&client_b_); env_.Stop(); } -TEST_F(P2pClusterPcpHandlerTest, CanConnect) { +TEST_P(P2pClusterPcpHandlerTest, CanConnect) { env_.Start(); std::string endpoint_name_a{"endpoint_name"}; Mediums mediums_a; @@ -110,20 +141,20 @@ TEST_F(P2pClusterPcpHandlerTest, CanConnect) { 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); + 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; + ByteArray endpoint_info; std::string service_id; } discovered; EXPECT_EQ( handler_a.StartAdvertising( &client_a_, service_id_, options_, { - .name = endpoint_name_a, + .endpoint_info = ByteArray{endpoint_name_a}, .listener = { .initiated_cb = @@ -142,13 +173,13 @@ TEST_F(P2pClusterPcpHandlerTest, CanConnect) { .endpoint_found_cb = [&discover_latch, &discovered]( const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, 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, + .endpoint_info = endpoint_info, .service_id = service_id, }; discover_latch.CountDown(); @@ -157,12 +188,12 @@ TEST_F(P2pClusterPcpHandlerTest, CanConnect) { Status{Status::kSuccess}); EXPECT_TRUE(discover_latch.Await(absl::Milliseconds(1000)).result()); - EXPECT_EQ(endpoint_name_a, discovered.endpoint_name); + EXPECT_EQ(endpoint_name_a, std::string{discovered.endpoint_info}); handler_b.RequestConnection( &client_b_, discovered.endpoint_id, { - .name = discovered.endpoint_name, + .endpoint_info = discovered.endpoint_info, .listener = { .initiated_cb = @@ -173,11 +204,15 @@ TEST_F(P2pClusterPcpHandlerTest, CanConnect) { connect_latch.CountDown(); }, }, - }); + }, + options_); EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result()); env_.Stop(); } +INSTANTIATE_TEST_SUITE_P(ParametrisedPcpHandlerTest, P2pClusterPcpHandlerTest, + ::testing::ValuesIn(kTestCases)); + } // namespace } // namespace connections } // namespace nearby 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 index 60da6883..c3525bdd 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -7,8 +7,7 @@ namespace connections { P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, Pcp pcp) - : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp), - mediums_(&mediums) {} + : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp) {} std::vector P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { 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 index e6da2dd9..cd9cb39b 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -3,7 +3,6 @@ #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" @@ -15,7 +14,7 @@ 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. +// not all mediums in P2P_POINT_TO_POINT are valid for P2P_STAR. // // Currently, this implementation advertises/discovers over Bluetooth // and connects over Bluetooth. @@ -31,9 +30,6 @@ class P2pPointToPointPcpHandler : public P2pStarPcpHandler { bool CanSendOutgoingConnection(ClientProxy* client) const override; bool CanReceiveIncomingConnection(ClientProxy* client) const override; - - private: - Mediums* mediums_; }; } // namespace connections diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc index 25901ebc..acb45e38 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -10,8 +10,8 @@ P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, Pcp pcp) - : P2pClusterPcpHandler(mediums, &endpoint_manager, &channel_manager, pcp), - mediums_(&mediums) {} + : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, pcp) { +} std::vector P2pStarPcpHandler::GetConnectionMediumsByPriority() { diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h index a50bd054..203bfcf5 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -6,7 +6,6 @@ #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" @@ -17,7 +16,7 @@ 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 +// in P2P_CLUSTER are valid for P2P_STAR, but not all mediums in P2P_STAR are // valid for P2P_CLUSTER. // // Currently, this implementation advertises/discovers over Bluetooth @@ -35,9 +34,6 @@ class P2pStarPcpHandler : public P2pClusterPcpHandler { bool CanSendOutgoingConnection(ClientProxy* client) const override; bool CanReceiveIncomingConnection(ClientProxy* client) const override; - - private: - Mediums* mediums_; }; } // namespace connections diff --git a/cpp/core_v2/internal/payload_manager_test.cc b/cpp/core_v2/internal/payload_manager_test.cc index e88a050c..bf843881 100644 --- a/cpp/core_v2/internal/payload_manager_test.cc +++ b/cpp/core_v2/internal/payload_manager_test.cc @@ -20,12 +20,27 @@ constexpr absl::string_view kMessage = "message"; constexpr absl::Duration kProgressTimeout = absl::Milliseconds(1000); constexpr absl::Duration kDefaultTimeout = absl::Milliseconds(1000); +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + class PayloadSimulationUser : public SimulationUser { public: - explicit PayloadSimulationUser(absl::string_view name) - : SimulationUser(std::string(name)) {} + explicit PayloadSimulationUser( + absl::string_view name, + BooleanMediumSelector allowed = BooleanMediumSelector()) + : SimulationUser(std::string(name), allowed) {} ~PayloadSimulationUser() override { - NEARBY_LOGS(INFO) << "PayloadSimulationUser: [down] name=" << name_; + NEARBY_LOGS(INFO) << "PayloadSimulationUser: [down] name=" << info_.data(); // SystemClock::Sleep(kDefaultTimeout); } @@ -51,7 +66,8 @@ class PayloadSimulationUser : public SimulationUser { Payload::Id sender_payload_id_ = 0; }; -class PayloadManagerTest : public ::testing::Test { +class PayloadManagerTest + : public ::testing::TestWithParam { protected: PayloadManagerTest() { env_.Stop(); } @@ -61,7 +77,7 @@ class PayloadManagerTest : public ::testing::Test { 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_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); EXPECT_FALSE(user_b.GetDiscovered().endpoint_id.empty()); NEARBY_LOG(INFO, "EP-B: [discovered] %s", user_b.GetDiscovered().endpoint_id.c_str()); @@ -85,23 +101,23 @@ class PayloadManagerTest : public ::testing::Test { MediumEnvironment& env_{MediumEnvironment::Instance()}; }; -TEST_F(PayloadManagerTest, CanCreateOne) { +TEST_P(PayloadManagerTest, CanCreateOne) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); + PayloadSimulationUser user_a(kDeviceA, GetParam()); env_.Stop(); } -TEST_F(PayloadManagerTest, CanCreateMultiple) { +TEST_P(PayloadManagerTest, CanCreateMultiple) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); - PayloadSimulationUser user_b(kDeviceB); + PayloadSimulationUser user_a(kDeviceA, GetParam()); + PayloadSimulationUser user_b(kDeviceB, GetParam()); env_.Stop(); } -TEST_F(PayloadManagerTest, CanSendBytePayload) { +TEST_P(PayloadManagerTest, CanSendBytePayload) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); - PayloadSimulationUser user_b(kDeviceB); + PayloadSimulationUser user_a(kDeviceA, GetParam()); + PayloadSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); user_a.ExpectPayload(payload_latch_); @@ -115,10 +131,10 @@ TEST_F(PayloadManagerTest, CanSendBytePayload) { env_.Stop(); } -TEST_F(PayloadManagerTest, CanSendStreamPayload) { +TEST_P(PayloadManagerTest, CanSendStreamPayload) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); - PayloadSimulationUser user_b(kDeviceB); + PayloadSimulationUser user_a(kDeviceA, GetParam()); + PayloadSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); auto pipe = std::make_shared(); @@ -165,10 +181,10 @@ TEST_F(PayloadManagerTest, CanSendStreamPayload) { env_.Stop(); } -TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { +TEST_P(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); - PayloadSimulationUser user_b(kDeviceB); + PayloadSimulationUser user_a(kDeviceA, GetParam()); + PayloadSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); auto pipe = std::make_shared(); @@ -212,7 +228,7 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { [status = PayloadProgressInfo::Status::kCanceled]( const PayloadProgressInfo& info) { return info.status == status; }, kProgressTimeout)); - NEARBY_LOG(INFO, "Stream cancelation recevied."); + NEARBY_LOG(INFO, "Stream cancelation received."); tx.Close(); rx.Close(); @@ -223,10 +239,10 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnReceiverSide) { env_.Stop(); } -TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) { +TEST_P(PayloadManagerTest, CanCancelPayloadOnSenderSide) { env_.Start(); - PayloadSimulationUser user_a(kDeviceA); - PayloadSimulationUser user_b(kDeviceB); + PayloadSimulationUser user_a(kDeviceA, GetParam()); + PayloadSimulationUser user_b(kDeviceB, GetParam()); ASSERT_TRUE(SetupConnection(user_a, user_b)); auto pipe = std::make_shared(); @@ -270,7 +286,7 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) { [status = PayloadProgressInfo::Status::kCanceled]( const PayloadProgressInfo& info) { return info.status == status; }, kProgressTimeout)); - NEARBY_LOG(INFO, "Stream cancelation recevied."); + NEARBY_LOG(INFO, "Stream cancelation received."); tx.Close(); rx.Close(); @@ -281,6 +297,9 @@ TEST_F(PayloadManagerTest, CanCancelPayloadOnSenderSide) { env_.Stop(); } +INSTANTIATE_TEST_SUITE_P(ParametrisedPayloadManagerTest, PayloadManagerTest, + ::testing::ValuesIn(kTestCases)); + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h index cb181dd9..8997f8a7 100644 --- a/cpp/core_v2/internal/pcp_handler.h +++ b/cpp/core_v2/internal/pcp_handler.h @@ -79,12 +79,13 @@ class PcpHandler { // connection, update state on ClientProxy. virtual Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) = 0; + const ConnectionRequestInfo& info, + const ConnectionOptions& options) = 0; // Either party may call this to accept connection on their part. // Until both parties call it, connection will not reach a data phase. // Update state in ClientProxy. - virtual Status AcceptConnection(ClientProxy* clientProxy, + virtual Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) = 0; diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index b6f071c0..c3c62aee 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -13,7 +13,7 @@ PcpManager::PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, EndpointManager& endpoint_manager) { handlers_[Pcp::kP2pCluster] = std::make_unique( - mediums, &endpoint_manager, &channel_manager); + &mediums, &endpoint_manager, &channel_manager); handlers_[Pcp::kP2pStar] = std::make_unique( mediums, endpoint_manager, channel_manager); handlers_[Pcp::kP2pPointToPoint] = @@ -69,12 +69,13 @@ void PcpManager::StopDiscovery(ClientProxy* client) { Status PcpManager::RequestConnection(ClientProxy* client, const string& endpoint_id, - const ConnectionRequestInfo& info) { + const ConnectionRequestInfo& info, + const ConnectionOptions& options) { if (!current_) { return {Status::kOutOfOrderApiCall}; } - return current_->RequestConnection(client, endpoint_id, info); + return current_->RequestConnection(client, endpoint_id, info, options); } Status PcpManager::AcceptConnection(ClientProxy* client, diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index 68228b38..ddeb4107 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -32,21 +32,22 @@ class PcpManager { EndpointManager& endpoint_manager); ~PcpManager(); - Status StartAdvertising(ClientProxy* client_proxy, const string& service_id, + Status StartAdvertising(ClientProxy* client, const string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info); - void StopAdvertising(ClientProxy* client_proxy); + void StopAdvertising(ClientProxy* client); - Status StartDiscovery(ClientProxy* client_proxy, const string& service_id, + Status StartDiscovery(ClientProxy* client, const string& service_id, const ConnectionOptions& options, DiscoveryListener listener); - void StopDiscovery(ClientProxy* client_proxy); + void StopDiscovery(ClientProxy* client); - Status RequestConnection(ClientProxy* client_proxy, const string& endpoint_id, - const ConnectionRequestInfo& info); - Status AcceptConnection(ClientProxy* client_proxy, const string& endpoint_id, + Status RequestConnection(ClientProxy* client, const string& endpoint_id, + const ConnectionRequestInfo& info, + const ConnectionOptions& options); + Status AcceptConnection(ClientProxy* client, const string& endpoint_id, const PayloadListener& payload_listener); - Status RejectConnection(ClientProxy* client_proxy, const string& endpoint_id); + Status RejectConnection(ClientProxy* client, const string& endpoint_id); proto::connections::Medium GetBandwidthUpgradeMedium(); void DisconnectFromEndpointManager(); diff --git a/cpp/core_v2/internal/pcp_manager_test.cc b/cpp/core_v2/internal/pcp_manager_test.cc index a2404719..ec261f8d 100644 --- a/cpp/core_v2/internal/pcp_manager_test.cc +++ b/cpp/core_v2/internal/pcp_manager_test.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/simulation_user.h" +#include "core_v2/options.h" #include "platform_v2/base/medium_environment.h" #include "platform_v2/public/count_down_latch.h" #include "gmock/gmock.h" @@ -19,58 +20,71 @@ constexpr char kServiceId[] = "service-id"; constexpr char kDeviceA[] = "device-A"; constexpr char kDeviceB[] = "device-B"; -class PcpManagerTest : public ::testing::Test { +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + +class PcpManagerTest : public ::testing::TestWithParam { protected: PcpManagerTest() { env_.Stop(); } MediumEnvironment& env_{MediumEnvironment::Instance()}; }; -TEST_F(PcpManagerTest, CanCreateOne) { +TEST_P(PcpManagerTest, CanCreateOne) { env_.Start(); - SimulationUser user(kDeviceA); + SimulationUser user(kDeviceA, GetParam()); env_.Stop(); } -TEST_F(PcpManagerTest, CanCreateMany) { +TEST_P(PcpManagerTest, CanCreateMany) { env_.Start(); - SimulationUser user_a(kDeviceA); - SimulationUser user_b(kDeviceB); + SimulationUser user_a(kDeviceA, GetParam()); + SimulationUser user_b(kDeviceB, GetParam()); env_.Stop(); } -TEST_F(PcpManagerTest, CanAdvertise) { +TEST_P(PcpManagerTest, CanAdvertise) { env_.Start(); - SimulationUser user_a(kDeviceA); - SimulationUser user_b(kDeviceB); + SimulationUser user_a(kDeviceA, GetParam()); + SimulationUser user_b(kDeviceB, GetParam()); user_a.StartAdvertising(kServiceId, nullptr); env_.Stop(); } -TEST_F(PcpManagerTest, CanDiscover) { +TEST_P(PcpManagerTest, CanDiscover) { env_.Start(); - SimulationUser user_a("device-a"); - SimulationUser user_b("device-b"); + SimulationUser user_a("device-a", GetParam()); + SimulationUser user_b("device-b", GetParam()); 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()); + EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); env_.Stop(); } -TEST_F(PcpManagerTest, CanConnect) { +TEST_P(PcpManagerTest, CanConnect) { env_.Start(); - SimulationUser user_a("device-a"); - SimulationUser user_b("device-b"); + SimulationUser user_a("device-a", GetParam()); + SimulationUser user_b("device-b", GetParam()); 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()); + EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); user_b.RequestConnection(&connection_latch); EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); user_a.Stop(); @@ -78,10 +92,10 @@ TEST_F(PcpManagerTest, CanConnect) { env_.Stop(); } -TEST_F(PcpManagerTest, CanAccept) { +TEST_P(PcpManagerTest, CanAccept) { env_.Start(); - SimulationUser user_a("device-a"); - SimulationUser user_b("device-b"); + SimulationUser user_a("device-a", GetParam()); + SimulationUser user_b("device-b", GetParam()); CountDownLatch discovery_latch(1); CountDownLatch connection_latch(2); CountDownLatch accept_latch(2); @@ -89,7 +103,7 @@ TEST_F(PcpManagerTest, CanAccept) { 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()); + EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); user_b.RequestConnection(&connection_latch); EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); user_a.AcceptConnection(&accept_latch); @@ -100,10 +114,10 @@ TEST_F(PcpManagerTest, CanAccept) { env_.Stop(); } -TEST_F(PcpManagerTest, CanReject) { +TEST_P(PcpManagerTest, CanReject) { env_.Start(); - SimulationUser user_a("device-a"); - SimulationUser user_b("device-b"); + SimulationUser user_a("device-a", GetParam()); + SimulationUser user_b("device-b", GetParam()); CountDownLatch discovery_latch(1); CountDownLatch connection_latch(2); CountDownLatch reject_latch(1); @@ -111,7 +125,7 @@ TEST_F(PcpManagerTest, CanReject) { 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()); + EXPECT_EQ(user_b.GetDiscovered().endpoint_info, user_a.GetInfo()); user_b.RequestConnection(&connection_latch); EXPECT_TRUE(connection_latch.Await(absl::Milliseconds(1000)).result()); user_b.ExpectRejectedConnection(reject_latch); @@ -122,6 +136,9 @@ TEST_F(PcpManagerTest, CanReject) { env_.Stop(); } +INSTANTIATE_TEST_SUITE_P(ParametrisedPcpManagerTest, PcpManagerTest, + ::testing::ValuesIn(kTestCases)); + } // namespace } // namespace connections } // namespace nearby diff --git a/cpp/core_v2/internal/service_controller.h b/cpp/core_v2/internal/service_controller.h index 0b6e8c60..ce186949 100644 --- a/cpp/core_v2/internal/service_controller.h +++ b/cpp/core_v2/internal/service_controller.h @@ -49,7 +49,8 @@ class ServiceController { virtual Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, - const ConnectionRequestInfo& info) = 0; + const ConnectionRequestInfo& info, + const ConnectionOptions& options) = 0; virtual Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& listener) = 0; @@ -63,8 +64,7 @@ class ServiceController { const std::vector& endpoint_ids, Payload payload) = 0; - virtual Status CancelPayload(ClientProxy* client, - Payload::Id payload_id) = 0; + virtual Status CancelPayload(ClientProxy* client, Payload::Id payload_id) = 0; virtual void DisconnectFromEndpoint(ClientProxy* client, const std::string& endpoint_id) = 0; diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc index 17ef83c2..e5ce4496 100644 --- a/cpp/core_v2/internal/service_controller_router.cc +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -92,23 +92,25 @@ void ServiceControllerRouter::StopDiscovery(ClientProxy* client, void ServiceControllerRouter::RequestConnection( ClientProxy* client, absl::string_view endpoint_id, - const ConnectionRequestInfo& info, const ResultCallback& callback) { - RouteToServiceController( - [this, client, endpoint_id = std::string(endpoint_id), info, callback]() { - if (!ClientHasAcquiredServiceController(client)) { - callback.result_cb({Status::kOutOfOrderApiCall}); - return; - } + const ConnectionRequestInfo& info, const ConnectionOptions& options, + const ResultCallback& callback) { + RouteToServiceController([this, client, + endpoint_id = std::string(endpoint_id), info, + options, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } - if (client->HasPendingConnectionToEndpoint(endpoint_id) || - client->IsConnectedToEndpoint(endpoint_id)) { - callback.result_cb({Status::kAlreadyConnectedToEndpoint}); - return; - } + if (client->HasPendingConnectionToEndpoint(endpoint_id) || + client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } - callback.result_cb( - service_controller_->RequestConnection(client, endpoint_id, info)); - }); + callback.result_cb(service_controller_->RequestConnection( + client, endpoint_id, info, options)); + }); } void ServiceControllerRouter::AcceptConnection(ClientProxy* client, @@ -204,7 +206,7 @@ void ServiceControllerRouter::SendPayload( std::vector(endpoint_ids.begin(), endpoint_ids.end()); RouteToServiceController( - [this, client, shared_payload, endpoints, &callback]() { + [this, client, shared_payload, endpoints, callback]() { if (!ClientHasAcquiredServiceController(client)) { callback.result_cb({Status::kOutOfOrderApiCall}); return; diff --git a/cpp/core_v2/internal/service_controller_router.h b/cpp/core_v2/internal/service_controller_router.h index 8ccfd057..70e9742a 100644 --- a/cpp/core_v2/internal/service_controller_router.h +++ b/cpp/core_v2/internal/service_controller_router.h @@ -59,6 +59,7 @@ class ServiceControllerRouter { void RequestConnection(ClientProxy* client, absl::string_view endpoint_id, const ConnectionRequestInfo& info, + const ConnectionOptions& options, const ResultCallback& callback); void AcceptConnection(ClientProxy* client, absl::string_view endpoint_id, const PayloadListener& listener, diff --git a/cpp/core_v2/internal/service_controller_router_test.cc b/cpp/core_v2/internal/service_controller_router_test.cc index 2fc45d00..0f34225d 100644 --- a/cpp/core_v2/internal/service_controller_router_test.cc +++ b/cpp/core_v2/internal/service_controller_router_test.cc @@ -101,20 +101,22 @@ class ServiceControllerRouterTest : public testing::Test { ResultCallback callback) { EXPECT_CALL(mock_, RequestConnection) .WillOnce(Return(Status{Status::kSuccess})); + ConnectionOptions options; { MutexLock lock(&mutex_); complete_ = false; - router_.RequestConnection(client, endpoint_id, request_info, callback); + router_.RequestConnection(client, endpoint_id, request_info, options, + callback); while (!complete_) cond_.Wait(); EXPECT_EQ(result_, Status{Status::kSuccess}); } ConnectionResponseInfo response_info{ - .remote_endpoint_name = "endpoint_name", + .remote_endpoint_info = ByteArray{"endpoint_name"}, .authentication_token = "auth_token", - .raw_authentication_token = ByteArray("auth_token"), + .raw_authentication_token = ByteArray{"auth_token"}, .is_incoming_connection = true, }; - client->OnConnectionInitiated(endpoint_id, response_info, + client->OnConnectionInitiated(endpoint_id, response_info, options, request_info.listener); EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); } @@ -242,7 +244,7 @@ class ServiceControllerRouterTest : public testing::Test { std::vector mediums_{ proto::connections::Medium::BLUETOOTH}; const ConnectionRequestInfo kConnectionRequestInfo{ - .name = kRequestorName, + .endpoint_info = ByteArray{kRequestorName}, .listener = ConnectionListener(), }; diff --git a/cpp/core_v2/internal/simulation_user.cc b/cpp/core_v2/internal/simulation_user.cc index 54dac813..7c38f5e5 100644 --- a/cpp/core_v2/internal/simulation_user.cc +++ b/cpp/core_v2/internal/simulation_user.cc @@ -18,7 +18,7 @@ void SimulationUser::OnConnectionInitiated(const std::string& endpoint_id, NEARBY_LOG(INFO, "StartAdvertising: initiated_cb called"); discovered_ = DiscoveredInfo{ .endpoint_id = endpoint_id, - .endpoint_name = name_, + .endpoint_info = GetInfo(), .service_id = service_id_, }; } @@ -35,12 +35,12 @@ void SimulationUser::OnConnectionRejected(const std::string& endpoint_id, } void SimulationUser::OnEndpointFound(const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, 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, + .endpoint_info = endpoint_info, .service_id = service_id, }; if (found_latch_) found_latch_->CountDown(); @@ -97,7 +97,7 @@ void SimulationUser::StartAdvertising(const std::string& service_id, }; EXPECT_TRUE(mgr_.StartAdvertising(&client_, service_id_, options_, { - .name = name_, + .endpoint_info = info_, .listener = std::move(listener), }) .Ok()); @@ -128,12 +128,14 @@ void SimulationUser::RequestConnection(CountDownLatch* latch) { .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()); + EXPECT_TRUE( + mgr_.RequestConnection(&client_, discovered_.endpoint_id, + { + .endpoint_info = discovered_.endpoint_info, + .listener = std::move(listener), + }, + connection_options_) + .Ok()); } void SimulationUser::AcceptConnection(CountDownLatch* latch) { diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 6d24929c..4674be0d 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -8,6 +8,7 @@ #include "core_v2/internal/endpoint_manager.h" #include "core_v2/internal/payload_manager.h" #include "core_v2/internal/pcp_manager.h" +#include "core_v2/options.h" #include "platform_v2/base/medium_environment.h" #include "platform_v2/public/condition_variable.h" #include "platform_v2/public/count_down_latch.h" @@ -27,18 +28,22 @@ class SimulationUser { public: struct DiscoveredInfo { std::string endpoint_id; - std::string endpoint_name; + ByteArray endpoint_info; 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() { - Stop(); - } + explicit SimulationUser( + const std::string& device_name, + BooleanMediumSelector allowed = BooleanMediumSelector()) + : info_{ByteArray{device_name}}, + options_{ + .strategy = Strategy::kP2pCluster, + .allowed = allowed, + } {} + virtual ~SimulationUser() { Stop(); } void Stop() { pm_.DisconnectFromEndpointManager(); mgr_.DisconnectFromEndpointManager(); @@ -80,7 +85,7 @@ class SimulationUser { void ExpectPayload(CountDownLatch& latch) { payload_latch_ = &latch; } const DiscoveredInfo& GetDiscovered() const { return discovered_; } - std::string GetName() const { return name_; } + ByteArray GetInfo() const { return info_; } bool WaitForProgress(std::function pred, absl::Duration timeout); @@ -95,7 +100,7 @@ class SimulationUser { // DiscoveryListener callbacks void OnEndpointFound(const std::string& endpoint_id, - const std::string& endpoint_name, + const ByteArray& endpoint_info, const std::string& service_id); void OnEndpointLost(const std::string& endpoint_id); @@ -106,6 +111,7 @@ class SimulationUser { std::string service_id_; DiscoveredInfo discovered_; + ConnectionOptions connection_options_; Mutex progress_mutex_; ConditionVariable progress_sync_{&progress_mutex_}; PayloadProgressInfo progress_info_; @@ -118,9 +124,9 @@ class SimulationUser { CountDownLatch* payload_latch_ = nullptr; Future* future_ = nullptr; std::function predicate_; - std::string name_; + ByteArray info_; Mediums mediums_; - ConnectionOptions options_{.strategy = Strategy::kP2pCluster}; + ConnectionOptions options_; ClientProxy client_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc index 92496867..566cf40e 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -17,7 +17,7 @@ namespace connections { WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - absl::string_view endpoint_name) { + const ByteArray& endpoint_info) { if (version != Version::kV1 || endpoint_id.empty() || endpoint_id.length() != kEndpointIdLength || service_id_hash.size() != kServiceIdHashLength) { @@ -36,7 +36,7 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, pcp_ = pcp; service_id_hash_ = service_id_hash; endpoint_id_ = std::string(endpoint_id); - endpoint_name_ = std::string(endpoint_name); + endpoint_info_ = endpoint_info; } WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { @@ -66,11 +66,11 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { return; } - if (service_info_bytes.size() > kMaxEndpointNameLength) { + if (service_info_bytes.size() > kMaxEndpointInfoLength) { NEARBY_LOG(INFO, "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " "bytes, got %" PRIu64, - kMaxEndpointNameLength, service_info_bytes.size()); + kMaxEndpointInfoLength, service_info_bytes.size()); return; } @@ -105,24 +105,22 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { // 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 next 1 byte are supposed to be the length of the endpoint_info. + std::uint32_t expected_endpoint_info_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) { + // The rest bytes are supposed to be the endpoint_info + endpoint_info_ = base_input_stream.ReadBytes(expected_endpoint_info_length); + if (endpoint_info_.Empty() || + endpoint_info_.size() != expected_endpoint_info_length) { NEARBY_LOG(INFO, "Cannot deserialize WifiLanServiceInfo: expected " - "endpointName to be %d bytes, got %" PRIu64, - expected_endpoint_name_length, endpoint_name_bytes.size()); + "endpoint info to be %d bytes, got %" PRIu64, + expected_endpoint_info_length, endpoint_info_.size()); // Clear enpoint_id for validadity. endpoint_id_.clear(); return; } - endpoint_name_ = std::string{endpoint_name_bytes}; } WifiLanServiceInfo::operator std::string() const { @@ -137,22 +135,23 @@ WifiLanServiceInfo::operator std::string() const { version_and_pcp_byte |= static_cast(static_cast(pcp_) & kPcpBitmask); - std::string usable_endpoint_name(endpoint_name_); - if (endpoint_name_.size() > kMaxEndpointNameLength) { + ByteArray usable_endpoint_info(endpoint_info_); + if (endpoint_info_.size() > kMaxEndpointInfoLength) { NEARBY_LOG( INFO, - "While serializing WifiLanServiceInfo, truncating Endpoint Name %s " + "While serializing WifiLanServiceInfo, truncating Endpoint info %s " "(%lu bytes) down to %d bytes", - endpoint_name_.c_str(), endpoint_name_.size(), kMaxEndpointNameLength); - usable_endpoint_name.erase(kMaxEndpointNameLength); + std::string(endpoint_info_).c_str(), endpoint_info_.size(), + kMaxEndpointInfoLength); + usable_endpoint_info.SetData(endpoint_info_.data(), kMaxEndpointInfoLength); } // 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); + std::string(1, usable_endpoint_info.size()), + std::string(usable_endpoint_info)); // 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 dff5e0d4..4b6b3897 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.h +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -28,7 +28,7 @@ class WifiLanServiceInfo { WifiLanServiceInfo() = default; WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - absl::string_view endpoint_name); + const ByteArray& endpoint_info); explicit WifiLanServiceInfo(absl::string_view service_info_string); WifiLanServiceInfo(const WifiLanServiceInfo&) = default; WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; @@ -42,7 +42,7 @@ class WifiLanServiceInfo { Version GetVersion() const { return version_; } Pcp GetPcp() const { return pcp_; } std::string GetEndpointId() const { return endpoint_id_; } - std::string GetEndpointName() const { return endpoint_name_; } + ByteArray GetEndpointInfo() const { return endpoint_info_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } private: @@ -53,7 +53,7 @@ class WifiLanServiceInfo { // The length for endpoint id in encrypted WifiLanServiceInfo string. static constexpr int kEndpointIdLength = 4; // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. - static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kMaxEndpointInfoLength = 131; static constexpr int kVersionBitmask = 0x0E0; static constexpr int kPcpBitmask = 0x01F; @@ -67,8 +67,8 @@ class WifiLanServiceInfo { std::string endpoint_id_; // Connected hash service id. ByteArray service_id_hash_; - // Connected endpoint name. - std::string endpoint_name_; + // Connected endpoint info. + ByteArray endpoint_info_; }; } // namespace connections 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 31a09955..90215c06 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info_test.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -20,21 +20,23 @@ constexpr absl::string_view kEndPointName{"RAWK + ROWL!"}; TEST(WifiLanServiceInfoTest, ConstructionWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + ByteArray endpoint_info{std::string(kEndPointName)}; + WifiLanServiceInfo wifi_lan_service_info{ + kVersion, kPcp, kEndPointID, service_id_hash, endpoint_info}; EXPECT_TRUE(wifi_lan_service_info.IsValid()); EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); - EXPECT_EQ(kEndPointName, wifi_lan_service_info.GetEndpointName()); + EXPECT_EQ(endpoint_info, wifi_lan_service_info.GetEndpointInfo()); } TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo org_wifi_lan_service_info{kVersion, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; std::string wifi_lan_service_info_string{org_wifi_lan_service_info}; WifiLanServiceInfo wifi_lan_service_info{wifi_lan_service_info_string}; @@ -44,15 +46,16 @@ 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()); + EXPECT_EQ(endpoint_info, wifi_lan_service_info.GetEndpointInfo()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { auto bad_version = static_cast(666); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{bad_version, kPcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -61,8 +64,9 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { auto bad_pcp = static_cast(666); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, bad_pcp, kEndPointID, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -71,8 +75,9 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { std::string short_endpoint_id("AB1"); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, short_endpoint_id, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -81,8 +86,9 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { std::string long_endpoint_id("AB12X"); ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, long_endpoint_id, - service_id_hash, kEndPointName}; + service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -91,8 +97,9 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { char short_service_id_hash_bytes[] = "\x0a\x0b"; ByteArray short_service_id_hash{short_service_id_hash_bytes}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{ - kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName}; + kVersion, kPcp, kEndPointID, short_service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -101,8 +108,9 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; ByteArray long_service_id_hash{long_service_id_hash_bytes}; + ByteArray endpoint_info{std::string(kEndPointName)}; WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, - long_service_id_hash, kEndPointName}; + long_service_id_hash, endpoint_info}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h index 649ea6d9..90bddc7f 100644 --- a/cpp/core_v2/listeners.h +++ b/cpp/core_v2/listeners.h @@ -35,10 +35,9 @@ struct ResultCallback { }; struct ConnectionResponseInfo { - std::string remote_endpoint_name; + ByteArray remote_endpoint_info; std::string authentication_token; ByteArray raw_authentication_token; - ByteArray endpoint_info; bool is_incoming_connection = false; bool is_connection_verified = false; }; @@ -121,13 +120,13 @@ struct DiscoveryListener { // Called when a remote endpoint is discovered. // // endpoint_id - The ID of the remote endpoint that was discovered. - // endpoint_name - The human readable name of the remote endpoint. + // endpoint_info - The info of the remote endpoint representd by ByteArray. // service_id - The ID of the service advertised by the remote endpoint. std::function endpoint_found_cb = - DefaultCallback(); // Called when a remote endpoint is no longer discoverable; only called for diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index 86fe59dc..9ee207ee 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -2,17 +2,64 @@ #define CORE_V2_OPTIONS_H_ #include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "proto/connections_enums.pb.h" +#include "proto/connections_enums.pb.h" namespace location { namespace nearby { namespace connections { +using Medium = ::location::nearby::proto::connections::Medium; + // Generic type: allows definition of a feature T for every Medium. template struct MediumSelector { T bluetooth; + T ble; T web_rtc; T wifi_lan; + + constexpr MediumSelector() = default; + constexpr MediumSelector(const MediumSelector&) = default; + constexpr MediumSelector& operator=(const MediumSelector&) = default; + + constexpr bool Any(T value) const { + return bluetooth == value || ble == value || web_rtc == value || + wifi_lan == value; + } + + constexpr bool All(T value) const { + return bluetooth == value && ble == value && web_rtc == value && + wifi_lan == value; + } + + constexpr int Count(T value) const { + int count = 0; + if (bluetooth == value) count++; + if (ble == value) count++; + if (wifi_lan == value) count++; + if (web_rtc == value) count++; + return count; + } + + constexpr MediumSelector& SetAll(T value) { + bluetooth = value; + ble = value; + web_rtc = value; + wifi_lan = value; + return *this; + } + + std::vector GetMediums(T value) const { + std::vector mediums; + // Mediums are sorted in order of decreasing preference. + if (wifi_lan == value) mediums.push_back(Medium::WIFI_LAN); + if (web_rtc == value) mediums.push_back(Medium::WEB_RTC); + if (ble == value) mediums.push_back(Medium::BLE); + if (bluetooth == value) mediums.push_back(Medium::BLUETOOTH); + return mediums; + } }; // Feature On/Off switch for mediums. @@ -22,17 +69,23 @@ using BooleanMediumSelector = MediumSelector; // All fields are mutable, to make the type copy-assignable. struct ConnectionOptions { Strategy strategy; - BooleanMediumSelector allowed; + BooleanMediumSelector allowed{BooleanMediumSelector().SetAll(true)}; bool auto_upgrade_bandwidth; bool enforce_topology_constraints; + ByteArray remote_bluetooth_mac_address; // Verify if ConnectionOptions is in a not-initialized (Empty) state. - bool Empty() const { - return strategy.IsNone(); - } + bool Empty() const { return strategy.IsNone(); } // Bring ConnectionOptions to a not-initialized (Empty) state. - void Clear() { - strategy.Clear(); + void Clear() { strategy.Clear(); } + // Returns a copy, but if no mediums are allowed, allowes all mediums. + ConnectionOptions CompatibleOptions() const { + ConnectionOptions result = *this; + if (!allowed.Any(true)) { + result.allowed.SetAll(true); + } + return result; } + std::vector GetMediums() const { return allowed.GetMediums(true); } }; } // namespace connections diff --git a/cpp/core_v2/params.h b/cpp/core_v2/params.h index b0ddde22..2cbc89b7 100644 --- a/cpp/core_v2/params.h +++ b/cpp/core_v2/params.h @@ -4,6 +4,7 @@ #include #include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" namespace location { namespace nearby { @@ -12,11 +13,11 @@ namespace connections { // Used by Discovery in Core::RequestConnection(). // Used by Advertising in Core::StartAdvertising(). struct ConnectionRequestInfo { - // name - A human readable name for this endpoint, to appear on - // other devices. - // listener - A set of callbacks notified when remote endpoints request a - // connection to this endpoint. - std::string name; + // endpoint_info - Identifing information about this endpoint (eg. name, + // device type). + // listener - A set of callbacks notified when remote endpoints request a + // connection to this endpoint. + ByteArray endpoint_info; ConnectionListener listener; }; diff --git a/cpp/core_v2/status.h b/cpp/core_v2/status.h index d56dab42..c5d49740 100644 --- a/cpp/core_v2/status.h +++ b/cpp/core_v2/status.h @@ -24,6 +24,7 @@ struct Status { kAlreadyConnectedToEndpoint, kNotConnectedToEndpoint, kBluetoothError, + kBleError, kWifiLanError, kPayloadUnknown, }; diff --git a/cpp/platform/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD index c231beae..ddb412ed 100644 --- a/cpp/platform/impl/g3/BUILD +++ b/cpp/platform/impl/g3/BUILD @@ -7,7 +7,6 @@ cc_library( "system_clock_impl.h", ], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", "//platform:__subpackages__", ], diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index dc3922f7..fdba4e14 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -5,11 +5,7 @@ cc_library( "sample_platform.cc", "settable_future_impl.h", ], - visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//core:__subpackages__", - "//location/nearby/setup/core:__subpackages__", - ], + visibility = ["//visibility:private"], deps = [ "//platform:types", "//platform:utils", diff --git a/cpp/platform/impl/shared/sample/BUILD b/cpp/platform/impl/shared/sample/BUILD index a1d0605f..0a29de7d 100644 --- a/cpp/platform/impl/shared/sample/BUILD +++ b/cpp/platform/impl/shared/sample/BUILD @@ -10,7 +10,6 @@ cc_library( "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", "//platform/impl:__subpackages__", - "//location/nearby/setup/core:__subpackages__", ], deps = [ "//platform:types", diff --git a/cpp/platform_v2/api/ble.h b/cpp/platform_v2/api/ble.h index 26883dec..49c9107c 100644 --- a/cpp/platform_v2/api/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -5,7 +5,6 @@ #include "platform_v2/base/byte_array.h" #include "platform_v2/base/input_stream.h" #include "platform_v2/base/output_stream.h" -#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -15,15 +14,17 @@ namespace api { // particular BLE device to connect to its GATT server. class BlePeripheral { public: - virtual ~BlePeripheral() {} + virtual ~BlePeripheral() = default; - // The returned reference lifetime matches BlePeripheral object. - virtual BluetoothDevice& GetBluetoothDevice() = 0; + virtual std::string GetName() const = 0; + + virtual ByteArray GetAdvertisementBytes( + const std::string& service_id) const = 0; }; class BleSocket { public: - virtual ~BleSocket() {} + virtual ~BleSocket() = default; // Returns the InputStream of the BleSocket. // On error, returned stream will report Exception::kIo on any operation. @@ -45,64 +46,58 @@ class BleSocket { // Returns Exception::kIo on error, Exception::kSuccess otherwise. virtual Exception Close() = 0; - // The returned object is not owned by the caller, and can be invalidated once - // the BleSocket object is destroyed. - virtual BlePeripheral& GetRemotePeripheral() = 0; + // Returns valid BlePeripheral pointer if there is a connection, and + // nullptr otherwise. + virtual BlePeripheral* GetRemotePeripheral() = 0; }; // Container of operations that can be performed over the BLE medium. class BleMedium { public: - virtual ~BleMedium() {} + virtual ~BleMedium() = default; - virtual bool StartAdvertising(absl::string_view service_id, - const ByteArray& advertisement) = 0; - virtual void StopAdvertising(absl::string_view service_id) = 0; + virtual bool StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) = 0; + virtual bool StopAdvertising(const std::string& service_id) = 0; - class DiscoveredPeripheralCallback { - public: - virtual ~DiscoveredPeripheralCallback() {} - - // The BlePeripheral* 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. - virtual void OnPeripheralDiscovered(BlePeripheral* ble_peripheral, - absl::string_view service_id, - const ByteArray& advertisement) = 0; - virtual void OnPeripheralLost(BlePeripheral* ble_peripheral, - absl::string_view service_id) = 0; + // Callback that is invoked when a discovered peripheral is found or lost. + struct DiscoveredPeripheralCallback { + std::function + peripheral_discovered_cb = + DefaultCallback(); + std::function + peripheral_lost_cb = + DefaultCallback(); }; // Returns true once the BLE scan has been initiated. - virtual bool StartScanning( - absl::string_view service_id, - const DiscoveredPeripheralCallback& discovered_peripheral_callback) = 0; + virtual bool StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) = 0; // Returns true once BLE scanning for service_id is well and truly stopped; // after this returns, there must be no more invocations of the // DiscoveredPeripheralCallback passed in to StartScanning() for service_id. - virtual void StopScanning(absl::string_view service_id) = 0; + virtual bool StopScanning(const std::string& service_id) = 0; // Callback that is invoked when a new connection is accepted. - class AcceptedConnectionCallback { - public: - virtual ~AcceptedConnectionCallback() {} - - virtual void OnConnectionAccepted(std::unique_ptr socket, - absl::string_view service_id) = 0; + struct AcceptedConnectionCallback { + std::function + accepted_cb = DefaultCallback(); }; // Returns true once BLE socket connection requests to service_id can be // accepted. virtual bool StartAcceptingConnections( - absl::string_view service_id, - const AcceptedConnectionCallback& accepted_connection_callback) = 0; - virtual void StopAcceptingConnections(const std::string& service_id) = 0; + const std::string& service_id, AcceptedConnectionCallback callback) = 0; + virtual bool StopAcceptingConnections(const std::string& service_id) = 0; - // BlePeripheral* is not owned by this call; - // it must remain valid for the duration of a call. - virtual std::unique_ptr Connect(BlePeripheral* ble_peripheral, - absl::string_view service_id) = 0; + // Connects to a BLE peripheral. + // On success, returns a new BleSocket. + // On error, returns nullptr. + virtual std::unique_ptr Connect(BlePeripheral& peripheral, + const std::string& service_id) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/bluetooth_adapter.h b/cpp/platform_v2/api/bluetooth_adapter.h index a18bbef3..96ec2e7a 100644 --- a/cpp/platform_v2/api/bluetooth_adapter.h +++ b/cpp/platform_v2/api/bluetooth_adapter.h @@ -49,6 +49,9 @@ class BluetoothAdapter { virtual std::string GetName() const = 0; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) virtual bool SetName(absl::string_view name) = 0; + + // Returns BT MAC address assigned to this adapter. + virtual std::string GetMacAddress() const = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h index fa3a6061..6dddd606 100644 --- a/cpp/platform_v2/api/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -21,6 +21,9 @@ class BluetoothDevice { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() virtual std::string GetName() const = 0; + + // Returns BT MAC address assigned to this device. + virtual std::string GetMacAddress() const = 0; }; // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. @@ -132,6 +135,8 @@ class BluetoothClassicMedium { // Returns nullptr error. virtual std::unique_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) = 0; + + virtual BluetoothDevice* FindRemoteDevice(const std::string& mac_address) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h index 10e6cdb2..12a9e423 100644 --- a/cpp/platform_v2/api/wifi_lan.h +++ b/cpp/platform_v2/api/wifi_lan.h @@ -20,6 +20,10 @@ class WifiLanService { virtual ~WifiLanService() = default; virtual std::string GetName() const = 0; + + // Returns the local device's as a pair. + // IP address is in byte sequence, in network order. + virtual std::pair GetServiceAddress() const = 0; }; class WifiLanSocket { @@ -88,8 +92,7 @@ class WifiLanMedium { // Returns true once WifiLan socket connection requests to service_id can be // accepted. virtual bool StartAcceptingConnections( - const std::string& service_id, - AcceptedConnectionCallback callback) = 0; + const std::string& service_id, AcceptedConnectionCallback callback) = 0; virtual bool StopAcceptingConnections(const std::string& service_id) = 0; // Connects to a WifiLan service. @@ -97,6 +100,9 @@ class WifiLanMedium { // On error, returns nullptr. virtual std::unique_ptr Connect( WifiLanService& service, const std::string& service_id) = 0; + + virtual WifiLanService* FindRemoteService(const std::string& ip_address, + int port) = 0; }; } // namespace api diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD index 2fd6a8ca..f9d16585 100644 --- a/cpp/platform_v2/base/BUILD +++ b/cpp/platform_v2/base/BUILD @@ -4,10 +4,12 @@ cc_library( name = "base", srcs = [ "base64_utils.cc", + "bluetooth_utils.cc", "prng.cc", ], hdrs = [ "base64_utils.h", + "bluetooth_utils.h", "byte_array.h", "callable.h", "exception.h", @@ -28,6 +30,7 @@ cc_library( deps = [ "//absl/meta:type_traits", "//absl/strings", + "//absl/strings:str_format", "//absl/time", ], ) @@ -96,6 +99,7 @@ cc_library( cc_test( name = "platform_base_test", srcs = [ + "bluetooth_utils_test.cc", "byte_array_test.cc", "prng_test.cc", ], diff --git a/cpp/platform_v2/base/bluetooth_utils.cc b/cpp/platform_v2/base/bluetooth_utils.cc new file mode 100644 index 00000000..e3221878 --- /dev/null +++ b/cpp/platform_v2/base/bluetooth_utils.cc @@ -0,0 +1,61 @@ +#include "platform_v2/base/bluetooth_utils.h" + +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" + +namespace location { +namespace nearby { + +std::string BluetoothUtils::ToString(const ByteArray& bluetooth_mac_address) { + std::string colon_delimited_string; + + if (bluetooth_mac_address.size() != kBluetoothMacAddressLength) + return colon_delimited_string; + + if (IsBluetoothMacAddressUnset(bluetooth_mac_address)) + return colon_delimited_string; + + for (auto byte : std::string(bluetooth_mac_address)) { + if (!colon_delimited_string.empty()) + absl::StrAppend(&colon_delimited_string, ":"); + absl::StrAppend(&colon_delimited_string, absl::StrFormat("%02X", byte)); + } + return colon_delimited_string; +} + +ByteArray BluetoothUtils::FromString(absl::string_view bluetooth_mac_address) { + std::string bt_mac_address(bluetooth_mac_address); + + // Remove the colon delimiters. + bt_mac_address.erase( + std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'), + bt_mac_address.end()); + + // If the bluetooth mac address is invalid (wrong size), return a null byte + // array. + if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) { + return ByteArray(); + } + + // Convert to bytes. If MAC Address bytes are unset, return a null byte array. + auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address)); + auto bt_mac_address_bytes = + ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size()); + if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) { + return ByteArray(); + } + return bt_mac_address_bytes; +} + +bool BluetoothUtils::IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) { + for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) { + if (bluetooth_mac_address_bytes.data()[i] != 0) { + return false; + } + } + return true; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/bluetooth_utils.h b/cpp/platform_v2/base/bluetooth_utils.h new file mode 100644 index 00000000..a8a8a20f --- /dev/null +++ b/cpp/platform_v2/base/bluetooth_utils.h @@ -0,0 +1,32 @@ +#ifndef PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_ +#define PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_ + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class BluetoothUtils { + public: + static constexpr int kBluetoothMacAddressLength = 6; + + // Converts a Bluetooth MAC address from byte array to String format. Returns + // empty if input byte array is not of correct format. + // e.g. {-84, 55, 67, -68, -87, 40} -> "AC:37:43:BC:A9:28". + static std::string ToString(const ByteArray& bluetooth_mac_address); + + // Converts a Bluetooth MAC address from String format to byte array. Returns + // empty if input string is not of correct format. + // e.g. "AC:37:43:BC:A9:28" -> {-84, 55, 67, -68, -87, 40}. + static ByteArray FromString(absl::string_view bluetooth_mac_address); + + // Checks if a Bluetooth MAC address is zero for every byte. + static bool IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_ diff --git a/cpp/platform_v2/base/bluetooth_utils_test.cc b/cpp/platform_v2/base/bluetooth_utils_test.cc new file mode 100644 index 00000000..7cc6f53e --- /dev/null +++ b/cpp/platform_v2/base/bluetooth_utils_test.cc @@ -0,0 +1,75 @@ +#include "platform_v2/base/bluetooth_utils.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"}; +constexpr char kBluetoothMacAddressBytes[] = {0x00, 0x00, 0xe6, + 0x88, 0x64, 0x13}; + +TEST(BluetoothUtilsTest, ToStringWorks) { + ByteArray bt_mac_address_bytes{ + kBluetoothMacAddressBytes, sizeof(kBluetoothMacAddressBytes)}; + + auto bt_mac_address = BluetoothUtils::ToString(bt_mac_address_bytes); + + EXPECT_EQ(kBluetoothMacAddress, bt_mac_address); +} + +TEST(BluetoothUtilsTest, FromStringWorks) { + ByteArray bt_mac_address_bytes{ + kBluetoothMacAddressBytes, sizeof(kBluetoothMacAddressBytes)}; + + auto bt_mac_address_bytes_result = + BluetoothUtils::FromString(kBluetoothMacAddress); + + EXPECT_EQ(bt_mac_address_bytes, bt_mac_address_bytes_result); +} + +TEST(BluetoothUtilsTest, InvalidBytesReturnsEmptyString) { + std::string string_result; + + char bad_bt_mac_address_1[] = {0x02, 0x20, 0x00}; + ByteArray bad_bt_mac_address_bytes_1{bad_bt_mac_address_1, + sizeof(bad_bt_mac_address_1)}; + string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_1); + EXPECT_TRUE(string_result.empty()); + + char bad_bt_mac_address_2[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + ByteArray bad_bt_mac_address_bytes_2{bad_bt_mac_address_2, + sizeof(bad_bt_mac_address_2)}; + string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_2); + EXPECT_TRUE(string_result.empty()); + + char bad_bt_mac_address_3[] = {0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99}; + ByteArray bad_bt_mac_address_bytes_3{bad_bt_mac_address_3, + sizeof(bad_bt_mac_address_3)}; + string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_3); + EXPECT_TRUE(string_result.empty()); +} + +TEST(BluetoothUtilsTest, InvalidStringReturnsEmptyByteArray) { + ByteArray bytes_result; + + std::string bad_bt_mac_address_1 = "022:00"; + bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_1); + EXPECT_TRUE(bytes_result.Empty()); + + std::string bad_bt_mac_address_2 = "22:00:11:33:77:aa::bb::99"; + bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_2); + EXPECT_TRUE(bytes_result.Empty()); + + std::string bad_bt_mac_address_3 = "00:00:00:00:00:00"; + bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_3); + EXPECT_TRUE(bytes_result.Empty()); + + std::string bad_bt_mac_address_4 = "BLUETOOTHCHIP"; + bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_4); + EXPECT_TRUE(bytes_result.Empty()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h index df84edb9..1cdaf118 100644 --- a/cpp/platform_v2/base/byte_array.h +++ b/cpp/platform_v2/base/byte_array.h @@ -74,7 +74,7 @@ class ByteArray { // Moves string out of temporary ByteArray, allowing for a zero-copy // operation. - explicit operator std::string() const&& { return std::move(data_); } + explicit operator std::string() && { return std::move(data_); } private: std::string data_; diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 8d1cbece..8687703e 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -5,6 +5,7 @@ #include #include +#include "platform_v2/api/ble.h" #include "platform_v2/api/bluetooth_adapter.h" #include "platform_v2/api/bluetooth_classic.h" #include "platform_v2/api/wifi_lan.h" @@ -42,6 +43,7 @@ void MediumEnvironment::Reset() { NEARBY_LOG(INFO, "MediumEnvironment::Reset()"); bluetooth_adapters_.clear(); bluetooth_mediums_.clear(); + ble_mediums_.clear(); wifi_lan_mediums_.clear(); }); Sync(); @@ -154,6 +156,48 @@ void MediumEnvironment::OnBluetoothDeviceStateChanged( } } +api::BluetoothDevice* MediumEnvironment::FindBluetoothDevice( + const std::string& mac_address) { + api::BluetoothDevice* device = nullptr; + CountDownLatch latch(1); + RunOnMediumEnvironmentThread([this, &device, &latch, &mac_address](){ + for (auto& item : bluetooth_mediums_) { + auto* adapter = item.second.adapter; + if (!adapter) continue; + if (adapter->GetMacAddress() == mac_address) { + device = bluetooth_adapters_[adapter]; + break; + } + } + latch.CountDown(); + }); + latch.Await(); + return device; +} + +void MediumEnvironment::OnBlePeripheralStateChanged( + BleMediumContext& info, api::BlePeripheral& peripheral, + const std::string& service_id, bool enabled) { + if (!enabled_) return; + NEARBY_LOG(INFO, + "G3 OnBleServiceStateChanged [peripheral impl=%p]; context=%p; " + "service_id=%s; notify=%d", + &peripheral, &info, service_id.c_str(), + enable_notifications_.load()); + if (!enable_notifications_) return; + RunOnMediumEnvironmentThread([&info, enabled, &peripheral, service_id]() { + NEARBY_LOG(INFO, + "G3 [Run] OnBlePeripheralStateChanged [peripheral impl=%p]; " + "context=%p; service_id=%s; enabled=%d", + &peripheral, &info, service_id.c_str(), enabled); + if (enabled) { + info.discovery_callback.peripheral_discovered_cb(peripheral, service_id); + } else { + info.discovery_callback.peripheral_lost_cb(peripheral, service_id); + } + }); +} + void MediumEnvironment::OnWifiLanServiceStateChanged( WifiLanMediumContext& info, api::WifiLanService& service, const std::string& service_id, bool enabled) { @@ -164,6 +208,10 @@ void MediumEnvironment::OnWifiLanServiceStateChanged( &service, &info, service_id.c_str(), enable_notifications_.load()); if (!enable_notifications_) return; RunOnMediumEnvironmentThread([&info, enabled, &service, service_id]() { + NEARBY_LOG(INFO, + "G3 [Run] OnWifiLanServiceStateChanged [service impl=%p]; " + "context=%p; service_id=%s; enabled=%d", + &service, &info, service_id.c_str(), enabled); auto service_id_context = info.services.find(service_id); if (service_id_context == info.services.end()) return; @@ -246,6 +294,125 @@ void MediumEnvironment::UnregisterBluetoothMedium( }); } +void MediumEnvironment::RegisterBleMedium(api::BleMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + ble_mediums_.insert({&medium, BleMediumContext{}}); + NEARBY_LOG(INFO, "Registered: medium=%p", &medium); + }); +} + +void MediumEnvironment::UpdateBleMediumForAdvertising( + api::BleMedium& medium, api::BlePeripheral& peripheral, + const std::string& service_id, bool enabled) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, &peripheral, service_id, + enabled]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG(INFO, + "UpdateBleMediumForAdvertising failed. There is no medium " + "registered."); + return; + } + auto& context = item->second; + context.ble_peripheral = &peripheral; + context.advertising = enabled; + NEARBY_LOG(INFO, + "Update Ble medium for advertising: this=%p; medium=%p; " + "service_id=%s; name=%s; enabled=%d; ", + this, &medium, service_id.c_str(), peripheral.GetName().c_str(), + enabled); + for (auto& medium_info : ble_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + OnBlePeripheralStateChanged(info, peripheral, service_id, enabled); + } + }); +} + +void MediumEnvironment::UpdateBleMediumForScanning( + api::BleMedium& medium, const std::string& service_id, + BleDiscoveredPeripheralCallback callback, bool enabled) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, service_id, + callback = std::move(callback), enabled]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG(INFO, + "UpdateBleMediumFoScanning failed. There is no medium " + "registered."); + return; + } + auto& context = item->second; + context.discovery_callback = std::move(callback); + NEARBY_LOG(INFO, + "Update Ble medium for scanning: this=%p; medium=%p; " + "service_id=%s; enabled=%d ;", + this, &medium, service_id.c_str(), enabled); + for (auto& medium_info : ble_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + // Search advertising mediums and send notification. + if (info.advertising && enabled) { + OnBlePeripheralStateChanged(context, *(info.ble_peripheral), service_id, + enabled); + } + } + }); +} + +void MediumEnvironment::UpdateBleMediumForAcceptedConnection( + api::BleMedium& medium, const std::string& service_id, + BleAcceptedConnectionCallback callback) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, service_id, + callback = std::move(callback)]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG( + INFO, "Update Ble medium failed. There is no medium registered."); + return; + } + auto& context = item->second; + context.accepted_connection_callback = std::move(callback); + NEARBY_LOG(INFO, + "Update Ble medium for accepted callback: this=%p; " + "medium=%p; service_id=%s; ", + this, &medium, service_id.c_str()); + }); +} + +void MediumEnvironment::UnregisterBleMedium(api::BleMedium& medium) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium]() { + auto item = ble_mediums_.extract(&medium); + if (item.empty()) return; + NEARBY_LOG(INFO, "Unregistered Ble medium"); + }); +} + +void MediumEnvironment::CallBleAcceptedConnectionCallback( + api::BleMedium& medium, api::BleSocket& socket, + const std::string& service_id) { + if (!enabled_) return; + RunOnMediumEnvironmentThread([this, &medium, &socket, service_id]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG(INFO, + "Call AcceptedConnectionCallback failed.. There is no medium " + "registered."); + return; + } + auto& info = item->second; + info.accepted_connection_callback.accepted_cb(socket, service_id); + }); +} + void MediumEnvironment::RegisterWebRtcSignalingMessenger( absl::string_view self_id, OnSignalingMessageCallback callback) { if (!enabled_) return; @@ -437,5 +604,26 @@ void MediumEnvironment::CallWifiLanAcceptedConnectionCallback( }); } +api::WifiLanService* MediumEnvironment::FindWifiLanService( + const std::string& ip_address, int port) { + api::WifiLanService* remote_service = nullptr; + CountDownLatch latch(1); + RunOnMediumEnvironmentThread( + [this, &remote_service, &ip_address, port, &latch]() { + for (auto& item : wifi_lan_mediums_) { + auto* service = item.second.wifi_lan_service; + if (!service) continue; + auto addr = remote_service->GetServiceAddress(); + if (addr.first == ip_address && addr.second == port) { + remote_service = service; + break; + } + } + latch.CountDown(); + }); + latch.Await(); + return remote_service; +} + } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 0464a598..a1a0f27e 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -33,6 +33,10 @@ class MediumEnvironment { public: using BluetoothDiscoveryCallback = api::BluetoothClassicMedium::DiscoveryCallback; + using BleDiscoveredPeripheralCallback = + api::BleMedium::DiscoveredPeripheralCallback; + using BleAcceptedConnectionCallback = + api::BleMedium::AcceptedConnectionCallback; using OnSignalingMessageCallback = api::WebRtcSignalingMessenger::OnSignalingMessageCallback; using WifiLanDiscoveredServiceCallback = @@ -103,6 +107,9 @@ class MediumEnvironment { // Removes medium-related info. This should correspond to device power off. void UnregisterBluetoothMedium(api::BluetoothClassicMedium& medium); + // Returns a Bluetooth Device object matching given mac address to nullptr. + api::BluetoothDevice* FindBluetoothDevice(const std::string& mac_address); + const EnvironmentConfig& GetEnvironmentConfig(); // Registers |callback| to receive messages sent to device with id |self_id|. @@ -116,6 +123,48 @@ class MediumEnvironment { // |peer_id|. void SendWebRtcSignalingMessage(absl::string_view peer_id, const ByteArray& message); + + // Adds medium-related info to allow for scanning/advertising to work. + // This provides acccess to this medium from other mediums, when protocol + // expects they should communicate. + void RegisterBleMedium(api::BleMedium& medium); + + // Updates advertising info to indicate the current medium is exposing + // advertising event. + void UpdateBleMediumForAdvertising(api::BleMedium& medium, + api::BlePeripheral& peripheral, + const std::string& service_id, + bool enabled); + + // Updates discovery callback info to allow for dispatch of discovery events. + // + // Invokes callback asynchronously when any changes happen to discoverable + // devices, or if the defice is turned off, whether or not it is discoverable, + // if it was ever reported as discoverable. + // + // This should be called when discoverable state changes. + // with user-specified callback when discovery is enabled, and with default + // (empty) callback otherwise. + void UpdateBleMediumForScanning(api::BleMedium& medium, + const std::string& service_id, + BleDiscoveredPeripheralCallback callback, + bool enabled); + + // Updates Accepted connection callback info to allow for dispatch of + // advertising events. + void UpdateBleMediumForAcceptedConnection( + api::BleMedium& medium, const std::string& service_id, + BleAcceptedConnectionCallback callback); + + // Removes medium-related info. This should correspond to device power off. + void UnregisterBleMedium(api::BleMedium& medium); + + // Call back when advertising has created the server socket and is ready for + // connect. + void CallBleAcceptedConnectionCallback(api::BleMedium& medium, + api::BleSocket& socket, + const std::string& service_id); + // Adds medium-related info to allow for discovery/advertising to work. // This provides acccess to this medium from other mediums, when protocol // expects they should communicate. @@ -123,9 +172,10 @@ class MediumEnvironment { // Updates advertising info to indicate the current medium is exposing // advertising event. - void UpdateWifiLanMediumForAdvertising( - api::WifiLanMedium& medium, api::WifiLanService& service, - const std::string& service_id, bool enabled); + void UpdateWifiLanMediumForAdvertising(api::WifiLanMedium& medium, + api::WifiLanService& service, + const std::string& service_id, + bool enabled); // Updates discovery callback info to allow for dispatch of discovery events. // @@ -155,6 +205,10 @@ class MediumEnvironment { api::WifiLanSocket& socket, const std::string& service_id); + // Returns WiFi LAN service matching IP address and port, or nullptr. + api::WifiLanService* FindWifiLanService(const std::string& ip_address, + int port); + private: struct BluetoothMediumContext { BluetoothDiscoveryCallback callback; @@ -163,6 +217,13 @@ class MediumEnvironment { absl::flat_hash_map devices; }; + struct BleMediumContext { + BleDiscoveredPeripheralCallback discovery_callback; + BleAcceptedConnectionCallback accepted_connection_callback; + api::BlePeripheral* ble_peripheral = nullptr; + bool advertising = false; + }; + struct WifiLanServiceIdContext { WifiLanDiscoveredServiceCallback discovery_callback; WifiLanAcceptedConnectionCallback accepted_connection_callback; @@ -187,6 +248,10 @@ class MediumEnvironment { api::BluetoothAdapter::ScanMode mode, bool enabled); + void OnBlePeripheralStateChanged(BleMediumContext& info, + api::BlePeripheral& peripheral, + const std::string& service_id, bool enabled); + void OnWifiLanServiceStateChanged(WifiLanMediumContext& info, api::WifiLanService& service, const std::string& service_id, @@ -207,6 +272,8 @@ class MediumEnvironment { absl::flat_hash_map bluetooth_mediums_; + absl::flat_hash_map ble_mediums_; + // Maps peer id to callback for receiving signaling messages. absl::flat_hash_map webrtc_signaling_callback_; diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index ae64a943..4dd926da 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -39,12 +39,14 @@ cc_library( name = "comm", testonly = True, srcs = [ + "ble.cc", "bluetooth_adapter.cc", "bluetooth_classic.cc", "webrtc.cc", "wifi_lan.cc", ], hdrs = [ + "ble.h", "bluetooth_adapter.h", "bluetooth_classic.h", "webrtc.h", @@ -76,9 +78,7 @@ cc_library( srcs = [ "crypto.cc", ], - visibility = [ - "//platform_v2/g3:__pkg__", - ], + visibility = ["//visibility:private"], deps = [ "//platform_v2/api:types", "//platform_v2/base", @@ -94,7 +94,6 @@ cc_library( "platform.cc", ], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core_v2:__subpackages__", "//platform_v2:__subpackages__", ], diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc new file mode 100644 index 00000000..9b143494 --- /dev/null +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -0,0 +1,341 @@ +#include "platform_v2/impl/g3/ble.h" + +#include +#include +#include + +#include "platform_v2/api/ble.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 { + +BleSocket::~BleSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +void BleSocket::Connect(BleSocket& other) { + absl::MutexLock lock(&mutex_); + remote_socket_ = &other; + input_ = other.output_; +} + +InputStream& BleSocket::GetInputStream() { + auto* remote_socket = GetRemoteSocket(); + CHECK(remote_socket != nullptr); + return remote_socket->GetLocalInputStream(); +} + +OutputStream& BleSocket::GetOutputStream() { + return GetLocalOutputStream(); +} + +BleSocket* BleSocket::GetRemoteSocket() { + absl::MutexLock lock(&mutex_); + return remote_socket_; +} + +bool BleSocket::IsConnected() const { + absl::MutexLock lock(&mutex_); + return IsConnectedLocked(); +} + +bool BleSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +Exception BleSocket::Close() { + absl::MutexLock lock(&mutex_); + DoClose(); + return {Exception::kSuccess}; +} + +BlePeripheral* BleSocket::GetRemotePeripheral() { + absl::MutexLock lock(&mutex_); + return peripheral_; +} + +void BleSocket::DoClose() { + if (!closed_) { + remote_socket_ = nullptr; + output_->GetOutputStream().Close(); + output_->GetInputStream().Close(); + if (IsConnectedLocked()) { + input_->GetOutputStream().Close(); + input_->GetInputStream().Close(); + } + closed_ = true; + } +} + +bool BleSocket::IsConnectedLocked() const { return input_ != nullptr; } + +InputStream& BleSocket::GetLocalInputStream() { + absl::MutexLock lock(&mutex_); + return output_->GetInputStream(); +} + +OutputStream& BleSocket::GetLocalOutputStream() { + absl::MutexLock lock(&mutex_); + return output_->GetOutputStream(); +} + +std::unique_ptr BleServerSocket::Accept( + BlePeripheral* peripheral) { + absl::MutexLock lock(&mutex_); + if (closed_) return {}; + while (pending_sockets_.empty()) { + cond_.Wait(&mutex_); + if (closed_) break; + } + if (closed_) return {}; + auto* remote_socket = + pending_sockets_.extract(pending_sockets_.begin()).value(); + CHECK(remote_socket); + auto local_socket = std::make_unique(peripheral); + local_socket->Connect(*remote_socket); + remote_socket->Connect(*local_socket); + cond_.SignalAll(); + return local_socket; +} + +bool BleServerSocket::Connect(BleSocket& socket) { + absl::MutexLock lock(&mutex_); + if (closed_) return false; + if (socket.IsConnected()) { + NEARBY_LOG(ERROR, + "Failed to connect to Ble server socket: already connected"); + return true; // already connected. + } + // add client socket to the pending list + pending_sockets_.emplace(&socket); + cond_.SignalAll(); + while (!socket.IsConnected()) { + cond_.Wait(&mutex_); + if (closed_) return false; + } + return true; +} + +void BleServerSocket::SetCloseNotifier(std::function notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +BleServerSocket::~BleServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +Exception BleServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception BleServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + +BleMedium::BleMedium(api::BluetoothAdapter& adapter) + : adapter_(static_cast(&adapter)) { + adapter_->SetBleMedium(this); + auto& env = MediumEnvironment::Instance(); + env.RegisterBleMedium(*this); +} + +BleMedium::~BleMedium() { + adapter_->SetBleMedium(nullptr); + auto& env = MediumEnvironment::Instance(); + env.UnregisterBleMedium(*this); + + StopAdvertising(advertising_info_.service_id); + StopScanning(scanning_info_.service_id); + + accept_loops_runner_.Shutdown(); + NEARBY_LOG(INFO, "BleMedium dtor advertising_accept_thread_running_ = %d", + acceptance_thread_running_.load()); + // If acceptance thread is still running, wait to finish. + if (acceptance_thread_running_) { + while (acceptance_thread_running_) { + CountDownLatch latch(1); + close_accept_loops_runner_.Execute([&latch]() { latch.CountDown(); }); + latch.Await(); + } + } +} + +bool BleMedium::StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) { + NEARBY_LOGS(INFO) << "G3 Ble StartAdvertising: service_id=" << service_id + << ", advertisement bytes=" << advertisement_bytes.data() + << "(" << advertisement_bytes.size() << ")"; + auto& env = MediumEnvironment::Instance(); + auto& peripheral = adapter_->GetPeripheral(); + peripheral.SetAdvertisementBytes(service_id, advertisement_bytes); + env.UpdateBleMediumForAdvertising(*this, peripheral, service_id, true); + + absl::MutexLock lock(&mutex_); + if (server_socket_ != nullptr) server_socket_.release(); + server_socket_ = std::make_unique(); + + acceptance_thread_running_.exchange(true); + accept_loops_runner_.Execute([&env, this, service_id]() mutable { + if (!accept_loops_runner_.InShutdown()) { + while (true) { + auto client_socket = + server_socket_->Accept(&(this->adapter_->GetPeripheral())); + if (client_socket == nullptr) break; + env.CallBleAcceptedConnectionCallback(*this, *(client_socket.release()), + service_id); + } + } + acceptance_thread_running_.exchange(false); + }); + advertising_info_.service_id = service_id; + return true; +} + +bool BleMedium::StopAdvertising(const std::string& service_id) { + NEARBY_LOGS(INFO) << "G3 Ble StopAdvertising: service_id=" << service_id; + { + absl::MutexLock lock(&mutex_); + if (advertising_info_.Empty()) { + NEARBY_LOGS(INFO) << "G3 Ble StopAdvertising: Can't stop advertising " + "because we never started advertising."; + return false; + } + advertising_info_.Clear(); + } + + auto& env = MediumEnvironment::Instance(); + env.UpdateBleMediumForAdvertising(*this, adapter_->GetPeripheral(), + service_id, false); + accept_loops_runner_.Shutdown(); + if (server_socket_ == nullptr) { + NEARBY_LOGS(ERROR) << "G3 Ble StopAdvertising: Failed to find Ble Server " + "socket: service_id=" + << service_id; + // Fall through for server socket not found. + return true; + } + + if (!server_socket_->Close().Ok()) { + NEARBY_LOGS(INFO) + << "G3 Ble StopAdvertising: Failed to close Ble server socket for " + << service_id; + return false; + } + return true; +} + +bool BleMedium::StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) { + NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id; + auto& env = MediumEnvironment::Instance(); + env.UpdateBleMediumForScanning(*this, service_id, std::move(callback), true); + { + absl::MutexLock lock(&mutex_); + scanning_info_.service_id = service_id; + } + return true; +} + +bool BleMedium::StopScanning(const std::string& service_id) { + NEARBY_LOGS(INFO) << "G3 Ble StopScanning: service_id=" << service_id; + { + absl::MutexLock lock(&mutex_); + if (scanning_info_.Empty()) { + NEARBY_LOGS(INFO) << "G3 Ble StopDiscovery: Can't stop scanning because " + "we never started scanning."; + return false; + } + scanning_info_.Clear(); + } + + auto& env = MediumEnvironment::Instance(); + env.UpdateBleMediumForScanning(*this, service_id, {}, false); + return true; +} + +bool BleMedium::StartAcceptingConnections(const std::string& service_id, + AcceptedConnectionCallback callback) { + NEARBY_LOGS(INFO) << "G3 Ble StartAcceptingConnections: service_id=" + << service_id; + auto& env = MediumEnvironment::Instance(); + env.UpdateBleMediumForAcceptedConnection(*this, service_id, callback); + return true; +} + +bool BleMedium::StopAcceptingConnections(const std::string& service_id) { + NEARBY_LOGS(INFO) << "G3 Ble StopAcceptingConnections: service_id=" + << service_id; + auto& env = MediumEnvironment::Instance(); + env.UpdateBleMediumForAcceptedConnection(*this, service_id, {}); + return true; +} + +std::unique_ptr BleMedium::Connect( + api::BlePeripheral& remote_peripheral, const std::string& service_id) { + NEARBY_LOG(INFO, + "G3 Ble Connect [self]: medium=%p, adapter=%p, peripheral=%p, " + "service_id=%s", + this, &GetAdapter(), &GetAdapter().GetPeripheral(), + service_id.c_str()); + // First, find an instance of remote medium, that exposed this peripheral. + auto& adapter = static_cast(remote_peripheral).GetAdapter(); + auto* medium = static_cast(adapter.GetBleMedium()); + + if (!medium) return {}; // Can't find medium. Bail out. + + BleServerSocket* remote_server_socket = nullptr; + NEARBY_LOG(INFO, + "G3 Ble Connect [peer]: medium=%p, adapter=%p, peripheral=%p, " + "service_id=%s", + medium, &adapter, &remote_peripheral, service_id.c_str()); + // Then, find our server socket context in this medium. + { + absl::MutexLock medium_lock(&medium->mutex_); + remote_server_socket = medium->server_socket_.get(); + if (remote_server_socket == nullptr) { + NEARBY_LOGS(ERROR) + << "G3 Ble Connect: Failed to find Ble Server socket: service_id=" + << service_id; + return {}; + } + } + + BlePeripheral peripheral = static_cast(remote_peripheral); + auto socket = std::make_unique(&peripheral); + // Finally, Request to connect to this socket. + if (!remote_server_socket->Connect(*socket)) { + NEARBY_LOGS(ERROR) << "G3 Ble Connect: Failed to connect to existing Ble " + "Server socket: service_id=" + << service_id; + return {}; + } + + NEARBY_LOG(INFO, "G3 Ble Connect: connected: socket=%p", socket.get()); + return socket; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h new file mode 100644 index 00000000..5ea80a55 --- /dev/null +++ b/cpp/platform_v2/impl/g3/ble.h @@ -0,0 +1,213 @@ +#ifndef PLATFORM_V2_IMPL_G3_BLE_H_ +#define PLATFORM_V2_IMPL_G3_BLE_H_ + +#include +#include + +#include "platform_v2/api/ble.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/bluetooth_adapter.h" +#include "platform_v2/impl/g3/bluetooth_classic.h" +#include "platform_v2/impl/g3/multi_thread_executor.h" +#include "platform_v2/impl/g3/pipe.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class BleMedium; + +class BleSocket : public api::BleSocket { + public: + BleSocket() = default; + explicit BleSocket(BlePeripheral* peripheral) : peripheral_(peripheral) {} + ~BleSocket() override; + + // Connect to another BleSocket, to form a functional low-level channel. + // from this point on, and until Close is called, connection exists. + void Connect(BleSocket& other) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns the InputStream of this connected BleSocket. + InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns the OutputStream of this connected BleSocket. + // This stream is for local side to write. + OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns address of a remote BleSocket or nullptr. + BleSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if socket is closed. + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns valid BlePeripheral pointer if there is a connection, and + // nullptr otherwise. + BlePeripheral* GetRemotePeripheral() 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_); + + // Returns InputStream of our side of a connection. + // This is what the remote side is supposed to read from. + // This is a helper for GetInputStream() method. + InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns OutputStream of our side of a connection. + // This is what the local size is supposed to write to. + // This is a helper for GetOutputStream() method. + OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // 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. + std::shared_ptr output_ {new Pipe}; + std::shared_ptr input_; + mutable absl::Mutex mutex_; + BlePeripheral* peripheral_; + BleSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +class BleServerSocket { + public: + ~BleServerSocket(); + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + // + // Called by the server side of a connection. + // Returns BleSocket to the server side. + // If not null, returned socket is connected to its remote (client-side) peer. + std::unique_ptr Accept(BlePeripheral* peripheral) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Blocks until either: + // - connection is available, or + // - server socket is closed, or + // - error happens. + // + // Called by the client side of a connection. + // Returns true, if socket is successfully connected. + bool Connect(BleSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_); + + // Called by the server side of a connection before passing ownership of + // BleServerSocker to user, to track validity of a pointer to this + // server socket, + void SetCloseNotifier(std::function notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + // Calls close_notifier if it was previously set, and marks socket as closed. + Exception Close() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + absl::Mutex mutex_; + absl::CondVar cond_; + absl::flat_hash_set pending_sockets_ ABSL_GUARDED_BY(mutex_); + std::function close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +// Container of operations that can be performed over the BLE medium. +class BleMedium : public api::BleMedium { + public: + explicit BleMedium(api::BluetoothAdapter& adapter); + ~BleMedium() override; + + // Returns true once the Ble advertising has been initiated. + bool StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool StopAdvertising(const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once the Ble scanning has been initiated. + bool StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once Ble scanning for service_id is well and truly + // stopped; after this returns, there must be no more invocations of the + // DiscoveredPeripheralCallback passed in to StartScanning() for service_id. + bool StopScanning(const std::string& service_id) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true once Ble 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_); + + // Connects to existing remote Ble peripheral. + // + // On success, returns a new BleSocket. + // On error, returns nullptr. + std::unique_ptr Connect( + api::BlePeripheral& remote_peripheral, + const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothAdapter& GetAdapter() { return *adapter_; } + + private: + static constexpr int kMaxConcurrentAcceptLoops = 5; + + struct AdvertisingInfo { + bool Empty() const { return service_id.empty(); } + void Clear() { service_id.clear(); } + + std::string service_id; + }; + + struct ScanningInfo { + bool Empty() const { return service_id.empty(); } + void Clear() { service_id.clear(); } + + std::string service_id; + }; + + absl::Mutex mutex_; + BluetoothAdapter* adapter_; // Our device adapter; read-only. + + // A thread pool dedicated to running all the accept loops from + // StartAdvertising(). + MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops}; + std::atomic_bool acceptance_thread_running_ = false; + + // A thread pool dedicated to wait to complete the accept_loops_runner_. + MultiThreadExecutor close_accept_loops_runner_{kMaxConcurrentAcceptLoops}; + + // A server socket is established when start advertising. + std::unique_ptr server_socket_; + AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); + ScanningInfo scanning_info_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLE_H_ diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc index 748513b7..877747ee 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -3,21 +3,57 @@ #include #include "platform_v2/base/medium_environment.h" +#include "platform_v2/base/prng.h" #include "platform_v2/impl/g3/bluetooth_classic.h" namespace location { namespace nearby { namespace g3 { +BlePeripheral::BlePeripheral(BluetoothAdapter* adapter) : adapter_(*adapter) {} + +std::string BlePeripheral::GetName() const { return adapter_.GetName(); } + +ByteArray BlePeripheral::GetAdvertisementBytes( + const std::string& service_id) const { + return advertisement_bytes_; +} + +void BlePeripheral::SetAdvertisementBytes( + const std::string& service_id, const ByteArray& advertisement_bytes) { + advertisement_bytes_ = advertisement_bytes; +} + BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) : adapter_(*adapter) {} std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } +std::string BluetoothDevice::GetMacAddress() const { + return adapter_.GetMacAddress(); +} + +BluetoothAdapter::BluetoothAdapter() { + std::string mac_address; + mac_address.resize(6); + int64_t raw_mac_addr = Prng().NextInt64(); + mac_address[0] = static_cast(raw_mac_addr >> 40); + mac_address[1] = static_cast(raw_mac_addr >> 32); + mac_address[2] = static_cast(raw_mac_addr >> 24); + mac_address[3] = static_cast(raw_mac_addr >> 16); + mac_address[4] = static_cast(raw_mac_addr >> 8); + mac_address[5] = static_cast(raw_mac_addr >> 0); + SetMacAddress(mac_address); +} BluetoothAdapter::~BluetoothAdapter() { SetStatus(Status::kDisabled); } -void BluetoothAdapter::SetMedium(api::BluetoothClassicMedium* medium) { - medium_ = medium; +void BluetoothAdapter::SetBluetoothClassicMedium( + api::BluetoothClassicMedium* medium) { + bluetooth_classic_medium_ = medium; +} + +void BluetoothAdapter::SetBleMedium(api::BleMedium* medium) { + ble_medium_ = medium; } bool BluetoothAdapter::SetStatus(Status status) { diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h index 8ce2b719..71220a9a 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.h +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -3,6 +3,7 @@ #include +#include "platform_v2/api/ble.h" #include "platform_v2/api/bluetooth_adapter.h" #include "platform_v2/api/bluetooth_classic.h" #include "platform_v2/impl/g3/single_thread_executor.h" @@ -17,6 +18,28 @@ namespace g3 { // BluetoothDevice and BluetoothAdapter have a mutual dependency. class BluetoothAdapter; +// Opaque wrapper over a Ble peripheral. Must contain enough data about a +// particular Ble device to connect to its GATT server. +class BlePeripheral : public api::BlePeripheral { + public: + ~BlePeripheral() override = default; + + std::string GetName() const override; + ByteArray GetAdvertisementBytes(const std::string& service_id) const override; + void SetAdvertisementBytes(const std::string& service_id, + const ByteArray& advertisement_bytes); + BluetoothAdapter& GetAdapter() { return adapter_; } + + private: + // Only BluetoothAdapter may instantiate BlePeripheral. + friend class BluetoothAdapter; + + explicit BlePeripheral(BluetoothAdapter* adapter); + + BluetoothAdapter& adapter_; + ByteArray advertisement_bytes_; +}; + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice : public api::BluetoothDevice { public: @@ -24,6 +47,7 @@ class BluetoothDevice : public api::BluetoothDevice { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() std::string GetName() const override; + std::string GetMacAddress() const override; BluetoothAdapter& GetAdapter() { return adapter_; } private: @@ -41,7 +65,7 @@ class BluetoothAdapter : public api::BluetoothAdapter { using Status = api::BluetoothAdapter::Status; using ScanMode = api::BluetoothAdapter::ScanMode; - explicit BluetoothAdapter() = default; + BluetoothAdapter(); ~BluetoothAdapter() override; // Synchronously sets the status of the BluetoothAdapter to 'status', and @@ -68,15 +92,30 @@ class BluetoothAdapter : public api::BluetoothAdapter { // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) bool SetName(absl::string_view name) override ABSL_LOCKS_EXCLUDED(mutex_); + // Returns BT MAC address assigned to this adapter. + std::string GetMacAddress() const override { return mac_address_; } + BluetoothDevice& GetDevice() { return device_; } - void SetMedium(api::BluetoothClassicMedium* medium); - api::BluetoothClassicMedium* GetMedium() { return medium_; } + void SetBluetoothClassicMedium(api::BluetoothClassicMedium* medium); + api::BluetoothClassicMedium* GetBluetoothClassicMedium() { + return bluetooth_classic_medium_; + } + + BlePeripheral& GetPeripheral() { return peripheral_; } + + void SetBleMedium(api::BleMedium* medium); + api::BleMedium* GetBleMedium() { return ble_medium_; } + + void SetMacAddress(std::string& mac_address) { mac_address_ = mac_address; } private: mutable absl::Mutex mutex_; BluetoothDevice device_{this}; - api::BluetoothClassicMedium* medium_ = nullptr; + BlePeripheral peripheral_{this}; + api::BluetoothClassicMedium* bluetooth_classic_medium_ = nullptr; + api::BleMedium* ble_medium_ = nullptr; + std::string mac_address_; ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; bool enabled_ ABSL_GUARDED_BY(mutex_) = false; diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc index f0226452..a0c040d3 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -34,9 +34,7 @@ bool BluetoothSocket::IsClosed() const { return closed_; } -bool BluetoothSocket::IsConnectedLocked() const { - return input_ != nullptr; -} +bool BluetoothSocket::IsConnectedLocked() const { return input_ != nullptr; } InputStream& BluetoothSocket::GetInputStream() { auto* remote_socket = GetRemoteSocket(); @@ -163,13 +161,13 @@ Exception BluetoothServerSocket::DoClose() { BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter) // TODO(apolyudov): implement and use downcast<> with static assertions. : adapter_(static_cast(&adapter)) { - adapter_->SetMedium(this); + adapter_->SetBluetoothClassicMedium(this); auto& env = MediumEnvironment::Instance(); env.RegisterBluetoothMedium(*this, GetAdapter()); } BluetoothClassicMedium::~BluetoothClassicMedium() { - adapter_->SetMedium(nullptr); + adapter_->SetBluetoothClassicMedium(nullptr); auto& env = MediumEnvironment::Instance(); env.UnregisterBluetoothMedium(*this); } @@ -193,7 +191,8 @@ std::unique_ptr BluetoothClassicMedium::ConnectToService( this, &GetAdapter(), &GetAdapter().GetDevice()); // First, find an instance of remote medium, that exposed this device. auto& adapter = static_cast(remote_device).GetAdapter(); - auto* medium = static_cast(adapter.GetMedium()); + auto* medium = + static_cast(adapter.GetBluetoothClassicMedium()); if (!medium) return {}; // Adapter is not bound to medium. Bail out. @@ -241,6 +240,12 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name, return socket; } +api::BluetoothDevice* BluetoothClassicMedium::FindRemoteDevice( + const std::string& mac_address) { + auto& env = MediumEnvironment::Instance(); + return env.FindBluetoothDevice(mac_address); +} + } // namespace g3 } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h index ede548b7..8d199863 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.h +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -82,7 +82,7 @@ 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. - std::shared_ptr output_ {new Pipe}; + std::shared_ptr output_{new Pipe}; std::shared_ptr input_; mutable absl::Mutex mutex_; BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. @@ -207,6 +207,9 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { const std::string& service_name, const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); + api::BluetoothDevice* FindRemoteDevice( + const std::string& mac_address) override; + private: absl::Mutex mutex_; BluetoothAdapter* adapter_; // Our device adapter; read-only. diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index 31d17c6a..8392e7d2 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -5,7 +5,6 @@ #include "platform_v2/api/atomic_boolean.h" #include "platform_v2/api/atomic_reference.h" -#include "platform_v2/api/ble.h" #include "platform_v2/api/ble_v2.h" #include "platform_v2/api/bluetooth_adapter.h" #include "platform_v2/api/bluetooth_classic.h" @@ -21,6 +20,7 @@ #include "platform_v2/base/medium_environment.h" #include "platform_v2/impl/g3/atomic_boolean.h" #include "platform_v2/impl/g3/atomic_reference.h" +#include "platform_v2/impl/g3/ble.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" @@ -112,7 +112,7 @@ ImplementationPlatform::CreateBluetoothClassicMedium( std::unique_ptr ImplementationPlatform::CreateBleMedium( api::BluetoothAdapter& adapter) { - return std::unique_ptr(); + return absl::make_unique(adapter); } std::unique_ptr ImplementationPlatform::CreateBleV2Medium( diff --git a/cpp/platform_v2/impl/g3/wifi_lan.cc b/cpp/platform_v2/impl/g3/wifi_lan.cc index e310c76d..9afb97d5 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.cc +++ b/cpp/platform_v2/impl/g3/wifi_lan.cc @@ -7,6 +7,7 @@ #include "platform_v2/api/wifi_lan.h" #include "platform_v2/base/logging.h" #include "platform_v2/base/medium_environment.h" +#include "platform_v2/base/prng.h" #include "absl/synchronization/mutex.h" namespace location { @@ -85,7 +86,8 @@ OutputStream& WifiLanSocket::GetLocalOutputStream() { return output_->GetOutputStream(); } -std::unique_ptr WifiLanServerSocket::Accept() { +std::unique_ptr WifiLanServerSocket::Accept( + WifiLanService* service) { absl::MutexLock lock(&mutex_); if (closed_) return {}; while (pending_sockets_.empty()) { @@ -96,7 +98,7 @@ std::unique_ptr WifiLanServerSocket::Accept() { auto* remote_socket = pending_sockets_.extract(pending_sockets_.begin()).value(); CHECK(remote_socket); - auto local_socket = std::make_unique(); + auto local_socket = std::make_unique(service); local_socket->Connect(*remote_socket); remote_socket->Connect(*local_socket); cond_.SignalAll(); @@ -155,6 +157,15 @@ Exception WifiLanServerSocket::DoClose() { WifiLanMedium::WifiLanMedium() { service_.SetMedium(this); + std::string ip_address; + ip_address.resize(4); + uint32_t raw_ip_addr = Prng().NextUint32(); + uint16_t port = Prng().NextUint32(); + ip_address[0] = static_cast(raw_ip_addr >> 24); + ip_address[1] = static_cast(raw_ip_addr >> 16); + ip_address[2] = static_cast(raw_ip_addr >> 8); + ip_address[3] = static_cast(raw_ip_addr >> 0); + service_.SetServiceAddress(ip_address, port); auto& env = MediumEnvironment::Instance(); env.RegisterWifiLanMedium(*this); } @@ -167,8 +178,7 @@ WifiLanMedium::~WifiLanMedium() { StopAdvertising(advertising_info_.service_id); StopDiscovery(discovering_info_.service_id); - NEARBY_LOG(INFO, - "WifiLanMedium dtor advertising_accept_thread_running_ = %d", + NEARBY_LOG(INFO, "WifiLanMedium dtor advertising_accept_thread_running_ = %d", acceptance_thread_running_.load()); // If acceptance thread is still running, wait to finish. if (acceptance_thread_running_) { @@ -186,6 +196,7 @@ bool WifiLanMedium::StartAdvertising(const std::string& service_id, "G3 WifiLan StartAdvertising: service_id=%s, service_info_name=%s", service_id.c_str(), service_info_name.c_str()); auto& env = MediumEnvironment::Instance(); + service_.SetName(service_info_name); env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, true); absl::MutexLock lock(&mutex_); @@ -196,10 +207,10 @@ bool WifiLanMedium::StartAdvertising(const std::string& service_id, accept_loops_runner_.Execute([&env, this, service_id]() mutable { if (!accept_loops_runner_.InShutdown()) { while (true) { - auto client_socket = server_socket_->Accept(); + auto client_socket = server_socket_->Accept(&service_); if (client_socket == nullptr) break; - env.CallWifiLanAcceptedConnectionCallback(*this, *client_socket, - service_id); + env.CallWifiLanAcceptedConnectionCallback( + *this, *(client_socket.release()), service_id); } } acceptance_thread_running_.exchange(false); @@ -227,8 +238,8 @@ bool WifiLanMedium::StopAdvertising(const std::string& service_id) { accept_loops_runner_.Shutdown(); if (server_socket_ == nullptr) { NEARBY_LOGS(ERROR) << "G3 WifiLan StopAdvertising: failed to find WifiLan " - "Server socket: service_id=" - << service_id; + "Server socket: service_id=" + << service_id; // Fall through for server socket not found. return true; } @@ -296,8 +307,11 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { std::unique_ptr WifiLanMedium::Connect( api::WifiLanService& remote_service, const std::string& service_id) { - NEARBY_LOG(INFO, "G3 WifiLan Connect: medium=%p, service=%p, service_id=%s", - this, &service_, service_id.c_str()); + NEARBY_LOG(INFO, + "G3 WifiLan Connect: medium=%p, service=%p, service_info_name=%s, " + "service_id=%s", + this, &service_, remote_service.GetName().c_str(), + service_id.c_str()); // First, find an instance of remote medium, that exposed this service. auto* medium = static_cast(remote_service).GetMedium(); @@ -305,8 +319,10 @@ std::unique_ptr WifiLanMedium::Connect( WifiLanServerSocket* remote_server_socket = nullptr; NEARBY_LOG(INFO, - "G3 WifiLan Connect [peer]: medium=%p, service=%p, service_id=%s", - medium, &remote_service, service_id.c_str()); + "G3 WifiLan Connect [peer]: medium=%p, service=%p, " + "service_info_name=%s, service_id=%s", + medium, &remote_service, remote_service.GetName().c_str(), + service_id.c_str()); // Then, find our server socket context in this medium. { absl::MutexLock medium_lock(&medium->mutex_); @@ -321,7 +337,8 @@ std::unique_ptr WifiLanMedium::Connect( } } - auto socket = std::make_unique(); + WifiLanService service = static_cast(remote_service); + auto socket = std::make_unique(&service); // Finally, Request to connect to this socket. if (!remote_server_socket->Connect(*socket)) { NEARBY_LOG(ERROR, @@ -335,6 +352,12 @@ std::unique_ptr WifiLanMedium::Connect( return socket; } +api::WifiLanService* WifiLanMedium::FindRemoteService( + const std::string& ip_address, int port) { + auto& env = MediumEnvironment::Instance(); + return env.FindWifiLanService(ip_address, port); +} + } // 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 index 7bc0e0dd..c6aa8292 100644 --- a/cpp/platform_v2/impl/g3/wifi_lan.h +++ b/cpp/platform_v2/impl/g3/wifi_lan.h @@ -3,6 +3,7 @@ #include #include +#include #include "platform_v2/api/wifi_lan.h" #include "platform_v2/base/byte_array.h" @@ -32,13 +33,23 @@ class WifiLanService : public api::WifiLanService { service_info_name_ = std::move(service_info_name); } std::string GetName() const override { return service_info_name_; } + std::pair GetServiceAddress() const override { + return std::make_pair(ip_address_, port_); + } void SetMedium(WifiLanMedium* medium) { medium_ = medium; } WifiLanMedium* GetMedium() { return medium_; } + void SetServiceAddress(const std::string& ip_address, int port) { + ip_address_ = ip_address; + port_ = port; + } + private: std::string service_info_name_; WifiLanMedium* medium_ = nullptr; + std::string ip_address_; + int port_; }; class WifiLanSocket : public api::WifiLanSocket { @@ -94,7 +105,7 @@ class WifiLanSocket : public api::WifiLanSocket { // 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. - std::shared_ptr output_ {new Pipe}; + std::shared_ptr output_{new Pipe}; std::shared_ptr input_; mutable absl::Mutex mutex_; WifiLanService* service_; @@ -116,7 +127,8 @@ class WifiLanServerSocket { // Called by the server side of a connection. // Returns WifiLanSocket to the server side. // If not null, returned socket is connected to its remote (client-side) peer. - std::unique_ptr Accept() ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr Accept(WifiLanService* service) + ABSL_LOCKS_EXCLUDED(mutex_); // Blocks until either: // - connection is available, or @@ -186,6 +198,9 @@ class WifiLanMedium : public api::WifiLanMedium { api::WifiLanService& remote_service, const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); + api::WifiLanService* FindRemoteService(const std::string& ip_address, + int port) override; + private: static constexpr int kMaxConcurrentAcceptLoops = 5; diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD index 9787bf31..87115901 100644 --- a/cpp/platform_v2/impl/shared/BUILD +++ b/cpp/platform_v2/impl/shared/BUILD @@ -20,9 +20,7 @@ cc_library( hdrs = [ "posix_condition_variable.h", ], - visibility = [ - "//platform_v2/impl:__subpackages__", - ], + visibility = ["//visibility:private"], deps = [ ":posix_mutex", "//platform_v2/api:types", diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 59902e1e..6bd9ad66 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -45,10 +45,12 @@ cc_library( cc_library( name = "comm", srcs = [ + "ble.cc", "bluetooth_classic.cc", "wifi_lan.cc", ], hdrs = [ + "ble.h", "bluetooth_adapter.h", "bluetooth_classic.h", "webrtc.h", @@ -91,6 +93,7 @@ cc_test( srcs = [ "atomic_boolean_test.cc", "atomic_reference_test.cc", + "ble_test.cc", "bluetooth_adapter_test.cc", "bluetooth_classic_test.cc", "cancelable_alarm_test.cc", @@ -115,6 +118,7 @@ cc_test( "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep "//testing/base/public:gunit_main", + "//absl/strings", "//absl/synchronization", "//absl/time", ], diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc new file mode 100644 index 00000000..7161eb7e --- /dev/null +++ b/cpp/platform_v2/public/ble.cc @@ -0,0 +1,127 @@ +#include "platform_v2/public/ble.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +bool BleMedium::StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes) { + return impl_->StartAdvertising(service_id, advertisement_bytes); +} + +bool BleMedium::StopAdvertising(const std::string& service_id) { + return impl_->StopAdvertising(service_id); +} + +bool BleMedium::StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback) { + { + MutexLock lock(&mutex_); + discovered_peripheral_callback_ = std::move(callback); + peripherals_.clear(); + } + return impl_->StartScanning( + service_id, + { + .peripheral_discovered_cb = + [this](api::BlePeripheral& peripheral, + const std::string& service_id) { + MutexLock lock(&mutex_); + auto pair = peripherals_.emplace( + &peripheral, absl::make_unique()); + auto& context = *pair.first->second; + if (!pair.second) { + NEARBY_LOG(INFO, + "Discovering (again) peripheral=%p, impl=%p, " + "peripheral name=%s", + &context.peripheral, &peripheral, + peripheral.GetName().c_str()); + } else { + context.peripheral = BlePeripheral(&peripheral); + NEARBY_LOG(INFO, + "Discovering peripheral=%p, impl=%p, " + "peripheral name=%s", + &context.peripheral, &peripheral, + peripheral.GetName().c_str()); + discovered_peripheral_callback_.peripheral_discovered_cb( + context.peripheral, service_id); + } + }, + .peripheral_lost_cb = + [this](api::BlePeripheral& peripheral, + const std::string& service_id) { + MutexLock lock(&mutex_); + if (peripherals_.empty()) return; + auto context = peripherals_.find(&peripheral); + if (context == peripherals_.end()) return; + NEARBY_LOG(INFO, "Removing peripheral=%p, impl=%p", + &(context->second->peripheral), &peripheral); + discovered_peripheral_callback_.peripheral_lost_cb( + context->second->peripheral, service_id); + }, + }); +} + +bool BleMedium::StopScanning(const std::string& service_id) { + { + MutexLock lock(&mutex_); + discovered_peripheral_callback_ = {}; + peripherals_.clear(); + NEARBY_LOG(INFO, "Ble Scanning disabled: impl=%p", &GetImpl()); + } + return impl_->StopScanning(service_id); +} + +bool BleMedium::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::BleSocket& 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, "Accepting (again) socket=%p, impl=%p", + &context.socket, &socket); + } else { + context.socket = BleSocket(&socket); + NEARBY_LOG(INFO, "Accepting socket=%p, impl=%p", + &context.socket, &socket); + } + accepted_connection_callback_.accepted_cb(context.socket, + service_id); + }, + }); +} + +bool BleMedium::StopAcceptingConnections(const std::string& service_id) { + { + MutexLock lock(&mutex_); + accepted_connection_callback_ = {}; + sockets_.clear(); + NEARBY_LOG(INFO, "Ble accepted connection disabled: impl=%p", &GetImpl()); + } + return impl_->StopAcceptingConnections(service_id); +} + +BleSocket BleMedium::Connect(BlePeripheral& peripheral, + const std::string& service_id) { + { + MutexLock lock(&mutex_); + NEARBY_LOG(INFO, "BleMedium::Connect: peripheral=%p [impl=%p]", &peripheral, + &peripheral.GetImpl()); + } + return BleSocket(impl_->Connect(peripheral.GetImpl(), service_id)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h new file mode 100644 index 00000000..5cb89f08 --- /dev/null +++ b/cpp/platform_v2/public/ble.h @@ -0,0 +1,146 @@ +#ifndef PLATFORM_V2_PUBLIC_BLE_H_ +#define PLATFORM_V2_PUBLIC_BLE_H_ + +#include "platform_v2/api/ble.h" +#include "platform_v2/api/platform.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/bluetooth_adapter.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { + +class BleSocket final { + public: + BleSocket() = default; + BleSocket(const BleSocket&) = default; + BleSocket& operator=(const BleSocket&) = default; + explicit BleSocket(api::BleSocket* socket) : impl_(socket) {} + explicit BleSocket(std::unique_ptr socket) + : impl_(socket.release()) {} + ~BleSocket() = default; + + // Returns the InputStream of the BleSocket. + // 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 BleSocket object is destroyed. + InputStream& GetInputStream() { return impl_->GetInputStream(); } + + // Returns the OutputStream of the BleSocket. + // 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 BleSocket object is destroyed. + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + BlePeripheral GetRemotePeripheral() { + return BlePeripheral(impl_->GetRemotePeripheral()); + } + + // 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 BleMedium::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 BleSocket object is + // itself valid. Typically BleSocket lifetime matches duration of the + // connection, and is controlled by end user, since they hold the instance. + api::BleSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// Container of operations that can be performed over the BLE medium. +class BleMedium final { + public: + using Platform = api::ImplementationPlatform; + struct DiscoveredPeripheralCallback { + std::function + peripheral_discovered_cb = + DefaultCallback(); + std::function + peripheral_lost_cb = + DefaultCallback(); + }; + struct ScanningInfo { + BlePeripheral peripheral; + }; + + struct AcceptedConnectionCallback { + std::function + accepted_cb = DefaultCallback(); + }; + struct AcceptedConnectionInfo { + BleSocket socket; + }; + + explicit BleMedium(BluetoothAdapter& adapter) + : impl_(Platform::CreateBleMedium(adapter.GetImpl())), + adapter_(adapter) {} + ~BleMedium() = default; + + // Returns true once the BLE advertising has been initiated. + bool StartAdvertising(const std::string& service_id, + const ByteArray& advertisement_bytes); + bool StopAdvertising(const std::string& service_id); + + // Returns true once the BLE scan has been initiated. + bool StartScanning(const std::string& service_id, + DiscoveredPeripheralCallback callback); + + // Returns true once BLE scanning for service_id is well and truly stopped; + // after this returns, there must be no more invocations of the + // DiscoveredPeripheralCallback passed in to StartScanning() for service_id. + bool StopScanning(const std::string& service_id); + + // Returns true once BLE 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 BleSocket. On Success, BleSocket::IsValid() + // returns true. + BleSocket Connect(BlePeripheral& peripheral, const std::string& service_id); + + bool IsValid() const { return impl_ != nullptr; } + + api::BleMedium& GetImpl() { return *impl_; } + BluetoothAdapter& GetAdapter() { return adapter_; } + + private: + Mutex mutex_; + std::unique_ptr impl_; + BluetoothAdapter& adapter_; + absl::flat_hash_map> + peripherals_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map> + sockets_ ABSL_GUARDED_BY(mutex_); + DiscoveredPeripheralCallback discovered_peripheral_callback_ + ABSL_GUARDED_BY(mutex_); + AcceptedConnectionCallback accepted_connection_callback_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLE_H_ diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc new file mode 100644 index 00000000..d1fcf653 --- /dev/null +++ b/cpp/platform_v2/public/ble_test.cc @@ -0,0 +1,189 @@ +#include "platform_v2/public/ble.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::Duration kWaitDuration = absl::Milliseconds(1000); +constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; +constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; + +class BleMediumTest : public ::testing::Test { + protected: + using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback; + using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback; + + BleMediumTest() { env_.Stop(); } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; +}; + +TEST_F(BleMediumTest, ConstructorDestructorWorks) { + env_.Start(); + BluetoothAdapter adapter_a_; + BluetoothAdapter adapter_b_; + BleMedium ble_a{adapter_a_}; + BleMedium ble_b{adapter_b_}; + + // Make sure we can create functional mediums. + ASSERT_TRUE(ble_a.IsValid()); + ASSERT_TRUE(ble_b.IsValid()); + + // Make sure we can create 2 distinct mediums. + EXPECT_NE(&ble_a.GetImpl(), &ble_b.GetImpl()); + env_.Stop(); +} + +TEST_F(BleMediumTest, CanStartAdvertising) { + env_.Start(); + BluetoothAdapter adapter_a_; + BluetoothAdapter adapter_b_; + BleMedium ble_a{adapter_a_}; + BleMedium ble_b{adapter_b_}; + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + + ble_a.StartAdvertising(service_id, advertisement_bytes); + + EXPECT_TRUE(ble_b.StartScanning( + service_id, DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch](BlePeripheral& peripheral, + const std::string& service_id) { + found_latch.CountDown(); + }, + })); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_a.StopAdvertising(service_id)); + EXPECT_TRUE(ble_b.StopScanning(service_id)); + env_.Stop(); +} + +TEST_F(BleMediumTest, CanStartScanning) { + env_.Start(); + BluetoothAdapter adapter_a_; + BluetoothAdapter adapter_b_; + BleMedium ble_a{adapter_a_}; + BleMedium ble_b{adapter_b_}; + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + + ble_a.StartScanning(service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch](BlePeripheral& peripheral, + const std::string& service_id) { + found_latch.CountDown(); + }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + }); + EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_b.StopAdvertising(service_id)); + EXPECT_TRUE(lost_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_a.StopScanning(service_id)); + env_.Stop(); +} + +TEST_F(BleMediumTest, CanStopDiscovery) { + env_.Start(); + BluetoothAdapter adapter_a_; + BluetoothAdapter adapter_b_; + BleMedium ble_a{adapter_a_}; + BleMedium ble_b{adapter_b_}; + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + + ble_a.StartScanning(service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch](BlePeripheral& peripheral, + const std::string& service_id) { + found_latch.CountDown(); + }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + }); + EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(ble_a.StopScanning(service_id)); + EXPECT_TRUE(ble_b.StopAdvertising(service_id)); + EXPECT_FALSE(lost_latch.Await(kWaitDuration).result()); + env_.Stop(); +} + +TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { + env_.Start(); + BluetoothAdapter adapter_a_; + BluetoothAdapter adapter_b_; + BleMedium ble_a{adapter_a_}; + BleMedium ble_b{adapter_b_}; + std::string service_id(kServiceID); + ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + CountDownLatch found_latch(1); + CountDownLatch accepted_latch(1); + + BlePeripheral* discovered_peripheral = nullptr; + ble_a.StartScanning( + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch, &discovered_peripheral]( + BlePeripheral& peripheral, const std::string& service_id) { + NEARBY_LOG(INFO, "Peripheral discovered: %s, %p", + peripheral.GetName().c_str(), &peripheral); + discovered_peripheral = &peripheral; + found_latch.CountDown(); + }, + }); + ble_b.StartAdvertising(service_id, advertisement_bytes); + ble_b.StartAcceptingConnections( + service_id, + AcceptedConnectionCallback{ + .accepted_cb = [&accepted_latch](BleSocket socket, + const std::string& service_id) { + NEARBY_LOG(INFO, "Connection accepted: socket=%p, service_id=%s", + &socket, service_id.c_str()); + accepted_latch.CountDown(); + }}); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + + BleSocket socket_a; + EXPECT_FALSE(socket_a.IsValid()); + { + SingleThreadExecutor client_executor; + client_executor.Execute( + [&ble_a, &socket_a, discovered_peripheral, &service_id]() { + socket_a = ble_a.Connect(*discovered_peripheral, service_id); + }); + } + EXPECT_TRUE(accepted_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(socket_a.IsValid()); + ble_b.StopAdvertising(service_id); + ble_a.StopScanning(service_id); + env_.Stop(); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h index beaaf4d3..1baa6751 100644 --- a/cpp/platform_v2/public/bluetooth_adapter.h +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -11,6 +11,29 @@ namespace location { namespace nearby { +// Opaque wrapper over a BLE peripheral. Must contain enough data about a +// particular BLE peripheral to connect to its GATT server. +class BlePeripheral final { + public: + BlePeripheral() = default; + BlePeripheral(const BlePeripheral&) = default; + BlePeripheral& operator=(const BlePeripheral&) = default; + explicit BlePeripheral(api::BlePeripheral* peripheral) : impl_(peripheral) {} + ~BlePeripheral() = default; + + std::string GetName() const { return impl_->GetName(); } + + ByteArray GetAdvertisementBytes(const std::string& service_id) const { + return impl_->GetAdvertisementBytes(service_id); + } + + api::BlePeripheral& GetImpl() { return *impl_; } + bool IsValid() const { return impl_ != nullptr; } + + private: + api::BlePeripheral* impl_; +}; + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice final { public: diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h index 459d74b1..420e0684 100644 --- a/cpp/platform_v2/public/bluetooth_classic.h +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -187,6 +187,9 @@ class BluetoothClassicMedium final { api::BluetoothClassicMedium& GetImpl() { return *impl_; } BluetoothAdapter& GetAdapter() { return adapter_; } + BluetoothDevice FindRemoteDevice(const std::string& mac_address) { + return BluetoothDevice(impl_->FindRemoteDevice(mac_address)); + } private: Mutex mutex_; diff --git a/cpp/platform_v2/public/wifi_lan.cc b/cpp/platform_v2/public/wifi_lan.cc index f5882f7e..9a1e0240 100644 --- a/cpp/platform_v2/public/wifi_lan.cc +++ b/cpp/platform_v2/public/wifi_lan.cc @@ -6,9 +6,8 @@ namespace location { namespace nearby { -bool WifiLanMedium::StartAdvertising( - const std::string& service_id, - const std::string& service_info_name) { +bool WifiLanMedium::StartAdvertising(const std::string& service_id, + const std::string& service_info_name) { return impl_->StartAdvertising(service_id, service_info_name); } @@ -39,13 +38,13 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, "service_info_name=%s", &context.service, &service, service.GetName().c_str()); - return; + } else { + context.service = WifiLanService(&service); + NEARBY_LOG( + INFO, + "Discovering service=%p, impl=%p, service_info_name=%s", + &context.service, &service, service.GetName().c_str()); } - context.service = WifiLanService(&service); - NEARBY_LOG( - INFO, - "Discovering service=%p, impl=%p, service_info_name=%s", - &context.service, &service, service.GetName().c_str()); discovered_service_callback_.service_discovered_cb( context.service, service_id); }, @@ -54,12 +53,12 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id, const std::string& service_id) { MutexLock lock(&mutex_); if (services_.empty()) return; - auto item = services_.extract(&service); - auto& context = *item.mapped(); + auto context = services_.find(&service); + if (context == services_.end()) return; NEARBY_LOG(INFO, "Removing service=%p, impl=%p", - &context.service, &service); - discovered_service_callback_.service_lost_cb(context.service, - service_id); + &(context->second->service), &service); + discovered_service_callback_.service_lost_cb( + context->second->service, service_id); }, }); } @@ -93,8 +92,8 @@ bool WifiLanMedium::StartAcceptingConnections( if (!pair.second) { NEARBY_LOG(INFO, "Accepting (again) socket=%p, impl=%p", &context.socket, &socket); - context.socket = WifiLanSocket(&socket); } else { + context.socket = WifiLanSocket(&socket); NEARBY_LOG(INFO, "Accepting socket=%p, impl=%p", &context.socket, &socket); } @@ -117,10 +116,17 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) { WifiLanSocket WifiLanMedium::Connect(WifiLanService& service, const std::string& service_id) { - NEARBY_LOG(INFO, "WifiLanMedium::Connect: service=%p [impl=%p]", &service, - &service.GetImpl()); + NEARBY_LOG( + INFO, + "WifiLanMedium::Connect: service=%p [impl=%p, service_info_name=%s]", + &service, &service.GetImpl(), service.GetName().c_str()); return WifiLanSocket(impl_->Connect(service.GetImpl(), service_id)); } +WifiLanService WifiLanMedium::FindRemoteService(const std::string& ip_address, + int port) { + return WifiLanService(impl_->FindRemoteService(ip_address, port)); +} + } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/public/wifi_lan.h b/cpp/platform_v2/public/wifi_lan.h index c94ac1b8..fa6ba565 100644 --- a/cpp/platform_v2/public/wifi_lan.h +++ b/cpp/platform_v2/public/wifi_lan.h @@ -140,6 +140,8 @@ class WifiLanMedium final { api::WifiLanMedium& GetImpl() { return *impl_; } + WifiLanService FindRemoteService(const std::string& ip_address, int port); + private: Mutex mutex_; std::unique_ptr impl_; diff --git a/cpp/platform_v2/public/wifi_lan_test.cc b/cpp/platform_v2/public/wifi_lan_test.cc index 8a701efa..2e89e09f 100644 --- a/cpp/platform_v2/public/wifi_lan_test.cc +++ b/cpp/platform_v2/public/wifi_lan_test.cc @@ -7,13 +7,14 @@ #include "platform_v2/public/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { namespace { constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; -constexpr absl::string_view kServiceName{"service name"}; +constexpr absl::string_view kServiceInfoName{"Simulated service info name"}; class WifiLanMediumTest : public ::testing::Test { protected: @@ -44,10 +45,10 @@ TEST_F(WifiLanMediumTest, CanStartAdvertising) { WifiLanMedium wifi_a; WifiLanMedium wifi_b; std::string service_id(kServiceID); - std::string service_name{kServiceName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); - wifi_a.StartAdvertising(service_id, service_name); + wifi_a.StartAdvertising(service_id, service_info_name); EXPECT_TRUE(wifi_b.StartDiscovery( service_id, DiscoveredServiceCallback{ @@ -68,7 +69,7 @@ TEST_F(WifiLanMediumTest, CanStartDiscovery) { WifiLanMedium wifi_a; WifiLanMedium wifi_b; std::string service_id(kServiceID); - std::string service_name{kServiceName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); CountDownLatch lost_latch(1); @@ -76,16 +77,16 @@ TEST_F(WifiLanMediumTest, CanStartDiscovery) { DiscoveredServiceCallback{ .service_discovered_cb = [&found_latch](WifiLanService& service, - const std::string& service_id) { + absl::string_view service_id) { found_latch.CountDown(); }, .service_lost_cb = [&lost_latch](WifiLanService& service, - const std::string& service_id) { + absl::string_view service_id) { lost_latch.CountDown(); }, }); - EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_name)); + EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_info_name)); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); EXPECT_TRUE(wifi_b.StopAdvertising(service_id)); EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result()); @@ -98,7 +99,7 @@ TEST_F(WifiLanMediumTest, CanStopDiscovery) { WifiLanMedium wifi_a; WifiLanMedium wifi_b; std::string service_id(kServiceID); - std::string service_name{kServiceName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); CountDownLatch lost_latch(1); @@ -106,16 +107,16 @@ TEST_F(WifiLanMediumTest, CanStopDiscovery) { DiscoveredServiceCallback{ .service_discovered_cb = [&found_latch](WifiLanService& service, - const std::string& service_id) { + absl::string_view service_id) { found_latch.CountDown(); }, .service_lost_cb = [&lost_latch](WifiLanService& service, - const std::string& service_id) { + absl::string_view service_id) { lost_latch.CountDown(); }, }); - EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_name)); + EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_info_name)); EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); EXPECT_TRUE(wifi_a.StopDiscovery(service_id)); EXPECT_TRUE(wifi_b.StopAdvertising(service_id)); @@ -128,7 +129,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) { WifiLanMedium wifi_a; WifiLanMedium wifi_b; std::string service_id(kServiceID); - std::string service_name{kServiceName}; + std::string service_info_name{kServiceInfoName}; CountDownLatch found_latch(1); CountDownLatch accepted_latch(1); @@ -145,7 +146,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) { found_latch.CountDown(); }, }); - wifi_b.StartAdvertising(service_id, service_name); + wifi_b.StartAdvertising(service_id, service_info_name); wifi_b.StartAcceptingConnections( service_id, AcceptedConnectionCallback{ @@ -168,6 +169,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) { } EXPECT_TRUE(accepted_latch.Await(absl::Milliseconds(1000)).result()); EXPECT_TRUE(socket_a.IsValid()); + wifi_b.StopAcceptingConnections(service_id); wifi_b.StopAdvertising(service_id); wifi_a.StopDiscovery(service_id); env_.Stop(); diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index 7bce87eb..c7169827 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -193,6 +193,11 @@ message BandwidthUpgradeNegotiationFrame { optional int32 frequency = 4; } + // Accompanies Medium.WEB_RTC + message WebRtcCredentials { + optional string peer_id = 1; + } + optional Medium medium = 1; // Exactly one of the following fields will be set. @@ -201,6 +206,7 @@ message BandwidthUpgradeNegotiationFrame { optional BluetoothCredentials bluetooth_credentials = 4; optional WifiAwareCredentials wifi_aware_credentials = 5; optional WifiDirectCredentials wifi_direct_credentials = 6; + optional WebRtcCredentials web_rtc_credentials = 8; // Disable Encryption for this upgrade medium to improve throughput. optional bool supports_disabling_encryption = 7; diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 99729d7f..3fd7dc8b 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -162,7 +162,7 @@ enum PayloadStatus { REMOTE_CANCELLATION = 8; } -// next_id: 16 +// next_id: 17 // Result of an upgrade attempt. enum BandwidthUpgradeResult { UNKNOWN_BANDWIDTH_UPGRADE_RESULT = 0; @@ -208,9 +208,12 @@ enum BandwidthUpgradeResult { // Error during setting up WIFI Direct. WIFI_DIRECT_MEDIUM_ERROR = 15; + + // Error during setting up WebRTC. + WEB_RTC_MEDIUM_ERROR = 16; } -// next_id: 34 +// next_id: 35 // The stage at which an error occurred. enum BandwidthUpgradeErrorStage { UNKNOWN_BANDWIDTH_UPGRADE_ERROR_STAGE = 0; @@ -293,4 +296,6 @@ enum BandwidthUpgradeErrorStage { // WEB_RTC // Creating the WEB_RTC EndpointChannel WEB_RTC_SOCKET_CREATION = 33; + // On the incoming side, listening for incoming WebRTC connections. + WEB_RTC_LISTEN_INCOMING = 34; } diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 0464f602..0eeb5080 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -83,8 +83,12 @@ enum CommonError { // Others error, the error happens when user cancel the flow, it's not a // real failure. FLOW_CANCELED = 5; + // Developing error, an unexpect call that the medium not ready, need to do + // something before this call. e.g. call WifiAwareImpli#connectToSocket but + // never join network before this call. + UNEXPECTED_CALL = 6; - // Reserved 5 to 30 + // Reserved 7 to 30 } // The error for event START_ADVERTISING. The range between 31 and 99. @@ -161,7 +165,27 @@ enum StartDiscoveringError { // System error, failed to acquire WifiAwareSession ACQUIRE_WIFI_AWARE_SESSION_FOR_DISCOVERING_FAILED = 40; - // Next ID :40 + // Next ID :41 +} + +// The error for event START_LISTENING_INCOMING_CONNECTION. The range between 31 +// and 99. +enum StartListeningIncomingConnectionError { + // Developing error, this service ID already requested, should not request it + // again without stop accepting. + DUPLICATE_ACCEPTING_CONNECTION_REQUESTED = 31; + // System error, failed to open a GATT server for listening incoming GATT + // connection. + OPEN_GATT_SERVER_FAILED = 32; + // System error, failed to accept the incoming GATT connection + ACCEPT_GATT_CONNECTION_FAILED = 33; + // System error, failed to accept the incoming L2CAP connection + ACCEPT_L2CAP_CONNECTION_FAILED = 34; + // Network error, wait the GATT connection ready after the connection + // established but never. + CREATE_GATT_SERVER_SOCKET_NOT_READY = 35; + + // Next ID :36 } // The error for event CONNECT. The range between 31 and 99. @@ -194,10 +218,22 @@ enum ConnectError { // Network error, failed to change connection for data transferring on GATT // connection. GATT_SWITCH_TO_DATA_TRANSFERRING_FAILED = 39; + // System error, failed to establish connection + ESTABLISH_CONNECTION_FAILED = 40; + // Developing error, this connection already established, should not request + // it again. + DUPLICATE_CONNECTION_REQUESTED = 41; + // Network error, the connection lost. + CONNECTION_LOST = 42; + // Network error, failed to connect to the network. e.g. an aware network, + // hotspot or a direct network. + CONNECT_TO_NETWORK_FAILED = 43; + + // Next ID :44 } enum Description { - reserved 28; + reserved 28, 29; UNKNOWN = 0; NULL_SERVICE_ID = 1; @@ -227,7 +263,6 @@ enum Description { 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; @@ -271,4 +306,64 @@ enum Description { SET_CONNECTION_PRIORITY_INTERRUPTED = 70; UNKNOWN_IO_EXCEPTION = 71; READ_CHARACTERISTIC_FAILED = 72; + WIFI_HOTSPOT_ENABLED = 73; + AWARE_UNAVAILABLE = 74; + IN_BLACK_LIST = 75; + FEATURE_WIFI_NOT_SUPPORTED = 76; + NULL_WIFI_MANAGER = 77; + SOCKET_CLOSED = 78; + SOCKET_ALREADY_CONNECTED = 79; + NFC_TECH_NOT_SUPPORTED = 80; + NFC_SERVICE_DIED = 81; + BIND_NFC_SERVICE_FAILED = 82; + NFC_CREATE_SOCKET_FAILED = 83; + NULL_WIFI_AWARE_PEER = 84; + NETWORK_ALREADY_JOINED = 85; + JOIN_AWARE_NETWORK_CANCELLED = 86; + NETWORK_UNAVAILABLE = 87; + WITHOUT_ACTIVE_AWARE_NETWORK = 88; + WITHOUT_JOINED_AWARE_NETWORK = 89; + CONNET_TO_SOCKET_CANCELLED = 90; + NULL_SSID = 91; + NULL_PASSWORD = 92; + FEATURE_WIFI_DIRECT_NOT_SUPPORTED = 93; + NULL_WIFI_P2P_MANAGER = 94; + P2P_GROUP_FORMED = 95; + ACQUIRE_P2P_CHANNEL_FAILED = 96; + P2P_UNSUPPORTED = 97; + INTERNAL_ERROR = 98; + BUSY = 99; + REFLECTION_ERROR = 100; + NETWORK_ERROR_EHOSTUNREACH = 101; + NETWORK_ERROR_ENETUNREACH = 102; + ADD_NETWORK_FAILED = 103; + UPDATE_NETWORK_FAILED = 104; + ALREADY_IN_PROGRESS = 105; + INVALID_ARGS = 106; + NOT_AUTHORIZED = 107; + INVALID_NETWORK_ID = 108; + WIFI_MANAGER_ENABLE_NETWORK_FAILED = 109; + WIFI_MANAGER_RECONNECT_FAILED = 110; + WITHOUT_ACTIVE_NETWORK = 111; + WEBRTC_CONNECTION_FLOW_EXIST = 112; + NULL_DROID_GUARD_RESULT = 113; + TACHYON_SIGNALING_MESSENGER_EXIST = 114; + TACHYON_ALREADY_START_RECEIVE_MESSAGE = 115; + TACHYON_RECEIVE_MESSAGE_FAILED = 116; + TACHYON_RECEIVE_MESSAGE_INTERRUPTED = 117; + TACHYON_RECEIVE_MESSAGE_EXECUTION_EXCEPTION = 118; + TACHYON_RECEIVE_MESSAGE_TIMEOUT = 119; + TACHYON_RECEIVE_MESSAGE_AUTH_EXCEPTION = 120; + TACHYON_RECEIVE_MESSAGE_STATUS_EXCEPTION = 121; + TACHYON_SEND_MESSAGE_AUTH_EXCEPTION = 122; + TACHYON_SEND_MESSAGE_STATUS_EXCEPTION = 123; + TACHYON_GET_ICE_SERVER_AUTH_EXCEPTION = 124; + TACHYON_GET_ICE_SERVER_STATUS_EXCEPTION = 125; + EMPTY_TACHYON_ICE_SERVER = 126; + POTENTIAL_WEBRTC_LIB_LOADING_FAILURE = 127; + UNEXPECTED_GATT_DESCRIPTOR = 128; + FAIL_TO_RECEIVE_L2CAP_PACKET = 129; + WITHOUT_PSM_VALUE = 130; + SOCKET_BIND_LISTEN_FAILED = 131; + UNEXPECTED_PACKET_CONTENT = 132; } diff --git a/proto/magic_pair_enums.proto b/proto/magic_pair_enums.proto index 506c116a..8cd616ec 100644 --- a/proto/magic_pair_enums.proto +++ b/proto/magic_pair_enums.proto @@ -44,6 +44,9 @@ message MagicPairEvent { // Parsing something (e.g. BR/EDR Handover data) failed. PARSE_EXCEPTION = 6; + + // A failure at MDH. + MDH_REMOTE_EXCEPTION = 7; } enum BrEdrHandoverErrorCode { diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index 962d8e82..442d2d4b 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -138,6 +138,12 @@ enum EventType { // Receiver removes quick settings tile. REMOVE_QUICK_SETTINGS_TILE = 37; + + // Receiver phone consent clicked. + LAUNCH_PHONE_CONSENT = 38; + + // Receiver taps quick settings tile. + TAP_QUICK_SETTINGS_TILE = 39; } // Event category to differentiate whether this comes from sender or receiver, @@ -181,6 +187,19 @@ enum AttachmentTransmissionStatus { COMPLETE_ATTACHMENT_TRANSMISSION_STATUS = 1; CANCELED_ATTACHMENT_TRANSMISSION_STATUS = 2; FAILED_ATTACHMENT_TRANSMISSION_STATUS = 3; + REJECTED_ATTACHMENT = 4; + TIMED_OUT_ATTACHMENT = 5; + AWAITING_REMOTE_ACCEPTANCE_FAILED_ATTACHMENT = 6; + NOT_ENOUGH_SPACE_ATTACHMENT = 7; + FAILED_NO_TRANSFER_UPDATE_CALLBACK = 8; + MEDIA_UNAVAILABLE_ATTACHMENT = 9; + UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT = 10; + NO_ATTACHMENT_FOUND = 11; + FAILED_NO_SHARE_TARGET_ENDPOINT = 12; + FAILED_PAIRED_KEYHANDSHAKE = 13; + FAILED_NULL_CONNECTION = 14; + FAILED_NO_PAYLOAD = 15; + FAILED_WRITE_INTRODUCTION = 16; } // The status of advertising and discovering sessions. Used by From 2d564a5539e69b6df232d2baf5621e3b3b3c31cd Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 25 Aug 2020 11:36:27 -0700 Subject: [PATCH 41/52] OSS fixes Signed-off-by: Alexey Polyudov Change-Id: I7589a4099e17cf812494d9bf758b479eaffff329 --- cpp/core_v2/internal/ble_endpoint_channel.cc | 14 ++++++++++++++ cpp/core_v2/internal/ble_endpoint_channel.h | 14 ++++++++++++++ cpp/core_v2/internal/mediums/ble.cc | 14 ++++++++++++++ cpp/core_v2/internal/mediums/ble.h | 14 ++++++++++++++ cpp/core_v2/internal/mediums/ble_test.cc | 14 ++++++++++++++ cpp/platform_v2/base/bluetooth_utils.cc | 14 ++++++++++++++ cpp/platform_v2/base/bluetooth_utils.h | 14 ++++++++++++++ cpp/platform_v2/base/bluetooth_utils_test.cc | 14 ++++++++++++++ cpp/platform_v2/impl/g3/ble.cc | 14 ++++++++++++++ cpp/platform_v2/impl/g3/ble.h | 14 ++++++++++++++ cpp/platform_v2/public/ble.cc | 14 ++++++++++++++ cpp/platform_v2/public/ble.h | 14 ++++++++++++++ cpp/platform_v2/public/ble_test.cc | 14 ++++++++++++++ 13 files changed, 182 insertions(+) diff --git a/cpp/core_v2/internal/ble_endpoint_channel.cc b/cpp/core_v2/internal/ble_endpoint_channel.cc index aba332d0..f6b8d683 100644 --- a/cpp/core_v2/internal/ble_endpoint_channel.cc +++ b/cpp/core_v2/internal/ble_endpoint_channel.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/ble_endpoint_channel.h" #include diff --git a/cpp/core_v2/internal/ble_endpoint_channel.h b/cpp/core_v2/internal/ble_endpoint_channel.h index 74d68993..89bc4164 100644 --- a/cpp/core_v2/internal/ble_endpoint_channel.h +++ b/cpp/core_v2/internal/ble_endpoint_channel.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ #define CORE_V2_INTERNAL_BLE_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index ae1efae9..712246b9 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/ble.h" #include diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 7880f837..b60504e2 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_H_ #define CORE_V2_INTERNAL_MEDIUMS_BLE_H_ diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 5ce85562..e1c67078 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/mediums/ble.h" #include diff --git a/cpp/platform_v2/base/bluetooth_utils.cc b/cpp/platform_v2/base/bluetooth_utils.cc index e3221878..689083d4 100644 --- a/cpp/platform_v2/base/bluetooth_utils.cc +++ b/cpp/platform_v2/base/bluetooth_utils.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/base/bluetooth_utils.h" #include "absl/strings/escaping.h" diff --git a/cpp/platform_v2/base/bluetooth_utils.h b/cpp/platform_v2/base/bluetooth_utils.h index a8a8a20f..bf750798 100644 --- a/cpp/platform_v2/base/bluetooth_utils.h +++ b/cpp/platform_v2/base/bluetooth_utils.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_ #define PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_ diff --git a/cpp/platform_v2/base/bluetooth_utils_test.cc b/cpp/platform_v2/base/bluetooth_utils_test.cc index 7cc6f53e..c32f66e2 100644 --- a/cpp/platform_v2/base/bluetooth_utils_test.cc +++ b/cpp/platform_v2/base/bluetooth_utils_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/base/bluetooth_utils.h" #include "gtest/gtest.h" diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc index 9b143494..c07a79a5 100644 --- a/cpp/platform_v2/impl/g3/ble.cc +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/impl/g3/ble.h" #include diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h index 5ea80a55..8c7ee1b3 100644 --- a/cpp/platform_v2/impl/g3/ble.h +++ b/cpp/platform_v2/impl/g3/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_G3_BLE_H_ #define PLATFORM_V2_IMPL_G3_BLE_H_ diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 7161eb7e..e652fd8d 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/ble.h" #include "platform_v2/public/logging.h" diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 5cb89f08..c5f23568 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_PUBLIC_BLE_H_ #define PLATFORM_V2_PUBLIC_BLE_H_ diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index d1fcf653..200dc0fc 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/public/ble.h" #include From d5bed12d400839a0c200987ca6ad3c0c739a9c90 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 3 Sep 2020 02:08:42 -0700 Subject: [PATCH 42/52] Roll forward to cl/329875420 Signed-off-by: Alexey Polyudov Change-Id: I50e284b30472160a409829957b2cb3926babfede --- cpp/core/internal/mediums/BUILD | 4 - cpp/core_v2/internal/BUILD | 1 + cpp/core_v2/internal/mediums/BUILD | 17 +- .../internal/mediums/ble_advertisement.cc | 173 ------ .../mediums/ble_advertisement_test.cc | 219 -------- cpp/core_v2/internal/mediums/ble_test.cc | 49 +- cpp/core_v2/internal/mediums/ble_v2/BUILD | 49 ++ .../{ => ble_v2}/advertisement_read_result.cc | 2 +- .../{ => ble_v2}/advertisement_read_result.h | 6 +- .../advertisement_read_result_test.cc | 2 +- .../mediums/ble_v2/ble_advertisement.cc | 244 +++++++++ .../mediums/{ => ble_v2}/ble_advertisement.h | 62 ++- .../{ => ble_v2}/ble_advertisement_header.cc | 2 +- .../{ => ble_v2}/ble_advertisement_header.h | 6 +- .../ble_advertisement_header_test.cc | 2 +- .../mediums/ble_v2/ble_advertisement_test.cc | 505 ++++++++++++++++++ .../mediums/{ => ble_v2}/ble_packet.cc | 2 +- .../mediums/{ => ble_v2}/ble_packet.h | 6 +- .../mediums/{ => ble_v2}/ble_packet_test.cc | 2 +- .../mediums/{ => ble_v2}/ble_peripheral.h | 6 +- .../{ => ble_v2}/ble_peripheral_test.cc | 2 +- .../ble_v2/discovered_peripheral_callback.h | 31 ++ cpp/core_v2/internal/mediums/utils.cc | 6 +- cpp/core_v2/internal/mediums/utils.h | 1 + cpp/core_v2/internal/mediums/webrtc.cc | 3 + cpp/core_v2/internal/mediums/webrtc.h | 4 +- .../mediums/webrtc/connection_flow.cc | 9 +- .../mediums/webrtc/connection_flow_test.cc | 10 + cpp/core_v2/internal/mediums/webrtc_test.cc | 32 ++ .../internal/p2p_cluster_pcp_handler.cc | 88 +-- .../internal/p2p_cluster_pcp_handler.h | 9 +- cpp/core_v2/options.h | 3 +- cpp/platform_v2/base/medium_environment.cc | 145 ++--- cpp/platform_v2/base/medium_environment.h | 10 +- cpp/platform_v2/impl/g3/webrtc.cc | 6 + cpp/platform_v2/public/ble.cc | 3 +- cpp/platform_v2/public/ble.h | 5 +- cpp/platform_v2/public/ble_test.cc | 76 +-- proto/error_code_enums.proto | 30 ++ 39 files changed, 1210 insertions(+), 622 deletions(-) delete mode 100644 cpp/core_v2/internal/mediums/ble_advertisement.cc delete mode 100644 cpp/core_v2/internal/mediums/ble_advertisement_test.cc create mode 100644 cpp/core_v2/internal/mediums/ble_v2/BUILD rename cpp/core_v2/internal/mediums/{ => ble_v2}/advertisement_read_result.cc (98%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/advertisement_read_result.h (93%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/advertisement_read_result_test.cc (98%) create mode 100644 cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_advertisement.h (52%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_advertisement_header.cc (98%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_advertisement_header.h (93%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_advertisement_header_test.cc (99%) create mode 100644 cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_packet.cc (96%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_packet.h (88%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_packet_test.cc (97%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_peripheral.h (82%) rename cpp/core_v2/internal/mediums/{ => ble_v2}/ble_peripheral_test.cc (91%) create mode 100644 cpp/core_v2/internal/mediums/ble_v2/discovered_peripheral_callback.h diff --git a/cpp/core/internal/mediums/BUILD b/cpp/core/internal/mediums/BUILD index 1cf244df..a8dff808 100644 --- a/cpp/core/internal/mediums/BUILD +++ b/cpp/core/internal/mediums/BUILD @@ -87,7 +87,6 @@ cc_test( deps = [ ":mediums", "//platform:utils", - "//platform/api", "//platform/impl/g3", "//testing/base/public:gunit_main", ], @@ -98,7 +97,6 @@ cc_test( srcs = ["ble_advertisement_test.cc"], deps = [ ":mediums", - "//platform/api", "//platform/impl/g3", "//testing/base/public:gunit_main", ], @@ -109,7 +107,6 @@ cc_test( srcs = ["ble_packet_test.cc"], deps = [ ":mediums", - "//platform/api", "//platform/impl/g3", "//testing/base/public:gunit_main", ], @@ -120,7 +117,6 @@ cc_test( srcs = ["bloom_filter_test.cc"], deps = [ ":mediums", - "//platform/api", "//platform/impl/g3", "//testing/base/public:gunit_main", ], diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index e68a6c28..7aea432f 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -61,6 +61,7 @@ cc_library( "//core/internal:message_lite", "//core_v2:core_types", "//core_v2/internal/mediums", + "//core_v2/internal/mediums:utils", "//core_v2/internal/mediums/webrtc", "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index 02190bcc..b2125c5c 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -1,11 +1,7 @@ cc_library( name = "mediums", srcs = [ - "advertisement_read_result.cc", "ble.cc", - "ble_advertisement.cc", - "ble_advertisement_header.cc", - "ble_packet.cc", "bloom_filter.cc", "bluetooth_classic.cc", "bluetooth_radio.cc", @@ -15,12 +11,7 @@ cc_library( "wifi_lan.cc", ], hdrs = [ - "advertisement_read_result.h", "ble.h", - "ble_advertisement.h", - "ble_advertisement_header.h", - "ble_packet.h", - "ble_peripheral.h", "bloom_filter.h", "bluetooth_classic.h", "bluetooth_radio.h", @@ -37,7 +28,6 @@ cc_library( "//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", @@ -59,11 +49,11 @@ cc_library( hdrs = ["utils.h"], visibility = [ "//core_v2/internal:__pkg__", + "//core_v2/internal/mediums/ble_v2:__pkg__", "//core_v2/internal/mediums/webrtc:__pkg__", ], deps = [ "//platform_v2/base", - "//platform_v2/public:comm", "//platform_v2/public:types", ], ) @@ -72,11 +62,6 @@ cc_test( name = "core_v2_internal_mediums_test", size = "small", srcs = [ - "advertisement_read_result_test.cc", - "ble_advertisement_header_test.cc", - "ble_advertisement_test.cc", - "ble_packet_test.cc", - "ble_peripheral_test.cc", "ble_test.cc", "bloom_filter_test.cc", "bluetooth_classic_test.cc", diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_advertisement.cc deleted file mode 100644 index c3772e4c..00000000 --- a/cpp/core_v2/internal/mediums/ble_advertisement.cc +++ /dev/null @@ -1,173 +0,0 @@ -#include "core_v2/internal/mediums/ble_advertisement.h" - -#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 { -namespace connections { -namespace mediums { - -BleAdvertisement::BleAdvertisement(Version version, - SocketVersion socket_version, - const ByteArray &service_id_hash, - const ByteArray &data) { - // Check that the given input is valid. - if (!IsSupportedVersion(version) || - !IsSupportedSocketVersion(socket_version) || - service_id_hash.size() != kServiceIdHashLength || - data.size() > kMaxDataSize) { - return; - } - - version_ = version; - socket_version_ = socket_version; - service_id_hash_ = service_id_hash; - data_ = data; -} - -BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { - if (ble_advertisement_bytes.Empty()) { - NEARBY_LOG(INFO, - "Cannot deserialize BleAdvertisement: null bytes passed in."); - return; - } - - if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { - NEARBY_LOG(INFO, - "Cannot deserialize BleAdvertisement: expecting min %d raw " - "bytes, got %" PRIu64, - kMinAdvertisementLength, ble_advertisement_bytes.size()); - return; - } - - 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()); - - // Version. - version_ = static_cast( - (version_and_socket_version_byte & kVersionBitmask) >> 5); - if (!IsSupportedVersion(version_)) { - NEARBY_LOG(INFO, - "Cannot deserialize BleAdvertisement: unsupported Version %u", - version_); - return; - } - - // 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", - socket_version_); - version_ = Version::kUndefined; - return; - } - - // 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 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 %d", - expected_data_size); - version_ = Version::kUndefined; - return; - } - - // 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 { - if (!IsValid()) { - return ByteArray{}; - } - - // The first 3 bits are the Version. - char version_and_socket_version_byte = - (static_cast(version_) << 5) & kVersionBitmask; - // The next 3 bits are the Socket version. 2 bits left are reserved. - version_and_socket_version_byte |= - (static_cast(socket_version_) << 2) & kSocketVersionBitmask; - // Serialize Data size bytes(4). - ByteArray data_size_bytes{kDataSizeLength}; - auto *data_size_bytes_write_ptr = data_size_bytes.data(); - SerializeDataSize(data_size_bytes_write_ptr, data_.size()); - - // 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)}; -} - -bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const { - return this->GetVersion() == rhs.GetVersion() && - this->GetSocketVersion() == rhs.GetSocketVersion() && - this->GetServiceIdHash() == rhs.GetServiceIdHash() && - this->GetData() == rhs.GetData(); -} - -bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const { - if (this->GetVersion() != rhs.GetVersion()) { - return this->GetVersion() < rhs.GetVersion(); - } - if (this->GetSocketVersion() != rhs.GetSocketVersion()) { - return this->GetSocketVersion() < rhs.GetSocketVersion(); - } - if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) { - return this->GetServiceIdHash() < rhs.GetServiceIdHash(); - } - return this->GetData() < rhs.GetData(); -} - -bool BleAdvertisement::IsSupportedVersion(Version version) const { - return version >= Version::kV1 && version <= Version::kV2; -} - -bool BleAdvertisement::IsSupportedSocketVersion( - SocketVersion socket_version) const { - return socket_version >= SocketVersion::kV1 && - socket_version <= SocketVersion::kV2; -} - -void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr, - size_t data_size) const { - // Get a raw representation of the data size bytes in memory. - char *data_size_bytes = reinterpret_cast(&data_size); - - // Append these raw bytes to advertisement bytes, keeping in mind that we need - // to convert from Little Endian to Big Endian in the process. - for (int i = 0; i < kDataSizeLength; ++i) { - data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1]; - } -} - -} // namespace mediums -} // namespace connections -} // namespace nearby -} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc deleted file mode 100644 index 68b80836..00000000 --- a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc +++ /dev/null @@ -1,219 +0,0 @@ -#include "core_v2/internal/mediums/ble_advertisement.h" - -#include - -#include "gtest/gtest.h" - -namespace location { -namespace nearby { -namespace connections { -namespace mediums { -namespace { - -constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; -constexpr BleAdvertisement::SocketVersion kSocketVersion = - BleAdvertisement::SocketVersion::kV2; -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. -constexpr size_t kAdvertisementLength = 77; -constexpr size_t kLongAdvertisementLength = kAdvertisementLength + 1000; - -TEST(BleAdvertisementTest, ConstructionWorksV1) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, - BleAdvertisement::SocketVersion::kV1, - service_id_hash, data}; - - EXPECT_TRUE(ble_advertisement.IsValid()); - EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); - EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, - ble_advertisement.GetSocketVersion()); - EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); - EXPECT_EQ(data, ble_advertisement.GetData()); -} - -TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { - BleAdvertisement::Version bad_version = - static_cast(666); - - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement ble_advertisement{bad_version, kSocketVersion, - service_id_hash, data}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { - BleAdvertisement::SocketVersion bad_socket_version = - static_cast(666); - - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement ble_advertisement{kVersion, bad_socket_version, - service_id_hash, data}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { - char short_service_id_hash_bytes[] = "\x0a\x0b"; - - ByteArray bad_service_id_hash{short_service_id_hash_bytes}; - ByteArray data{std::string(kData)}; - - BleAdvertisement ble_advertisement{kVersion, kSocketVersion, - bad_service_id_hash, data}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { - char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; - - ByteArray bad_service_id_hash{long_service_id_hash_bytes}; - ByteArray data{std::string(kData)}; - - BleAdvertisement ble_advertisement{kVersion, kSocketVersion, - bad_service_id_hash, data}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { - // BleAdvertisement shouldn't be able to support data with the max GATT - // attribute length because it needs some room for the preceding fields. - char long_data[512]{}; - - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray bad_data{long_data, 512}; - - BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash, - bad_data}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, data}; - ByteArray ble_advertisement_bytes{org_ble_advertisement}; - BleAdvertisement ble_advertisement{ble_advertisement_bytes}; - - EXPECT_TRUE(ble_advertisement.IsValid()); - EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); - EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); - EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); - EXPECT_EQ(data, ble_advertisement.GetData()); -} - -TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - - BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, ByteArray()}; - ByteArray ble_advertisement_bytes{org_ble_advertisement}; - BleAdvertisement ble_advertisement{ble_advertisement_bytes}; - - EXPECT_TRUE(ble_advertisement.IsValid()); - EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); - EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); - EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); - EXPECT_TRUE(ble_advertisement.GetData().Empty()); -} - -TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, data}; - ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; - - // Copy the bytes into a new array with extra bytes. We must explicitly - // define how long our array is because we can't use variable length arrays. - char raw_ble_advertisement_bytes[kLongAdvertisementLength]{}; - memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), - std::min(sizeof(raw_ble_advertisement_bytes), - org_ble_advertisement_bytes.size())); - - // Re-parse the Ble advertisement using our extra long advertisement bytes. - ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, - kLongAdvertisementLength}; - BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; - - EXPECT_TRUE(long_ble_advertisement.IsValid()); - EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); - EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); - EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); - EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size()); - EXPECT_EQ(data, long_ble_advertisement.GetData()); -} - -TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { - BleAdvertisement ble_advertisement{ByteArray{}}; - - EXPECT_FALSE(ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, data}; - ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; - - // Cut off the advertisement so that it's too short. - ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), - 7}; - BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; - - EXPECT_FALSE(short_ble_advertisement.IsValid()); -} - -TEST(BleAdvertisementTest, - ConstructionFromSerializedBytesWithInvalidDataLengthFails) { - ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - ByteArray data{std::string(kData)}; - - BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, - service_id_hash, data}; - ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; - - // Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble - // advertisement bytes so we can modify it. We must explicitly define how - // long our array is because we can't use variable length arrays. - char raw_ble_advertisement_bytes[kAdvertisementLength]; - memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), - kAdvertisementLength); - - // The data size field lives in indices 4-7. Corrupt it. - memset(raw_ble_advertisement_bytes + 4, 0xFF, 4); - - // Try to parse the Ble advertisement using our corrupted advertisement bytes. - ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, - kAdvertisementLength}; - BleAdvertisement corrupted_ble_advertisement{ - corrupted_ble_advertisement_bytes}; - - EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); -} - -} // namespace -} // namespace mediums -} // namespace connections -} // namespace nearby -} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 5ce85562..f1936af7 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -57,14 +57,14 @@ TEST_F(BleTest, CanStartAdvertising) { ByteArray advertisement_bytes{std::string(kAdvertisementString)}; CountDownLatch found_latch(1); - ble_b.StartScanning(service_id, - DiscoveredPeripheralCallback{ - .peripheral_discovered_cb = - [&found_latch](BlePeripheral& peripheral, - const std::string& service_id) { - found_latch.CountDown(); - }, - }); + ble_b.StartScanning( + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch]( + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { found_latch.CountDown(); }, + }); EXPECT_TRUE(ble_a.StartAdvertising(service_id, advertisement_bytes)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); @@ -89,18 +89,18 @@ TEST_F(BleTest, CanStartDiscovery) { ble_b.StartAdvertising(service_id, advertisement_bytes); EXPECT_TRUE(ble_a.StartScanning( - service_id, DiscoveredPeripheralCallback{ - .peripheral_discovered_cb = - [&accept_latch](BlePeripheral& peripheral, - const std::string& service_id) { - accept_latch.CountDown(); - }, - .peripheral_lost_cb = - [&lost_latch](BlePeripheral& peripheral, - const std::string& service_id) { - lost_latch.CountDown(); - }, - })); + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&accept_latch]( + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { accept_latch.CountDown(); }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + })); EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); ble_b.StopAdvertising(service_id); EXPECT_TRUE(lost_latch.Await(kWaitDuration).result()); @@ -135,10 +135,13 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { { .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( - BlePeripheral& peripheral, const std::string& service_id) { + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { discovered_peripheral = peripheral; - NEARBY_LOG(INFO, "Discovered peripheral=%p [impl=%p]", - &peripheral, &peripheral.GetImpl()); + NEARBY_LOG( + INFO, + "Discovered peripheral=%p [impl=%p], fast advertisement=%d", + &peripheral, &peripheral.GetImpl(), fast_advertisement); found_latch.CountDown(); }, }); diff --git a/cpp/core_v2/internal/mediums/ble_v2/BUILD b/cpp/core_v2/internal/mediums/ble_v2/BUILD new file mode 100644 index 00000000..85ba9fd9 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_v2/BUILD @@ -0,0 +1,49 @@ +cc_library( + name = "ble_v2", + srcs = [ + "advertisement_read_result.cc", + "ble_advertisement.cc", + "ble_advertisement_header.cc", + "ble_packet.cc", + ], + hdrs = [ + "advertisement_read_result.h", + "ble_advertisement.h", + "ble_advertisement_header.h", + "ble_packet.h", + "ble_peripheral.h", + "discovered_peripheral_callback.h", + ], + visibility = [ + "//core_v2/internal:__subpackages__", + ], + deps = [ + "//core_v2:core_types", + "//platform_v2/base", + "//platform_v2/base:util", + "//platform_v2/public:logging", + "//platform_v2/public:types", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + ], +) + +cc_test( + name = "ble_v2_test", + srcs = [ + "advertisement_read_result_test.cc", + "ble_advertisement_header_test.cc", + "ble_advertisement_test.cc", + "ble_packet_test.cc", + "ble_peripheral_test.cc", + ], + deps = [ + ":ble_v2", + "//platform_v2/base", + "//platform_v2/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.cc b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.cc similarity index 98% rename from cpp/core_v2/internal/mediums/advertisement_read_result.cc rename to cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.cc index fbd97e34..63e43127 100644 --- a/cpp/core_v2/internal/mediums/advertisement_read_result.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/advertisement_read_result.h" +#include "core_v2/internal/mediums/ble_v2/advertisement_read_result.h" #include #include diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.h b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.h similarity index 93% rename from cpp/core_v2/internal/mediums/advertisement_read_result.h rename to cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.h index c4d2c566..ebf5b535 100644 --- a/cpp/core_v2/internal/mediums/advertisement_read_result.h +++ b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result.h @@ -1,5 +1,5 @@ -#ifndef CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ -#define CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_ #include #include @@ -87,4 +87,4 @@ class AdvertisementReadResult { } // namespace nearby } // namespace location -#endif // CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result_test.cc similarity index 98% rename from cpp/core_v2/internal/mediums/advertisement_read_result_test.cc rename to cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result_test.cc index 0d822274..7acfef4e 100644 --- a/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/advertisement_read_result_test.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/advertisement_read_result.h" +#include "core_v2/internal/mediums/ble_v2/advertisement_read_result.h" #include "gtest/gtest.h" #include "absl/time/clock.h" diff --git a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc new file mode 100644 index 00000000..d988a869 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc @@ -0,0 +1,244 @@ +#include "core_v2/internal/mediums/ble_v2/ble_advertisement.h" + +#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 { +namespace connections { +namespace mediums { + +BleAdvertisement::BleAdvertisement(Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, + const ByteArray &data, + const ByteArray &device_token) { + DoInitialize(/*fast_advertisement=*/false, version, socket_version, + service_id_hash, data, device_token); +} + +BleAdvertisement::BleAdvertisement(Version version, + SocketVersion socket_version, + const ByteArray &data, + const ByteArray &device_token) { + DoInitialize(/*fast_advertisement=*/true, version, socket_version, + {}, data, device_token); +} + +void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, + const ByteArray &data, + const ByteArray &device_token) { + // Check that the given input is valid. + fast_advertisement_ = fast_advertisement; + if (!fast_advertisement_) { + if (service_id_hash.size() != kServiceIdHashLength) return; + } + if (!IsSupportedVersion(version) || + !IsSupportedSocketVersion(socket_version) || + (!device_token.Empty() && device_token.size() != kDeviceTokenLength)) { + return; + } + + int advertisement_Length = ComputeAdvertisementLength( + data.size(), device_token.size(), fast_advertisement_); + int max_advertisement_length = fast_advertisement + ? kMaxFastAdvertisementLength + : kMaxAdvertisementLength; + if (advertisement_Length > max_advertisement_length) { + return; + } + + version_ = version; + socket_version_ = socket_version; + if (!fast_advertisement_) service_id_hash_ = service_id_hash; + data_ = data; + device_token_ = device_token; +} + +BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kVersionLength) { + NEARBY_LOG( + INFO, + "Cannot deserialize BleAdvertisement: expecting min %d raw bytes to " + "parse the version, got %" PRIu64, + kVersionLength, ble_advertisement_bytes.size()); + return; + } + + ByteArray advertisement_bytes{ble_advertisement_bytes}; + BaseInputStream base_input_stream{advertisement_bytes}; + // The first 1 byte is supposed to be the version, socket version and the fast + // advertisement flag. + auto version_byte = + static_cast(base_input_stream.ReadUint8()); + + // Version. + version_ = static_cast((version_byte & kVersionBitmask) >> 5); + if (!IsSupportedVersion(version_)) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: unsupported Version %u", + version_); + return; + } + + // Socket version. + socket_version_ = + static_cast((version_byte & kSocketVersionBitmask) >> 2); + if (!IsSupportedSocketVersion(socket_version_)) { + NEARBY_LOG( + INFO, + "Cannot deserialize BleAdvertisement: unsupported SocketVersion %u", + socket_version_); + version_ = Version::kUndefined; + return; + } + + // Fast advertisement flag. + fast_advertisement_ = + static_cast((version_byte & kFastAdvertisementFlagBitmask) >> 1); + + // The next 3 bytes are supposed to be the service_id_hash if not fast + // advertisement. + if (!fast_advertisement_) { + service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength); + } + + // Data length. + int expected_data_size = + fast_advertisement_ + ? static_cast( + base_input_stream.ReadBytes(kFastDataSizeLength).data()[0]) + : static_cast(base_input_stream.ReadUint32()); + if (expected_data_size < 0) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: negative data size %d", + expected_data_size); + version_ = Version::kUndefined; + return; + } + + // 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; + } + + // Device token. If the number of remaining bytes are valid for device token, + // then read it. + if (base_input_stream.IsAvailable(kDeviceTokenLength)) { + device_token_ = base_input_stream.ReadBytes(kDeviceTokenLength); + } +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray{}; + } + + // The first 3 bits are the Version. + char version_byte = (static_cast(version_) << 5) & kVersionBitmask; + // The next 3 bits are the Socket version. 2 bits left are reserved. + version_byte |= + (static_cast(socket_version_) << 2) & kSocketVersionBitmask; + // The next 1 bit is the fast advertisement flag. 1 bit left is reserved. + version_byte |= (static_cast(fast_advertisement_ ? 1 : 0) << 1) & + kFastAdvertisementFlagBitmask; + + // Serialize Data size bytes + ByteArray data_size_bytes{static_cast( + fast_advertisement_ ? kFastDataSizeLength : kDataSizeLength)}; + auto *data_size_bytes_write_ptr = data_size_bytes.data(); + SerializeDataSize(fast_advertisement_, data_size_bytes_write_ptr, + data_.size()); + + // clang-format on + if (fast_advertisement_) { + std::string out = + absl::StrCat(std::string(1, version_byte), + std::string(data_size_bytes), + std::string(data_), + std::string(device_token_)); + return ByteArray{std::move(out)}; + } else { + std::string out = + absl::StrCat(std::string(1, version_byte), + std::string(service_id_hash_), + std::string(data_size_bytes), + std::string(data_), + std::string(device_token_)); + return ByteArray{std::move(out)}; + } + // clang-format on +} + +bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const { + return this->GetVersion() == rhs.GetVersion() && + this->GetSocketVersion() == rhs.GetSocketVersion() && + this->GetServiceIdHash() == rhs.GetServiceIdHash() && + this->GetData() == rhs.GetData() && + this->GetDeviceToken() == rhs.GetDeviceToken(); +} + +bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const { + if (this->GetVersion() != rhs.GetVersion()) { + return this->GetVersion() < rhs.GetVersion(); + } + if (this->GetSocketVersion() != rhs.GetSocketVersion()) { + return this->GetSocketVersion() < rhs.GetSocketVersion(); + } + if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) { + return this->GetServiceIdHash() < rhs.GetServiceIdHash(); + } + if (this->GetDeviceToken() != rhs.GetDeviceToken()) { + return this->GetDeviceToken() < rhs.GetDeviceToken(); + } + return this->GetData() < rhs.GetData(); +} + +bool BleAdvertisement::IsSupportedVersion(Version version) const { + return version >= Version::kV1 && version <= Version::kV2; +} + +bool BleAdvertisement::IsSupportedSocketVersion( + SocketVersion socket_version) const { + return socket_version >= SocketVersion::kV1 && + socket_version <= SocketVersion::kV2; +} + +void BleAdvertisement::SerializeDataSize(bool fast_advertisement, + char *data_size_bytes_write_ptr, + size_t data_size) const { + // Get a raw representation of the data size bytes in memory. + char *data_size_bytes = reinterpret_cast(&data_size); + + const int data_size_length = + fast_advertisement ? kFastDataSizeLength : kDataSizeLength; + + // Append these raw bytes to advertisement bytes, keeping in mind that we need + // to convert from Little Endian to Big Endian in the process. + for (int i = 0; i < data_size_length; ++i) { + data_size_bytes_write_ptr[i] = data_size_bytes[data_size_length - i - 1]; + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h similarity index 52% rename from cpp/core_v2/internal/mediums/ble_advertisement.h rename to cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h index a1da4d4d..203b3614 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement.h +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h @@ -1,5 +1,5 @@ -#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ -#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_ #include @@ -10,10 +10,14 @@ namespace nearby { namespace connections { namespace mediums { -// Represents the format of the Mediums Ble Advertisement used in advertising -// and discovery. +// Represents the format of the Mediums BLE Advertisement used in Advertising + +// Discovery. // -// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA] +// [VERSION][SOCKET_VERSION][FAST_ADVERTISEMENT_FLAG][1_RESERVED_BIT][SERVICE_ID_HASH][DATA_SIZE][DATA][DEVICE_TOKEN] +// +// For fast advertisement, we remove SERVICE_ID_HASH since we already have one +// copy in Nearby Connections(b/138447288) +// [VERSION][SOCKET_VERSION][FAST_ADVERTISEMENT_FLAG][1_RESERVED_BIT][DATA_SIZE][DATA][DEVICE_TOKEN] // // See go/nearby-ble-design for more information. class BleAdvertisement { @@ -37,10 +41,14 @@ class BleAdvertisement { }; static constexpr int kServiceIdHashLength = 3; + static constexpr int kDeviceTokenLength = 2; BleAdvertisement() = default; BleAdvertisement(Version version, SocketVersion socket_version, - const ByteArray &service_id_hash, const ByteArray &data); + const ByteArray &service_id_hash, const ByteArray &data, + const ByteArray &device_token); + BleAdvertisement(Version version, SocketVersion socket_version, + const ByteArray &data, const ByteArray &device_token); explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes); BleAdvertisement(const BleAdvertisement &) = default; BleAdvertisement &operator=(const BleAdvertisement &) = default; @@ -56,37 +64,59 @@ class BleAdvertisement { bool IsValid() const { return IsSupportedVersion(version_); } Version GetVersion() const { return version_; } SocketVersion GetSocketVersion() const { return socket_version_; } + bool IsFastAdvertisement() const { return fast_advertisement_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } ByteArray &GetData() & { return data_; } const ByteArray &GetData() const & { return data_; } ByteArray &&GetData() && { return std::move(data_); } const ByteArray &&GetData() const && { return std::move(data_); } + ByteArray GetDeviceToken() const { return device_token_; } private: + void DoInitialize(bool fast_advertisement, Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, const ByteArray &data, + const ByteArray &device_token); bool IsSupportedVersion(Version version) const; bool IsSupportedSocketVersion(SocketVersion socket_version) const; - void SerializeDataSize(char *data_size_bytes_write_ptr, + void SerializeDataSize(bool fast_advertisement, + char *data_size_bytes_write_ptr, size_t data_size) const; + int ComputeAdvertisementLength(int data_length, int total_optional_length, + bool fast_advertisement) const { + // The advertisement length is the minimum length + the length of the data + + // the length of in-use optional fields. + return fast_advertisement ? (kMinFastAdvertisementLegth + data_length + + total_optional_length) + : (kMinAdvertisementLength + data_length + + total_optional_length); + } static constexpr int kVersionLength = 1; - // Length of one int. Be sure to re-evaluate how we compute data size in this - // class if this constant ever changes! - static constexpr int kDataSizeLength = 4; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kSocketVersionBitmask = 0x01C; + static constexpr int kFastAdvertisementFlagBitmask = 0x002; + static constexpr int kDataSizeLength = 4; // Length of one int. + static constexpr int kFastDataSizeLength = 1; // Length of one byte. static constexpr int kMinAdvertisementLength = kVersionLength + kServiceIdHashLength + kDataSizeLength; // The maximum length for a Gatt characteristic value is 512 bytes, so make // sure the entire advertisement is less than that. The data can take up // whatever space is remaining after the bytes preceding it. - static constexpr int kMaxGattCharacteristicValueSize = 512; - static constexpr int kMaxDataSize = - kMaxGattCharacteristicValueSize - kMinAdvertisementLength; - static constexpr int kVersionBitmask = 0x0E0; - static constexpr int kSocketVersionBitmask = 0x01C; + static constexpr int kMaxAdvertisementLength = 512; + static constexpr int kMinFastAdvertisementLegth = + kVersionLength + kFastDataSizeLength; + // The maximum length for the scan response is 31 bytes. However, with the + // required header that comes before the service data, this leaves the + // advertiser with 27 leftover bytes. + static constexpr int kMaxFastAdvertisementLength = 27; Version version_{Version::kUndefined}; SocketVersion socket_version_{SocketVersion::kUndefined}; + bool fast_advertisement_ = false; ByteArray service_id_hash_; ByteArray data_; + ByteArray device_token_; }; } // namespace mediums @@ -94,4 +124,4 @@ class BleAdvertisement { } // namespace nearby } // namespace location -#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.cc similarity index 98% rename from cpp/core_v2/internal/mediums/ble_advertisement_header.cc rename to cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.cc index d1c55de5..5c35fafa 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/ble_advertisement_header.h" +#include "core_v2/internal/mediums/ble_v2/ble_advertisement_header.h" #include diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.h b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.h similarity index 93% rename from cpp/core_v2/internal/mediums/ble_advertisement_header.h rename to cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.h index bcec8d55..b4c1289e 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header.h +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header.h @@ -1,5 +1,5 @@ -#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ -#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_ #include @@ -80,4 +80,4 @@ class BleAdvertisementHeader { } // namespace nearby } // namespace location -#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header_test.cc similarity index 99% rename from cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc rename to cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header_test.cc index b4911c95..10aa62d0 100644 --- a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_header_test.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/ble_advertisement_header.h" +#include "core_v2/internal/mediums/ble_v2/ble_advertisement_header.h" #include "platform_v2/base/base64_utils.h" #include "gtest/gtest.h" diff --git a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc new file mode 100644 index 00000000..46a18850 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc @@ -0,0 +1,505 @@ +#include "core_v2/internal/mediums/ble_v2/ble_advertisement.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; +constexpr BleAdvertisement::SocketVersion kSocketVersion = + BleAdvertisement::SocketVersion::kV2; +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?"}; +constexpr absl::string_view kFastData{"Fast Advertise"}; +constexpr absl::string_view kDeviceToken{"\x04\x20"}; +// kAdvertisementLength/kFastAdvertisementLength corresponds to the length of a +// specific BleAdvertisement packed with the kData/kFastData given above. Be +// sure to update this if kData/kFastData ever changes. +constexpr size_t kAdvertisementLength = 77; +constexpr size_t kFastAdvertisementLength = 16; +constexpr size_t kLongAdvertisementLength = kAdvertisementLength + 1000; + +TEST(BleAdvertisementTest, ConstructionWorksV1) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, + BleAdvertisement::SocketVersion::kV1, + service_id_hash, + data, + device_token}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); + EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, + ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); + EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, ConstructionWorksV1ForFastAdvertisement) { + ByteArray fast_data{std::string(kFastData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, + BleAdvertisement::SocketVersion::kV1, + fast_data, + device_token}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); + EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, + ble_advertisement.GetSocketVersion()); + EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(fast_data, ble_advertisement.GetData()); + EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + BleAdvertisement::Version bad_version = + static_cast(666); + + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{bad_version, + kSocketVersion, + service_id_hash, + data, + device_token}; + EXPECT_FALSE(ble_advertisement.IsValid()); + + BleAdvertisement fast_ble_advertisement{bad_version, + kSocketVersion, + data, + device_token}; + EXPECT_FALSE(fast_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { + BleAdvertisement::SocketVersion bad_socket_version = + static_cast(666); + + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{kVersion, + bad_socket_version, + service_id_hash, + data, + device_token}; + EXPECT_FALSE(ble_advertisement.IsValid()); + + BleAdvertisement fast_ble_advertisement{kVersion, + bad_socket_version, + data, + device_token}; + EXPECT_FALSE(fast_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = "\x0a\x0b"; + + ByteArray bad_service_id_hash{short_service_id_hash_bytes}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{kVersion, + kSocketVersion, + bad_service_id_hash, + data, + device_token}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; + + ByteArray bad_service_id_hash{long_service_id_hash_bytes}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{kVersion, + kSocketVersion, + bad_service_id_hash, + data, + device_token}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { + // BleAdvertisement shouldn't be able to support data with the max GATT + // attribute length because it needs some room for the preceding fields. + char long_data[512]{}; + + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray bad_data{long_data, 512}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + bad_data, + device_token}; + EXPECT_FALSE(ble_advertisement.IsValid()); + + BleAdvertisement fast_ble_advertisement{kVersion, + kSocketVersion, + bad_data, + device_token}; + EXPECT_FALSE(fast_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmptyDeviceToken) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + + BleAdvertisement ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + data, + ByteArray{}}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); + 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.GetDeviceToken().Empty()); +} + +TEST(BleAdvertisementTest, + ConstructionWorksWithEmptyDeviceTokenForFastAdvertisement) { + ByteArray fast_data{std::string(kFastData)}; + + BleAdvertisement ble_advertisement{kVersion, + kSocketVersion, + fast_data, + ByteArray{}}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(fast_data, ble_advertisement.GetData()); + EXPECT_TRUE(ble_advertisement.GetDeviceToken().Empty()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithWrongSizeofDeviceToken) { + char wrong_device_token_bytes_1[] = "\x04\x2\x10"; // over 2 bytes + char wrong_device_token_bytes_2[] = "\x04"; // 1 byte + + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray bad_device_token_1{wrong_device_token_bytes_1}; + ByteArray bad_device_token_2{wrong_device_token_bytes_2}; + + BleAdvertisement ble_advertisement_1{kVersion, + kSocketVersion, + service_id_hash, + data, + bad_device_token_1}; + EXPECT_FALSE(ble_advertisement_1.IsValid()); + + BleAdvertisement ble_advertisement_2{kVersion, + kSocketVersion, + service_id_hash, + data, + bad_device_token_2}; + EXPECT_FALSE(ble_advertisement_2.IsValid()); + + BleAdvertisement fast_ble_advertisement_1{kVersion, + kSocketVersion, + data, + bad_device_token_1}; + EXPECT_FALSE(fast_ble_advertisement_1.IsValid()); + + BleAdvertisement fast_ble_advertisement_2{kVersion, + kSocketVersion, + data, + bad_device_token_2}; + EXPECT_FALSE(fast_ble_advertisement_2.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + data, + device_token}; + + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); + 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_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWorksForAdvertisement) { + ByteArray fast_data{std::string(kFastData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + fast_data, + device_token}; + + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(fast_data, ble_advertisement.GetData()); + EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + ByteArray(), + device_token}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_TRUE(ble_advertisement.GetData().Empty()); + EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithEmptyDataWorksForFastAdvertisement) { + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + ByteArray(), + device_token}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_TRUE(ble_advertisement.GetData().Empty()); + EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Copy the bytes into a new array with extra bytes. We must explicitly + // define how long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kLongAdvertisementLength]{}; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + std::min(sizeof(raw_ble_advertisement_bytes), + org_ble_advertisement_bytes.size())); + + // Re-parse the Ble advertisement using our extra long advertisement bytes. + ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kLongAdvertisementLength}; + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + + EXPECT_TRUE(long_ble_advertisement.IsValid()); + EXPECT_FALSE(long_ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size()); + EXPECT_EQ(data, long_ble_advertisement.GetData()); + EXPECT_EQ(device_token, long_ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, + ConstructionFromExtraSerializedBytesWorksForFastAdvertisement) { + ByteArray fast_data{std::string(kFastData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + fast_data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Copy the bytes into a new array with extra bytes. We must explicitly + // define how long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kLongAdvertisementLength]{}; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + std::min(sizeof(raw_ble_advertisement_bytes), + org_ble_advertisement_bytes.size())); + + // Re-parse the Ble advertisement using our extra long advertisement bytes. + ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kLongAdvertisementLength}; + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + + EXPECT_TRUE(long_ble_advertisement.IsValid()); + EXPECT_TRUE(long_ble_advertisement.IsFastAdvertisement()); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); + EXPECT_EQ(fast_data.size(), long_ble_advertisement.GetData().size()); + EXPECT_EQ(fast_data, long_ble_advertisement.GetData()); + EXPECT_EQ(device_token, long_ble_advertisement.GetDeviceToken()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + BleAdvertisement ble_advertisement{ByteArray{}}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Cut off the advertisement so that it's too short. + ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), + 7}; + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromShortLengthSerializedBytesFailsForFastAdvertisement) { + ByteArray fast_data{std::string(kFastData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + fast_data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Cut off the advertisement so that it's too short. + ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), + 2}; + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithInvalidDataLengthFails) { + ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; + ByteArray data{std::string(kData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + service_id_hash, + data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble + // advertisement bytes so we can modify it. We must explicitly define how + // long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + kAdvertisementLength); + + // The data size field lives in indices 4-7. Corrupt it. + memset(raw_ble_advertisement_bytes + 4, 0xFF, 4); + + // Try to parse the Ble advertisement using our corrupted advertisement bytes. + ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kAdvertisementLength}; + BleAdvertisement corrupted_ble_advertisement{ + corrupted_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithInvalidDataLengthFails2) { + ByteArray fast_data{std::string(kFastData)}; + ByteArray device_token{std::string(kDeviceToken)}; + + BleAdvertisement org_ble_advertisement{kVersion, + kSocketVersion, + fast_data, + device_token}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble + // advertisement bytes so we can modify it. We must explicitly define how + // long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kFastAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + kFastAdvertisementLength); + + // The data size field lives in index 1. Corrupt it. + memset(raw_ble_advertisement_bytes + 1, 0xFF, 1); + + // Try to parse the Ble advertisement using our corrupted advertisement bytes. + ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kFastAdvertisementLength}; + BleAdvertisement corrupted_ble_advertisement{ + corrupted_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_packet.cc similarity index 96% rename from cpp/core_v2/internal/mediums/ble_packet.cc rename to cpp/core_v2/internal/mediums/ble_v2/ble_packet.cc index bd05ab8d..c98d7c38 100644 --- a/cpp/core_v2/internal/mediums/ble_packet.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_packet.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/ble_packet.h" +#include "core_v2/internal/mediums/ble_v2/ble_packet.h" #include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" diff --git a/cpp/core_v2/internal/mediums/ble_packet.h b/cpp/core_v2/internal/mediums/ble_v2/ble_packet.h similarity index 88% rename from cpp/core_v2/internal/mediums/ble_packet.h rename to cpp/core_v2/internal/mediums/ble_v2/ble_packet.h index bbdae131..1e7172ae 100644 --- a/cpp/core_v2/internal/mediums/ble_packet.h +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_packet.h @@ -1,5 +1,5 @@ -#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ -#define CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_ #include @@ -47,4 +47,4 @@ class BlePacket { } // namespace nearby } // namespace location -#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_ diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_packet_test.cc similarity index 97% rename from cpp/core_v2/internal/mediums/ble_packet_test.cc rename to cpp/core_v2/internal/mediums/ble_v2/ble_packet_test.cc index 6df5b07d..9b0f6a99 100644 --- a/cpp/core_v2/internal/mediums/ble_packet_test.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_packet_test.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/ble_packet.h" +#include "core_v2/internal/mediums/ble_v2/ble_packet.h" #include "gtest/gtest.h" diff --git a/cpp/core_v2/internal/mediums/ble_peripheral.h b/cpp/core_v2/internal/mediums/ble_v2/ble_peripheral.h similarity index 82% rename from cpp/core_v2/internal/mediums/ble_peripheral.h rename to cpp/core_v2/internal/mediums/ble_v2/ble_peripheral.h index 520b93ca..e144489f 100644 --- a/cpp/core_v2/internal/mediums/ble_peripheral.h +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_peripheral.h @@ -1,5 +1,5 @@ -#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ -#define CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_ #include "platform_v2/base/byte_array.h" @@ -32,4 +32,4 @@ class BlePeripheral { } // namespace nearby } // namespace location -#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_ diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_peripheral_test.cc similarity index 91% rename from cpp/core_v2/internal/mediums/ble_peripheral_test.cc rename to cpp/core_v2/internal/mediums/ble_v2/ble_peripheral_test.cc index b3aba76f..59a06260 100644 --- a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_peripheral_test.cc @@ -1,4 +1,4 @@ -#include "core_v2/internal/mediums/ble_peripheral.h" +#include "core_v2/internal/mediums/ble_v2/ble_peripheral.h" #include "gtest/gtest.h" diff --git a/cpp/core_v2/internal/mediums/ble_v2/discovered_peripheral_callback.h b/cpp/core_v2/internal/mediums/ble_v2/discovered_peripheral_callback.h new file mode 100644 index 00000000..92b63adf --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_v2/discovered_peripheral_callback.h @@ -0,0 +1,31 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_ + +#include "core_v2/internal/mediums/ble_v2/ble_peripheral.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +/** Callback that is invoked when a {@link BlePeripheral} is discovered. */ +struct DiscoveredPeripheralCallback { + std::function + peripheral_discovered_cb = + DefaultCallback(); + std::function + peripheral_lost_cb = + DefaultCallback(); +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_ diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc index 6785345a..33b141ac 100644 --- a/cpp/core_v2/internal/mediums/utils.cc +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -31,8 +31,12 @@ ByteArray Utils::GenerateRandomBytes(size_t length) { } ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) { + return Utils::Sha256Hash(std::string(source), length); +} + +ByteArray Utils::Sha256Hash(const std::string& source, size_t length) { ByteArray full_hash(length); - full_hash.CopyAt(0, Crypto::Sha256(std::string(source))); + full_hash.CopyAt(0, Crypto::Sha256(source)); return full_hash; } diff --git a/cpp/core_v2/internal/mediums/utils.h b/cpp/core_v2/internal/mediums/utils.h index 7234a897..4804c31c 100644 --- a/cpp/core_v2/internal/mediums/utils.h +++ b/cpp/core_v2/internal/mediums/utils.h @@ -13,6 +13,7 @@ class Utils { public: static ByteArray GenerateRandomBytes(size_t length); static ByteArray Sha256Hash(const ByteArray& source, size_t length); + static ByteArray Sha256Hash(const std::string& source, size_t length); }; } // namespace connections diff --git a/cpp/core_v2/internal/mediums/webrtc.cc b/cpp/core_v2/internal/mediums/webrtc.cc index 4b9510c7..ba498346 100644 --- a/cpp/core_v2/internal/mediums/webrtc.cc +++ b/cpp/core_v2/internal/mediums/webrtc.cc @@ -248,6 +248,9 @@ bool WebRtc::InitWebRtcFlow(Role role, const PeerId& self_id) { connection_flow_ = ConnectionFlow::Create(GetLocalIceCandidateListener(), GetDataChannelListener(), medium_); + if (!connection_flow_) + return false; + return true; } diff --git a/cpp/core_v2/internal/mediums/webrtc.h b/cpp/core_v2/internal/mediums/webrtc.h index 1322b5bb..4c09e319 100644 --- a/cpp/core_v2/internal/mediums/webrtc.h +++ b/cpp/core_v2/internal/mediums/webrtc.h @@ -10,14 +10,14 @@ #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/public/cancelable_alarm.h" -#include "platform_v2/public/scheduled_executor.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/base/listeners.h" #include "platform_v2/base/runnable.h" #include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/cancelable_alarm.h" #include "platform_v2/public/future.h" #include "platform_v2/public/mutex.h" +#include "platform_v2/public/scheduled_executor.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" diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 401cb0dc..3cb4e4ca 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -235,6 +235,11 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { &peer_connection_observer_, [this, &success_future]( rtc::scoped_refptr peer_connection) { + if (!peer_connection) { + success_future.Set(false); + return; + } + peer_connection_ = peer_connection; success_future.Set(true); }); @@ -324,7 +329,9 @@ bool ConnectionFlow::CloseLocked() { state_ = State::kEnded; data_channel_future_.SetException({Exception::kInterrupted}); - peer_connection_->Close(); + if (peer_connection_) + peer_connection_->Close(); + data_channel_observer_.reset(); NEARBY_LOG(INFO, "Closed WebRTC connection."); return true; 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 7a3a859c..c9767dff 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow_test.cc @@ -184,6 +184,16 @@ TEST_F(ConnectionFlowTest, CannotReceiveOfferAfterClose) { EXPECT_FALSE(answerer->OnOfferReceived(offer)); } +TEST_F(ConnectionFlowTest, NullPeerConnection) { + MediumEnvironment::Instance().SetUseValidPeerConnection( + /*use_valid_peer_connection=*/false); + + WebRtcMedium medium; + std::unique_ptr answerer = ConnectionFlow::Create( + LocalIceCandidateListener(), DataChannelListener(), medium); + EXPECT_EQ(answerer, nullptr); +} + } // namespace } // namespace mediums } // namespace connections diff --git a/cpp/core_v2/internal/mediums/webrtc_test.cc b/cpp/core_v2/internal/mediums/webrtc_test.cc index 6e450e3d..749f21c1 100644 --- a/cpp/core_v2/internal/mediums/webrtc_test.cc +++ b/cpp/core_v2/internal/mediums/webrtc_test.cc @@ -233,6 +233,38 @@ TEST_F(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) { EXPECT_EQ(message, received_msg.result()); } +TEST_F(WebRtcTest, StartAcceptingConnections_NullPeerConnection) { + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + + MediumEnvironment::Instance().SetUseValidPeerConnection( + /*use_valid_peer_connection=*/false); + + WebRtc webrtc; + PeerId self_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + EXPECT_FALSE(webrtc.StartAcceptingConnections( + self_id, {mock_accepted_callback_.AsStdFunction()})); +} + +TEST_F(WebRtcTest, Connect_NullPeerConnection) { + using MockAcceptedCallback = + testing::MockFunction; + testing::StrictMock mock_accepted_callback_; + + MediumEnvironment::Instance().SetUseValidPeerConnection( + /*use_valid_peer_connection=*/false); + + WebRtc webrtc; + PeerId self_id("peer_id"); + + ASSERT_TRUE(webrtc.IsAvailable()); + WebRtcSocketWrapper wrapper = webrtc.Connect(PeerId("random_peer_id")); + EXPECT_FALSE(wrapper.IsValid()); +} + } // namespace } // namespace mediums diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index ad7c8bc3..d0154914 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/ble_advertisement.h" #include "core_v2/internal/ble_endpoint_channel.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/mediums/utils.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "core_v2/internal/webrtc_endpoint_channel.h" #include "core_v2/internal/wifi_lan_endpoint_channel.h" @@ -19,10 +20,7 @@ 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; + return Utils::Sha256Hash(source, size); } P2pClusterPcpHandler::P2pClusterPcpHandler( @@ -103,10 +101,8 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartAdvertisingImpl( } if (options.allowed.ble) { - const ByteArray ble_hash = - GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); proto::connections::Medium ble_medium = StartBleAdvertising( - client, service_id, ble_hash, local_endpoint_id, local_endpoint_info); + client, service_id, local_endpoint_id, local_endpoint_info, options); if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartAdvertisingImpl: Ble added"); mediums_started_successfully.push_back(ble_medium); @@ -264,12 +260,12 @@ bool P2pClusterPcpHandler::IsRecognizedBleEndpoint( return false; } - if (advertisement.GetVersion() != BleAdvertisement::Version::kV1) { + if (advertisement.GetVersion() != kBleAdvertisementVersion) { NEARBY_LOG( INFO, "P2pClusterPcpHandler::IsRecognizedBluetoothEndpoint: Version is " "not matched; advertisement.Version=%d, Version=%d", - advertisement.GetVersion(), BleAdvertisement::Version::kV1); + advertisement.GetVersion(), kBleAdvertisementVersion); return false; } @@ -281,17 +277,21 @@ bool P2pClusterPcpHandler::IsRecognizedBleEndpoint( return false; } - ByteArray expected_service_id_hash = - GenerateHash(service_id, BluetoothDeviceName::kServiceIdHashLength); + // Check ServiceId for normal advertisement. + // ServiceIdHash is empty for fast advertisement. + if (!advertisement.IsFastAdvertisement()) { + ByteArray expected_service_id_hash = + GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); - if (advertisement.GetServiceIdHash() != expected_service_id_hash) { - NEARBY_LOG(INFO, - "P2pClusterPcpHandler::IsRecognizedBleEndpoint: service " - "id hash is " - "not matched; advertisement.service_id_hash=%s, expected=%s", - advertisement.GetServiceIdHash().data(), - expected_service_id_hash.data()); - return false; + if (advertisement.GetServiceIdHash() != expected_service_id_hash) { + NEARBY_LOG(INFO, + "P2pClusterPcpHandler::IsRecognizedBleEndpoint: service " + "id hash is " + "not matched; advertisement.service_id_hash=%s, expected=%s", + advertisement.GetServiceIdHash().data(), + expected_service_id_hash.data()); + return false; + } } return true; @@ -299,8 +299,9 @@ bool P2pClusterPcpHandler::IsRecognizedBleEndpoint( void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( ClientProxy* client, BlePeripheral& peripheral, - const std::string& service_id) { - RunOnPcpHandlerThread([this, client, service_id, &peripheral]() { + const std::string& service_id, bool fast_advertisement) { + RunOnPcpHandlerThread([this, client, &peripheral, service_id, + fast_advertisement]() { // Make sure we are still discovering before proceeding. if (!client->IsDiscovering()) { NEARBY_LOG(INFO, @@ -312,7 +313,7 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( // Parse the Ble advertisement bytes. BleAdvertisement advertisement( - /*fast_advertisement=*/false, + fast_advertisement, peripheral.GetAdvertisementBytes(service_id)); // Make sure the Ble advertisement points to a valid @@ -341,6 +342,8 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( }, peripheral, })); + + // TODO(b/156632928): Check for Bluetooth device with remote mac address. }); } @@ -671,8 +674,8 @@ proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising( absl::BytesToHexString(local_endpoint_info.data()).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_info)); + kBluetoothDeviceNameVersion, GetPcp(), local_endpoint_id, service_id_hash, + local_endpoint_info)); if (device_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothAdvertising: generate " @@ -747,8 +750,10 @@ BasePcpHandler::ConnectImplResult P2pClusterPcpHandler::BluetoothConnectImpl( proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( ClientProxy* client, const std::string& service_id, - const ByteArray& service_id_hash, const std::string& local_endpoint_id, - const ByteArray& local_endpoint_info) { + const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, + const ConnectionOptions& options) { + bool fast_advertisement = !options.fast_advertisement_service_uuid.empty(); + // Start listening for connections before advertising in case a connection // request comes in very quickly. NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" @@ -792,19 +797,30 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( << service_id; return proto::connections::UNKNOWN_MEDIUM; } + // TODO(b/156632928): Should check for Bluetooth connection here NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBleAdvertising: service=%s: " - "make advertisement; id=%s, hash=%s, name=%s", + "make advertisement; id=%s, name=%s", service_id.c_str(), local_endpoint_id.c_str(), - std::string(service_id_hash).c_str(), std::string(local_endpoint_info).c_str()); - // Generate a BleAdvertisement with which to become Ble discoverable. - // TODO(edwinwu): Add a bluetooth_adapter method to get the mac address. - std::string bluetooth_mac_address; - ByteArray advertisement_bytes(BleAdvertisement( - BleAdvertisement::Version::kV1, GetPcp(), service_id_hash, - local_endpoint_id, local_endpoint_info, bluetooth_mac_address)); + // Generate a BleAdvertisement. If a fast advertisement service UUID was + // provided, create a fast BleAdvertisement. + ByteArray advertisement_bytes; + if (fast_advertisement) { + advertisement_bytes = + ByteArray(BleAdvertisement(kBleAdvertisementVersion, GetPcp(), + local_endpoint_id, local_endpoint_info)); + } else { + const ByteArray service_id_hash = + GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); + // TODO(b/156632928): Should advertise Bluetooth MacAddress Over Ble + std::string bluetooth_mac_address; + + advertisement_bytes = ByteArray(BleAdvertisement( + kBleAdvertisementVersion, GetPcp(), service_id_hash, local_endpoint_id, + local_endpoint_info, bluetooth_mac_address)); + } if (advertisement_bytes.Empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBleAdvertising: generate " @@ -922,8 +938,8 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( absl::BytesToHexString(local_endpoint_info.data()).c_str()); // Generate a WifiLanServiceInfo with which to become WifiLan discoverable. std::string service_info_name(WifiLanServiceInfo( - WifiLanServiceInfo::Version::kV1, GetPcp(), local_endpoint_id, - service_id_hash, local_endpoint_info)); + kWifiLanServiceInfoVersion, GetPcp(), local_endpoint_id, service_id_hash, + local_endpoint_info)); if (service_info_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index a18be31f..b276a3f9 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -111,6 +111,8 @@ class P2pClusterPcpHandler : public BasePcpHandler { static constexpr BluetoothDeviceName::Version kBluetoothDeviceNameVersion = BluetoothDeviceName::Version::kV1; + static constexpr BleAdvertisement::Version kBleAdvertisementVersion = + BleAdvertisement::Version::kV1; static constexpr WifiLanServiceInfo::Version kWifiLanServiceInfoVersion = WifiLanServiceInfo::Version::kV1; @@ -143,13 +145,14 @@ class P2pClusterPcpHandler : public BasePcpHandler { const BleAdvertisement& advertisement) const; void BlePeripheralDiscoveredHandler(ClientProxy* client, BlePeripheral& peripheral, - const std::string& service_id); + const std::string& service_id, + bool fast_advertisement); void BlePeripheralLostHandler(ClientProxy* client, BlePeripheral& peripheral, const std::string& service_id); proto::connections::Medium StartBleAdvertising( ClientProxy* client, const std::string& service_id, - const ByteArray& service_id_hash, const std::string& local_endpoint_id, - const ByteArray& local_endpoint_info); + const std::string& local_endpoint_id, + const ByteArray& local_endpoint_info, const ConnectionOptions& options); proto::connections::Medium StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, const std::string& service_id); diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index 9ee207ee..6e0b0a66 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -56,8 +56,8 @@ struct MediumSelector { // Mediums are sorted in order of decreasing preference. if (wifi_lan == value) mediums.push_back(Medium::WIFI_LAN); if (web_rtc == value) mediums.push_back(Medium::WEB_RTC); - if (ble == value) mediums.push_back(Medium::BLE); if (bluetooth == value) mediums.push_back(Medium::BLUETOOTH); + if (ble == value) mediums.push_back(Medium::BLE); return mediums; } }; @@ -73,6 +73,7 @@ struct ConnectionOptions { bool auto_upgrade_bandwidth; bool enforce_topology_constraints; ByteArray remote_bluetooth_mac_address; + std::string fast_advertisement_service_uuid; // Verify if ConnectionOptions is in a not-initialized (Empty) state. bool Empty() const { return strategy.IsNone(); } // Bring ConnectionOptions to a not-initialized (Empty) state. diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 8687703e..3d8463be 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -160,7 +160,7 @@ api::BluetoothDevice* MediumEnvironment::FindBluetoothDevice( const std::string& mac_address) { api::BluetoothDevice* device = nullptr; CountDownLatch latch(1); - RunOnMediumEnvironmentThread([this, &device, &latch, &mac_address](){ + RunOnMediumEnvironmentThread([this, &device, &latch, &mac_address]() { for (auto& item : bluetooth_mediums_) { auto* adapter = item.second.adapter; if (!adapter) continue; @@ -306,85 +306,85 @@ void MediumEnvironment::UpdateBleMediumForAdvertising( api::BleMedium& medium, api::BlePeripheral& peripheral, const std::string& service_id, bool enabled) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, &peripheral, service_id, - enabled]() { - auto item = ble_mediums_.find(&medium); - if (item == ble_mediums_.end()) { - NEARBY_LOG(INFO, - "UpdateBleMediumForAdvertising failed. There is no medium " - "registered."); - return; - } - auto& context = item->second; - context.ble_peripheral = &peripheral; - context.advertising = enabled; - NEARBY_LOG(INFO, - "Update Ble medium for advertising: this=%p; medium=%p; " - "service_id=%s; name=%s; enabled=%d; ", - this, &medium, service_id.c_str(), peripheral.GetName().c_str(), - enabled); - for (auto& medium_info : ble_mediums_) { - auto& local_medium = medium_info.first; - auto& info = medium_info.second; - // Do not send notification to the same medium. - if (local_medium == &medium) continue; - OnBlePeripheralStateChanged(info, peripheral, service_id, enabled); - } - }); + RunOnMediumEnvironmentThread( + [this, &medium, &peripheral, service_id, enabled]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG(INFO, + "UpdateBleMediumForAdvertising failed. There is no medium " + "registered."); + return; + } + auto& context = item->second; + context.ble_peripheral = &peripheral; + context.advertising = enabled; + NEARBY_LOG(INFO, + "Update Ble medium for advertising: this=%p; medium=%p; " + "service_id=%s; name=%s; enabled=%d; ", + this, &medium, service_id.c_str(), + peripheral.GetName().c_str(), enabled); + for (auto& medium_info : ble_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + OnBlePeripheralStateChanged(info, peripheral, service_id, enabled); + } + }); } void MediumEnvironment::UpdateBleMediumForScanning( api::BleMedium& medium, const std::string& service_id, BleDiscoveredPeripheralCallback callback, bool enabled) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, service_id, - callback = std::move(callback), enabled]() { - auto item = ble_mediums_.find(&medium); - if (item == ble_mediums_.end()) { - NEARBY_LOG(INFO, - "UpdateBleMediumFoScanning failed. There is no medium " - "registered."); - return; - } - auto& context = item->second; - context.discovery_callback = std::move(callback); - NEARBY_LOG(INFO, - "Update Ble medium for scanning: this=%p; medium=%p; " - "service_id=%s; enabled=%d ;", - this, &medium, service_id.c_str(), enabled); - for (auto& medium_info : ble_mediums_) { - auto& local_medium = medium_info.first; - auto& info = medium_info.second; - // Do not send notification to the same medium. - if (local_medium == &medium) continue; - // Search advertising mediums and send notification. - if (info.advertising && enabled) { - OnBlePeripheralStateChanged(context, *(info.ble_peripheral), service_id, - enabled); - } - } - }); + RunOnMediumEnvironmentThread( + [this, &medium, service_id, callback = std::move(callback), enabled]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG(INFO, + "UpdateBleMediumFoScanning failed. There is no medium " + "registered."); + return; + } + auto& context = item->second; + context.discovery_callback = std::move(callback); + NEARBY_LOG(INFO, + "Update Ble medium for scanning: this=%p; medium=%p; " + "service_id=%s; enabled=%d ;", + this, &medium, service_id.c_str(), enabled); + for (auto& medium_info : ble_mediums_) { + auto& local_medium = medium_info.first; + auto& info = medium_info.second; + // Do not send notification to the same medium. + if (local_medium == &medium) continue; + // Search advertising mediums and send notification. + if (info.advertising && enabled) { + OnBlePeripheralStateChanged(context, *(info.ble_peripheral), + service_id, enabled); + } + } + }); } void MediumEnvironment::UpdateBleMediumForAcceptedConnection( api::BleMedium& medium, const std::string& service_id, BleAcceptedConnectionCallback callback) { if (!enabled_) return; - RunOnMediumEnvironmentThread([this, &medium, service_id, - callback = std::move(callback)]() { - auto item = ble_mediums_.find(&medium); - if (item == ble_mediums_.end()) { - NEARBY_LOG( - INFO, "Update Ble medium failed. There is no medium registered."); - return; - } - auto& context = item->second; - context.accepted_connection_callback = std::move(callback); - NEARBY_LOG(INFO, - "Update Ble medium for accepted callback: this=%p; " - "medium=%p; service_id=%s; ", - this, &medium, service_id.c_str()); - }); + RunOnMediumEnvironmentThread( + [this, &medium, service_id, callback = std::move(callback)]() { + auto item = ble_mediums_.find(&medium); + if (item == ble_mediums_.end()) { + NEARBY_LOG( + INFO, "Update Ble medium failed. There is no medium registered."); + return; + } + auto& context = item->second; + context.accepted_connection_callback = std::move(callback); + NEARBY_LOG(INFO, + "Update Ble medium for accepted callback: this=%p; " + "medium=%p; service_id=%s; ", + this, &medium, service_id.c_str()); + }); } void MediumEnvironment::UnregisterBleMedium(api::BleMedium& medium) { @@ -451,6 +451,15 @@ void MediumEnvironment::SendWebRtcSignalingMessage(absl::string_view peer_id, }); } +void MediumEnvironment::SetUseValidPeerConnection( + bool use_valid_peer_connection) { + use_valid_peer_connection_ = use_valid_peer_connection; +} + +bool MediumEnvironment::GetUseValidPeerConnection() { + return use_valid_peer_connection_; +} + void MediumEnvironment::RegisterWifiLanMedium(api::WifiLanMedium& medium) { if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium]() { diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index a1a0f27e..f873d53d 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -124,6 +124,12 @@ class MediumEnvironment { void SendWebRtcSignalingMessage(absl::string_view peer_id, const ByteArray& message); + // Used to set if WebRtcMedium should use a valid peer connection or nullptr + // in tests. + void SetUseValidPeerConnection(bool use_valid_peer_connection); + + bool GetUseValidPeerConnection(); + // Adds medium-related info to allow for scanning/advertising to work. // This provides acccess to this medium from other mediums, when protocol // expects they should communicate. @@ -207,7 +213,7 @@ class MediumEnvironment { // Returns WiFi LAN service matching IP address and port, or nullptr. api::WifiLanService* FindWifiLanService(const std::string& ip_address, - int port); + int port); private: struct BluetoothMediumContext { @@ -280,6 +286,8 @@ class MediumEnvironment { absl::flat_hash_map wifi_lan_mediums_; + + bool use_valid_peer_connection_ = true; }; } // namespace nearby diff --git a/cpp/platform_v2/impl/g3/webrtc.cc b/cpp/platform_v2/impl/g3/webrtc.cc index 2d98544c..7dc16a2f 100644 --- a/cpp/platform_v2/impl/g3/webrtc.cc +++ b/cpp/platform_v2/impl/g3/webrtc.cc @@ -33,6 +33,12 @@ void WebRtcSignalingMessenger::StopReceivingMessages() { void WebRtcMedium::CreatePeerConnection( webrtc::PeerConnectionObserver* observer, PeerConnectionCallback callback) { + auto& env = MediumEnvironment::Instance(); + if (!env.GetUseValidPeerConnection()) { + callback(nullptr); + return; + } + webrtc::PeerConnectionInterface::RTCConfiguration rtc_config; webrtc::PeerConnectionDependencies dependencies(observer); diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 7161eb7e..5c3207e5 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -46,7 +46,8 @@ bool BleMedium::StartScanning(const std::string& service_id, &context.peripheral, &peripheral, peripheral.GetName().c_str()); discovered_peripheral_callback_.peripheral_discovered_cb( - context.peripheral, service_id); + context.peripheral, service_id, + /*fast_advertisement=*/false); } }, .peripheral_lost_cb = diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 5cb89f08..233b1abb 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -72,9 +72,10 @@ class BleMedium final { using Platform = api::ImplementationPlatform; struct DiscoveredPeripheralCallback { std::function + const std::string& service_id, + bool fast_advertisement)> peripheral_discovered_cb = - DefaultCallback(); + DefaultCallback(); std::function peripheral_lost_cb = diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index d1fcf653..0d4e2590 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -55,13 +55,13 @@ TEST_F(BleMediumTest, CanStartAdvertising) { ble_a.StartAdvertising(service_id, advertisement_bytes); EXPECT_TRUE(ble_b.StartScanning( - service_id, DiscoveredPeripheralCallback{ - .peripheral_discovered_cb = - [&found_latch](BlePeripheral& peripheral, - const std::string& service_id) { - found_latch.CountDown(); - }, - })); + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch]( + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { found_latch.CountDown(); }, + })); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_a.StopAdvertising(service_id)); EXPECT_TRUE(ble_b.StopScanning(service_id)); @@ -79,19 +79,19 @@ TEST_F(BleMediumTest, CanStartScanning) { CountDownLatch found_latch(1); CountDownLatch lost_latch(1); - ble_a.StartScanning(service_id, - DiscoveredPeripheralCallback{ - .peripheral_discovered_cb = - [&found_latch](BlePeripheral& peripheral, - const std::string& service_id) { - found_latch.CountDown(); - }, - .peripheral_lost_cb = - [&lost_latch](BlePeripheral& peripheral, - const std::string& service_id) { - lost_latch.CountDown(); - }, - }); + ble_a.StartScanning( + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch]( + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { found_latch.CountDown(); }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + }); EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_b.StopAdvertising(service_id)); @@ -111,19 +111,19 @@ TEST_F(BleMediumTest, CanStopDiscovery) { CountDownLatch found_latch(1); CountDownLatch lost_latch(1); - ble_a.StartScanning(service_id, - DiscoveredPeripheralCallback{ - .peripheral_discovered_cb = - [&found_latch](BlePeripheral& peripheral, - const std::string& service_id) { - found_latch.CountDown(); - }, - .peripheral_lost_cb = - [&lost_latch](BlePeripheral& peripheral, - const std::string& service_id) { - lost_latch.CountDown(); - }, - }); + ble_a.StartScanning( + service_id, + DiscoveredPeripheralCallback{ + .peripheral_discovered_cb = + [&found_latch]( + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { found_latch.CountDown(); }, + .peripheral_lost_cb = + [&lost_latch](BlePeripheral& peripheral, + const std::string& service_id) { + lost_latch.CountDown(); + }, + }); EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_a.StopScanning(service_id)); @@ -149,9 +149,13 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( - BlePeripheral& peripheral, const std::string& service_id) { - NEARBY_LOG(INFO, "Peripheral discovered: %s, %p", - peripheral.GetName().c_str(), &peripheral); + BlePeripheral& peripheral, const std::string& service_id, + bool fast_advertisement) { + NEARBY_LOG( + INFO, + "Peripheral discovered: %s, %p, fast advertisement: %d", + peripheral.GetName().c_str(), &peripheral, + fast_advertisement); discovered_peripheral = &peripheral; found_latch.CountDown(); }, diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 0eeb5080..1e94c48a 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -136,6 +136,20 @@ enum StartAdvertisingError { // Next ID :46 } +// The error for event START_ADVERTISING. The range between 31 and 99. +enum StopAdvertisingError { + // System error, failed to stop advertising. + STOP_ADVERTISING_FAILED = 31; + // System error, failed to modify the Bluetooth name. + RESTORE_BLUETOOTH_NAME_FAILED = 32; + // System error, failed to stop advertising for BLE legacy advertisements. + STOP_LEGACY_ADVERTISING_FAILED = 33; + // System error, failed to stop advertising for BLE extended advertisements. + STOP_EXTENDED_ADVERTISING_FAILED = 34; + + // Next ID :35 +} + // The error for event START_DISCOVERING. The range between 31 and 99. enum StartDiscoveringError { // Developing error, this service ID already requested, should not request it @@ -184,6 +198,11 @@ enum StartListeningIncomingConnectionError { // Network error, wait the GATT connection ready after the connection // established but never. CREATE_GATT_SERVER_SOCKET_NOT_READY = 35; + // System error, failed to accept the incoming connection + ACCEPT_CONNECTION_FAILED = 36; + // System error, failed to create a server socket for listening incoming + // connection. + CREATE_SERVER_SOCKET_FAILED = 37; // Next ID :36 } @@ -366,4 +385,15 @@ enum Description { WITHOUT_PSM_VALUE = 130; SOCKET_BIND_LISTEN_FAILED = 131; UNEXPECTED_PACKET_CONTENT = 132; + UNREGISTER_NSD_MANAGER_FAILED = 133; + PUBLISH_EMPTY_ADVERTISEMENT_FAILED = 134; + BLUETOOTH_SOCKET_NOT_IN_LISTENING_STATE = 135; + INVALID_BLUETOOTH_SOCKET_SIGNAL_SIZE = 136; + INVALID_BLUETOOTH_SOCKET_SIGNAL_STATUS = 137; + GET_ADDRESS_FAILED = 138; + NULL_LOCAL_ADDRESS = 139; + IS_LOOPBACK_ADDRESS = 140; + SOCKET_NOT_BOUND = 141; + INVALID_REMOTE_ADDRESS = 142; + SOCKET_ALREADY_BOUND = 143; } From 3b7071cf0f84f2acfe1ad5aedd695b02ce8ba04a Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Mon, 14 Sep 2020 02:00:04 -0700 Subject: [PATCH 43/52] Roll forward to cl/331498408 Signed-off-by: Alexey Polyudov Change-Id: If30a8df80799adec3aafba5b8dd4c539fc21e16d --- cpp/core_v2/internal/BUILD | 8 + cpp/core_v2/internal/base_bwu_handler.h | 48 ++ .../internal/base_pcp_handler_test.cc.orig | 538 +++++++++++++ cpp/core_v2/internal/bwu_handler.h | 74 ++ cpp/core_v2/internal/bwu_manager.cc | 757 ++++++++++++++++++ cpp/core_v2/internal/bwu_manager.h | 176 ++++ cpp/core_v2/internal/bwu_manager_test.cc | 41 + cpp/core_v2/internal/client_proxy.cc | 13 +- cpp/core_v2/internal/client_proxy.h | 13 +- cpp/core_v2/internal/client_proxy_test.cc | 2 +- cpp/core_v2/internal/mediums/ble.cc | 14 +- cpp/core_v2/internal/mediums/ble.h | 3 +- cpp/core_v2/internal/mediums/ble_test.cc | 13 +- .../internal/mediums/bluetooth_classic.cc | 5 + .../internal/mediums/bluetooth_classic.h | 2 + cpp/core_v2/internal/mediums/utils.cc | 20 + cpp/core_v2/internal/mediums/utils.h | 2 + cpp/core_v2/internal/offline_frames.cc | 18 + cpp/core_v2/internal/offline_frames.h | 1 + .../internal/offline_service_controller.cc | 5 +- .../internal/offline_service_controller.h | 18 +- .../offline_service_controller.h.orig | 81 ++ .../internal/p2p_cluster_pcp_handler.cc | 3 +- cpp/core_v2/internal/webrtc_bwu_handler.cc | 142 ++++ cpp/core_v2/internal/webrtc_bwu_handler.h | 79 ++ cpp/core_v2/listeners.h | 13 +- cpp/core_v2/listeners_test.cc | 4 +- cpp/platform_v2/api/ble.h | 11 +- cpp/platform_v2/base/medium_environment.cc | 30 +- cpp/platform_v2/base/medium_environment.h | 6 +- cpp/platform_v2/impl/g3/BUILD | 8 +- cpp/platform_v2/impl/g3/ble.cc | 16 +- cpp/platform_v2/impl/g3/ble.h | 5 +- cpp/platform_v2/public/ble.cc | 13 +- cpp/platform_v2/public/ble.h | 3 +- cpp/platform_v2/public/ble_test.cc | 17 +- cpp/platform_v2/public/bluetooth_adapter.h | 2 + .../public/bluetooth_adapter_test.cc | 10 + cpp/platform_v2/public/bluetooth_classic.h | 1 + proto/BUILD | 5 + proto/error_code_enums.proto | 14 +- 41 files changed, 2153 insertions(+), 81 deletions(-) create mode 100644 cpp/core_v2/internal/base_bwu_handler.h create mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc.orig create mode 100644 cpp/core_v2/internal/bwu_handler.h create mode 100644 cpp/core_v2/internal/bwu_manager.cc create mode 100644 cpp/core_v2/internal/bwu_manager.h create mode 100644 cpp/core_v2/internal/bwu_manager_test.cc create mode 100644 cpp/core_v2/internal/offline_service_controller.h.orig create mode 100644 cpp/core_v2/internal/webrtc_bwu_handler.cc create mode 100644 cpp/core_v2/internal/webrtc_bwu_handler.h diff --git a/cpp/core_v2/internal/BUILD b/cpp/core_v2/internal/BUILD index 7aea432f..565a6d85 100644 --- a/cpp/core_v2/internal/BUILD +++ b/cpp/core_v2/internal/BUILD @@ -7,6 +7,7 @@ cc_library( "ble_endpoint_channel.cc", "bluetooth_device_name.cc", "bluetooth_endpoint_channel.cc", + "bwu_manager.cc", "client_proxy.cc", "encryption_runner.cc", "endpoint_channel_manager.cc", @@ -21,17 +22,21 @@ cc_library( "payload_manager.cc", "pcp_manager.cc", "service_controller_router.cc", + "webrtc_bwu_handler.cc", "webrtc_endpoint_channel.cc", "wifi_lan_endpoint_channel.cc", "wifi_lan_service_info.cc", ], hdrs = [ + "base_bwu_handler.h", "base_endpoint_channel.h", "base_pcp_handler.h", "ble_advertisement.h", "ble_endpoint_channel.h", "bluetooth_device_name.h", "bluetooth_endpoint_channel.h", + "bwu_handler.h", + "bwu_manager.h", "client_proxy.h", "encryption_runner.h", "endpoint_channel.h", @@ -50,6 +55,7 @@ cc_library( "pcp_manager.h", "service_controller.h", "service_controller_router.h", + "webrtc_bwu_handler.h", "webrtc_endpoint_channel.h", "wifi_lan_endpoint_channel.h", "wifi_lan_service_info.h", @@ -119,6 +125,7 @@ cc_test( "base_pcp_handler_test.cc", "ble_advertisement_test.cc", "bluetooth_device_name_test.cc", + "bwu_manager_test.cc", "client_proxy_test.cc", "encryption_runner_test.cc", "endpoint_channel_manager_test.cc", @@ -137,6 +144,7 @@ cc_test( ":internal", ":internal_test", "//core_v2:core_types", + "//core_v2/internal/mediums", "//proto/connections:offline_wire_formats_portable_proto", "//platform_v2/base", "//platform_v2/base:test_util", diff --git a/cpp/core_v2/internal/base_bwu_handler.h b/cpp/core_v2/internal/base_bwu_handler.h new file mode 100644 index 00000000..23b0abd0 --- /dev/null +++ b/cpp/core_v2/internal/base_bwu_handler.h @@ -0,0 +1,48 @@ +#ifndef CORE_V2_INTERNAL_BASE_BWU_HANDLER_H_ +#define CORE_V2_INTERNAL_BASE_BWU_HANDLER_H_ + +#include +#include +#include + +#include "core_v2/internal/bwu_handler.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +class BaseBwuHandler : public BwuHandler { + public: + using ClientIntroduction = BwuNegotiationFrame::ClientIntroduction; + + BaseBwuHandler(EndpointChannelManager& channel_manager, + BwuNotifications bwu_notifications) + : channel_manager_(&channel_manager), + bwu_notifications_(std::move(bwu_notifications)) {} + ~BaseBwuHandler() override = default; + void OnIncomingConnection(ClientProxy* client, + IncomingSocketConnection* connection); + + protected: + // Represents the incoming Socket the Initiator has gotten after initializing + // its upgraded bandwidth medium. + EndpointChannelManager* GetEndpointChannelManager(); + EndpointChannelManager* channel_manager_; + BwuNotifications bwu_notifications_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BASE_BWU_HANDLER_H_ diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig b/cpp/core_v2/internal/base_pcp_handler_test.cc.orig new file mode 100644 index 00000000..c9009413 --- /dev/null +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc.orig @@ -0,0 +1,538 @@ +#include "core_v2/internal/base_pcp_handler.h" + +#include +#include + +#include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/encryption_runner.h" +#include "core_v2/internal/offline_frames.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +constexpr BooleanMediumSelector kTestCases[] = { + BooleanMediumSelector{}, + BooleanMediumSelector{ + .bluetooth = true, + }, + BooleanMediumSelector{ + .wifi_lan = true, + }, + BooleanMediumSelector{ + .bluetooth = true, + .wifi_lan = true, + }, +}; + +class MockEndpointChannel : public BaseEndpointChannel { + public: + explicit MockEndpointChannel(Pipe* reader, Pipe* writer) + : BaseEndpointChannel("channel", &reader->GetInputStream(), + &writer->GetOutputStream()) {} + + ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } + Exception DoWrite(const ByteArray& data) { + return BaseEndpointChannel::Write(data); + } + absl::Time DoGetLastReadTimestamp() { + return BaseEndpointChannel::GetLastReadTimestamp(); + } + + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, CloseImpl, (), (override)); + MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); +}; + +class MockPcpHandler : public BasePcpHandler { + public: + using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; + + MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) + : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} + + // Expose protected inner types of a base type for mocking. + using BasePcpHandler::ConnectImplResult; + using BasePcpHandler::DiscoveredEndpoint; + using BasePcpHandler::StartOperationResult; + + MOCK_METHOD(Strategy, GetStrategy, (), (const override)); + MOCK_METHOD(Pcp, GetPcp, (), (const override)); + + MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), + (const, override)); + MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), + (const, override)); + + MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), + (const, override)); + MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), + (const, override)); + + MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, + (ClientProxy * client, const string& service_id, + const string& local_endpoint_id, + const string& local_endpoint_name, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); + MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, + (ClientProxy * client, const string& service_id, + const ConnectionOptions& options), + (override)); + MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); + MOCK_METHOD(ConnectImplResult, ConnectImpl, + (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); + MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), + (override)); + + std::vector GetConnectionMediumsByPriority() + override { + return GetDiscoveryMediums(); + } + + // Mock adapters for protected non-virtual methods of a base class. + void OnEndpointFound(ClientProxy* client, + std::shared_ptr endpoint) { + BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); + } + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { + BasePcpHandler::OnEndpointLost(client, endpoint); + } + + std::vector GetDiscoveryMediums() { + std::vector mediums; + auto allowed = + BasePcpHandler::GetDiscoveryOptions().CompatibleOptions().allowed; + // Mediums are sorted in order of decreasing preference. + if (allowed.wifi_lan) + mediums.push_back(proto::connections::Medium::WIFI_LAN); + if (allowed.web_rtc) mediums.push_back(proto::connections::Medium::WEB_RTC); + if (allowed.bluetooth) + mediums.push_back(proto::connections::Medium::BLUETOOTH); + return mediums; + } + + std::vector GetDiscoveredEndpoints( + const std::string& endpoint_id) { + return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); + } +}; + +class MockContext { + public: + explicit MockContext(std::atomic_int* destroyed = nullptr) { + destroyed_ = destroyed; + } + MockContext(MockContext&&) = default; + MockContext& operator=(MockContext&&) = default; + + ~MockContext() { + if (destroyed_) (*destroyed_)++; + } + + private: + Swapper destroyed_{nullptr}; +}; + +struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { + MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) + : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} + + MockContext context; +}; + +class BasePcpHandlerTest + : public ::testing::TestWithParam { + protected: + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + StrictMock< + MockFunction> + endpoint_distance_changed_cb; + }; + + void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, + BooleanMediumSelector allowed = GetParam()) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .allowed = allowed, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + ConnectionRequestInfo info{ + .name = "remote_endpoint_name", + .listener = connection_listener_, + }; + EXPECT_CALL(*pcp_handler, + StartAdvertisingImpl(client, service_id, _, info.name, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, + BooleanMediumSelector allowed = GetParam()) { + std::string service_id{"service"}; + ConnectionOptions options{ + .strategy = Strategy::kP2pCluster, + .allowed = allowed, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) + .WillOnce(Return(MockPcpHandler::StartOperationResult{ + .status = {Status::kSuccess}, + .mediums = {Medium::BLE}, + })); + EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, + discovery_listener_), + Status{Status::kSuccess}); + EXPECT_TRUE(client->IsDiscovering()); + } + + std::pair, + std::unique_ptr> + SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT + auto channel_a = std::make_unique(&pipe_b, &pipe_a); + auto channel_b = std::make_unique(&pipe_a, &pipe_b); + // On initiator (A) side, we drop the first write, since this is a + // connection establishment packet, and we don't have the peer entity, just + // the peer channel. The rest of the exchange must happen for the benefit of + // DH key exchange. + EXPECT_CALL(*channel_a, Read()) + .WillRepeatedly(Invoke( + [channel = channel_a.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_a, Write(_)) + .WillOnce(Return(Exception{Exception::kSuccess})) + .WillRepeatedly( + Invoke([channel = channel_a.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_a, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); + EXPECT_CALL(*channel_b, Read()) + .WillRepeatedly(Invoke( + [channel = channel_b.get()]() { return channel->DoRead(); })); + EXPECT_CALL(*channel_b, Write(_)) + .WillRepeatedly( + Invoke([channel = channel_b.get()](const ByteArray& data) { + return channel->DoWrite(data); + })); + EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel_b, GetLastReadTimestamp) + .WillRepeatedly(Return(absl::Now())); + EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); + return std::make_pair(std::move(channel_a), std::move(channel_b)); + } + + void RequestConnection(const std::string& endpoint_id, + std::unique_ptr channel_a, + MockEndpointChannel* channel_b, ClientProxy* client, + MockPcpHandler* pcp_handler, + std::atomic_int* flag = nullptr) { + ConnectionRequestInfo info{ + .name = "ABCD", + .listener = connection_listener_, + }; + EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); + EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*pcp_handler, GetStrategy) + .WillRepeatedly(Return(Strategy::kP2pCluster)); + EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); + // Simulate successful discovery. + auto encryption_runner = std::make_unique(); + auto allowed_mediums = pcp_handler->GetDiscoveryMediums(); + + EXPECT_CALL(*pcp_handler, ConnectImpl) + .WillOnce(Invoke([&channel_a, medium = allowed_mediums[0]]( + ClientProxy* client, + MockPcpHandler::DiscoveredEndpoint* endpoint) { + return MockPcpHandler::ConnectImplResult{ + .medium = medium, + .status = {Status::kSuccess}, + .endpoint_channel = std::move(channel_a), + }; + })); + + for (const auto& medium : allowed_mediums) { + pcp_handler->OnEndpointFound( + client, + std::make_shared(MockDiscoveredEndpoint{ + { + endpoint_id, + info.name, + "service", + medium, + }, + MockContext{flag}, + })); + } + auto other_client = std::make_unique(); + + // Run peer crypto in advance, if channel_b is provided. + // Otherwise stay in not-encrypted state. + if (channel_b != nullptr) { + encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, + {}); + } + EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Stopping Encryption Runner"); + } + + Pipe pipe_a_; + Pipe pipe_b_; + MockConnectionListener mock_connection_listener_; + MockDiscoveryListener mock_discovery_listener_; + ConnectionListener connection_listener_{ + .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_connection_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = + mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = + mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), + .endpoint_distance_changed_cb = + mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), + }; +}; + +TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + SUCCEED(); +} + +TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartAdvertising(&client, &pcp_handler); +} + +TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartAdvertising(&client, &pcp_handler); + EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); + EXPECT_TRUE(client.IsAdvertising()); + pcp_handler.StopAdvertising(&client); + EXPECT_FALSE(client.IsAdvertising()); +} + +TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartDiscovery(&client, &pcp_handler); +} + +TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { + ClientProxy client; + EndpointChannelManager ecm; + EndpointManager em(&ecm); + MockPcpHandler pcp_handler(&em, &ecm); + StartDiscovery(&client, &pcp_handler); + EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); + EXPECT_TRUE(client.IsDiscovering()); + pcp_handler.StopDiscovery(&client); + EXPECT_FALSE(client.IsDiscovering()); +} + +TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { + 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_a = channel_pair.first; + auto& channel_b = channel_pair.second; + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); + NEARBY_LOG(INFO, "RequestConnection complete"); + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); +} + +TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { + 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_a = channel_pair.first; + auto& channel_b = channel_pair.second; + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), + Status{Status::kSuccess}); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); +} + +TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { + 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; + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); + RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), + &client, &pcp_handler); + NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; + EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), + Status{Status::kSuccess}); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); +} + +TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { + 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_a = channel_pair.first; + auto& channel_b = channel_pair.second; + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, + &pcp_handler); + NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; + EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); + EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) + .Times(AtLeast(0)); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), + Status{Status::kSuccess}); + NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); + auto frame = + parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); + pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, + Medium::BLE); + NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); +} + +TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { + std::atomic_int destroyed_flag = 0; + int mediums_count = 0; + { + 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_a = channel_pair.first; + auto& channel_b = channel_pair.second; + EXPECT_CALL(*channel_a, CloseImpl).Times(1); + EXPECT_CALL(*channel_b, CloseImpl).Times(1); + RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), + &client, &pcp_handler, &destroyed_flag); + mediums_count = pcp_handler.GetDiscoveryMediums().size(); + NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", + endpoint_id.c_str()); + EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), + Status{Status::kSuccess}); + EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); + NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); + channel_b->Close(); + pcp_handler.DisconnectFromEndpointManager(); + } + EXPECT_EQ(destroyed_flag.load(), mediums_count); +} + +INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, + ::testing::ValuesIn(kTestCases)); + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/bwu_handler.h b/cpp/core_v2/internal/bwu_handler.h new file mode 100644 index 00000000..8e926a8c --- /dev/null +++ b/cpp/core_v2/internal/bwu_handler.h @@ -0,0 +1,74 @@ +#ifndef CORE_V2_INTERNAL_BWU_HANDLER_H_ +#define CORE_V2_INTERNAL_BWU_HANDLER_H_ + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/public/count_down_latch.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +using BwuNegotiationFrame = BandwidthUpgradeNegotiationFrame; + +// Defines the set of methods that need to be implemented to handle the +// per-Medium-specific operations needed to upgrade an EndpointChannel. +class BwuHandler { + public: + using UpgradePathInfo = parser::UpgradePathInfo; + + virtual ~BwuHandler() = default; + + // Called by the Initiator to setup the upgraded medium for this endpoint (if + // that hasn't already been done), and returns a serialized UpgradePathInfo + // that can be sent to the Responder. + // @BwuHandlerThread + virtual ByteArray InitializeUpgradedMediumForEndpoint( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id) = 0; + // Called to revert any state changed by the Initiator to setup the upgraded + // medium for an endpoint. + // @BwuHandlerThread + virtual void Revert() = 0; + + // Called by the Responder to setup the upgraded medium for this endpoint (if + // that hasn't already been done) using the UpgradePathInfo sent by the + // Initiator, and returns a new EndpointChannel for the upgraded medium. + // @BwuHandlerThread + virtual std::unique_ptr CreateUpgradedEndpointChannel( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info) = 0; + // Returns the upgrade medium of the BwuHandler. + // @BwuHandlerThread + virtual Medium GetUpgradeMedium() const = 0; + virtual void OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id) = 0; + + class IncomingSocket { + public: + virtual ~IncomingSocket() = default; + + virtual std::string ToString() = 0; + virtual void Close() = 0; + }; + + struct IncomingSocketConnection { + std::unique_ptr socket; + std::unique_ptr channel; + }; + + struct BwuNotifications { + std::function + incoming_connection_cb; + }; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BWU_HANDLER_H_ diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc new file mode 100644 index 00000000..4756690d --- /dev/null +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -0,0 +1,757 @@ +#include "core_v2/internal/bwu_manager.h" + +#include + +#include "core_v2/internal/bwu_handler.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "proto/connections_enums.pb.h" +#include "absl/functional/bind_front.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +using ::location::nearby::proto::connections::ConnectionAttemptResult; +using ::location::nearby::proto::connections::DisconnectionReason; + +BwuManager::BwuManager( + Mediums& mediums, EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, + absl::flat_hash_map> handlers, + Config config) + : config_(config), + mediums_(&mediums), + endpoint_manager_(&endpoint_manager), + channel_manager_(&channel_manager) { + if (config_.bandwidth_upgrade_retry_delay == absl::ZeroDuration()) { + config_.bandwidth_upgrade_retry_delay = absl::Seconds(5); + } + if (config_.bandwidth_upgrade_retry_delay == absl::ZeroDuration()) { + config_.bandwidth_upgrade_retry_delay = absl::Seconds(10); + } + if (config_.allow_upgrade_to.All(false)) { + config.allow_upgrade_to.web_rtc = true; + } + if (!handlers.empty()) { + handlers_ = std::move(handlers); + } else { + InitBwuHandlers(); + } + + // Register the offline frame processor. + endpoint_manager.RegisterFrameProcessor( + V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, this); +} + +void BwuManager::InitBwuHandlers() { + // Register the supported concrete BwuMedium implementations. + BwuHandler::BwuNotifications notifications{ + .incoming_connection_cb = + absl::bind_front(&BwuManager::OnIncomingConnection, this), + }; + // TODO(apolyudov): inject instances of supported upgrade medium handlers. +} + +void BwuManager::Shutdown() { + NEARBY_LOG(INFO, "Initiating shutdown of BwuManager."); + + endpoint_manager_->UnregisterFrameProcessor( + V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, this); + + CountDownLatch latch(1); + + RunOnBwuManagerThread([this, &latch]() { + for (auto& item : previous_endpoint_channels_) { + EndpointChannel* channel = item.second.get(); + if (!channel) continue; + channel->Close(DisconnectionReason::SHUTDOWN); + } + + CancelAllRetryUpgradeAlarms(); + medium_ = Medium::UNKNOWN_MEDIUM; + for (auto& item : handlers_) { + BwuHandler& handler = *item.second; + handler.Revert(); + } + handlers_.clear(); + latch.CountDown(); + }); + + latch.Await(); + + // Stop all the ongoing Runnables (as gracefully as possible). + alarm_executor_.Shutdown(); + serial_executor_.Shutdown(); + + NEARBY_LOG(INFO, "BwuHandler has shut down."); +} + +// This is the point on the Initiator side where the +// currentBwuMedium is set. +void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + RunOnBwuManagerThread([this, client, endpoint_id]() { + auto* handler = SetCurrentBwuHandler(ChooseBestUpgradeMedium( + client->GetUpgradeMediums(endpoint_id).GetMediums(true))); + + if (!handler) return; + + if (in_progress_upgrades_.contains(endpoint_id)) { + return; + } + + auto channel = channel_manager_->GetChannelForEndpoint(endpoint_id); + + if (channel == nullptr) { + return; + } + + // Ignore requests where the medium we're upgrading to is the medium we're + // already connected over. This can happen now that Bluetooth is both an + // advertising medium and a potential bandwidth upgrade, and will continue + // to be possible as we add other new advertising mediums like mDNS (WiFi + // LAN). Very specifically, this happens now when a device uses P2P_CLUSTER, + // connects over Bluetooth, and is not connected to LAN. Bluetooth is the + // best medium, and we attempt to upgrade from Bluetooth to Bluetooth. + if (medium_ == channel->GetMedium()) { + return; + } + + std::string service_id = client->GetServiceId(); + ByteArray bytes = handler->InitializeUpgradedMediumForEndpoint( + client, service_id, endpoint_id); + + // Because we grab the endpointChannel first thing, it is possible the + // endpointChannel is stale by the time we attempt to write over it. + if (bytes.Empty()) { + NEARBY_LOG(ERROR, + "Couldn't complete the upgrade for endpoint " + "%s to %d because it failed to initialize the " + "BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE OfflineFrame.", + endpoint_id.c_str(), medium_); + UpgradePathInfo info; + info.set_medium(parser::MediumToUpgradePathInfoMedium(medium_)); + + ProcessUpgradeFailureEvent(client, endpoint_id, info); + return; + } + if (!channel->Write(bytes).Ok()) { + NEARBY_LOG(ERROR, + "Couldn't complete the upgrade for endpoint %s to %d because " + "it failed to write the " + "BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE OfflineFrame.", + endpoint_id.c_str(), medium_); + return; + } + + NEARBY_LOG(INFO, + "Successfully wrote the BWU_NEGOTIATION.UPGRADE_PATH_AVAILABLE " + "OfflineFrame while upgrading endpoint %s to %d.", + endpoint_id.c_str(), medium_); + in_progress_upgrades_.emplace(endpoint_id, client); + }); +} + +void BwuManager::OnIncomingFrame(OfflineFrame& frame, + const std::string& endpoint_id, + ClientProxy* client, Medium medium) { + if (parser::GetFrameType(frame) != V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION) + return; + auto bwu_frame = frame.v1().bandwidth_upgrade_negotiation(); + CountDownLatch latch(1); + RunOnBwuManagerThread([this, client, endpoint_id, &bwu_frame, &latch]() { + OnBwuNegotiationFrame(client, bwu_frame, endpoint_id); + latch.CountDown(); + }); + latch.Await(); +} + +void BwuManager::OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier) { + RunOnBwuManagerThread([this, client, endpoint_id, barrier]() { + if (medium_ == Medium::UNKNOWN_MEDIUM) { + barrier->CountDown(); + return; + } + + if (handler_) { + handler_->OnEndpointDisconnect(client, endpoint_id); + } + + auto item = old_channels_.extract(endpoint_id); + + if (!item.empty()) { + auto old_channel = item.mapped(); + if (old_channel != nullptr) { + old_channel->Close(DisconnectionReason::SHUTDOWN); + } + } + in_progress_upgrades_.erase(endpoint_id); + CancelRetryUpgradeAlarm(endpoint_id); + + successfully_upgraded_endpoints_.erase(endpoint_id); + + // If this was our very last endpoint: + // + // a) revert all the changes for currentBwuMedium. + // b) reset currentBwuMedium. + if (channel_manager_->GetConnectedEndpointsCount() <= 1) { + Revert(); + } + barrier->CountDown(); + }); +} + +BwuHandler* BwuManager::SetCurrentBwuHandler(Medium medium) { + handler_ = nullptr; + medium_ = medium; + if (medium != Medium::UNKNOWN_MEDIUM) { + auto item = handlers_.find(medium); + if (item != handlers_.end()) { + handler_ = item->second.get(); + } + } + return handler_; +} + +void BwuManager::Revert() { + if (handler_) { + handler_->Revert(); + medium_ = Medium::UNKNOWN_MEDIUM; + handler_ = nullptr; + } +} + +void BwuManager::OnBwuNegotiationFrame(ClientProxy* client, + const BwuNegotiationFrame& frame, + const string& endpoint_id) { + switch (frame.event_type()) { + case BwuNegotiationFrame::UPGRADE_PATH_AVAILABLE: + ProcessBwuPathAvailableEvent(client, endpoint_id, + frame.upgrade_path_info()); + break; + case BwuNegotiationFrame::UPGRADE_FAILURE: + ProcessUpgradeFailureEvent(client, endpoint_id, + frame.upgrade_path_info()); + break; + case BwuNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL: + ProcessLastWriteToPriorChannelEvent(client, endpoint_id); + break; + case BwuNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL: + ProcessSafeToClosePriorChannelEvent(client, endpoint_id); + break; + default: + break; + } +} + +void BwuManager::OnIncomingConnection( + ClientProxy* client, BwuHandler::IncomingSocketConnection* connection) { + RunOnBwuManagerThread([this, client, connection]() { + EndpointChannel* channel = connection->channel.get(); + if (channel == nullptr) { + connection->socket->Close(); + return; + } + + ClientIntroduction introduction; + if (!ReadClientIntroductionFrame(channel, introduction)) { + // This was never a fully EstablishedConnection, no need to provide a + // closure reason. + channel->Close(); + return; + } + + const std::string& endpoint_id = introduction.endpoint_id(); + auto item = in_progress_upgrades_.extract(endpoint_id); + if (item.empty()) return; + ClientProxy* mapped_client = item.mapped(); + CancelRetryUpgradeAlarm(endpoint_id); + if (mapped_client == nullptr) { + // This was never a fully EstablishedConnection, no need to provide a + // closure reason. + channel->Close(); + return; + } + + CHECK(client == mapped_client); + + // Use the introductory client information sent over to run the upgrade + // protocol. + RunUpgradeProtocol(mapped_client, endpoint_id, + std::move(connection->channel)); + }); +} + +void BwuManager::RunOnBwuManagerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +void BwuManager::RunUpgradeProtocol( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr new_channel) { + // First, register this new EndpointChannel as *the* EndpointChannel to use + // for this endpoint here onwards. NOTE: We pause this new EndpointChannel + // until we've completely drained the old EndpointChannel to avoid out of + // order reads on the other side. This is a consequence of using the same + // UKEY2 context for both the previous and new EndpointChannels. UKEY2 uses + // sequence numbers for writes and reads, and simultaneously sending Payloads + // on the new channel and control messages on the old channel cause the other + // side to read messages out of sequence + new_channel->Pause(); + auto old_channel = channel_manager_->GetChannelForEndpoint(endpoint_id); + if (!old_channel) return; + channel_manager_->ReplaceChannelForEndpoint(client, endpoint_id, + std::move(new_channel)); + + // Next, initiate a clean shutdown for the previous EndpointChannel used for + // this endpoint by telling the remote device that it will not receive any + // more writes over that EndpointChannel. + if (!old_channel->Write(parser::ForBwuLastWrite()).Ok()) { + return; + } + + // The remainder of this clean shutdown for the previous EndpointChannel will + // continue when we receive a corresponding + // BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame from + // the remote device, so for now, just store that previous EndpointChannel. + old_channels_.emplace(endpoint_id, old_channel); + + // If we already read LAST_WRITE on the old endpoint channel, then we can + // safely close it now. + auto item = successfully_upgraded_endpoints_.extract(endpoint_id); + if (!item.empty()) { + ProcessLastWriteToPriorChannelEvent(client, endpoint_id); + } +} + +// Outgoing BWU session. +void BwuManager::ProcessBwuPathAvailableEvent( + ClientProxy* client, const string& endpoint_id, + const UpgradePathInfo& upgrade_path_info) { + Medium medium = + parser::UpgradePathInfoMediumToMedium(upgrade_path_info.medium()); + if (medium_ == Medium::UNKNOWN_MEDIUM) { + SetCurrentBwuHandler(medium); + } + // Check for the correct medium so we don't process an incorrect OfflineFrame. + if (medium != medium_) { + RunUpgradeFailedProtocol(client, endpoint_id, upgrade_path_info); + return; + } + + auto channel = ProcessBwuPathAvailableEventInternal(client, endpoint_id, + upgrade_path_info); + ConnectionAttemptResult connectionAttemptResult; + if (channel != nullptr) { + connectionAttemptResult = ConnectionAttemptResult::RESULT_SUCCESS; + } else { + connectionAttemptResult = ConnectionAttemptResult::RESULT_ERROR; + } + + if (channel == nullptr) { + RunUpgradeFailedProtocol(client, endpoint_id, upgrade_path_info); + return; + } + + RunUpgradeProtocol(client, endpoint_id, std::move(channel)); +} + +std::unique_ptr +BwuManager::ProcessBwuPathAvailableEventInternal( + ClientProxy* client, const string& endpoint_id, + const UpgradePathInfo& upgrade_path_info) { + std::unique_ptr channel = + handler_->CreateUpgradedEndpointChannel(client, client->GetServiceId(), + endpoint_id, upgrade_path_info); + if (!channel) { + return nullptr; + } + + // Write the requisite BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION as + // the first OfflineFrame on this new EndpointChannel. + if (!channel->Write(parser::ForBwuIntroduction(client->GetLocalEndpointId())) + .Ok()) { + // This was never a fully EstablishedConnection, no need to provide a + // closure reason. + channel->Close(); + + NEARBY_LOG( + ERROR, + "Failed to write BWU_NEGOTIATION.CLIENT_INTRODUCTION OfflineFrame to " + "newly-created EndpointChannel %s, aborting upgrade.", + channel->GetName().c_str()); + + return {}; + } + + NEARBY_LOG( + INFO, + "Successfully wrote BWU_NEGOTIATION.CLIENT_INTRODUCTION OfflineFrame to " + "newly-created EndpointChannel %s while upgrading endpoint %s.", + channel->GetName().c_str(), endpoint_id.c_str()); + + // Set the AnalyticsRecorder so that the future closure of this + // EndpointChannel will be recorded. + return channel; +} + +void BwuManager::RunUpgradeFailedProtocol( + ClientProxy* client, const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info) { + // We attempted to connect to the new medium that the remote device has set up + // for us but we failed. We need to let the remote device know so that they + // can pick another medium for us to try. + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (!channel) { + NEARBY_LOG(ERROR, + "Couldn't find a previous EndpointChannel for %s " + "when sending an upgrade failure frame, short-circuiting the " + "upgrade protocol.", + endpoint_id.c_str()); + return; + } + + // Report UPGRADE_FAILURE to the remote device. + if (!channel->Write(parser::ForBwuFailure(upgrade_path_info)).Ok()) { + channel->Close(DisconnectionReason::IO_ERROR); + + NEARBY_LOG( + ERROR, + "Failed to write BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_FAILURE " + "OfflineFrame to endpoint %s, short-circuiting the upgrade protocol.", + endpoint_id.c_str()); + return; + } + + // And lastly, clean up our currentBwuMedium since we failed to + // utilize it anyways. + if (medium_ != Medium::UNKNOWN_MEDIUM) { + Revert(); + } +} + +bool BwuManager::ReadClientIntroductionFrame(EndpointChannel* channel, + ClientIntroduction& introduction) { + auto data = channel->Read(); + if (!data.ok()) return false; + auto transfer(parser::FromBytes(data.result())); + if (!transfer.ok()) return false; + OfflineFrame frame = transfer.result(); + if (!frame.has_v1() || !frame.v1().has_bandwidth_upgrade_negotiation()) + return false; + const auto& frame_intro = + frame.v1().bandwidth_upgrade_negotiation().client_introduction(); + introduction = frame_intro; + return true; +} + +void BwuManager::ProcessLastWriteToPriorChannelEvent( + ClientProxy* client, const std::string& endpoint_id) { + // By this point in the upgrade protocol, there is the guarantee that both + // involved endpoints have registered a new EndpointChannel with the + // EndpointChannelManager as the official channel for communication; given + // the way communication is structured in the EndpointManager, this means + // that all new writes are happening over that new EndpointChannel, but + // reads are still happening over this prior EndpointChannel (to avoid data + // loss). But now that we've received this definitive final write over that + // prior EndpointChannel, we can let the remote device that they can safely + // close their end of this now-dormant EndpointChannel. + EndpointChannel* previous_endpoint_channel = + previous_endpoint_channels_[endpoint_id].get(); + if (!previous_endpoint_channel) { + NEARBY_LOG( + ERROR, + "Received a BWU_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame " + "for unknown endpoint %s, can't complete the upgrade protocol.", + endpoint_id.c_str()); + + successfully_upgraded_endpoints_.emplace(endpoint_id); + return; + } + try { + previous_endpoint_channel->Write(parser::ForBwuSafeToClose()); + } catch (IOException e) { + previous_endpoint_channel->Close(DisconnectionReason::IO_ERROR); + // Remove this prior EndpointChannel from previous_endpoint_channels to + // avoid leaks. + previous_endpoint_channels_.erase(endpoint_id); + + NEARBY_LOG( + ERROR, + "Failed to write BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL " + "OfflineFrame to endpoint %s, short-circuiting the upgrade protocol.", + endpoint_id.c_str()); + return; + } + // The upgrade protocol's clean shutdown of the prior EndpointChannel will + // conclude when we receive a corresponding + // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame + // from the remote device. +} + +void BwuManager::ProcessSafeToClosePriorChannelEvent( + ClientProxy* client, const std::string& endpoint_id) { + // By this point in the upgrade protocol, there's no more writes happening + // over the prior EndpointChannel, and the remote device has given us the + // go-ahead to close this EndpointChannel [1], so we can safely close it + // (and depend on the EndpointManager querying the EndpointChannelManager to + // start reading from the new EndpointChannel). + // + // [1] Which also implies that they've received our + // BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame), + // so there can be no data loss, regardless of whether the EndpointChannel + // allows reads of queued, unread data after the EndpointChannel has been + // closed from the other end (as is the case with conventional TCP sockets) + // or not (as is the case with Android's Bluetooth sockets, where closing + // instantly throws an IOException on the remote device). + auto item = previous_endpoint_channels_.extract(endpoint_id); + auto& previous_endpoint_channel = item.mapped(); + if (previous_endpoint_channel == nullptr) { + NEARBY_LOG( + ERROR, + "Received a BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame " + "for unknown endpoint %s, can't complete the upgrade protocol.", + endpoint_id.c_str()); + return; + } + + NEARBY_LOG(INFO, + "BwuManager successfully received a " + "BWU_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame while " + "trying to upgrade endpoint %s.", + endpoint_id.c_str()); + + // Wait for in-flight messages to reach their peers. + SystemClock::Sleep(absl::Seconds(1)); + previous_endpoint_channel->Close(DisconnectionReason::UPGRADED); + + // Now that the old channel has been drained, we can unpause the new channel + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + + if (!channel) { + NEARBY_LOG(ERROR, + "Attempted to resume the current EndpointChannel with endpoint " + "%s, but none was found", + endpoint_id.c_str()); + return; + } + + channel->Resume(); + + // Report the success to the client + client->OnBandwidthChanged(endpoint_id, channel->GetMedium()); +} + +void BwuManager::ProcessUpgradeFailureEvent( + ClientProxy* client, const std::string& endpoint_id, + const UpgradePathInfo& upgrade_info) { + // The remote device failed to upgrade to the new medium we set up for them. + // That's alright! We'll just try the next available medium (if there is + // one). + in_progress_upgrades_.erase(endpoint_id); + + // The first thing we have to do is to replace our + // currentBwuMedium with the next best upgrade medium we share + // with the remote device. The catch is that we can only do this if we only + // have one connected endpoint. Otherwise, we'll end up disrupting our other + // connected peers. + if (channel_manager_->GetConnectedEndpointsCount() > 1) { + // We can't change the currentBwuMedium, so there are no more + // upgrade attempts for this endpoint. Sorry. + NEARBY_LOG( + ERROR, + "Failed to attempt a new bandwidth upgrade for endpoint %s because we " + "have other connected endpoints and can't try a new upgrade medium.", + endpoint_id.c_str()); + return; + } + + // Revert the existing upgrade medium for now. + if (medium_ != Medium::UNKNOWN_MEDIUM) { + Revert(); + } + + // Loop through the ordered list of upgrade mediums. One by one, remove the + // top element until we get to the medium we last attempted to upgrade to. + // The remainder of the list will contain the mediums we haven't attempted + // yet. + Medium last = parser::UpgradePathInfoMediumToMedium(upgrade_info.medium()); + std::vector all_possible_mediums = + client->GetUpgradeMediums(endpoint_id).GetMediums(true); + std::vector untried_mediums(all_possible_mediums); + for (Medium medium : all_possible_mediums) { + untried_mediums.erase(untried_mediums.begin()); + if (medium == last) { + break; + } + } + + RetryUpgradeMediums(client, endpoint_id, untried_mediums); +} + +void BwuManager::RetryUpgradeMediums(ClientProxy* client, + const std::string& endpoint_id, + std::vector upgrade_mediums) { + Medium next_medium = ChooseBestUpgradeMedium(upgrade_mediums); + + // If current medium is not WiFi and we have not succeeded with upgrading + // yet, retry upgrade. + Medium current_medium = GetEndpointMedium(endpoint_id); + if (current_medium != Medium::WIFI_LAN && + (next_medium == current_medium || next_medium == Medium::UNKNOWN_MEDIUM || + upgrade_mediums.empty())) { + RetryUpgradesAfterDelay(client, endpoint_id); + return; + } + + // Attempt to set the new upgrade medium. + if (!SetCurrentBwuHandler(next_medium)) { + NEARBY_LOG( + INFO, + "BwuManager failed to attempt a new bandwidth upgrade for endpoint %s " + "because we couldn't set a new bandwidth upgrade medium.", + endpoint_id.c_str()); + return; + } + + // Now that we've successfully picked a new upgrade medium to try, + // re-initiate the bandwidth upgrade. + NEARBY_LOG(INFO, + "BwuManager is attempting to upgrade endpoint %s again with a new " + " bandwidth upgrade medium.", + endpoint_id.c_str()); + InitiateBwuForEndpoint(client, endpoint_id); +} + +std::vector BwuManager::StripOutUnavailableMediums( + const std::vector& mediums) { + std::vector available_mediums; + for (Medium m : mediums) { + bool available = false; + switch (m) { + case Medium::WIFI_LAN: + available = mediums_->GetWifiLan().IsAvailable(); + break; + case Medium::BLUETOOTH: + available = mediums_->GetBluetoothClassic().IsAvailable(); + break; + default: + break; + } + if (available) { + available_mediums.push_back(m); + } + } + return available_mediums; +} + +// Returns the optimal medium supported by both devices. +// Each medium in the passed in list is checked for its availability with the +// medium_manager_ to ensure that the chosen upgrade medium is supported and +// available locally before continuing the upgrade. Once we pick a medium, all +// future connections will use it too. eg. If we chose Wifi LAN, we'll attempt +// to upgrade the 2nd, 3rd, etc remote endpoints with Wifi LAN even if they're +// on a different network (or had a better medium). This is a quick and easy +// way to prevent mediums, like Wifi Hotspot, from interfering with active +// connections (although it's suboptimal for bandwidth throughput). When all +// endpoints disconnect, we reset the bandwidth upgrade medium. +Medium BwuManager::ChooseBestUpgradeMedium(const std::vector& mediums) { + auto available_mediums = StripOutUnavailableMediums(mediums); + if (medium_ == Medium::UNKNOWN_MEDIUM) { + if (!available_mediums.empty()) { + // Case 1: This is our first time upgrading, and we have at least one + // supported medium to choose from. Return the first medium in the list, + // since they are ordered by preference. + return available_mediums[0]; + } + // Case 2: This is our first time upgrading, but there are no available + // upgrade mediums. Fall through to returning UNKNOWN_MEDIUM at the + // bottom. + NEARBY_LOG( + INFO, + "Current upgrade medium is unset, but there are no common supported " + "upgrade mediums."); + } else { + // Case 3: We have already upgraded, and there is a list of supported + // mediums to check against. Return the current upgrade medium if it's in + // the supported list. + if (std::find(available_mediums.begin(), available_mediums.end(), + medium_) != available_mediums.end()) { + return medium_; + } + // Case 4: We have already upgraded, but the current medium is not + // supported by the remote endpoint (it's not in the list, or the list is + // empty). Fall through and return Medium.UNKNOWN_MEDIUM because we cannot + // continue with the current upgrade medium, and we are not allowed to + // switch. + NEARBY_LOG( + INFO, + "Current upgrade medium %s is not supported by the remote endpoint", + medium_); + } + + return Medium::UNKNOWN_MEDIUM; +} + +void BwuManager::RetryUpgradesAfterDelay(ClientProxy* client, + const std::string& endpoint_id) { + absl::Duration delay = CalculateNextRetryDelay(endpoint_id); + CancelRetryUpgradeAlarm(endpoint_id); + CancelableAlarm alarm( + "BWU alarm", + [this, client, endpoint_id]() { + RunOnBwuManagerThread([this, client, endpoint_id]() { + if (!client->IsConnectedToEndpoint(endpoint_id)) { + return; + } + RetryUpgradeMediums( + client, endpoint_id, + client->GetUpgradeMediums(endpoint_id).GetMediums(true)); + }); + }, + delay, &alarm_executor_); + + retry_upgrade_alarms_.emplace(endpoint_id, + std::make_pair(std::move(alarm), delay)); + NEARBY_LOGS(INFO) << "Retry bandwidth upgrade after " << delay; +} + +absl::Duration BwuManager::CalculateNextRetryDelay( + const std::string& endpoint_id) { + auto item = retry_upgrade_alarms_.find(endpoint_id); + auto initial_delay = config_.bandwidth_upgrade_retry_delay; + auto delay = item == retry_upgrade_alarms_.end() + ? initial_delay + : item->second.second + initial_delay; + return std::min(delay, config_.bandwidth_upgrade_retry_max_delay); +} + +void BwuManager::CancelRetryUpgradeAlarm(const std::string& endpoint_id) { + auto item = retry_upgrade_alarms_.extract(endpoint_id); + if (item.empty()) return; + auto& pair = item.mapped(); + pair.first.Cancel(); +} + +void BwuManager::CancelAllRetryUpgradeAlarms() { + for (const auto& item : retry_upgrade_alarms_) { + const std::string& endpoint_id = item.first; + CancelRetryUpgradeAlarm(endpoint_id); + } +} + +Medium BwuManager::GetEndpointMedium(const std::string& endpoint_id) { + auto channel = channel_manager_->GetChannelForEndpoint(endpoint_id); + return channel == nullptr ? Medium::UNKNOWN_MEDIUM : channel->GetMedium(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h new file mode 100644 index 00000000..8ade19d7 --- /dev/null +++ b/cpp/core_v2/internal/bwu_manager.h @@ -0,0 +1,176 @@ +#ifndef CORE_V2_INTERNAL_BWU_MANAGER_H_ +#define CORE_V2_INTERNAL_BWU_MANAGER_H_ + +#include + +#include "core_v2/internal/bwu_handler.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_manager.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/options.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/scheduled_executor.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Base class for managing the upgrade of endpoints to a different medium for +// communication (from whatever they were previously using). +// +// The sequencing of the upgrade protocol is as follows: +// - Initiator sets up an upgrade path, sends +// BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE to Responder over +// the prior EndpointChannel. +// - Responder joins the upgrade path, sends (possibly without encryption) +// BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION over the new +// EndpointChannel, and sends +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the +// prior EndpointChannel. +// - Initiator receives BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION +// over the newly-established EndpointChannel, and sends +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the +// prior EndpointChannel. +// - Both wait to receive +// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL from the +// other, and upon doing so, send +// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL to each other +// - Both then wait to receive +// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the +// other, and upon doing so, close the prior EndpointChannel. +class BwuManager : public EndpointManager::FrameProcessor { + public: + using UpgradePathInfo = BwuHandler::UpgradePathInfo; + + struct Config { + BooleanMediumSelector allow_upgrade_to; + absl::Duration bandwidth_upgrade_retry_delay; + absl::Duration bandwidth_upgrade_retry_max_delay; + }; + + BwuManager(Mediums& mediums, EndpointManager& endpoint_manager, + EndpointChannelManager& channel_manager, + absl::flat_hash_map> handlers, + Config config); + + ~BwuManager() override = default; + + // This is the point on the outbound BWU protocol where the handler_ is set. + // Function initiates the bandwidth upgrade and sends an + // UPGRADE_PATH_AVAILABLE OfflineFrame. + void InitiateBwuForEndpoint(ClientProxy* client_proxy, + const std::string& endpoint_id); + + // == EndpointManager::FrameProcessor interface ==. + // This is the point on the inbound BWU protocol where the handler_ is set. + // This is also an entry point for handling messages for both outbound and + // inbound BWU protocol. + // @EndpointManagerReaderThread + void OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, + ClientProxy* client, Medium medium) override; + + // Cleans up in-progress upgrades after endpoint disconnection. + // @EndpointManagerReaderThread + void OnEndpointDisconnect(ClientProxy* client_proxy, + const std::string& endpoint_id, + CountDownLatch* barrier) override; + void Shutdown(); + + private: + BwuHandler* SetCurrentBwuHandler(Medium medium); + void InitBwuHandlers(); + void RunOnBwuManagerThread(std::function runnable); + std::vector StripOutUnavailableMediums( + const std::vector& mediums); + Medium ChooseBestUpgradeMedium(const std::vector& mediums); + + // BaseBwuHandler + using ClientIntroduction = BwuNegotiationFrame::ClientIntroduction; + + // Processes the BwuNegotiationFrames that come over the + // EndpointChannel on both initiator and responder side of the upgrade. + void OnBwuNegotiationFrame(ClientProxy* client, + const BwuNegotiationFrame& frame, + const string& endpoint_id); + + // Called to revert any state changed by the Initiator or Responder in the + // course of setting up the upgraded medium for an endpoint. + void Revert(); + + // Common functionality to take an incoming connection and go through the + // upgrade process. This is a callback, invoked by concrete handlers, once + // connection is available. + void OnIncomingConnection(ClientProxy* client, + BwuHandler::IncomingSocketConnection* connection); + + void RunUpgradeProtocol(ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr new_channel); + void RunUpgradeFailedProtocol(ClientProxy* client, + const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info); + void ProcessBwuPathAvailableEvent(ClientProxy* client, + const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info); + std::unique_ptr ProcessBwuPathAvailableEventInternal( + ClientProxy* client, const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info); + void ProcessLastWriteToPriorChannelEvent(ClientProxy* client, + const std::string& endpoint_id); + void ProcessSafeToClosePriorChannelEvent(ClientProxy* client, + const std::string& endpoint_id); + bool ReadClientIntroductionFrame(EndpointChannel* endpoint_channel, + ClientIntroduction& introduction); + void ProcessEndpointDisconnection(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier); + void ProcessUpgradeFailureEvent(ClientProxy* client, + const std::string& endpoint_id, + const UpgradePathInfo& upgrade_info); + void CancelRetryUpgradeAlarm(const std::string& endpoint_id); + void CancelAllRetryUpgradeAlarms(); + void RetryUpgradeMediums(ClientProxy* client, const std::string& endpoint_id, + std::vector upgrade_mediums); + Medium GetEndpointMedium(const std::string& endpoint_id); + absl::Duration CalculateNextRetryDelay(const std::string& endpoint_id); + void RetryUpgradesAfterDelay(ClientProxy* client, + const std::string& endpoint_id); + + Config config_; + + Medium medium_ = Medium::UNKNOWN_MEDIUM; + BwuHandler* handler_ = nullptr; + Mediums* mediums_; + absl::flat_hash_map> handlers_; + + EndpointManager* endpoint_manager_; + EndpointChannelManager* channel_manager_; + ScheduledExecutor alarm_executor_; + SingleThreadExecutor serial_executor_; + // Stores each upgraded endpoint's previous EndpointChannel (that was + // displaced in favor of a new EndpointChannel) temporarily, until it can + // safely be shut down for good in processLastWriteToPriorChannelEvent(). + absl::flat_hash_map> + previous_endpoint_channels_; + absl::flat_hash_map> + old_channels_; + absl::flat_hash_set successfully_upgraded_endpoints_; + // Maps endpointId -> ClientProxy for which + // initiateBwuForEndpoint() has been called but which have not + // yet completed the upgrade via onIncomingConnection(). + absl::flat_hash_map in_progress_upgrades_; + // Maps endpointId -> timestamp of when the SAFE_TO_CLOSE message was written. + absl::flat_hash_map safe_to_close_write_timestamps_; + absl::flat_hash_map> + retry_upgrade_alarms_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BWU_MANAGER_H_ diff --git a/cpp/core_v2/internal/bwu_manager_test.cc b/cpp/core_v2/internal/bwu_manager_test.cc new file mode 100644 index 00000000..c130c239 --- /dev/null +++ b/cpp/core_v2/internal/bwu_manager_test.cc @@ -0,0 +1,41 @@ +#include "core_v2/internal/bwu_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 "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(BwuManagerTest, CanCreateInstance) { + Mediums mediums; + EndpointChannelManager ecm; + EndpointManager em{&ecm}; + BwuManager bwu_manager{mediums, em, ecm, {}, {}}; +} + +TEST(BwuManagerTest, CanInitiateBwu) { + ClientProxy client; + std::string endpoint_id("EP_A"); + Mediums mediums; + EndpointChannelManager ecm; + EndpointManager em{&ecm}; + BwuManager bwu_manager{mediums, em, ecm, {}, {}}; + + // Method returns void, so we just verify we did not SEGFAULT while calling. + bwu_manager.InitiateBwuForEndpoint(&client, endpoint_id); + + bwu_manager.Shutdown(); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc index bd5a36a8..e4e5f24c 100644 --- a/cpp/core_v2/internal/client_proxy.cc +++ b/cpp/core_v2/internal/client_proxy.cc @@ -81,6 +81,15 @@ std::string ClientProxy::GetAdvertisingServiceId() const { return advertising_info_.service_id; } +std::string ClientProxy::GetServiceId() const { + MutexLock lock(&mutex_); + if (IsAdvertising()) + return advertising_info_.service_id; + if (IsDiscovering()) + return discovery_info_.service_id; + return "idle_service_id"; +} + void ClientProxy::StartedDiscovery( const std::string& service_id, Strategy strategy, const DiscoveryListener& listener, @@ -225,12 +234,12 @@ void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, } void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id, - std::int32_t quality) { + Medium new_medium) { MutexLock lock(&mutex_); const Connection* item = LookupConnection(endpoint_id); if (item != nullptr) { - item->connection_listener.bandwidth_changed_cb(endpoint_id, quality); + item->connection_listener.bandwidth_changed_cb(endpoint_id, new_medium); } } diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h index 3185f785..d2e88d3f 100644 --- a/cpp/core_v2/internal/client_proxy.h +++ b/cpp/core_v2/internal/client_proxy.h @@ -51,11 +51,14 @@ class ClientProxy final { bool IsAdvertising() const; std::string GetAdvertisingServiceId() const; + // Get service ID of a surrently active link (either advertising, or + // discovering). + std::string GetServiceId() const; + // Marks this client as discovering with the given callback. - void StartedDiscovery( - const std::string& service_id, Strategy strategy, - const DiscoveryListener& discovery_listener, - absl::Span mediums); + void StartedDiscovery(const std::string& service_id, Strategy strategy, + const DiscoveryListener& discovery_listener, + absl::Span mediums); // Marks this client as not discovering at all. void StoppedDiscovery(); bool IsDiscoveringServiceId(const std::string& service_id) const; @@ -83,7 +86,7 @@ class ClientProxy final { void OnConnectionRejected(const std::string& endpoint_id, const Status& status); - void OnBandwidthChanged(const std::string& endpoint_id, std::int32_t quality); + void OnBandwidthChanged(const std::string& endpoint_id, Medium new_medium); // Removes the endpoint from this client's list of connected endpoints. If // notify is true, also calls the client's diff --git a/cpp/core_v2/internal/client_proxy_test.cc b/cpp/core_v2/internal/client_proxy_test.cc index 5c091852..94d22a67 100644 --- a/cpp/core_v2/internal/client_proxy_test.cc +++ b/cpp/core_v2/internal/client_proxy_test.cc @@ -153,7 +153,7 @@ class ClientProxyTest : public testing::Test { void OnDiscoveryBandwidthChanged(ClientProxy* client, const Endpoint& endpoint) { EXPECT_CALL(mock_discovery_connection_.bandwidth_changed_cb, Call).Times(1); - client->OnBandwidthChanged(endpoint.id, 1); + client->OnBandwidthChanged(endpoint.id, Medium::WIFI_LAN); } void OnDiscoveryConnectionDisconnected(ClientProxy* client, diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index ae1efae9..d0ab8b16 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -22,7 +22,8 @@ bool Ble::IsAvailable() const { bool Ble::IsAvailableLocked() const { return medium_.IsValid(); } bool Ble::StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) { + const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) { MutexLock lock(&mutex_); if (advertisement_bytes.Empty()) { @@ -59,12 +60,17 @@ bool Ble::StartAdvertising(const std::string& service_id, NEARBY_LOGS(INFO) << "Turning on BLE advertising with advertisement bytes=" << advertisement_bytes.data() << "(" << advertisement_bytes.size() << ")" - << ", service id=" << service_id; - if (!medium_.StartAdvertising(service_id, advertisement_bytes)) { + << ", service id=" << service_id + << ", fast advertisement service uuid=" + << fast_advertisement_service_uuid; + if (!medium_.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid)) { NEARBY_LOGS(INFO) << "Failed to turn on BLE advertising with advertisement bytes=" << advertisement_bytes.data() << "(" << advertisement_bytes.size() - << ")"; + << ")" + << ", fast advertisement service uuid=" + << fast_advertisement_service_uuid; return false; } diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 7880f837..42c1cd9c 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -31,7 +31,8 @@ class Ble { // Sets custom advertisement data, and then enables Ble advertising. // Returns true, if data is successfully set, and false otherwise. bool StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) + const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) ABSL_LOCKS_EXCLUDED(mutex_); // Disables Ble advertising. diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index f1936af7..6a2d43f0 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -18,6 +18,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; class BleTest : public ::testing::Test { protected: @@ -55,6 +56,7 @@ TEST_F(BleTest, CanStartAdvertising) { radio_b.Enable(); std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); ble_b.StartScanning( @@ -66,7 +68,8 @@ TEST_F(BleTest, CanStartAdvertising) { bool fast_advertisement) { found_latch.CountDown(); }, }); - EXPECT_TRUE(ble_a.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(ble_a.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_a.StopAdvertising(service_id)); EXPECT_TRUE(ble_b.StopScanning(service_id)); @@ -83,10 +86,12 @@ TEST_F(BleTest, CanStartDiscovery) { radio_b.Enable(); std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch accept_latch(1); CountDownLatch lost_latch(1); - ble_b.StartAdvertising(service_id, advertisement_bytes); + ble_b.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid); EXPECT_TRUE(ble_a.StartScanning( service_id, @@ -118,10 +123,12 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { radio_b.Enable(); std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); CountDownLatch accept_latch(1); - ble_a.StartAdvertising(service_id, advertisement_bytes); + ble_a.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid); ble_a.StartAcceptingConnections( service_id, { diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc index 97da7811..b6620e96 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -374,6 +374,11 @@ BluetoothDevice BluetoothClassic::FindRemoteDevice( return medium_.FindRemoteDevice(mac_address); } +std::string BluetoothClassic::GetMacAddress() const { + MutexLock lock(&mutex_); + return medium_.GetMacAddress(); +} + std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) { return std::string(Uuid(data)); } diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h index f45ab79d..3ed3a33a 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.h +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -100,6 +100,8 @@ class BluetoothClassic { const std::string& service_name) ABSL_LOCKS_EXCLUDED(mutex_); + std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_); + BluetoothDevice FindRemoteDevice(const std::string& mac_address) ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc index 33b141ac..921ccaa5 100644 --- a/cpp/core_v2/internal/mediums/utils.cc +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -10,6 +10,10 @@ namespace location { namespace nearby { namespace connections { +namespace { +constexpr absl::string_view kUpgradeServiceIdPostfix = "_UPGRADE"; +} + ByteArray Utils::GenerateRandomBytes(size_t length) { Prng rng; std::string data; @@ -40,6 +44,22 @@ ByteArray Utils::Sha256Hash(const std::string& source, size_t length) { return full_hash; } +std::string Utils::WrapUpgradeServiceId(const std::string& service_id) { + if (service_id.empty()) { + return {}; + } + return service_id + std::string(kUpgradeServiceIdPostfix); +} + +std::string Utils::UnwrapUpgradeServiceId( + const std::string& upgrade_service_id) { + auto pos = upgrade_service_id.find(kUpgradeServiceIdPostfix); + if (pos != std::string::npos) { + return std::string(upgrade_service_id, 0, pos); + } + return upgrade_service_id; +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.h b/cpp/core_v2/internal/mediums/utils.h index 4804c31c..00e93c35 100644 --- a/cpp/core_v2/internal/mediums/utils.h +++ b/cpp/core_v2/internal/mediums/utils.h @@ -14,6 +14,8 @@ class Utils { static ByteArray GenerateRandomBytes(size_t length); static ByteArray Sha256Hash(const ByteArray& source, size_t length); static ByteArray Sha256Hash(const std::string& source, size_t length); + static std::string WrapUpgradeServiceId(const std::string& service_id); + static std::string UnwrapUpgradeServiceId(const std::string& service_id); }; } // namespace connections diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 636334ff..a046486d 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -169,6 +169,24 @@ ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, return ToBytes(std::move(frame)); } +ByteArray ForBwuWebrtcPathAvailable(const std::string& peer_id) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium(UpgradePathInfo::WEB_RTC); + auto* webrtc_credentials = + upgrade_path_info->mutable_web_rtc_credentials(); + webrtc_credentials->set_peer_id(peer_id); + + return ToBytes(std::move(frame)); +} + ByteArray ForBwuLastWrite() { OfflineFrame frame; diff --git a/cpp/core_v2/internal/offline_frames.h b/cpp/core_v2/internal/offline_frames.h index 339c543a..0b9f6614 100644 --- a/cpp/core_v2/internal/offline_frames.h +++ b/cpp/core_v2/internal/offline_frames.h @@ -51,6 +51,7 @@ ByteArray ForBwuWifiLanPathAvailable(const std::string& ip_address, std::int32_t port); ByteArray ForBwuBluetoothPathAvailable(const std::string& service_id, const std::string& mac_address); +ByteArray ForBwuWebrtcPathAvailable(const std::string& peer_id); ByteArray ForBwuFailure(const UpgradePathInfo& info); ByteArray ForBwuLastWrite(); ByteArray ForBwuSafeToClose(); diff --git a/cpp/core_v2/internal/offline_service_controller.cc b/cpp/core_v2/internal/offline_service_controller.cc index 3c1de259..1ae47348 100644 --- a/cpp/core_v2/internal/offline_service_controller.cc +++ b/cpp/core_v2/internal/offline_service_controller.cc @@ -53,7 +53,10 @@ Status OfflineServiceController::RejectConnection( void OfflineServiceController::InitiateBandwidthUpgrade( ClientProxy* client, const std::string& endpoint_id) { - // TODO(apolyudov): implement. + NEARBY_LOGS(INFO) << "Client " << client->GetClientId() + << " initiated a manual bandwidth upgrade with endpoint id=" + << endpoint_id; + bwu_manager_.InitiateBwuForEndpoint(client, endpoint_id); } void OfflineServiceController::SendPayload( diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index 97517fa7..03ebbc33 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -5,6 +5,7 @@ #include #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -26,24 +27,20 @@ class OfflineServiceController : public ServiceController { OfflineServiceController() = default; ~OfflineServiceController() override; - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, + Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; void StopAdvertising(ClientProxy* client) override; - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, + Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; void StopDiscovery(ClientProxy* client) override; - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, + Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, const ConnectionRequestInfo& info, const ConnectionOptions& options) override; - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, + Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& listener) override; Status RejectConnection(ClientProxy* client, const std::string& endpoint_id) override; @@ -54,8 +51,7 @@ class OfflineServiceController : public ServiceController { void SendPayload(ClientProxy* client, const std::vector& endpoint_ids, Payload payload) override; - Status CancelPayload(ClientProxy* client, - Payload::Id payload_id) override; + Status CancelPayload(ClientProxy* client, Payload::Id payload_id) override; void DisconnectFromEndpoint(ClientProxy* client, const std::string& endpoint_id) override; @@ -72,6 +68,8 @@ class OfflineServiceController : public ServiceController { EndpointManager endpoint_manager_{&channel_manager_}; PayloadManager payload_manager_{endpoint_manager_}; PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; + BwuManager bwu_manager_{ + mediums_, endpoint_manager_, channel_manager_, {}, {}}; }; } // namespace connections diff --git a/cpp/core_v2/internal/offline_service_controller.h.orig b/cpp/core_v2/internal/offline_service_controller.h.orig new file mode 100644 index 00000000..97517fa7 --- /dev/null +++ b/cpp/core_v2/internal/offline_service_controller.h.orig @@ -0,0 +1,81 @@ +#ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ + +#include +#include +#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/payload_manager.h" +#include "core_v2/internal/pcp_manager.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" + +namespace location { +namespace nearby { +namespace connections { + +class OfflineServiceController : public ServiceController { + public: + OfflineServiceController() = default; + ~OfflineServiceController() override; + + Status StartAdvertising(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) override; + void StopAdvertising(ClientProxy* client) override; + + Status StartDiscovery(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) override; + void StopDiscovery(ClientProxy* client) override; + + Status RequestConnection(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionRequestInfo& info, + const ConnectionOptions& options) override; + Status AcceptConnection(ClientProxy* client, + const std::string& endpoint_id, + const PayloadListener& listener) override; + Status RejectConnection(ClientProxy* client, + const std::string& endpoint_id) override; + + void InitiateBandwidthUpgrade(ClientProxy* client, + const std::string& endpoint_id) override; + + void SendPayload(ClientProxy* client, + const std::vector& endpoint_ids, + Payload payload) override; + Status CancelPayload(ClientProxy* client, + Payload::Id payload_id) override; + + void DisconnectFromEndpoint(ClientProxy* client, + const std::string& endpoint_id) override; + + void Stop(); + + private: + // Note that the order of declaration of these is crucial, because we depend + // on the destructors running (strictly) in the reverse order; a deviation + // from that will lead to crashes at runtime. + AtomicBoolean stop_{false}; + Mediums mediums_; + EndpointChannelManager channel_manager_; + EndpointManager endpoint_manager_{&channel_manager_}; + PayloadManager payload_manager_{endpoint_manager_}; + PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index d0154914..a4f07de8 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -837,7 +837,8 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( INFO, "P2pClusterPcpHandler::StartBleAdvertising: service_id=%s: come up", service_id.c_str()); - if (!ble_medium_.StartAdvertising(service_id, advertisement_bytes)) { + if (!ble_medium_.StartAdvertising(service_id, advertisement_bytes, + options.fast_advertisement_service_uuid)) { NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: failed to " "start advertising, advertisement_bytes=%p" << advertisement_bytes.data(); diff --git a/cpp/core_v2/internal/webrtc_bwu_handler.cc b/cpp/core_v2/internal/webrtc_bwu_handler.cc new file mode 100644 index 00000000..2ba1d9ef --- /dev/null +++ b/cpp/core_v2/internal/webrtc_bwu_handler.cc @@ -0,0 +1,142 @@ +#include "core_v2/internal/webrtc_bwu_handler.h" + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mediums/utils.h" +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "core_v2/internal/offline_frames.h" +#include "core_v2/internal/webrtc_endpoint_channel.h" +#include "absl/functional/bind_front.h" + +// Manages the Bluetooth-specific methods needed to upgrade an {@link +// EndpointChannel}. + +namespace location { +namespace nearby { +namespace connections { + +WebrtcBwuHandler::WebrtcBwuHandler(Mediums& mediums, + EndpointChannelManager& channel_manager, + BwuNotifications notifications) + : BaseBwuHandler(channel_manager, std::move(notifications)), + mediums_(mediums) {} + +void WebrtcBwuHandler::Revert() { + if (!active_service_ids_.empty()) { + webrtc_.StopAcceptingConnections(); + active_service_ids_.clear(); + } + + NEARBY_LOG(INFO, "WebrtcBwuHandler successfully reverted state."); +} + +// Accept Connection Callback. +// Notifies that the remote party called WebRtc::Connect() +// for this socket. +void WebrtcBwuHandler::OnIncomingWebrtcConnection( + ClientProxy* client, const std::string& upgrade_service_id, + mediums::WebRtcSocketWrapper socket) { + std::string service_id = Utils::UnwrapUpgradeServiceId(upgrade_service_id); + auto channel = std::make_unique(service_id, socket); + IncomingSocketConnection connection{ + std::make_unique(service_id, socket), + std::move(channel)}; + + bwu_notifications_.incoming_connection_cb(client, &connection); +} + +// Called by BWU initiator. BT Medium is set up, and BWU request is prepared, +// with necessary info (service_id, MAC address) for remote party to perform +// discovery. +ByteArray WebrtcBwuHandler::InitializeUpgradedMediumForEndpoint( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id) { + // Use wrapped service ID to avoid have the same ID with the one for + // startAdvertising. Otherwise, the listening request would be ignored because + // the medium already start accepting the connection because the client not + // stop the advertising yet. + std::string upgrade_service_id = Utils::WrapUpgradeServiceId(service_id); + + mediums::PeerId self_id{mediums::PeerId::FromRandom()}; + if (!webrtc_.IsAcceptingConnections()) { + if (!webrtc_.StartAcceptingConnections( + self_id, { + .accepted_cb = absl::bind_front( + &WebrtcBwuHandler::OnIncomingWebrtcConnection, + this, client, upgrade_service_id), + })) { + NEARBY_LOG(ERROR, + "WebRtcBwuHandler couldn't initiate the WEB_RTC upgrade for " + "endpoint %s because it failed to start listening for " + "incoming WebRTC connections.", + endpoint_id.c_str()); + return {}; + } + NEARBY_LOG(INFO, + "WebRtcBwuHandler successfully started listening for incoming " + "WebRTC connections while upgrading endpoint %s", + endpoint_id.c_str()); + } + + // cache service ID to revert + active_service_ids_.emplace(upgrade_service_id); + + return parser::ForBwuWebrtcPathAvailable(self_id.GetId()); +} + +// Called by BWU target. Retrieves a new medium info from incoming message, +// and establishes connection over WebRTC using this info. +std::unique_ptr +WebrtcBwuHandler::CreateUpgradedEndpointChannel( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) { + const UpgradePathInfo::WebRtcCredentials& web_rtc_credentials = + upgrade_path_info.web_rtc_credentials(); + mediums::PeerId peer_id(web_rtc_credentials.peer_id()); + + NEARBY_LOG(INFO, + "WebRtcBwuHandler is attempting to connect to remote peer %s", + peer_id.GetId().c_str()); + + mediums::WebRtcSocketWrapper socket = webrtc_.Connect(peer_id); + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, + "WebRtcBwuHandler failed to connect to remote peer (%s) on " + "endpoint %s, aborting upgrade.", + peer_id.GetId().c_str(), endpoint_id.c_str()); + return nullptr; + } + + NEARBY_LOG(INFO, + "WebRtcBwuHandler successfully connected to remote " + "peer (%s) while upgrading endpoint %s.", + peer_id.GetId().c_str(), endpoint_id.c_str()); + + // Create a new WebRtcEndpointChannel. + auto channel = std::make_unique(service_id, socket); + if (channel == nullptr) { + socket.Close(); + NEARBY_LOG(ERROR, + "WebRtcBwuHandler failed to create new EndpointChannel for " + "outgoing socket %p, aborting upgrade.", + &socket.GetImpl()); + } + + return channel; +} + +void WebrtcBwuHandler::OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id) {} + +WebrtcBwuHandler::WebrtcIncomingSocket::WebrtcIncomingSocket( + const std::string& name, mediums::WebRtcSocketWrapper socket) + : name_(name), socket_(socket) {} + +void WebrtcBwuHandler::WebrtcIncomingSocket::Close() { socket_.Close(); } + +std::string WebrtcBwuHandler::WebrtcIncomingSocket::ToString() { return name_; } + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/webrtc_bwu_handler.h b/cpp/core_v2/internal/webrtc_bwu_handler.h new file mode 100644 index 00000000..793357d8 --- /dev/null +++ b/cpp/core_v2/internal/webrtc_bwu_handler.h @@ -0,0 +1,79 @@ +#ifndef CORE_V2_INTERNAL_WEBRTC_BWU_HANDLER_H_ +#define CORE_V2_INTERNAL_WEBRTC_BWU_HANDLER_H_ + +#include "core_v2/internal/base_bwu_handler.h" +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/mediums/mediums.h" +#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" + +namespace location { +namespace nearby { +namespace connections { + +using BwuNegotiationFrame = BandwidthUpgradeNegotiationFrame; + +// Defines the set of methods that need to be implemented to handle the +// per-Medium-specific operations needed to upgrade an EndpointChannel. +class WebrtcBwuHandler : public BaseBwuHandler { + public: + WebrtcBwuHandler(Mediums& mediums, EndpointChannelManager& channel_manager, + BwuNotifications notifications); + ~WebrtcBwuHandler() override = default; + + private: + // Called by the Initiator to setup the upgraded medium for this endpoint (if + // that hasn't already been done), and returns a serialized UpgradePathInfo + // that can be sent to the Responder. + // @BwuHandlerThread + ByteArray InitializeUpgradedMediumForEndpoint( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id) override; + // Called to revert any state changed by the Initiator to setup the upgraded + // medium for an endpoint. + // @BwuHandlerThread + void Revert() override; + + // Called by the Responder to setup the upgraded medium for this endpoint (if + // that hasn't already been done) using the UpgradePathInfo sent by the + // Initiator, and returns a new EndpointChannel for the upgraded medium. + // @BwuHandlerThread + std::unique_ptr CreateUpgradedEndpointChannel( + ClientProxy* client, const std::string& service_id, + const std::string& endpoint_id, + const UpgradePathInfo& upgrade_path_info) override; + // Returns the upgrade medium of the BwuHandler. + // @BwuHandlerThread + Medium GetUpgradeMedium() const override { return Medium::WEB_RTC; } + + void OnIncomingWebrtcConnection(ClientProxy* client, + const std::string& service_id, + mediums::WebRtcSocketWrapper socket); + + void OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id) override; + + class WebrtcIncomingSocket : public BwuHandler::IncomingSocket { + public: + explicit WebrtcIncomingSocket(const std::string& name, + mediums::WebRtcSocketWrapper socket); + ~WebrtcIncomingSocket() override = default; + + std::string ToString() override; + void Close() override; + + private: + std::string name_; + mediums::WebRtcSocketWrapper socket_; + }; + + Mediums& mediums_; + mediums::WebRtc& webrtc_{mediums_.GetWebRtc()}; + absl::flat_hash_set active_service_ids_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WEBRTC_BWU_HANDLER_H_ diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h index 90bddc7f..c58e5413 100644 --- a/cpp/core_v2/listeners.h +++ b/cpp/core_v2/listeners.h @@ -13,6 +13,7 @@ // default-initialized. // - callbacks may be initialized with lambdas; lambda definitions are concize. +#include "core_v2/options.h" #include "core_v2/payload.h" #include "core_v2/status.h" #include "platform_v2/base/byte_array.h" @@ -110,10 +111,9 @@ struct ConnectionListener { // Called when the connection's available bandwidth has changed. // // endpoint_id - The identifier for the remote endpoint. - // quality - TODO(apolyudov): document. - std::function - bandwidth_changed_cb = - DefaultCallback(); + // medium - Medium we upgraded to. + std::function + bandwidth_changed_cb = DefaultCallback(); }; struct DiscoveryListener { @@ -125,9 +125,8 @@ struct DiscoveryListener { std::function - endpoint_found_cb = - DefaultCallback(); + endpoint_found_cb = DefaultCallback(); // Called when a remote endpoint is no longer discoverable; only called for // endpoints that previously had been passed to {@link diff --git a/cpp/core_v2/listeners_test.cc b/cpp/core_v2/listeners_test.cc index 8f73b1c0..270c412c 100644 --- a/cpp/core_v2/listeners_test.cc +++ b/cpp/core_v2/listeners_test.cc @@ -18,7 +18,7 @@ TEST(ListenersTest, EnsureDefaultInitializedIsCallable) { listener.accepted_cb(endpoint_id); listener.rejected_cb(endpoint_id, {Status::kError}); listener.disconnected_cb(endpoint_id); - listener.bandwidth_changed_cb(endpoint_id, int()); + listener.bandwidth_changed_cb(endpoint_id, Medium()); SUCCEED(); } @@ -35,7 +35,7 @@ TEST(ListenersTest, EnsurePartiallyInitializedIsCallable) { listener.accepted_cb(endpoint_id); listener.rejected_cb(endpoint_id, {Status::kError}); listener.disconnected_cb(endpoint_id); - listener.bandwidth_changed_cb(endpoint_id, int()); + listener.bandwidth_changed_cb(endpoint_id, Medium()); EXPECT_TRUE(initiated_cb_called); } diff --git a/cpp/platform_v2/api/ble.h b/cpp/platform_v2/api/ble.h index 49c9107c..548aeb45 100644 --- a/cpp/platform_v2/api/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -56,16 +56,17 @@ class BleMedium { public: virtual ~BleMedium() = default; - virtual bool StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) = 0; + virtual bool StartAdvertising( + const std::string& service_id, const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) = 0; virtual bool StopAdvertising(const std::string& service_id) = 0; // Callback that is invoked when a discovered peripheral is found or lost. struct DiscoveredPeripheralCallback { - std::function + std::function peripheral_discovered_cb = - DefaultCallback(); + DefaultCallback(); std::function peripheral_lost_cb = diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 3d8463be..21d72fd7 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -177,7 +177,7 @@ api::BluetoothDevice* MediumEnvironment::FindBluetoothDevice( void MediumEnvironment::OnBlePeripheralStateChanged( BleMediumContext& info, api::BlePeripheral& peripheral, - const std::string& service_id, bool enabled) { + const std::string& service_id, bool fast_advertisement, bool enabled) { if (!enabled_) return; NEARBY_LOG(INFO, "G3 OnBleServiceStateChanged [peripheral impl=%p]; context=%p; " @@ -185,13 +185,15 @@ void MediumEnvironment::OnBlePeripheralStateChanged( &peripheral, &info, service_id.c_str(), enable_notifications_.load()); if (!enable_notifications_) return; - RunOnMediumEnvironmentThread([&info, enabled, &peripheral, service_id]() { + RunOnMediumEnvironmentThread([&info, enabled, &peripheral, service_id, + fast_advertisement]() { NEARBY_LOG(INFO, "G3 [Run] OnBlePeripheralStateChanged [peripheral impl=%p]; " "context=%p; service_id=%s; enabled=%d", &peripheral, &info, service_id.c_str(), enabled); if (enabled) { - info.discovery_callback.peripheral_discovered_cb(peripheral, service_id); + info.discovery_callback.peripheral_discovered_cb(peripheral, service_id, + fast_advertisement); } else { info.discovery_callback.peripheral_lost_cb(peripheral, service_id); } @@ -304,10 +306,10 @@ void MediumEnvironment::RegisterBleMedium(api::BleMedium& medium) { void MediumEnvironment::UpdateBleMediumForAdvertising( api::BleMedium& medium, api::BlePeripheral& peripheral, - const std::string& service_id, bool enabled) { + const std::string& service_id, bool fast_advertisement, bool enabled) { if (!enabled_) return; RunOnMediumEnvironmentThread( - [this, &medium, &peripheral, service_id, enabled]() { + [this, &medium, &peripheral, service_id, fast_advertisement, enabled]() { auto item = ble_mediums_.find(&medium); if (item == ble_mediums_.end()) { NEARBY_LOG(INFO, @@ -318,17 +320,20 @@ void MediumEnvironment::UpdateBleMediumForAdvertising( auto& context = item->second; context.ble_peripheral = &peripheral; context.advertising = enabled; - NEARBY_LOG(INFO, - "Update Ble medium for advertising: this=%p; medium=%p; " - "service_id=%s; name=%s; enabled=%d; ", - this, &medium, service_id.c_str(), - peripheral.GetName().c_str(), enabled); + context.fast_advertisement = fast_advertisement; + NEARBY_LOG( + INFO, + "Update Ble medium for advertising: this=%p; medium=%p; " + "service_id=%s; name=%s; fast_advertisement=%d; enabled=%d; ", + this, &medium, service_id.c_str(), peripheral.GetName().c_str(), + fast_advertisement, enabled); for (auto& medium_info : ble_mediums_) { auto& local_medium = medium_info.first; auto& info = medium_info.second; // Do not send notification to the same medium. if (local_medium == &medium) continue; - OnBlePeripheralStateChanged(info, peripheral, service_id, enabled); + OnBlePeripheralStateChanged(info, peripheral, service_id, + fast_advertisement, enabled); } }); } @@ -360,7 +365,8 @@ void MediumEnvironment::UpdateBleMediumForScanning( // Search advertising mediums and send notification. if (info.advertising && enabled) { OnBlePeripheralStateChanged(context, *(info.ble_peripheral), - service_id, enabled); + service_id, info.fast_advertisement, + enabled); } } }); diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index f873d53d..875de81a 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -140,7 +140,7 @@ class MediumEnvironment { void UpdateBleMediumForAdvertising(api::BleMedium& medium, api::BlePeripheral& peripheral, const std::string& service_id, - bool enabled); + bool fast_advertisement, bool enabled); // Updates discovery callback info to allow for dispatch of discovery events. // @@ -228,6 +228,7 @@ class MediumEnvironment { BleAcceptedConnectionCallback accepted_connection_callback; api::BlePeripheral* ble_peripheral = nullptr; bool advertising = false; + bool fast_advertisement = false; }; struct WifiLanServiceIdContext { @@ -256,7 +257,8 @@ class MediumEnvironment { void OnBlePeripheralStateChanged(BleMediumContext& info, api::BlePeripheral& peripheral, - const std::string& service_id, bool enabled); + const std::string& service_id, + bool fast_advertisement, bool enabled); void OnWifiLanServiceStateChanged(WifiLanMediumContext& info, api::WifiLanService& service, diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 4dd926da..53a8fd8d 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -18,9 +18,7 @@ cc_library( "scheduled_executor.h", "single_thread_executor.h", ], - visibility = [ - "//platform_v2/impl/g3:__pkg__", - ], + visibility = ["//visibility:private"], deps = [ "//base", "//platform_v2/api:platform", @@ -52,9 +50,7 @@ cc_library( "webrtc.h", "wifi_lan.h", ], - visibility = [ - "//platform_v2/impl/g3:__pkg__", - ], + visibility = ["//visibility:private"], deps = [ ":types", "//platform_v2/api:comm", diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc index 9b143494..c7bfa041 100644 --- a/cpp/platform_v2/impl/g3/ble.cc +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -182,15 +182,20 @@ BleMedium::~BleMedium() { } } -bool BleMedium::StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) { +bool BleMedium::StartAdvertising( + const std::string& service_id, const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) { NEARBY_LOGS(INFO) << "G3 Ble StartAdvertising: service_id=" << service_id << ", advertisement bytes=" << advertisement_bytes.data() - << "(" << advertisement_bytes.size() << ")"; + << "(" << advertisement_bytes.size() << ")," + << " fast advertisement service uuid=" + << fast_advertisement_service_uuid; auto& env = MediumEnvironment::Instance(); auto& peripheral = adapter_->GetPeripheral(); peripheral.SetAdvertisementBytes(service_id, advertisement_bytes); - env.UpdateBleMediumForAdvertising(*this, peripheral, service_id, true); + bool fast_advertisement = !fast_advertisement_service_uuid.empty(); + env.UpdateBleMediumForAdvertising(*this, peripheral, service_id, + fast_advertisement, true); absl::MutexLock lock(&mutex_); if (server_socket_ != nullptr) server_socket_.release(); @@ -227,7 +232,8 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { auto& env = MediumEnvironment::Instance(); env.UpdateBleMediumForAdvertising(*this, adapter_->GetPeripheral(), - service_id, false); + service_id, /*fast_advertisement=*/false, + /*enabled=*/false); accept_loops_runner_.Shutdown(); if (server_socket_ == nullptr) { NEARBY_LOGS(ERROR) << "G3 Ble StopAdvertising: Failed to find Ble Server " diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h index 5ea80a55..6bdacb09 100644 --- a/cpp/platform_v2/impl/g3/ble.h +++ b/cpp/platform_v2/impl/g3/ble.h @@ -137,8 +137,9 @@ class BleMedium : public api::BleMedium { ~BleMedium() override; // Returns true once the Ble advertising has been initiated. - bool StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) override + bool StartAdvertising( + const std::string& service_id, const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); bool StopAdvertising(const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 5c3207e5..43a81d1b 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -6,9 +6,11 @@ namespace location { namespace nearby { -bool BleMedium::StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes) { - return impl_->StartAdvertising(service_id, advertisement_bytes); +bool BleMedium::StartAdvertising( + const std::string& service_id, const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid) { + return impl_->StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid); } bool BleMedium::StopAdvertising(const std::string& service_id) { @@ -27,7 +29,7 @@ bool BleMedium::StartScanning(const std::string& service_id, { .peripheral_discovered_cb = [this](api::BlePeripheral& peripheral, - const std::string& service_id) { + const std::string& service_id, bool fast_advertisement) { MutexLock lock(&mutex_); auto pair = peripherals_.emplace( &peripheral, absl::make_unique()); @@ -46,8 +48,7 @@ bool BleMedium::StartScanning(const std::string& service_id, &context.peripheral, &peripheral, peripheral.GetName().c_str()); discovered_peripheral_callback_.peripheral_discovered_cb( - context.peripheral, service_id, - /*fast_advertisement=*/false); + context.peripheral, service_id, fast_advertisement); } }, .peripheral_lost_cb = diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 233b1abb..948903af 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -100,7 +100,8 @@ class BleMedium final { // Returns true once the BLE advertising has been initiated. bool StartAdvertising(const std::string& service_id, - const ByteArray& advertisement_bytes); + const ByteArray& advertisement_bytes, + const std::string& fast_advertisement_service_uuid); bool StopAdvertising(const std::string& service_id); // Returns true once the BLE scan has been initiated. diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index 0d4e2590..2af0c3de 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -15,6 +15,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; class BleMediumTest : public ::testing::Test { protected: @@ -50,9 +51,11 @@ TEST_F(BleMediumTest, CanStartAdvertising) { BleMedium ble_b{adapter_b_}; std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); - ble_a.StartAdvertising(service_id, advertisement_bytes); + ble_a.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid); EXPECT_TRUE(ble_b.StartScanning( service_id, @@ -76,6 +79,7 @@ TEST_F(BleMediumTest, CanStartScanning) { BleMedium ble_b{adapter_b_}; std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); CountDownLatch lost_latch(1); @@ -92,7 +96,8 @@ TEST_F(BleMediumTest, CanStartScanning) { lost_latch.CountDown(); }, }); - EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_b.StopAdvertising(service_id)); EXPECT_TRUE(lost_latch.Await(kWaitDuration).result()); @@ -108,6 +113,7 @@ TEST_F(BleMediumTest, CanStopDiscovery) { BleMedium ble_b{adapter_b_}; std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); CountDownLatch lost_latch(1); @@ -124,7 +130,8 @@ TEST_F(BleMediumTest, CanStopDiscovery) { lost_latch.CountDown(); }, }); - EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes)); + EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid)); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_TRUE(ble_a.StopScanning(service_id)); EXPECT_TRUE(ble_b.StopAdvertising(service_id)); @@ -140,6 +147,7 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { BleMedium ble_b{adapter_b_}; std::string service_id(kServiceID); ByteArray advertisement_bytes{std::string(kAdvertisementString)}; + std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid); CountDownLatch found_latch(1); CountDownLatch accepted_latch(1); @@ -160,7 +168,8 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { found_latch.CountDown(); }, }); - ble_b.StartAdvertising(service_id, advertisement_bytes); + ble_b.StartAdvertising(service_id, advertisement_bytes, + fast_advertisement_service_uuid); ble_b.StartAcceptingConnections( service_id, AcceptedConnectionCallback{ diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h index 1baa6751..d941b3b6 100644 --- a/cpp/platform_v2/public/bluetooth_adapter.h +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -45,6 +45,7 @@ class BluetoothDevice final { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() std::string GetName() const { return impl_->GetName(); } + std::string GetMacAddress() const { return impl_->GetMacAddress(); } api::BluetoothDevice& GetImpl() { return *impl_; } bool IsValid() const { return impl_ != nullptr; } @@ -90,6 +91,7 @@ class BluetoothAdapter final { // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() // Returns an empty string on error std::string GetName() const { return impl_->GetName(); } + std::string GetMacAddress() const { return impl_->GetMacAddress(); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) bool SetName(absl::string_view name) { return impl_->SetName(name); } diff --git a/cpp/platform_v2/public/bluetooth_adapter_test.cc b/cpp/platform_v2/public/bluetooth_adapter_test.cc index 3914b624..931ace51 100644 --- a/cpp/platform_v2/public/bluetooth_adapter_test.cc +++ b/cpp/platform_v2/public/bluetooth_adapter_test.cc @@ -1,5 +1,7 @@ #include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/base/bluetooth_utils.h" +#include "platform_v2/public/logging.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -39,6 +41,14 @@ TEST(BluetoothAdapterTest, CanSetMode) { EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kNone); } +TEST(BluetoothAdapterTest, CanGetMacAddress) { + BluetoothAdapter adapter; + std::string bt_mac = + BluetoothUtils::ToString(ByteArray(adapter.GetMacAddress())); + NEARBY_LOG(INFO, "BT MAC: '%s'", bt_mac.c_str()); + EXPECT_NE(bt_mac, ""); +} + } // namespace } // namespace nearby } // namespace location diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h index 420e0684..d8bf989d 100644 --- a/cpp/platform_v2/public/bluetooth_classic.h +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -187,6 +187,7 @@ class BluetoothClassicMedium final { api::BluetoothClassicMedium& GetImpl() { return *impl_; } BluetoothAdapter& GetAdapter() { return adapter_; } + std::string GetMacAddress() const { return adapter_.GetMacAddress(); } BluetoothDevice FindRemoteDevice(const std::string& mac_address) { return BluetoothDevice(impl_->FindRemoteDevice(mac_address)); } diff --git a/proto/BUILD b/proto/BUILD index b5d3eadb..6b1483d9 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -75,6 +75,11 @@ go_proto_library( deps = [":connections_enums_proto"], ) +java_proto_library( + name = "connections_enums_java_proto", + deps = [":connections_enums_proto"], +) + portable_proto_library( name = "connections_enums_portable_proto", config = ":connections_enums_proto_config", diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 1e94c48a..9296a49b 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -136,7 +136,7 @@ enum StartAdvertisingError { // Next ID :46 } -// The error for event START_ADVERTISING. The range between 31 and 99. +// The error for event STOP_ADVERTISING. The range between 31 and 99. enum StopAdvertisingError { // System error, failed to stop advertising. STOP_ADVERTISING_FAILED = 31; @@ -182,6 +182,18 @@ enum StartDiscoveringError { // Next ID :41 } +// The error for event STOP_DISCOVERING. The range between 31 and 99. +enum StopDiscoveringError { + // System error, failed to stop discovering. + STOP_DISCOVERING_FAILED = 31; + // System error, failed to stop discovering for BLE legacy scanning. + STOP_LEGACY_DISCOVERING_FAILED = 32; + // System error, failed to stop discovering for BLE extended scanning. + STOP_EXTENDED_DISCOVERING_FAILED = 33; + + // Next ID :34 +} + // The error for event START_LISTENING_INCOMING_CONNECTION. The range between 31 // and 99. enum StartListeningIncomingConnectionError { From d3dd15e460abbf9ab5caa99ea6e679552db6ab3d Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Mon, 14 Sep 2020 02:02:36 -0700 Subject: [PATCH 44/52] Apply OSS fixes Signed-off-by: Alexey Polyudov Change-Id: I0007cccbb113c72ea2d9786d04e3337b8e498611 --- cpp/core_v2/internal/base_bwu_handler.h | 14 ++++++++++++++ cpp/core_v2/internal/bwu_handler.h | 14 ++++++++++++++ cpp/core_v2/internal/bwu_manager.cc | 14 ++++++++++++++ cpp/core_v2/internal/bwu_manager.h | 14 ++++++++++++++ cpp/core_v2/internal/bwu_manager_test.cc | 14 ++++++++++++++ cpp/core_v2/internal/webrtc_bwu_handler.cc | 14 ++++++++++++++ cpp/core_v2/internal/webrtc_bwu_handler.h | 14 ++++++++++++++ 7 files changed, 98 insertions(+) diff --git a/cpp/core_v2/internal/base_bwu_handler.h b/cpp/core_v2/internal/base_bwu_handler.h index 23b0abd0..d489533a 100644 --- a/cpp/core_v2/internal/base_bwu_handler.h +++ b/cpp/core_v2/internal/base_bwu_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_BASE_BWU_HANDLER_H_ #define CORE_V2_INTERNAL_BASE_BWU_HANDLER_H_ diff --git a/cpp/core_v2/internal/bwu_handler.h b/cpp/core_v2/internal/bwu_handler.h index 8e926a8c..b05ad821 100644 --- a/cpp/core_v2/internal/bwu_handler.h +++ b/cpp/core_v2/internal/bwu_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_BWU_HANDLER_H_ #define CORE_V2_INTERNAL_BWU_HANDLER_H_ diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 4756690d..92c366bd 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/bwu_manager.h" #include diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index 8ade19d7..c0b00853 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_BWU_MANAGER_H_ #define CORE_V2_INTERNAL_BWU_MANAGER_H_ diff --git a/cpp/core_v2/internal/bwu_manager_test.cc b/cpp/core_v2/internal/bwu_manager_test.cc index c130c239..f1f04d66 100644 --- a/cpp/core_v2/internal/bwu_manager_test.cc +++ b/cpp/core_v2/internal/bwu_manager_test.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/bwu_manager.h" #include diff --git a/cpp/core_v2/internal/webrtc_bwu_handler.cc b/cpp/core_v2/internal/webrtc_bwu_handler.cc index 2ba1d9ef..cbe6c3b7 100644 --- a/cpp/core_v2/internal/webrtc_bwu_handler.cc +++ b/cpp/core_v2/internal/webrtc_bwu_handler.cc @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "core_v2/internal/webrtc_bwu_handler.h" #include diff --git a/cpp/core_v2/internal/webrtc_bwu_handler.h b/cpp/core_v2/internal/webrtc_bwu_handler.h index 793357d8..11ec5c00 100644 --- a/cpp/core_v2/internal/webrtc_bwu_handler.h +++ b/cpp/core_v2/internal/webrtc_bwu_handler.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef CORE_V2_INTERNAL_WEBRTC_BWU_HANDLER_H_ #define CORE_V2_INTERNAL_WEBRTC_BWU_HANDLER_H_ From af150850cd87c482c208f15851a9c870d9b55f66 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 15 Sep 2020 00:09:57 -0700 Subject: [PATCH 45/52] Roll forward to cl/331709254 Signed-off-by: Alexey Polyudov Change-Id: I3a4a4d6c1735c61204053bc82165ab91705d95f6 --- cpp/core/internal/p2p_cluster_pcp_handler.cc | 1 + cpp/core_v2/internal/bwu_manager.cc | 8 ++++---- cpp/core_v2/internal/bwu_manager.h | 1 + cpp/core_v2/internal/mediums/utils.cc | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cpp/core/internal/p2p_cluster_pcp_handler.cc b/cpp/core/internal/p2p_cluster_pcp_handler.cc index bd75a030..3d73f236 100644 --- a/cpp/core/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -1,4 +1,5 @@ #include "core/internal/p2p_cluster_pcp_handler.h" + #include "platform/api/hash_utils.h" namespace location { diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 4756690d..4564dce1 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -474,9 +474,8 @@ void BwuManager::ProcessLastWriteToPriorChannelEvent( successfully_upgraded_endpoints_.emplace(endpoint_id); return; } - try { - previous_endpoint_channel->Write(parser::ForBwuSafeToClose()); - } catch (IOException e) { + + if (!previous_endpoint_channel->Write(parser::ForBwuSafeToClose()).Ok()) { previous_endpoint_channel->Close(DisconnectionReason::IO_ERROR); // Remove this prior EndpointChannel from previous_endpoint_channels to // avoid leaks. @@ -489,6 +488,7 @@ void BwuManager::ProcessLastWriteToPriorChannelEvent( endpoint_id.c_str()); return; } + // The upgrade protocol's clean shutdown of the prior EndpointChannel will // conclude when we receive a corresponding // BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL OfflineFrame @@ -585,7 +585,7 @@ void BwuManager::ProcessUpgradeFailureEvent( Medium last = parser::UpgradePathInfoMediumToMedium(upgrade_info.medium()); std::vector all_possible_mediums = client->GetUpgradeMediums(endpoint_id).GetMediums(true); - std::vector untried_mediums(all_possible_mediums); + std::vector untried_mediums(all_possible_mediums); for (Medium medium : all_possible_mediums) { untried_mediums.erase(untried_mediums.begin()); if (medium == last) { diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index 8ade19d7..150f49d5 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -2,6 +2,7 @@ #define CORE_V2_INTERNAL_BWU_MANAGER_H_ #include +#include #include "core_v2/internal/bwu_handler.h" #include "core_v2/internal/client_proxy.h" diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc index 921ccaa5..289c13a6 100644 --- a/cpp/core_v2/internal/mediums/utils.cc +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -53,7 +53,7 @@ std::string Utils::WrapUpgradeServiceId(const std::string& service_id) { std::string Utils::UnwrapUpgradeServiceId( const std::string& upgrade_service_id) { - auto pos = upgrade_service_id.find(kUpgradeServiceIdPostfix); + auto pos = upgrade_service_id.find(std::string(kUpgradeServiceIdPostfix)); if (pos != std::string::npos) { return std::string(upgrade_service_id, 0, pos); } From f53f38144d66a237dc643adb0e782a5953479de5 Mon Sep 17 00:00:00 2001 From: Josh Nohle Date: Thu, 24 Sep 2020 13:15:16 -0700 Subject: [PATCH 46/52] Roll forward to cl/333580336 Signed-off-by: Josh Nohle --- .../mediums/webrtc/signaling_frames_test.cc | 6 +- cpp/core_v2/internal/base_pcp_handler.cc | 126 ++-- cpp/core_v2/internal/base_pcp_handler.h | 36 +- cpp/core_v2/internal/base_pcp_handler_test.cc | 39 +- .../internal/base_pcp_handler_test.cc.orig | 538 ------------------ cpp/core_v2/internal/bwu_manager.cc | 22 +- cpp/core_v2/internal/bwu_manager.h | 3 +- cpp/core_v2/internal/mediums/ble.cc | 4 +- cpp/core_v2/internal/mediums/ble.h | 1 + cpp/core_v2/internal/mediums/ble_test.cc | 5 +- .../internal/mediums/bluetooth_classic.cc | 4 +- .../internal/mediums/bluetooth_classic.h | 2 +- .../mediums/webrtc/connection_flow.cc | 7 +- .../internal/offline_service_controller.h | 3 +- .../offline_service_controller.h.orig | 81 --- .../internal/p2p_cluster_pcp_handler.cc | 180 ++++-- .../internal/p2p_cluster_pcp_handler.h | 8 +- .../internal/p2p_cluster_pcp_handler_test.cc | 29 +- .../p2p_point_to_point_pcp_handler.cc | 5 +- .../internal/p2p_point_to_point_pcp_handler.h | 1 + cpp/core_v2/internal/p2p_star_pcp_handler.cc | 6 +- cpp/core_v2/internal/p2p_star_pcp_handler.h | 1 + cpp/core_v2/internal/pcp_manager.cc | 9 +- cpp/core_v2/internal/pcp_manager.h | 3 +- cpp/core_v2/internal/simulation_user.h | 4 +- cpp/core_v2/options.h | 9 + cpp/platform/BUILD | 1 - cpp/platform_v2/api/ble.h | 1 + cpp/platform_v2/api/bluetooth_classic.h | 2 +- cpp/platform_v2/api/platform.h | 5 +- cpp/platform_v2/base/medium_environment.cc | 14 +- cpp/platform_v2/base/medium_environment.h | 8 +- cpp/platform_v2/impl/g3/ble.cc | 12 +- cpp/platform_v2/impl/g3/ble.h | 1 + cpp/platform_v2/impl/g3/bluetooth_classic.cc | 2 +- cpp/platform_v2/impl/g3/bluetooth_classic.h | 2 +- cpp/platform_v2/impl/ios/BUILD | 56 ++ cpp/platform_v2/impl/ios/atomic_boolean.h | 28 + cpp/platform_v2/impl/ios/atomic_reference.h | 33 ++ cpp/platform_v2/impl/ios/condition_variable.h | 37 ++ cpp/platform_v2/impl/ios/count_down_latch.h | 55 ++ cpp/platform_v2/impl/ios/log_message.h | 28 + cpp/platform_v2/impl/ios/log_message.mm | 56 ++ .../impl/ios/multi_thread_executor.h | 57 ++ cpp/platform_v2/impl/ios/mutex.h | 47 ++ cpp/platform_v2/impl/ios/platform.mm | 124 ++++ cpp/platform_v2/impl/ios/scheduled_executor.h | 43 ++ .../impl/ios/scheduled_executor.mm | 65 +++ .../impl/ios/single_thread_executor.h | 20 + cpp/platform_v2/public/ble.cc | 7 +- cpp/platform_v2/public/ble.h | 1 + cpp/platform_v2/public/ble_test.cc | 6 +- cpp/platform_v2/public/bluetooth_classic.h | 4 +- proto/connections/offline_wire_formats.proto | 13 +- proto/discovery_enums.proto | 6 +- proto/error_code_enums.proto | 5 + 56 files changed, 1070 insertions(+), 801 deletions(-) delete mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc.orig delete mode 100644 cpp/core_v2/internal/offline_service_controller.h.orig create mode 100644 cpp/platform_v2/impl/ios/BUILD create mode 100644 cpp/platform_v2/impl/ios/atomic_boolean.h create mode 100644 cpp/platform_v2/impl/ios/atomic_reference.h create mode 100644 cpp/platform_v2/impl/ios/condition_variable.h create mode 100644 cpp/platform_v2/impl/ios/count_down_latch.h create mode 100644 cpp/platform_v2/impl/ios/log_message.h create mode 100644 cpp/platform_v2/impl/ios/log_message.mm create mode 100644 cpp/platform_v2/impl/ios/multi_thread_executor.h create mode 100644 cpp/platform_v2/impl/ios/mutex.h create mode 100644 cpp/platform_v2/impl/ios/platform.mm create mode 100644 cpp/platform_v2/impl/ios/scheduled_executor.h create mode 100644 cpp/platform_v2/impl/ios/scheduled_executor.mm create mode 100644 cpp/platform_v2/impl/ios/single_thread_executor.h diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc index 3e468d23..4cc4df2e 100644 --- a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -96,7 +96,7 @@ TEST(SignalingFramesTest, EncodeValidOffer) { TEST(SignalingFramesTest, DecodeValidOffer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kOfferProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kOfferProto, &frame); Ptr decoded_offer = DecodeOffer(frame); EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); @@ -120,7 +120,7 @@ TEST(SignalingFramesTest, EncodeValidAnswer) { TEST(SignalingFramesTest, DecodeValidAnswer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kAnswerProto, &frame); Ptr decoded_answer = DecodeAnswer(frame); EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); @@ -163,7 +163,7 @@ TEST(SignalingFramesTest, DecodeValidIceCandidates) { std::vector encoded_candidates_vec; location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kIceCandidatesProto, &frame); std::vector> decoded_candidates = DecodeIceCandidates(frame); diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 44f6779d..12fdb676 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -9,6 +9,7 @@ #include "core_v2/internal/offline_frames.h" #include "core_v2/internal/pcp_handler.h" #include "core_v2/options.h" +#include "platform_v2/base/bluetooth_utils.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/system_clock.h" #include "securegcm/d2d_connection_context_v1.h" @@ -29,11 +30,13 @@ constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; BasePcpHandler::BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp) + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp) : mediums_(mediums), endpoint_manager_(endpoint_manager), channel_manager_(channel_manager), - pcp_(pcp) {} + pcp_(pcp), + bwu_manager_(bwu_manager) {} BasePcpHandler::~BasePcpHandler() { NEARBY_LOGS(INFO) << "BasePcpHandler: going down; strategy=" @@ -63,23 +66,23 @@ Status BasePcpHandler::StartAdvertising(ClientProxy* client, const ConnectionRequestInfo& info) { Future response; ConnectionOptions advertising_options = options.CompatibleOptions(); - RunOnPcpHandlerThread( - [this, client, &service_id, &info, &advertising_options, &response]() { - auto result = StartAdvertisingImpl( - client, service_id, client->GetLocalEndpointId(), - info.endpoint_info, advertising_options); - if (!result.status.Ok()) { - response.Set(result.status); - return; - } + RunOnPcpHandlerThread([this, client, &service_id, &info, &advertising_options, + &response]() { + auto result = + StartAdvertisingImpl(client, service_id, client->GetLocalEndpointId(), + info.endpoint_info, advertising_options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } - // Now that we've succeeded, mark the client as advertising. - advertising_options_ = advertising_options; - advertising_listener_ = info.listener; - client->StartedAdvertising(service_id, GetStrategy(), info.listener, - absl::MakeSpan(result.mediums)); - response.Set({Status::kSuccess}); - }); + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = advertising_options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); return WaitForResult( absl::StrCat("StartAdvertising(", std::string(info.endpoint_info), ")"), client->GetClientId(), &response); @@ -232,8 +235,8 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( .raw_authentication_token = raw_auth_token, .is_incoming_connection = connection_info.is_incoming, }, - connection_info.options, - std::move(connection_info.channel), connection_info.listener); + connection_info.options, std::move(connection_info.channel), + connection_info.listener); if (connection_info.result != nullptr) { NEARBY_LOG(INFO, "Connection established; Finalising future OK"); @@ -318,14 +321,20 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, OnEndpointFound(client, webrtc_endpoint); } - auto endpoints = GetDiscoveredEndpoints(endpoint_id); + auto discovered_endpoints = GetDiscoveredEndpoints(endpoint_id); std::unique_ptr channel; ConnectImplResult connect_impl_result; - // TODO(b/156634369): add GetRemoteBluetoothMacAddressEndpoint here for - // valid remote mac address. + auto remote_bluetooth_mac_address = + BluetoothUtils::ToString(options.remote_bluetooth_mac_address); + if (!remote_bluetooth_mac_address.empty()) { + auto additional_endpoint = GetRemoteBluetoothMacAddressEndpoint( + endpoint_id, remote_bluetooth_mac_address, discovered_endpoints); + if (additional_endpoint != nullptr) + discovered_endpoints.push_back(additional_endpoint.get()); + } - for (auto connect_endpoint : endpoints) { + for (auto connect_endpoint : discovered_endpoints) { connect_impl_result = ConnectImpl(client, connect_endpoint); if (connect_impl_result.status.Ok()) { channel = std::move(connect_impl_result.endpoint_channel); @@ -611,10 +620,6 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, client->GetClientId(), &response); } -// proto::connections::Medium BasePcpHandler::GetBandwidthUpgradeMedium() { -// return bandwidth_upgrade_medium_.Get(); -//} - void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, @@ -928,7 +933,7 @@ void BasePcpHandler::ProcessTieBreakLoss( void BasePcpHandler::InitiateBandwidthUpgrade( ClientProxy* client, const std::string& endpoint_id, - const std::vector& supported_mediums) { + const std::vector& supported_mediums) { // When we successfully connect to a remote endpoint and a bandwidth upgrade // medium has not yet been decided, we'll pick the highest bandwidth medium // supported by both us and the remote endpoint. Once we pick a medium, all @@ -938,16 +943,14 @@ void BasePcpHandler::InitiateBandwidthUpgrade( // way to prevent mediums, like Wifi Hotspot, from interfering with active // connections (although it's suboptimal for bandwidth throughput). When all // endpoints disconnect, we reset the bandwidth upgrade medium. - if (bandwidth_upgrade_medium_.Get() == - proto::connections::Medium::UNKNOWN_MEDIUM) { - bandwidth_upgrade_medium_.Set(ChooseBestUpgradeMedium(supported_mediums)); + Medium bwu_medium = bwu_medium_.Get(); + if (bwu_medium == Medium::UNKNOWN_MEDIUM) { + bwu_medium = ChooseBestUpgradeMedium(supported_mediums); + bwu_medium_.Set(bwu_medium); } - if (AutoUpgradeBandwidth() && (bandwidth_upgrade_medium_.Get() != - proto::connections::Medium::UNKNOWN_MEDIUM)) { - // TODO(apolyudov): Bring bandwidth upgrade back, when it is ready. - // bandwidth_upgrade_->InitiateBandwidthUpgradeForEndpoint( - // client, endpoint_id, bandwidth_upgrade_medium_.Get()); + if (AutoUpgradeBandwidth() && bwu_medium != Medium::UNKNOWN_MEDIUM) { + bwu_manager_->InitiateBwuForEndpoint(client, endpoint_id, bwu_medium); } } @@ -975,6 +978,55 @@ proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( return proto::connections::Medium::UNKNOWN_MEDIUM; } +std::unique_ptr +BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints) { + if (!discovery_options_.allowed.bluetooth) { + return nullptr; + } + + if (endpoints.empty()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because endpointId " + << endpoint_id << " has not been discovered"; + return nullptr; + } + + for (auto endpoint : endpoints) { + if (endpoint->medium == proto::connections::Medium::BLUETOOTH) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because the " + "endpoint has already been found over Bluetooth."; + return nullptr; + } + } + + auto remote_bluetooth_device = + mediums_->GetBluetoothClassic().GetRemoteDevice( + remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because a valid " + "Bluetooth device could not be derived."; + return nullptr; + } + + auto bluetooth_endpoint = + std::make_unique(BluetoothEndpoint{ + { + endpoint_id, + endpoints[0]->endpoint_info, + endpoints[0]->service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + }); + NEARBY_LOGS(INFO) << "Appended remote Bluetooth device " + << remote_bluetooth_mac_address; + return bluetooth_endpoint; +} + void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, const std::string& endpoint_id, bool can_close_immediately) { diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index d9ee3f92..262d92cb 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -6,6 +6,7 @@ #include #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/endpoint_channel_manager.h" @@ -81,7 +82,8 @@ class BasePcpHandler : public PcpHandler, // TODO(apolyudov): Add SecureRandom. BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp); + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp); ~BasePcpHandler() override; BasePcpHandler(BasePcpHandler&&) = delete; BasePcpHandler& operator=(BasePcpHandler&&) = delete; @@ -90,8 +92,7 @@ class BasePcpHandler : public PcpHandler, // Notifies ConnectionListener (info.listener) in case of any event. // See // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, + Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; @@ -102,8 +103,7 @@ class BasePcpHandler : public PcpHandler, // Starts discovery of endpoints that may be advertising. // Updates ClientProxy state once discovery started. // DiscoveryListener will get called in case of any event. - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, + Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; @@ -113,16 +113,14 @@ class BasePcpHandler : public PcpHandler, // Requests a newly discovered remote endpoint it to form a connection. // Updates state on ClientProxy. - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, + Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, const ConnectionRequestInfo& info, const ConnectionOptions& options) override; // Called by either party to accept connection on their part. // Until both parties call it, connection will not reach a data phase. // Updates state in ClientProxy. - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, + Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) override; // Called by either party to reject connection on their part. @@ -139,12 +137,12 @@ class BasePcpHandler : public PcpHandler, // Called when an endpoint disconnects while we're waiting for both sides to // approve/reject the connection. // @EndpointManagerThread - void OnEndpointDisconnect(ClientProxy* client, - const std::string& endpoint_id, + void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) override; Pcp GetPcp() const override { return pcp_; } Strategy GetStrategy() const override { return strategy_; } + Medium GetBwuMedium() const { return bwu_medium_.Get(); } void DisconnectFromEndpointManager(); protected: @@ -227,8 +225,7 @@ class BasePcpHandler : public PcpHandler, std::shared_ptr endpoint); // @PcpHandlerThread - void OnEndpointLost(ClientProxy* client, - const DiscoveredEndpoint& endpoint); + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint); Exception OnIncomingConnection( ClientProxy* client, const ByteArray& remote_endpoint_info, @@ -270,8 +267,8 @@ class BasePcpHandler : public PcpHandler, // Returns a vector of discovered endpoints, sorted in order of decreasing // preference. - std::vector - GetDiscoveredEndpoints(const std::string& endpoint_id); + std::vector GetDiscoveredEndpoints( + const std::string& endpoint_id); mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, const string& endpoint_id, @@ -402,6 +399,11 @@ class BasePcpHandler : public PcpHandler, proto::connections::Medium ChooseBestUpgradeMedium( const std::vector& supported_mediums); + std::unique_ptr + GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints); + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, EndpointChannel* channel, Status status, @@ -429,8 +431,7 @@ class BasePcpHandler : public PcpHandler, Status WaitForResult(const std::string& method_name, std::int64_t client_id, Future* future); - AtomicReference bandwidth_upgrade_medium_{ - proto::connections::Medium::UNKNOWN_MEDIUM}; + AtomicReference bwu_medium_{Medium::UNKNOWN_MEDIUM}; ScheduledExecutor alarm_executor_; SingleThreadExecutor serial_executor_; @@ -472,6 +473,7 @@ class BasePcpHandler : public PcpHandler, Strategy strategy_{PcpToStrategy(pcp_)}; Prng prng_; EncryptionRunner encryption_runner_; + BwuManager* bwu_manager_; EndpointManager::FrameProcessor::Handle handle_ = nullptr; }; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 1a580067..e939fda2 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/offline_frames.h" @@ -76,8 +77,9 @@ class MockPcpHandler : public BasePcpHandler { public: using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(m, em, ecm, Pcp::kP2pCluster) {} + MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm, + BwuManager* bwu) + : BasePcpHandler(m, em, ecm, bwu, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; @@ -367,7 +369,8 @@ TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); SUCCEED(); } @@ -376,7 +379,8 @@ TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); } @@ -385,7 +389,8 @@ TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); EXPECT_TRUE(client.IsAdvertising()); @@ -398,7 +403,8 @@ TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); } @@ -407,7 +413,8 @@ TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); EXPECT_TRUE(client.IsDiscovering()); @@ -421,7 +428,8 @@ TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -444,7 +452,8 @@ TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -471,7 +480,8 @@ TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -494,7 +504,8 @@ TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -530,7 +541,8 @@ TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -569,7 +581,8 @@ TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig b/cpp/core_v2/internal/base_pcp_handler_test.cc.orig deleted file mode 100644 index c9009413..00000000 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig +++ /dev/null @@ -1,538 +0,0 @@ -#include "core_v2/internal/base_pcp_handler.h" - -#include -#include - -#include "core_v2/internal/base_endpoint_channel.h" -#include "core_v2/internal/client_proxy.h" -#include "core_v2/internal/encryption_runner.h" -#include "core_v2/internal/offline_frames.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/params.h" -#include "proto/connections/offline_wire_formats.pb.h" -#include "platform_v2/base/byte_array.h" -#include "platform_v2/public/count_down_latch.h" -#include "platform_v2/public/pipe.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { -namespace connections { -namespace { - -using ::location::nearby::proto::connections::Medium; -using ::testing::_; -using ::testing::AtLeast; -using ::testing::Invoke; -using ::testing::MockFunction; -using ::testing::Return; -using ::testing::StrictMock; - -constexpr BooleanMediumSelector kTestCases[] = { - BooleanMediumSelector{}, - BooleanMediumSelector{ - .bluetooth = true, - }, - BooleanMediumSelector{ - .wifi_lan = true, - }, - BooleanMediumSelector{ - .bluetooth = true, - .wifi_lan = true, - }, -}; - -class MockEndpointChannel : public BaseEndpointChannel { - public: - explicit MockEndpointChannel(Pipe* reader, Pipe* writer) - : BaseEndpointChannel("channel", &reader->GetInputStream(), - &writer->GetOutputStream()) {} - - ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } - Exception DoWrite(const ByteArray& data) { - return BaseEndpointChannel::Write(data); - } - absl::Time DoGetLastReadTimestamp() { - return BaseEndpointChannel::GetLastReadTimestamp(); - } - - MOCK_METHOD(ExceptionOr, Read, (), (override)); - MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); - MOCK_METHOD(void, CloseImpl, (), (override)); - MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); - MOCK_METHOD(std::string, GetType, (), (const override)); - MOCK_METHOD(std::string, GetName, (), (const override)); - MOCK_METHOD(bool, IsPaused, (), (const override)); - MOCK_METHOD(void, Pause, (), (override)); - MOCK_METHOD(void, Resume, (), (override)); - MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); -}; - -class MockPcpHandler : public BasePcpHandler { - public: - using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - - MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} - - // Expose protected inner types of a base type for mocking. - using BasePcpHandler::ConnectImplResult; - using BasePcpHandler::DiscoveredEndpoint; - using BasePcpHandler::StartOperationResult; - - MOCK_METHOD(Strategy, GetStrategy, (), (const override)); - MOCK_METHOD(Pcp, GetPcp, (), (const override)); - - MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, - (ClientProxy * client, const string& service_id, - const string& local_endpoint_id, - const string& local_endpoint_name, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); - MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, - (ClientProxy * client, const string& service_id, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); - MOCK_METHOD(ConnectImplResult, ConnectImpl, - (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); - MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), - (override)); - - std::vector GetConnectionMediumsByPriority() - override { - return GetDiscoveryMediums(); - } - - // Mock adapters for protected non-virtual methods of a base class. - void OnEndpointFound(ClientProxy* client, - std::shared_ptr endpoint) { - BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); - } - void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { - BasePcpHandler::OnEndpointLost(client, endpoint); - } - - std::vector GetDiscoveryMediums() { - std::vector mediums; - auto allowed = - BasePcpHandler::GetDiscoveryOptions().CompatibleOptions().allowed; - // Mediums are sorted in order of decreasing preference. - if (allowed.wifi_lan) - mediums.push_back(proto::connections::Medium::WIFI_LAN); - if (allowed.web_rtc) mediums.push_back(proto::connections::Medium::WEB_RTC); - if (allowed.bluetooth) - mediums.push_back(proto::connections::Medium::BLUETOOTH); - return mediums; - } - - std::vector GetDiscoveredEndpoints( - const std::string& endpoint_id) { - return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); - } -}; - -class MockContext { - public: - explicit MockContext(std::atomic_int* destroyed = nullptr) { - destroyed_ = destroyed; - } - MockContext(MockContext&&) = default; - MockContext& operator=(MockContext&&) = default; - - ~MockContext() { - if (destroyed_) (*destroyed_)++; - } - - private: - Swapper destroyed_{nullptr}; -}; - -struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { - MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) - : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} - - MockContext context; -}; - -class BasePcpHandlerTest - : public ::testing::TestWithParam { - protected: - struct MockConnectionListener { - StrictMock> - initiated_cb; - StrictMock> accepted_cb; - StrictMock> - rejected_cb; - StrictMock> - disconnected_cb; - StrictMock> - bandwidth_changed_cb; - }; - struct MockDiscoveryListener { - StrictMock> - endpoint_found_cb; - StrictMock> - endpoint_lost_cb; - StrictMock< - MockFunction> - endpoint_distance_changed_cb; - }; - - void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - ConnectionRequestInfo info{ - .name = "remote_endpoint_name", - .listener = connection_listener_, - }; - EXPECT_CALL(*pcp_handler, - StartAdvertisingImpl(client, service_id, _, info.name, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsAdvertising()); - } - - void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, - discovery_listener_), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsDiscovering()); - } - - std::pair, - std::unique_ptr> - SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT - auto channel_a = std::make_unique(&pipe_b, &pipe_a); - auto channel_b = std::make_unique(&pipe_a, &pipe_b); - // On initiator (A) side, we drop the first write, since this is a - // connection establishment packet, and we don't have the peer entity, just - // the peer channel. The rest of the exchange must happen for the benefit of - // DH key exchange. - EXPECT_CALL(*channel_a, Read()) - .WillRepeatedly(Invoke( - [channel = channel_a.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_a, Write(_)) - .WillOnce(Return(Exception{Exception::kSuccess})) - .WillRepeatedly( - Invoke([channel = channel_a.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_a, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); - EXPECT_CALL(*channel_b, Read()) - .WillRepeatedly(Invoke( - [channel = channel_b.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_b, Write(_)) - .WillRepeatedly( - Invoke([channel = channel_b.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_b, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); - return std::make_pair(std::move(channel_a), std::move(channel_b)); - } - - void RequestConnection(const std::string& endpoint_id, - std::unique_ptr channel_a, - MockEndpointChannel* channel_b, ClientProxy* client, - MockPcpHandler* pcp_handler, - std::atomic_int* flag = nullptr) { - ConnectionRequestInfo info{ - .name = "ABCD", - .listener = connection_listener_, - }; - EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); - EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*pcp_handler, GetStrategy) - .WillRepeatedly(Return(Strategy::kP2pCluster)); - EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); - // Simulate successful discovery. - auto encryption_runner = std::make_unique(); - auto allowed_mediums = pcp_handler->GetDiscoveryMediums(); - - EXPECT_CALL(*pcp_handler, ConnectImpl) - .WillOnce(Invoke([&channel_a, medium = allowed_mediums[0]]( - ClientProxy* client, - MockPcpHandler::DiscoveredEndpoint* endpoint) { - return MockPcpHandler::ConnectImplResult{ - .medium = medium, - .status = {Status::kSuccess}, - .endpoint_channel = std::move(channel_a), - }; - })); - - for (const auto& medium : allowed_mediums) { - pcp_handler->OnEndpointFound( - client, - std::make_shared(MockDiscoveredEndpoint{ - { - endpoint_id, - info.name, - "service", - medium, - }, - MockContext{flag}, - })); - } - auto other_client = std::make_unique(); - - // Run peer crypto in advance, if channel_b is provided. - // Otherwise stay in not-encrypted state. - if (channel_b != nullptr) { - encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, - {}); - } - EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Stopping Encryption Runner"); - } - - Pipe pipe_a_; - Pipe pipe_b_; - MockConnectionListener mock_connection_listener_; - MockDiscoveryListener mock_discovery_listener_; - ConnectionListener connection_listener_{ - .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), - .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), - .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), - .disconnected_cb = - mock_connection_listener_.disconnected_cb.AsStdFunction(), - .bandwidth_changed_cb = - mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), - }; - DiscoveryListener discovery_listener_{ - .endpoint_found_cb = - mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), - .endpoint_lost_cb = - mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), - .endpoint_distance_changed_cb = - mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), - }; -}; - -TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - SUCCEED(); -} - -TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); - EXPECT_TRUE(client.IsAdvertising()); - pcp_handler.StopAdvertising(&client); - EXPECT_FALSE(client.IsAdvertising()); -} - -TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); - EXPECT_TRUE(client.IsDiscovering()); - pcp_handler.StopDiscovery(&client); - EXPECT_FALSE(client.IsDiscovering()); -} - -TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "RequestConnection complete"); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { - 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; - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - &client, &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; - EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), - Status{Status::kSuccess}); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; - EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); - EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) - .Times(AtLeast(0)); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); - auto frame = - parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); - pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, - Medium::BLE); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { - std::atomic_int destroyed_flag = 0; - int mediums_count = 0; - { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), - &client, &pcp_handler, &destroyed_flag); - mediums_count = pcp_handler.GetDiscoveryMediums().size(); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); - } - EXPECT_EQ(destroyed_flag.load(), mediums_count); -} - -INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, - ::testing::ValuesIn(kTestCases)); - -} // namespace -} // namespace connections -} // namespace nearby -} // namespace location diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 4564dce1..9ab910fe 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/bwu_handler.h" #include "core_v2/internal/offline_frames.h" +#include "core_v2/internal/webrtc_bwu_handler.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/public/count_down_latch.h" #include "proto/connections_enums.pb.h" @@ -52,7 +53,11 @@ void BwuManager::InitBwuHandlers() { .incoming_connection_cb = absl::bind_front(&BwuManager::OnIncomingConnection, this), }; - // TODO(apolyudov): inject instances of supported upgrade medium handlers. + if (config_.allow_upgrade_to.web_rtc) { + handlers_.emplace(Medium::WEB_RTC, + std::make_unique( + *mediums_, *channel_manager_, notifications)); + } } void BwuManager::Shutdown() { @@ -90,12 +95,17 @@ void BwuManager::Shutdown() { } // This is the point on the Initiator side where the -// currentBwuMedium is set. +// medium_ is set. void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, - const std::string& endpoint_id) { - RunOnBwuManagerThread([this, client, endpoint_id]() { - auto* handler = SetCurrentBwuHandler(ChooseBestUpgradeMedium( - client->GetUpgradeMediums(endpoint_id).GetMediums(true))); + const std::string& endpoint_id, + Medium new_medium) { + RunOnBwuManagerThread([this, client, endpoint_id, new_medium]() { + Medium proposed_medium = ChooseBestUpgradeMedium( + client->GetUpgradeMediums(endpoint_id).GetMediums(true)); + if (new_medium != Medium::UNKNOWN_MEDIUM) { + proposed_medium = new_medium; + } + auto* handler = SetCurrentBwuHandler(proposed_medium); if (!handler) return; diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index 150f49d5..b97d7138 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -65,7 +65,8 @@ class BwuManager : public EndpointManager::FrameProcessor { // Function initiates the bandwidth upgrade and sends an // UPGRADE_PATH_AVAILABLE OfflineFrame. void InitiateBwuForEndpoint(ClientProxy* client_proxy, - const std::string& endpoint_id); + const std::string& endpoint_id, + Medium new_medium = Medium::UNKNOWN_MEDIUM); // == EndpointManager::FrameProcessor interface ==. // This is the point on the inbound BWU protocol where the handler_ is set. diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index d0ab8b16..f8c7cf8f 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -106,6 +106,7 @@ bool Ble::IsAdvertisingLocked(const std::string& service_id) { } bool Ble::StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) { MutexLock lock(&mutex_); @@ -133,7 +134,8 @@ bool Ble::StartScanning(const std::string& service_id, return false; } - if (!medium_.StartScanning(service_id, callback)) { + if (!medium_.StartScanning(service_id, fast_advertisement_service_uuid, + callback)) { NEARBY_LOGS(INFO) << "Failed to start scan of BLE services."; return false; } diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 42c1cd9c..1a6b7643 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -45,6 +45,7 @@ class Ble { // range through a callback. Returns true, if scanning mode was enabled, // false otherwise. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 6a2d43f0..15e24d8f 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -18,7 +18,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleTest : public ::testing::Test { protected: @@ -61,6 +61,7 @@ TEST_F(BleTest, CanStartAdvertising) { ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -95,6 +96,7 @@ TEST_F(BleTest, CanStartDiscovery) { EXPECT_TRUE(ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&accept_latch]( @@ -139,6 +141,7 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral discovered_peripheral; ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc index b6620e96..15dad66c 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -368,10 +368,10 @@ BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, return socket; } -BluetoothDevice BluetoothClassic::FindRemoteDevice( +BluetoothDevice BluetoothClassic::GetRemoteDevice( const std::string& mac_address) { MutexLock lock(&mutex_); - return medium_.FindRemoteDevice(mac_address); + return medium_.GetRemoteDevice(mac_address); } std::string BluetoothClassic::GetMacAddress() const { diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h index 3ed3a33a..29ae73e5 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.h +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -102,7 +102,7 @@ class BluetoothClassic { std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_); - BluetoothDevice FindRemoteDevice(const std::string& mac_address) + BluetoothDevice GetRemoteDevice(const std::string& mac_address) ABSL_LOCKS_EXCLUDED(mutex_); private: diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 3cb4e4ca..86af87ec 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -233,8 +233,8 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { Future success_future; webrtc_medium.CreatePeerConnection( &peer_connection_observer_, - [this, &success_future]( - rtc::scoped_refptr peer_connection) { + [this, success_future](rtc::scoped_refptr + peer_connection) mutable { if (!peer_connection) { success_future.Set(false); return; @@ -329,8 +329,7 @@ bool ConnectionFlow::CloseLocked() { state_ = State::kEnded; data_channel_future_.SetException({Exception::kInterrupted}); - if (peer_connection_) - peer_connection_->Close(); + if (peer_connection_) peer_connection_->Close(); data_channel_observer_.reset(); NEARBY_LOG(INFO, "Closed WebRTC connection."); diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index 03ebbc33..7b6a1c5a 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -67,9 +67,10 @@ class OfflineServiceController : public ServiceController { EndpointChannelManager channel_manager_; EndpointManager endpoint_manager_{&channel_manager_}; PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; BwuManager bwu_manager_{ mediums_, endpoint_manager_, channel_manager_, {}, {}}; + PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_, + bwu_manager_}; }; } // namespace connections diff --git a/cpp/core_v2/internal/offline_service_controller.h.orig b/cpp/core_v2/internal/offline_service_controller.h.orig deleted file mode 100644 index 97517fa7..00000000 --- a/cpp/core_v2/internal/offline_service_controller.h.orig +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ -#define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ - -#include -#include -#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/payload_manager.h" -#include "core_v2/internal/pcp_manager.h" -#include "core_v2/internal/service_controller.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/payload.h" -#include "core_v2/status.h" - -namespace location { -namespace nearby { -namespace connections { - -class OfflineServiceController : public ServiceController { - public: - OfflineServiceController() = default; - ~OfflineServiceController() override; - - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const ConnectionRequestInfo& info) override; - void StopAdvertising(ClientProxy* client) override; - - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const DiscoveryListener& listener) override; - void StopDiscovery(ClientProxy* client) override; - - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, - const ConnectionRequestInfo& info, - const ConnectionOptions& options) override; - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, - const PayloadListener& listener) override; - Status RejectConnection(ClientProxy* client, - const std::string& endpoint_id) override; - - void InitiateBandwidthUpgrade(ClientProxy* client, - const std::string& endpoint_id) override; - - void SendPayload(ClientProxy* client, - const std::vector& endpoint_ids, - Payload payload) override; - Status CancelPayload(ClientProxy* client, - Payload::Id payload_id) override; - - void DisconnectFromEndpoint(ClientProxy* client, - const std::string& endpoint_id) override; - - void Stop(); - - private: - // Note that the order of declaration of these is crucial, because we depend - // on the destructors running (strictly) in the reverse order; a deviation - // from that will lead to crashes at runtime. - AtomicBoolean stop_{false}; - Mediums mediums_; - EndpointChannelManager channel_manager_; - EndpointManager endpoint_manager_{&channel_manager_}; - PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; -}; - -} // namespace connections -} // namespace nearby -} // namespace location - -#endif // CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index a4f07de8..116a04fd 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/ble_advertisement.h" #include "core_v2/internal/ble_endpoint_channel.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/mediums/utils.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "core_v2/internal/webrtc_endpoint_channel.h" @@ -23,10 +24,22 @@ ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source, return Utils::Sha256Hash(source, size); } +bool P2pClusterPcpHandler::ShouldAdvertiseBluetoothMacOverBle( + PowerLevel power_level) { + return power_level == PowerLevel::kHighPower; +} + +bool P2pClusterPcpHandler::ShouldAcceptBluetoothConnections( + const ConnectionOptions& options) { + return options.enable_bluetooth_listening; +} + P2pClusterPcpHandler::P2pClusterPcpHandler( Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* endpoint_channel_manager, Pcp pcp) - : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, pcp), + EndpointChannelManager* endpoint_channel_manager, BwuManager* bwu_manager, + Pcp pcp) + : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, + bwu_manager, pcp), bluetooth_radio_(mediums->GetBluetoothRadio()), bluetooth_medium_(mediums->GetBluetoothClassic()), ble_medium_(mediums->GetBle()), @@ -131,10 +144,12 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); webrtc_medium_.StopAcceptingConnections(); wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); return {Status::kSuccess}; } @@ -311,12 +326,11 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( return; } - // Parse the Ble advertisement bytes. + // Parse the BLE advertisement bytes. BleAdvertisement advertisement( - fast_advertisement, - peripheral.GetAdvertisementBytes(service_id)); + fast_advertisement, peripheral.GetAdvertisementBytes(service_id)); - // Make sure the Ble advertisement points to a valid + // Make sure the BLE advertisement points to a valid // endpoint we're discovering. if (!IsRecognizedBleEndpoint(service_id, advertisement)) return; @@ -343,7 +357,34 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( peripheral, })); - // TODO(b/156632928): Check for Bluetooth device with remote mac address. + // Make sure we can connect to this device via Classic Bluetooth. + std::string remote_bluetooth_mac_address = + advertisement.GetBluetoothMacAddress(); + if (remote_bluetooth_mac_address.empty()) { + NEARBY_LOGS(INFO) + << "No Bluetooth Classic MAC address found in advertisement"; + return; + } + + BluetoothDevice remote_bluetooth_device = + bluetooth_medium_.GetRemoteDevice(remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) << "A valid Bluetooth device could not be derived from " + "the MAC address " + << remote_bluetooth_mac_address; + return; + } + + OnEndpointFound(client, + std::make_shared(BluetoothEndpoint{ + { + advertisement.GetEndpointId(), + advertisement.GetEndpointInfo(), + service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + })); }); } @@ -547,7 +588,7 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( .peripheral_lost_cb = absl::bind_front( &P2pClusterPcpHandler::BlePeripheralLostHandler, this, client), }, - client, service_id); + client, service_id, options.fast_advertisement_service_uuid); if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: Ble added"); mediums_started_successfully.push_back(ble_medium); @@ -753,51 +794,90 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, const ConnectionOptions& options) { bool fast_advertisement = !options.fast_advertisement_service_uuid.empty(); + PowerLevel power_level = + options.low_power ? PowerLevel::kLowPower : PowerLevel::kHighPower; // Start listening for connections before advertising in case a connection - // request comes in very quickly. + // request comes in very quickly. BLE allows connecting over BLE itself, as + // well as advertising the Bluetooth MAC address to allow connecting over + // Bluetooth Classic. NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" << service_id << ": start"; - if (ble_medium_.IsAcceptingConnections(service_id)) { - NEARBY_LOGS(ERROR) << "Ble is already accepting connections for service_id=" - << service_id; - return proto::connections::UNKNOWN_MEDIUM; - } + if (!ble_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !ble_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BleSocket socket, + const std::string& service_id) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + service_id, + socket = std::move(socket)]() mutable { + std::string remote_peripheral_name = + socket.GetRemotePeripheral().GetName(); + auto channel = absl::make_unique( + remote_peripheral_name, socket); + ByteArray remote_peripheral_info = + socket.GetRemotePeripheral().GetAdvertisementBytes( + service_id); - NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" - << service_id << ": invoking"; - if (!bluetooth_radio_.Enable() || - !ble_medium_.StartAcceptingConnections( - service_id, - {.accepted_cb = [this, client, local_endpoint_info]( - BleSocket socket, const std::string& service_id) { - if (!socket.IsValid()) { - NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", - std::string(local_endpoint_info).c_str()); - return; - } - RunOnPcpHandlerThread([this, client, local_endpoint_info, - service_id, - socket = std::move(socket)]() mutable { - std::string remote_peripheral_name = - socket.GetRemotePeripheral().GetName(); - auto channel = absl::make_unique( - remote_peripheral_name, socket); - ByteArray remote_peripheral_info = - socket.GetRemotePeripheral().GetAdvertisementBytes( - service_id); - - OnIncomingConnection(client, remote_peripheral_info, - std::move(channel), - proto::connections::Medium::BLE); - }); - }})) { + OnIncomingConnection(client, remote_peripheral_info, + std::move(channel), + proto::connections::Medium::BLE); + }); + }})) { + NEARBY_LOGS(ERROR) + << "Ble failed to start accepting connections for service_id=" + << service_id; + return proto::connections::UNKNOWN_MEDIUM; + } NEARBY_LOGS(ERROR) - << "Ble failed to start accepting connections for service_id=" + << "Ble succeed to start accepting connections for service_id=" << service_id; - return proto::connections::UNKNOWN_MEDIUM; } - // TODO(b/156632928): Should check for Bluetooth connection here + + if (ShouldAdvertiseBluetoothMacOverBle(power_level) || + ShouldAcceptBluetoothConnections(options)) { + if (bluetooth_medium_.IsAvailable() && + !bluetooth_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !bluetooth_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BluetoothSocket socket) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, + "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + socket = std::move(socket)]() mutable { + std::string remote_device_name = + socket.GetRemoteDevice().GetName(); + auto channel = absl::make_unique( + remote_device_name, socket); + ByteArray remote_device_info{remote_device_name}; + + OnIncomingConnection(client, remote_device_info, + std::move(channel), + proto::connections::Medium::BLUETOOTH); + }); + }})) { + NEARBY_LOGS(ERROR) + << "BT failed to start accepting connections for service_id=" + << service_id; + ble_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + NEARBY_LOGS(ERROR) + << "BT succeed to start accepting connections for service_id=" + << service_id; + } + } NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBleAdvertising: service=%s: " @@ -814,8 +894,10 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( } else { const ByteArray service_id_hash = GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); - // TODO(b/156632928): Should advertise Bluetooth MacAddress Over Ble std::string bluetooth_mac_address; + if (bluetooth_medium_.IsAvailable() && + ShouldAdvertiseBluetoothMacOverBle(power_level)) + bluetooth_mac_address = bluetooth_medium_.GetMacAddress(); advertisement_bytes = ByteArray(BleAdvertisement( kBleAdvertisementVersion, GetPcp(), service_id_hash, local_endpoint_id, @@ -852,9 +934,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( proto::connections::Medium P2pClusterPcpHandler::StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id) { + const std::string& service_id, + const std::string& fast_advertisement_service_uuid) { if (bluetooth_radio_.Enable() && - ble_medium_.StartScanning(service_id, std::move(callback))) { + ble_medium_.StartScanning(service_id, fast_advertisement_service_uuid, + std::move(callback))) { NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleScanning: ok"; return proto::connections::BLE; } else { diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index b276a3f9..687075c4 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -7,6 +7,7 @@ #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/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -38,6 +39,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { public: P2pClusterPcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp = Pcp::kP2pCluster); ~P2pClusterPcpHandler() override = default; @@ -117,6 +119,9 @@ class P2pClusterPcpHandler : public BasePcpHandler { WifiLanServiceInfo::Version::kV1; static ByteArray GenerateHash(const std::string& source, size_t size); + static bool ShouldAdvertiseBluetoothMacOverBle(PowerLevel power_level); + static bool ShouldAcceptBluetoothConnections( + const ConnectionOptions& options); // Bluetooth bool IsRecognizedBluetoothEndpoint(const std::string& name_string, @@ -155,7 +160,8 @@ class P2pClusterPcpHandler : public BasePcpHandler { const ByteArray& local_endpoint_info, const ConnectionOptions& options); proto::connections::Medium StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id); + const std::string& service_id, + const std::string& fast_advertisement_service_uuid); BasePcpHandler::ConnectImplResult BleConnectImpl(ClientProxy* client, BleEndpoint* endpoint); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc index 51bce6df..8ac6064b 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -2,6 +2,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/options.h" #include "platform_v2/base/medium_environment.h" #include "platform_v2/public/count_down_latch.h" @@ -61,7 +62,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructOne) { Mediums mediums; EndpointChannelManager ecm; EndpointManager em(&ecm); - P2pClusterPcpHandler handler(&mediums, &em, &ecm); + BwuManager bwu(mediums, em, ecm, {}, {}); + P2pClusterPcpHandler handler(&mediums, &em, &ecm, &bwu); env_.Stop(); } @@ -73,8 +75,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructMultiple) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); env_.Stop(); } @@ -84,7 +88,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanAdvertise) { Mediums mediums_a; EndpointChannelManager ecm_a; EndpointManager em_a(&ecm_a); - P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, {.endpoint_info = ByteArray{endpoint_name}}), @@ -101,8 +106,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanDiscover) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch latch(1); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, @@ -141,8 +148,12 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch discover_latch(1); CountDownLatch connect_latch(2); struct DiscoveredInfo { @@ -207,6 +218,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { }, options_); EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result()); + bwu_a.Shutdown(); + bwu_b.Shutdown(); env_.Stop(); } 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 index c3525bdd..0b09d8bd 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -6,8 +6,9 @@ namespace connections { P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( Mediums& mediums, EndpointManager& endpoint_manager, - EndpointChannelManager& channel_manager, Pcp pcp) - : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp) {} + EndpointChannelManager& channel_manager, BwuManager& bwu_manager, Pcp pcp) + : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, bwu_manager, + pcp) {} std::vector P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { 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 index cd9cb39b..4b09ab3c 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -22,6 +22,7 @@ class P2pPointToPointPcpHandler : public P2pStarPcpHandler { public: P2pPointToPointPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pPointToPoint); protected: diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc index acb45e38..45a20d14 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -9,9 +9,9 @@ namespace connections { P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, - Pcp pcp) - : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, pcp) { -} + BwuManager& bwu_manager, Pcp pcp) + : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, + &bwu_manager, pcp) {} std::vector P2pStarPcpHandler::GetConnectionMediumsByPriority() { diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h index 203bfcf5..c1418ffd 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -25,6 +25,7 @@ class P2pStarPcpHandler : public P2pClusterPcpHandler { public: P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pStar); protected: diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index c3c62aee..3a537547 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -11,14 +11,15 @@ namespace connections { PcpManager::PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager) { + EndpointManager& endpoint_manager, + BwuManager& bwu_manager) { handlers_[Pcp::kP2pCluster] = std::make_unique( - &mediums, &endpoint_manager, &channel_manager); + &mediums, &endpoint_manager, &channel_manager, &bwu_manager); handlers_[Pcp::kP2pStar] = std::make_unique( - mediums, endpoint_manager, channel_manager); + mediums, endpoint_manager, channel_manager, bwu_manager); handlers_[Pcp::kP2pPointToPoint] = std::make_unique(mediums, endpoint_manager, - channel_manager); + channel_manager, bwu_manager); } void PcpManager::DisconnectFromEndpointManager() { diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index ddeb4107..bb4d9991 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_pcp_handler.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -29,7 +30,7 @@ namespace connections { class PcpManager { public: PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager); + EndpointManager& endpoint_manager, BwuManager& bwu_manager); ~PcpManager(); Status StartAdvertising(ClientProxy* client, const string& service_id, diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 4674be0d..2b48353c 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -3,6 +3,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -130,7 +131,8 @@ class SimulationUser { ClientProxy client_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; - PcpManager mgr_{mediums_, ecm_, em_}; + BwuManager bwu_{mediums_, em_, ecm_, {}, {}}; + PcpManager mgr_{mediums_, ecm_, em_, bwu_}; PayloadManager pm_{em_}; }; diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index 6e0b0a66..94c72f39 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -65,6 +65,13 @@ struct MediumSelector { // Feature On/Off switch for mediums. using BooleanMediumSelector = MediumSelector; +// Represents the various power levels that can be used, on mediums that support +// it. +enum class PowerLevel { + kHighPower = 0, + kLowPower = 1, +}; + // Connection Options: used for both Advertising and Discovery. // All fields are mutable, to make the type copy-assignable. struct ConnectionOptions { @@ -72,6 +79,8 @@ struct ConnectionOptions { BooleanMediumSelector allowed{BooleanMediumSelector().SetAll(true)}; bool auto_upgrade_bandwidth; bool enforce_topology_constraints; + bool low_power; + bool enable_bluetooth_listening; ByteArray remote_bluetooth_mac_address; std::string fast_advertisement_service_uuid; // Verify if ConnectionOptions is in a not-initialized (Empty) state. diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 2e9bcb30..1ee1d0df 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -61,7 +61,6 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", - "//platform_v2/base:__pkg__", ], deps = [ "//absl/base", diff --git a/cpp/platform_v2/api/ble.h b/cpp/platform_v2/api/ble.h index 548aeb45..608574cf 100644 --- a/cpp/platform_v2/api/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -75,6 +75,7 @@ class BleMedium { // Returns true once the BLE scan has been initiated. virtual bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) = 0; // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/api/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h index 6dddd606..5b334830 100644 --- a/cpp/platform_v2/api/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -136,7 +136,7 @@ class BluetoothClassicMedium { virtual std::unique_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) = 0; - virtual BluetoothDevice* FindRemoteDevice(const std::string& mac_address) = 0; + virtual BluetoothDevice* GetRemoteDevice(const std::string& mac_address) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index 2b5ca406..eee8bfad 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -42,7 +42,7 @@ class ImplementationPlatform { // - synchronization primitives: // - mutex (regular, and recursive) // - condition variable (must work with regular mutex only) - // - Future : to synchronize on Callable schduled to execute. + // - Future : to synchronize on Callable scheduled to execute. // - CountDownLatch : to ensure at least N threads are waiting. // - file I/O // - Logging @@ -58,8 +58,7 @@ class ImplementationPlatform { // 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 CreateAtomicUint32(std::uint32_t value); static std::unique_ptr CreateCountDownLatch( std::int32_t count); diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 21d72fd7..daa06267 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -340,10 +340,12 @@ void MediumEnvironment::UpdateBleMediumForAdvertising( void MediumEnvironment::UpdateBleMediumForScanning( api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, BleDiscoveredPeripheralCallback callback, bool enabled) { if (!enabled_) return; RunOnMediumEnvironmentThread( - [this, &medium, service_id, callback = std::move(callback), enabled]() { + [this, &medium, service_id, fast_advertisement_service_uuid, + callback = std::move(callback), enabled]() { auto item = ble_mediums_.find(&medium); if (item == ble_mediums_.end()) { NEARBY_LOG(INFO, @@ -353,10 +355,12 @@ void MediumEnvironment::UpdateBleMediumForScanning( } auto& context = item->second; context.discovery_callback = std::move(callback); - NEARBY_LOG(INFO, - "Update Ble medium for scanning: this=%p; medium=%p; " - "service_id=%s; enabled=%d ;", - this, &medium, service_id.c_str(), enabled); + NEARBY_LOG( + INFO, + "Update Ble medium for scanning: this=%p; medium=%p; " + "service_id=%s; fast_advertisement_service_uuid=%s; enabled=%d ;", + this, &medium, service_id.c_str(), + fast_advertisement_service_uuid.c_str(), enabled); for (auto& medium_info : ble_mediums_) { auto& local_medium = medium_info.first; auto& info = medium_info.second; diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 875de81a..826189d1 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -151,10 +151,10 @@ class MediumEnvironment { // This should be called when discoverable state changes. // with user-specified callback when discovery is enabled, and with default // (empty) callback otherwise. - void UpdateBleMediumForScanning(api::BleMedium& medium, - const std::string& service_id, - BleDiscoveredPeripheralCallback callback, - bool enabled); + void UpdateBleMediumForScanning( + api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + BleDiscoveredPeripheralCallback callback, bool enabled); // Updates Accepted connection callback info to allow for dispatch of // advertising events. diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc index c7bfa041..316d64fc 100644 --- a/cpp/platform_v2/impl/g3/ble.cc +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -252,11 +252,15 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return true; } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id; auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, std::move(callback), true); + env.UpdateBleMediumForScanning(*this, service_id, + fast_advertisement_service_uuid, + std::move(callback), true); { absl::MutexLock lock(&mutex_); scanning_info_.service_id = service_id; @@ -277,7 +281,7 @@ bool BleMedium::StopScanning(const std::string& service_id) { } auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, {}, false); + env.UpdateBleMediumForScanning(*this, service_id, {}, {}, false); return true; } diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h index 6bdacb09..9822200d 100644 --- a/cpp/platform_v2/impl/g3/ble.h +++ b/cpp/platform_v2/impl/g3/ble.h @@ -146,6 +146,7 @@ class BleMedium : public api::BleMedium { // Returns true once the Ble scanning has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) override ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc index a0c040d3..36403954 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -240,7 +240,7 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name, return socket; } -api::BluetoothDevice* BluetoothClassicMedium::FindRemoteDevice( +api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( const std::string& mac_address) { auto& env = MediumEnvironment::Instance(); return env.FindBluetoothDevice(mac_address); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h index 8d199863..0aa92c53 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.h +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -207,7 +207,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { const std::string& service_name, const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); - api::BluetoothDevice* FindRemoteDevice( + api::BluetoothDevice* GetRemoteDevice( const std::string& mac_address) override; private: diff --git a/cpp/platform_v2/impl/ios/BUILD b/cpp/platform_v2/impl/ios/BUILD new file mode 100644 index 00000000..fa133022 --- /dev/null +++ b/cpp/platform_v2/impl/ios/BUILD @@ -0,0 +1,56 @@ +objc_library( + name = "types", + srcs = [ + "log_message.mm", + "scheduled_executor.mm", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "condition_variable.h", + "count_down_latch.h", + "log_message.h", + "multi_thread_executor.h", + "mutex.h", + "scheduled_executor.h", + "single_thread_executor.h", + ], + visibility = [ + "//platform_v2/impl/ios:__pkg__", + ], + deps = [ + "//base", + "//platform_v2/api:platform", + "//platform_v2/api:types", + "//platform_v2/base", + "//platform_v2/base:util", + "//platform_v2/impl/shared:posix_mutex", + "//absl/base:core_headers", + "//absl/synchronization", + "//absl/time", + "//thread", + ], +) + +objc_library( + name = "ios", + srcs = [ + "platform.mm", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":types", + "//platform_v2/api:comm", + "//platform_v2/api:platform", + "//platform_v2/api:types", + "//platform_v2/impl/shared:file", + "//absl/base:core_headers", + "//absl/memory", + "//absl/strings", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/impl/ios/atomic_boolean.h b/cpp/platform_v2/impl/ios/atomic_boolean.h new file mode 100644 index 00000000..37a1d1f1 --- /dev/null +++ b/cpp/platform_v2/impl/ios/atomic_boolean.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace ios { + +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/ios/atomic_reference.h b/cpp/platform_v2/impl/ios/atomic_reference.h new file mode 100644 index 00000000..49bb2849 --- /dev/null +++ b/cpp/platform_v2/impl/ios/atomic_reference.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ + +#include +#include + +#include "platform_v2/api/atomic_reference.h" + +namespace location { +namespace nearby { +namespace ios { + +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 ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/impl/ios/condition_variable.h b/cpp/platform_v2/impl/ios/condition_variable.h new file mode 100644 index 00000000..4df6893a --- /dev/null +++ b/cpp/platform_v2/impl/ios/condition_variable.h @@ -0,0 +1,37 @@ +#ifndef PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/impl/ios/mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(ios::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + Exception Wait(absl::Duration timeout) override { + cond_var_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; + } + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/ios/count_down_latch.h b/cpp/platform_v2/impl/ios/count_down_latch.h new file mode 100644 index 00000000..a06bcc45 --- /dev/null +++ b/cpp/platform_v2/impl/ios/count_down_latch.h @@ -0,0 +1,55 @@ +#ifndef PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ + +#include "platform_v2/api/count_down_latch.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace ios { + +class CountDownLatch final : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + CountDownLatch(CountDownLatch&&) = delete; + CountDownLatch& operator=(CountDownLatch&&) = delete; + ExceptionOr Await(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + absl::Time deadline = absl::Now() + timeout; + while (count_ > 0) { + if (cond_.WaitWithDeadline(&mutex_, deadline)) { + return ExceptionOr(false); + } + } + return ExceptionOr(true); + } + Exception Await() override { + absl::MutexLock lock(&mutex_); + while (count_ > 0) { + cond_.Wait(&mutex_); + } + return {Exception::kSuccess}; + } + void CountDown() override { + absl::MutexLock lock(&mutex_); + if (count_ > 0 && --count_ == 0) { + cond_.SignalAll(); + } + } + + private: + absl::Mutex mutex_; // Mutex to be used with cond_.Wait...() method family. + absl::CondVar cond_; // Condition to synchronize up to N waiting threads. + int count_ + ABSL_GUARDED_BY(mutex_); // When zero, latch should release all waiters. +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.h b/cpp/platform_v2/impl/ios/log_message.h new file mode 100644 index 00000000..dd0a0c2a --- /dev/null +++ b/cpp/platform_v2/impl/ios/log_message.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ +#define PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ + +#include "base/logging.h" +#include "platform_v2/api/log_message.h" + +namespace location { +namespace nearby { +namespace ios { + +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 ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.mm b/cpp/platform_v2/impl/ios/log_message.mm new file mode 100644 index 00000000..0e6ac13c --- /dev/null +++ b/cpp/platform_v2/impl/ios/log_message.mm @@ -0,0 +1,56 @@ +#include "platform_v2/impl/ios/log_message.h" + +#include + +#include "base/stringprintf.h" + +namespace location { +namespace nearby { +namespace ios { + +api::LogMessage::Severity kMinLogSeverity = 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 ios + +namespace api { + +void LogMessage::SetMinLogSeverity(Severity severity) { + ios::kMinLogSeverity = severity; +} + +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + return severity >= ios::kMinLogSeverity; +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/multi_thread_executor.h b/cpp/platform_v2/impl/ios/multi_thread_executor.h new file mode 100644 index 00000000..e665df62 --- /dev/null +++ b/cpp/platform_v2/impl/ios/multi_thread_executor.h @@ -0,0 +1,57 @@ +#ifndef PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ + +#include + +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/impl/ios/count_down_latch.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace ios { + +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_parallelism) + : thread_pool_(max_parallelism) { + thread_pool_.StartWorkers(); + } + void Execute(Runnable&& runnable) override { + if (!shutdown_) { + thread_pool_.Schedule(std::move(runnable)); + } + } + bool DoSubmit(Runnable&& runnable) override { + if (shutdown_) return false; + thread_pool_.Schedule(std::move(runnable)); + return true; + } + void Shutdown() override { DoShutdown(); } + ~MultiThreadExecutor() override { DoShutdown(); } + + int GetTid(int index) const override { + const auto* thread = thread_pool_.thread(index); + return thread ? *(int*)(thread->tid()) : 0; + } + + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { + if (shutdown_) return; + thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); + } + bool InShutdown() const { return shutdown_; } + + private: + void DoShutdown() { + shutdown_ = true; + } + std::atomic_bool shutdown_ = false; + ThreadPool thread_pool_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/mutex.h b/cpp/platform_v2/impl/ios/mutex.h new file mode 100644 index 00000000..2986869f --- /dev/null +++ b/cpp/platform_v2/impl/ios/mutex.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_IOS_MUTEX_H_ +#define PLATFORM_V2_IMPL_IOS_MUTEX_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/impl/shared/posix_mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(bool check) : check_(check) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + if (!check_) mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + bool check_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex { + public: + ~RecursiveMutex() override = default; + RecursiveMutex() = default; + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_MUTEX_H_ diff --git a/cpp/platform_v2/impl/ios/platform.mm b/cpp/platform_v2/impl/ios/platform.mm new file mode 100644 index 00000000..dabd01d4 --- /dev/null +++ b/cpp/platform_v2/impl/ios/platform.mm @@ -0,0 +1,124 @@ +#include "platform_v2/api/platform.h" + +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/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/submittable_executor.h" +#include "platform_v2/impl/ios/atomic_boolean.h" +#include "platform_v2/impl/ios/atomic_reference.h" +#include "platform_v2/impl/ios/condition_variable.h" +#include "platform_v2/impl/ios/count_down_latch.h" +#include "platform_v2/impl/ios/log_message.h" +#include "platform_v2/impl/ios/multi_thread_executor.h" +#include "platform_v2/impl/ios/mutex.h" +#include "platform_v2/impl/ios/scheduled_executor.h" +#include "platform_v2/impl/ios/single_thread_executor.h" +#include "platform_v2/impl/shared/file.h" +#include "absl/memory/memory.h" + +namespace location { +namespace nearby { +namespace api { + +namespace { +std::string GetPayloadPath(PayloadId payload_id) { + return absl::StrCat("/tmp/", payload_id); +} +} // namespace + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean(bool initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicUint32(std::uint32_t value) { + return absl::make_unique(value); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch( + std::int32_t count) { + return absl::make_unique(count); +} + +std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + if (mode == Mutex::Mode::kRecursive) + return absl::make_unique(); + else + return absl::make_unique(mode == Mutex::Mode::kRegular); +} + +std::unique_ptr ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::unique_ptr( + new ios::ConditionVariable(static_cast(mutex))); +} + +std::unique_ptr ImplementationPlatform::CreateInputFile(PayloadId payload_id, + std::int64_t total_size) { + return absl::make_unique(GetPayloadPath(payload_id), total_size); +} + +std::unique_ptr ImplementationPlatform::CreateOutputFile(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::CreateSingleThreadExecutor() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateMultiThreadExecutor( + int max_concurrency) { + return absl::make_unique(max_concurrency); +} + +std::unique_ptr ImplementationPlatform::CreateScheduledExecutor() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateBluetoothAdapter() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateServerSyncMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { + return std::unique_ptr(); +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.h b/cpp/platform_v2/impl/ios/scheduled_executor.h new file mode 100644 index 00000000..6fb08fd7 --- /dev/null +++ b/cpp/platform_v2/impl/ios/scheduled_executor.h @@ -0,0 +1,43 @@ +#ifndef PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/impl/ios/single_thread_executor.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace ios { + +class ScheduledExecutor final : public api::ScheduledExecutor { + public: + ScheduledExecutor() = default; + ~ScheduledExecutor() override { + executor_.Shutdown(); + } + + void Execute(Runnable&& runnable) override { + executor_.Execute(std::move(runnable)); + } + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration delay) override; + void Shutdown() override { executor_.Shutdown(); } + + int GetTid(int index) const override { + return executor_.GetTid(index); + } + private: + SingleThreadExecutor executor_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.mm b/cpp/platform_v2/impl/ios/scheduled_executor.mm new file mode 100644 index 00000000..6d850b06 --- /dev/null +++ b/cpp/platform_v2/impl/ios/scheduled_executor.mm @@ -0,0 +1,65 @@ +#include "platform_v2/impl/ios/scheduled_executor.h" + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace ios { + +namespace { + +class ScheduledCancelable : public api::Cancelable { + public: + bool Cancel() override { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kCanceled)) { + return true; + } + } + return false; + } + bool MarkExecuted() { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kExecuted)) { + return true; + } + } + return false; + } + + private: + enum Status { + kNotRun, + kExecuted, + kCanceled, + }; + std::atomic status_ = kNotRun; +}; + +} // namespace + +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration delay) { + auto scheduled_cancelable = std::make_shared(); + if (executor_.InShutdown()) { + return scheduled_cancelable; + } + executor_.ScheduleAfter( + delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() { + if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) { + runnable(); + } + }); + return scheduled_cancelable; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/single_thread_executor.h b/cpp/platform_v2/impl/ios/single_thread_executor.h new file mode 100644 index 00000000..be5d99e0 --- /dev/null +++ b/cpp/platform_v2/impl/ios/single_thread_executor.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/impl/ios/multi_thread_executor.h" + +namespace location { +namespace nearby { +namespace ios { + +class SingleThreadExecutor final : public MultiThreadExecutor { + public: + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 43a81d1b..db8ab6b5 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -17,8 +17,10 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return impl_->StopAdvertising(service_id); } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { { MutexLock lock(&mutex_); discovered_peripheral_callback_ = std::move(callback); @@ -26,6 +28,7 @@ bool BleMedium::StartScanning(const std::string& service_id, } return impl_->StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [this](api::BlePeripheral& peripheral, diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 948903af..ca9bedbb 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -106,6 +106,7 @@ class BleMedium final { // Returns true once the BLE scan has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback); // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index 2af0c3de..41f6d091 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -15,7 +15,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleMediumTest : public ::testing::Test { protected: @@ -59,6 +59,7 @@ TEST_F(BleMediumTest, CanStartAdvertising) { EXPECT_TRUE(ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -85,6 +86,7 @@ TEST_F(BleMediumTest, CanStartScanning) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -119,6 +121,7 @@ TEST_F(BleMediumTest, CanStopDiscovery) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -154,6 +157,7 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral* discovered_peripheral = nullptr; ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h index d8bf989d..8d073f6b 100644 --- a/cpp/platform_v2/public/bluetooth_classic.h +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -188,8 +188,8 @@ class BluetoothClassicMedium final { api::BluetoothClassicMedium& GetImpl() { return *impl_; } BluetoothAdapter& GetAdapter() { return adapter_; } std::string GetMacAddress() const { return adapter_.GetMacAddress(); } - BluetoothDevice FindRemoteDevice(const std::string& mac_address) { - return BluetoothDevice(impl_->FindRemoteDevice(mac_address)); + BluetoothDevice GetRemoteDevice(const std::string& mac_address) { + return BluetoothDevice(impl_->GetRemoteDevice(mac_address)); } private: diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index c7169827..9cd1728d 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -81,8 +81,19 @@ message ConnectionResponseFrame { // // - ConnectionsStatusCodes.STATUS_OK // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. - optional int32 status = 1; + optional int32 status = 1 [deprecated = true]; optional bytes handshake_data = 2; + + // Used to replace the status integer parameter with a meaningful enum item. + // Map ConnectionsStatusCodes.STATUS_OK to ACCEPT and + // ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED to REJECT. + // Flag: connection_replace_status_with_response_connectionResponseFrame + enum ResponseStatus { + UNKNOWN_RESPONSE_STATUS = 0; + ACCEPT = 1; + REJECT = 2; + } + optional ResponseStatus response = 3; } message PayloadTransferFrame { diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index a3ca8c3e..cc095756 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -11,7 +11,7 @@ option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. -// NEXT ID: 132 +// NEXT ID: 133 enum DiscoveryEvent { UNKNOWN_DISCOVERY_EVENT = 0; @@ -395,6 +395,10 @@ enum DiscoveryEvent { // User has seen a low battery notification. FAST_PAIR_LOW_BATTERY_NOTIFICATION_SHOWN = 131; + // Connection Tracker Manager (Baymax) recovered the connection of the + // companion app. + FAST_PAIR_CONNECTION_TRACKER_RECOVER_COMPANION_APP = 132; + // Deprecated. reserved 65, 67 to 72; } diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 9296a49b..6ea1f745 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -408,4 +408,9 @@ enum Description { SOCKET_NOT_BOUND = 141; INVALID_REMOTE_ADDRESS = 142; SOCKET_ALREADY_BOUND = 143; + HOTSPOT_NOT_STARTED = 144; + WEBRTC_ALREADY_INITIALIZED = 145; + INVALID_WEBRTC_STATE = 146; + NULL_DATA_CHANNEL = 147; + CREATE_OFFER_FAILED = 148; } From 2400a0aa4c58a574d9203f5a4b3cd34fb7c917e0 Mon Sep 17 00:00:00 2001 From: Josh Nohle Date: Thu, 24 Sep 2020 13:15:16 -0700 Subject: [PATCH 47/52] Roll forward to cl/333580336 Signed-off-by: Josh Nohle --- .../mediums/webrtc/signaling_frames_test.cc | 6 +- cpp/core_v2/internal/base_pcp_handler.cc | 126 ++-- cpp/core_v2/internal/base_pcp_handler.h | 36 +- cpp/core_v2/internal/base_pcp_handler_test.cc | 39 +- .../internal/base_pcp_handler_test.cc.orig | 538 ------------------ cpp/core_v2/internal/bwu_manager.cc | 22 +- cpp/core_v2/internal/bwu_manager.h | 3 +- cpp/core_v2/internal/mediums/ble.cc | 4 +- cpp/core_v2/internal/mediums/ble.h | 1 + cpp/core_v2/internal/mediums/ble_test.cc | 5 +- .../internal/mediums/bluetooth_classic.cc | 4 +- .../internal/mediums/bluetooth_classic.h | 2 +- .../mediums/webrtc/connection_flow.cc | 7 +- .../internal/offline_service_controller.h | 3 +- .../offline_service_controller.h.orig | 81 --- .../internal/p2p_cluster_pcp_handler.cc | 180 ++++-- .../internal/p2p_cluster_pcp_handler.h | 8 +- .../internal/p2p_cluster_pcp_handler_test.cc | 29 +- .../p2p_point_to_point_pcp_handler.cc | 5 +- .../internal/p2p_point_to_point_pcp_handler.h | 1 + cpp/core_v2/internal/p2p_star_pcp_handler.cc | 6 +- cpp/core_v2/internal/p2p_star_pcp_handler.h | 1 + cpp/core_v2/internal/pcp_manager.cc | 9 +- cpp/core_v2/internal/pcp_manager.h | 3 +- cpp/core_v2/internal/simulation_user.h | 4 +- cpp/core_v2/options.h | 9 + cpp/platform/BUILD | 1 - cpp/platform/impl/ios/BUILD | 9 - cpp/platform_v2/api/ble.h | 1 + cpp/platform_v2/api/bluetooth_classic.h | 2 +- cpp/platform_v2/api/platform.h | 5 +- cpp/platform_v2/base/medium_environment.cc | 14 +- cpp/platform_v2/base/medium_environment.h | 8 +- cpp/platform_v2/impl/g3/ble.cc | 12 +- cpp/platform_v2/impl/g3/ble.h | 1 + cpp/platform_v2/impl/g3/bluetooth_classic.cc | 2 +- cpp/platform_v2/impl/g3/bluetooth_classic.h | 2 +- cpp/platform_v2/public/ble.cc | 7 +- cpp/platform_v2/public/ble.h | 1 + cpp/platform_v2/public/ble_test.cc | 6 +- cpp/platform_v2/public/bluetooth_classic.h | 4 +- proto/connections/offline_wire_formats.proto | 13 +- proto/discovery_enums.proto | 6 +- proto/error_code_enums.proto | 5 + 44 files changed, 421 insertions(+), 810 deletions(-) delete mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc.orig delete mode 100644 cpp/core_v2/internal/offline_service_controller.h.orig delete mode 100644 cpp/platform/impl/ios/BUILD diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc index 3e468d23..4cc4df2e 100644 --- a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -96,7 +96,7 @@ TEST(SignalingFramesTest, EncodeValidOffer) { TEST(SignalingFramesTest, DecodeValidOffer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kOfferProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kOfferProto, &frame); Ptr decoded_offer = DecodeOffer(frame); EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); @@ -120,7 +120,7 @@ TEST(SignalingFramesTest, EncodeValidAnswer) { TEST(SignalingFramesTest, DecodeValidAnswer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kAnswerProto, &frame); Ptr decoded_answer = DecodeAnswer(frame); EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); @@ -163,7 +163,7 @@ TEST(SignalingFramesTest, DecodeValidIceCandidates) { std::vector encoded_candidates_vec; location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kIceCandidatesProto, &frame); std::vector> decoded_candidates = DecodeIceCandidates(frame); diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 44f6779d..12fdb676 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -9,6 +9,7 @@ #include "core_v2/internal/offline_frames.h" #include "core_v2/internal/pcp_handler.h" #include "core_v2/options.h" +#include "platform_v2/base/bluetooth_utils.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/system_clock.h" #include "securegcm/d2d_connection_context_v1.h" @@ -29,11 +30,13 @@ constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; BasePcpHandler::BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp) + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp) : mediums_(mediums), endpoint_manager_(endpoint_manager), channel_manager_(channel_manager), - pcp_(pcp) {} + pcp_(pcp), + bwu_manager_(bwu_manager) {} BasePcpHandler::~BasePcpHandler() { NEARBY_LOGS(INFO) << "BasePcpHandler: going down; strategy=" @@ -63,23 +66,23 @@ Status BasePcpHandler::StartAdvertising(ClientProxy* client, const ConnectionRequestInfo& info) { Future response; ConnectionOptions advertising_options = options.CompatibleOptions(); - RunOnPcpHandlerThread( - [this, client, &service_id, &info, &advertising_options, &response]() { - auto result = StartAdvertisingImpl( - client, service_id, client->GetLocalEndpointId(), - info.endpoint_info, advertising_options); - if (!result.status.Ok()) { - response.Set(result.status); - return; - } + RunOnPcpHandlerThread([this, client, &service_id, &info, &advertising_options, + &response]() { + auto result = + StartAdvertisingImpl(client, service_id, client->GetLocalEndpointId(), + info.endpoint_info, advertising_options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } - // Now that we've succeeded, mark the client as advertising. - advertising_options_ = advertising_options; - advertising_listener_ = info.listener; - client->StartedAdvertising(service_id, GetStrategy(), info.listener, - absl::MakeSpan(result.mediums)); - response.Set({Status::kSuccess}); - }); + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = advertising_options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); return WaitForResult( absl::StrCat("StartAdvertising(", std::string(info.endpoint_info), ")"), client->GetClientId(), &response); @@ -232,8 +235,8 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( .raw_authentication_token = raw_auth_token, .is_incoming_connection = connection_info.is_incoming, }, - connection_info.options, - std::move(connection_info.channel), connection_info.listener); + connection_info.options, std::move(connection_info.channel), + connection_info.listener); if (connection_info.result != nullptr) { NEARBY_LOG(INFO, "Connection established; Finalising future OK"); @@ -318,14 +321,20 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, OnEndpointFound(client, webrtc_endpoint); } - auto endpoints = GetDiscoveredEndpoints(endpoint_id); + auto discovered_endpoints = GetDiscoveredEndpoints(endpoint_id); std::unique_ptr channel; ConnectImplResult connect_impl_result; - // TODO(b/156634369): add GetRemoteBluetoothMacAddressEndpoint here for - // valid remote mac address. + auto remote_bluetooth_mac_address = + BluetoothUtils::ToString(options.remote_bluetooth_mac_address); + if (!remote_bluetooth_mac_address.empty()) { + auto additional_endpoint = GetRemoteBluetoothMacAddressEndpoint( + endpoint_id, remote_bluetooth_mac_address, discovered_endpoints); + if (additional_endpoint != nullptr) + discovered_endpoints.push_back(additional_endpoint.get()); + } - for (auto connect_endpoint : endpoints) { + for (auto connect_endpoint : discovered_endpoints) { connect_impl_result = ConnectImpl(client, connect_endpoint); if (connect_impl_result.status.Ok()) { channel = std::move(connect_impl_result.endpoint_channel); @@ -611,10 +620,6 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, client->GetClientId(), &response); } -// proto::connections::Medium BasePcpHandler::GetBandwidthUpgradeMedium() { -// return bandwidth_upgrade_medium_.Get(); -//} - void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, @@ -928,7 +933,7 @@ void BasePcpHandler::ProcessTieBreakLoss( void BasePcpHandler::InitiateBandwidthUpgrade( ClientProxy* client, const std::string& endpoint_id, - const std::vector& supported_mediums) { + const std::vector& supported_mediums) { // When we successfully connect to a remote endpoint and a bandwidth upgrade // medium has not yet been decided, we'll pick the highest bandwidth medium // supported by both us and the remote endpoint. Once we pick a medium, all @@ -938,16 +943,14 @@ void BasePcpHandler::InitiateBandwidthUpgrade( // way to prevent mediums, like Wifi Hotspot, from interfering with active // connections (although it's suboptimal for bandwidth throughput). When all // endpoints disconnect, we reset the bandwidth upgrade medium. - if (bandwidth_upgrade_medium_.Get() == - proto::connections::Medium::UNKNOWN_MEDIUM) { - bandwidth_upgrade_medium_.Set(ChooseBestUpgradeMedium(supported_mediums)); + Medium bwu_medium = bwu_medium_.Get(); + if (bwu_medium == Medium::UNKNOWN_MEDIUM) { + bwu_medium = ChooseBestUpgradeMedium(supported_mediums); + bwu_medium_.Set(bwu_medium); } - if (AutoUpgradeBandwidth() && (bandwidth_upgrade_medium_.Get() != - proto::connections::Medium::UNKNOWN_MEDIUM)) { - // TODO(apolyudov): Bring bandwidth upgrade back, when it is ready. - // bandwidth_upgrade_->InitiateBandwidthUpgradeForEndpoint( - // client, endpoint_id, bandwidth_upgrade_medium_.Get()); + if (AutoUpgradeBandwidth() && bwu_medium != Medium::UNKNOWN_MEDIUM) { + bwu_manager_->InitiateBwuForEndpoint(client, endpoint_id, bwu_medium); } } @@ -975,6 +978,55 @@ proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( return proto::connections::Medium::UNKNOWN_MEDIUM; } +std::unique_ptr +BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints) { + if (!discovery_options_.allowed.bluetooth) { + return nullptr; + } + + if (endpoints.empty()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because endpointId " + << endpoint_id << " has not been discovered"; + return nullptr; + } + + for (auto endpoint : endpoints) { + if (endpoint->medium == proto::connections::Medium::BLUETOOTH) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because the " + "endpoint has already been found over Bluetooth."; + return nullptr; + } + } + + auto remote_bluetooth_device = + mediums_->GetBluetoothClassic().GetRemoteDevice( + remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because a valid " + "Bluetooth device could not be derived."; + return nullptr; + } + + auto bluetooth_endpoint = + std::make_unique(BluetoothEndpoint{ + { + endpoint_id, + endpoints[0]->endpoint_info, + endpoints[0]->service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + }); + NEARBY_LOGS(INFO) << "Appended remote Bluetooth device " + << remote_bluetooth_mac_address; + return bluetooth_endpoint; +} + void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, const std::string& endpoint_id, bool can_close_immediately) { diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index d9ee3f92..262d92cb 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -6,6 +6,7 @@ #include #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/endpoint_channel_manager.h" @@ -81,7 +82,8 @@ class BasePcpHandler : public PcpHandler, // TODO(apolyudov): Add SecureRandom. BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp); + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp); ~BasePcpHandler() override; BasePcpHandler(BasePcpHandler&&) = delete; BasePcpHandler& operator=(BasePcpHandler&&) = delete; @@ -90,8 +92,7 @@ class BasePcpHandler : public PcpHandler, // Notifies ConnectionListener (info.listener) in case of any event. // See // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, + Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; @@ -102,8 +103,7 @@ class BasePcpHandler : public PcpHandler, // Starts discovery of endpoints that may be advertising. // Updates ClientProxy state once discovery started. // DiscoveryListener will get called in case of any event. - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, + Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; @@ -113,16 +113,14 @@ class BasePcpHandler : public PcpHandler, // Requests a newly discovered remote endpoint it to form a connection. // Updates state on ClientProxy. - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, + Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, const ConnectionRequestInfo& info, const ConnectionOptions& options) override; // Called by either party to accept connection on their part. // Until both parties call it, connection will not reach a data phase. // Updates state in ClientProxy. - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, + Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) override; // Called by either party to reject connection on their part. @@ -139,12 +137,12 @@ class BasePcpHandler : public PcpHandler, // Called when an endpoint disconnects while we're waiting for both sides to // approve/reject the connection. // @EndpointManagerThread - void OnEndpointDisconnect(ClientProxy* client, - const std::string& endpoint_id, + void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) override; Pcp GetPcp() const override { return pcp_; } Strategy GetStrategy() const override { return strategy_; } + Medium GetBwuMedium() const { return bwu_medium_.Get(); } void DisconnectFromEndpointManager(); protected: @@ -227,8 +225,7 @@ class BasePcpHandler : public PcpHandler, std::shared_ptr endpoint); // @PcpHandlerThread - void OnEndpointLost(ClientProxy* client, - const DiscoveredEndpoint& endpoint); + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint); Exception OnIncomingConnection( ClientProxy* client, const ByteArray& remote_endpoint_info, @@ -270,8 +267,8 @@ class BasePcpHandler : public PcpHandler, // Returns a vector of discovered endpoints, sorted in order of decreasing // preference. - std::vector - GetDiscoveredEndpoints(const std::string& endpoint_id); + std::vector GetDiscoveredEndpoints( + const std::string& endpoint_id); mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, const string& endpoint_id, @@ -402,6 +399,11 @@ class BasePcpHandler : public PcpHandler, proto::connections::Medium ChooseBestUpgradeMedium( const std::vector& supported_mediums); + std::unique_ptr + GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints); + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, EndpointChannel* channel, Status status, @@ -429,8 +431,7 @@ class BasePcpHandler : public PcpHandler, Status WaitForResult(const std::string& method_name, std::int64_t client_id, Future* future); - AtomicReference bandwidth_upgrade_medium_{ - proto::connections::Medium::UNKNOWN_MEDIUM}; + AtomicReference bwu_medium_{Medium::UNKNOWN_MEDIUM}; ScheduledExecutor alarm_executor_; SingleThreadExecutor serial_executor_; @@ -472,6 +473,7 @@ class BasePcpHandler : public PcpHandler, Strategy strategy_{PcpToStrategy(pcp_)}; Prng prng_; EncryptionRunner encryption_runner_; + BwuManager* bwu_manager_; EndpointManager::FrameProcessor::Handle handle_ = nullptr; }; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 1a580067..e939fda2 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/offline_frames.h" @@ -76,8 +77,9 @@ class MockPcpHandler : public BasePcpHandler { public: using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(m, em, ecm, Pcp::kP2pCluster) {} + MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm, + BwuManager* bwu) + : BasePcpHandler(m, em, ecm, bwu, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; @@ -367,7 +369,8 @@ TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); SUCCEED(); } @@ -376,7 +379,8 @@ TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); } @@ -385,7 +389,8 @@ TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); EXPECT_TRUE(client.IsAdvertising()); @@ -398,7 +403,8 @@ TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); } @@ -407,7 +413,8 @@ TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); EXPECT_TRUE(client.IsDiscovering()); @@ -421,7 +428,8 @@ TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -444,7 +452,8 @@ TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -471,7 +480,8 @@ TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -494,7 +504,8 @@ TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -530,7 +541,8 @@ TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -569,7 +581,8 @@ TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig b/cpp/core_v2/internal/base_pcp_handler_test.cc.orig deleted file mode 100644 index c9009413..00000000 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig +++ /dev/null @@ -1,538 +0,0 @@ -#include "core_v2/internal/base_pcp_handler.h" - -#include -#include - -#include "core_v2/internal/base_endpoint_channel.h" -#include "core_v2/internal/client_proxy.h" -#include "core_v2/internal/encryption_runner.h" -#include "core_v2/internal/offline_frames.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/params.h" -#include "proto/connections/offline_wire_formats.pb.h" -#include "platform_v2/base/byte_array.h" -#include "platform_v2/public/count_down_latch.h" -#include "platform_v2/public/pipe.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { -namespace connections { -namespace { - -using ::location::nearby::proto::connections::Medium; -using ::testing::_; -using ::testing::AtLeast; -using ::testing::Invoke; -using ::testing::MockFunction; -using ::testing::Return; -using ::testing::StrictMock; - -constexpr BooleanMediumSelector kTestCases[] = { - BooleanMediumSelector{}, - BooleanMediumSelector{ - .bluetooth = true, - }, - BooleanMediumSelector{ - .wifi_lan = true, - }, - BooleanMediumSelector{ - .bluetooth = true, - .wifi_lan = true, - }, -}; - -class MockEndpointChannel : public BaseEndpointChannel { - public: - explicit MockEndpointChannel(Pipe* reader, Pipe* writer) - : BaseEndpointChannel("channel", &reader->GetInputStream(), - &writer->GetOutputStream()) {} - - ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } - Exception DoWrite(const ByteArray& data) { - return BaseEndpointChannel::Write(data); - } - absl::Time DoGetLastReadTimestamp() { - return BaseEndpointChannel::GetLastReadTimestamp(); - } - - MOCK_METHOD(ExceptionOr, Read, (), (override)); - MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); - MOCK_METHOD(void, CloseImpl, (), (override)); - MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); - MOCK_METHOD(std::string, GetType, (), (const override)); - MOCK_METHOD(std::string, GetName, (), (const override)); - MOCK_METHOD(bool, IsPaused, (), (const override)); - MOCK_METHOD(void, Pause, (), (override)); - MOCK_METHOD(void, Resume, (), (override)); - MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); -}; - -class MockPcpHandler : public BasePcpHandler { - public: - using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - - MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} - - // Expose protected inner types of a base type for mocking. - using BasePcpHandler::ConnectImplResult; - using BasePcpHandler::DiscoveredEndpoint; - using BasePcpHandler::StartOperationResult; - - MOCK_METHOD(Strategy, GetStrategy, (), (const override)); - MOCK_METHOD(Pcp, GetPcp, (), (const override)); - - MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, - (ClientProxy * client, const string& service_id, - const string& local_endpoint_id, - const string& local_endpoint_name, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); - MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, - (ClientProxy * client, const string& service_id, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); - MOCK_METHOD(ConnectImplResult, ConnectImpl, - (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); - MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), - (override)); - - std::vector GetConnectionMediumsByPriority() - override { - return GetDiscoveryMediums(); - } - - // Mock adapters for protected non-virtual methods of a base class. - void OnEndpointFound(ClientProxy* client, - std::shared_ptr endpoint) { - BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); - } - void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { - BasePcpHandler::OnEndpointLost(client, endpoint); - } - - std::vector GetDiscoveryMediums() { - std::vector mediums; - auto allowed = - BasePcpHandler::GetDiscoveryOptions().CompatibleOptions().allowed; - // Mediums are sorted in order of decreasing preference. - if (allowed.wifi_lan) - mediums.push_back(proto::connections::Medium::WIFI_LAN); - if (allowed.web_rtc) mediums.push_back(proto::connections::Medium::WEB_RTC); - if (allowed.bluetooth) - mediums.push_back(proto::connections::Medium::BLUETOOTH); - return mediums; - } - - std::vector GetDiscoveredEndpoints( - const std::string& endpoint_id) { - return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); - } -}; - -class MockContext { - public: - explicit MockContext(std::atomic_int* destroyed = nullptr) { - destroyed_ = destroyed; - } - MockContext(MockContext&&) = default; - MockContext& operator=(MockContext&&) = default; - - ~MockContext() { - if (destroyed_) (*destroyed_)++; - } - - private: - Swapper destroyed_{nullptr}; -}; - -struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { - MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) - : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} - - MockContext context; -}; - -class BasePcpHandlerTest - : public ::testing::TestWithParam { - protected: - struct MockConnectionListener { - StrictMock> - initiated_cb; - StrictMock> accepted_cb; - StrictMock> - rejected_cb; - StrictMock> - disconnected_cb; - StrictMock> - bandwidth_changed_cb; - }; - struct MockDiscoveryListener { - StrictMock> - endpoint_found_cb; - StrictMock> - endpoint_lost_cb; - StrictMock< - MockFunction> - endpoint_distance_changed_cb; - }; - - void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - ConnectionRequestInfo info{ - .name = "remote_endpoint_name", - .listener = connection_listener_, - }; - EXPECT_CALL(*pcp_handler, - StartAdvertisingImpl(client, service_id, _, info.name, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsAdvertising()); - } - - void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, - discovery_listener_), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsDiscovering()); - } - - std::pair, - std::unique_ptr> - SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT - auto channel_a = std::make_unique(&pipe_b, &pipe_a); - auto channel_b = std::make_unique(&pipe_a, &pipe_b); - // On initiator (A) side, we drop the first write, since this is a - // connection establishment packet, and we don't have the peer entity, just - // the peer channel. The rest of the exchange must happen for the benefit of - // DH key exchange. - EXPECT_CALL(*channel_a, Read()) - .WillRepeatedly(Invoke( - [channel = channel_a.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_a, Write(_)) - .WillOnce(Return(Exception{Exception::kSuccess})) - .WillRepeatedly( - Invoke([channel = channel_a.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_a, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); - EXPECT_CALL(*channel_b, Read()) - .WillRepeatedly(Invoke( - [channel = channel_b.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_b, Write(_)) - .WillRepeatedly( - Invoke([channel = channel_b.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_b, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); - return std::make_pair(std::move(channel_a), std::move(channel_b)); - } - - void RequestConnection(const std::string& endpoint_id, - std::unique_ptr channel_a, - MockEndpointChannel* channel_b, ClientProxy* client, - MockPcpHandler* pcp_handler, - std::atomic_int* flag = nullptr) { - ConnectionRequestInfo info{ - .name = "ABCD", - .listener = connection_listener_, - }; - EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); - EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*pcp_handler, GetStrategy) - .WillRepeatedly(Return(Strategy::kP2pCluster)); - EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); - // Simulate successful discovery. - auto encryption_runner = std::make_unique(); - auto allowed_mediums = pcp_handler->GetDiscoveryMediums(); - - EXPECT_CALL(*pcp_handler, ConnectImpl) - .WillOnce(Invoke([&channel_a, medium = allowed_mediums[0]]( - ClientProxy* client, - MockPcpHandler::DiscoveredEndpoint* endpoint) { - return MockPcpHandler::ConnectImplResult{ - .medium = medium, - .status = {Status::kSuccess}, - .endpoint_channel = std::move(channel_a), - }; - })); - - for (const auto& medium : allowed_mediums) { - pcp_handler->OnEndpointFound( - client, - std::make_shared(MockDiscoveredEndpoint{ - { - endpoint_id, - info.name, - "service", - medium, - }, - MockContext{flag}, - })); - } - auto other_client = std::make_unique(); - - // Run peer crypto in advance, if channel_b is provided. - // Otherwise stay in not-encrypted state. - if (channel_b != nullptr) { - encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, - {}); - } - EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Stopping Encryption Runner"); - } - - Pipe pipe_a_; - Pipe pipe_b_; - MockConnectionListener mock_connection_listener_; - MockDiscoveryListener mock_discovery_listener_; - ConnectionListener connection_listener_{ - .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), - .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), - .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), - .disconnected_cb = - mock_connection_listener_.disconnected_cb.AsStdFunction(), - .bandwidth_changed_cb = - mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), - }; - DiscoveryListener discovery_listener_{ - .endpoint_found_cb = - mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), - .endpoint_lost_cb = - mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), - .endpoint_distance_changed_cb = - mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), - }; -}; - -TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - SUCCEED(); -} - -TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); - EXPECT_TRUE(client.IsAdvertising()); - pcp_handler.StopAdvertising(&client); - EXPECT_FALSE(client.IsAdvertising()); -} - -TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); - EXPECT_TRUE(client.IsDiscovering()); - pcp_handler.StopDiscovery(&client); - EXPECT_FALSE(client.IsDiscovering()); -} - -TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "RequestConnection complete"); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { - 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; - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - &client, &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; - EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), - Status{Status::kSuccess}); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; - EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); - EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) - .Times(AtLeast(0)); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); - auto frame = - parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); - pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, - Medium::BLE); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { - std::atomic_int destroyed_flag = 0; - int mediums_count = 0; - { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), - &client, &pcp_handler, &destroyed_flag); - mediums_count = pcp_handler.GetDiscoveryMediums().size(); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); - } - EXPECT_EQ(destroyed_flag.load(), mediums_count); -} - -INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, - ::testing::ValuesIn(kTestCases)); - -} // namespace -} // namespace connections -} // namespace nearby -} // namespace location diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 4564dce1..9ab910fe 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/bwu_handler.h" #include "core_v2/internal/offline_frames.h" +#include "core_v2/internal/webrtc_bwu_handler.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/public/count_down_latch.h" #include "proto/connections_enums.pb.h" @@ -52,7 +53,11 @@ void BwuManager::InitBwuHandlers() { .incoming_connection_cb = absl::bind_front(&BwuManager::OnIncomingConnection, this), }; - // TODO(apolyudov): inject instances of supported upgrade medium handlers. + if (config_.allow_upgrade_to.web_rtc) { + handlers_.emplace(Medium::WEB_RTC, + std::make_unique( + *mediums_, *channel_manager_, notifications)); + } } void BwuManager::Shutdown() { @@ -90,12 +95,17 @@ void BwuManager::Shutdown() { } // This is the point on the Initiator side where the -// currentBwuMedium is set. +// medium_ is set. void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, - const std::string& endpoint_id) { - RunOnBwuManagerThread([this, client, endpoint_id]() { - auto* handler = SetCurrentBwuHandler(ChooseBestUpgradeMedium( - client->GetUpgradeMediums(endpoint_id).GetMediums(true))); + const std::string& endpoint_id, + Medium new_medium) { + RunOnBwuManagerThread([this, client, endpoint_id, new_medium]() { + Medium proposed_medium = ChooseBestUpgradeMedium( + client->GetUpgradeMediums(endpoint_id).GetMediums(true)); + if (new_medium != Medium::UNKNOWN_MEDIUM) { + proposed_medium = new_medium; + } + auto* handler = SetCurrentBwuHandler(proposed_medium); if (!handler) return; diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index 150f49d5..b97d7138 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -65,7 +65,8 @@ class BwuManager : public EndpointManager::FrameProcessor { // Function initiates the bandwidth upgrade and sends an // UPGRADE_PATH_AVAILABLE OfflineFrame. void InitiateBwuForEndpoint(ClientProxy* client_proxy, - const std::string& endpoint_id); + const std::string& endpoint_id, + Medium new_medium = Medium::UNKNOWN_MEDIUM); // == EndpointManager::FrameProcessor interface ==. // This is the point on the inbound BWU protocol where the handler_ is set. diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index d0ab8b16..f8c7cf8f 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -106,6 +106,7 @@ bool Ble::IsAdvertisingLocked(const std::string& service_id) { } bool Ble::StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) { MutexLock lock(&mutex_); @@ -133,7 +134,8 @@ bool Ble::StartScanning(const std::string& service_id, return false; } - if (!medium_.StartScanning(service_id, callback)) { + if (!medium_.StartScanning(service_id, fast_advertisement_service_uuid, + callback)) { NEARBY_LOGS(INFO) << "Failed to start scan of BLE services."; return false; } diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 42c1cd9c..1a6b7643 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -45,6 +45,7 @@ class Ble { // range through a callback. Returns true, if scanning mode was enabled, // false otherwise. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 6a2d43f0..15e24d8f 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -18,7 +18,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleTest : public ::testing::Test { protected: @@ -61,6 +61,7 @@ TEST_F(BleTest, CanStartAdvertising) { ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -95,6 +96,7 @@ TEST_F(BleTest, CanStartDiscovery) { EXPECT_TRUE(ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&accept_latch]( @@ -139,6 +141,7 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral discovered_peripheral; ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc index b6620e96..15dad66c 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -368,10 +368,10 @@ BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, return socket; } -BluetoothDevice BluetoothClassic::FindRemoteDevice( +BluetoothDevice BluetoothClassic::GetRemoteDevice( const std::string& mac_address) { MutexLock lock(&mutex_); - return medium_.FindRemoteDevice(mac_address); + return medium_.GetRemoteDevice(mac_address); } std::string BluetoothClassic::GetMacAddress() const { diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h index 3ed3a33a..29ae73e5 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.h +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -102,7 +102,7 @@ class BluetoothClassic { std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_); - BluetoothDevice FindRemoteDevice(const std::string& mac_address) + BluetoothDevice GetRemoteDevice(const std::string& mac_address) ABSL_LOCKS_EXCLUDED(mutex_); private: diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 3cb4e4ca..86af87ec 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -233,8 +233,8 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { Future success_future; webrtc_medium.CreatePeerConnection( &peer_connection_observer_, - [this, &success_future]( - rtc::scoped_refptr peer_connection) { + [this, success_future](rtc::scoped_refptr + peer_connection) mutable { if (!peer_connection) { success_future.Set(false); return; @@ -329,8 +329,7 @@ bool ConnectionFlow::CloseLocked() { state_ = State::kEnded; data_channel_future_.SetException({Exception::kInterrupted}); - if (peer_connection_) - peer_connection_->Close(); + if (peer_connection_) peer_connection_->Close(); data_channel_observer_.reset(); NEARBY_LOG(INFO, "Closed WebRTC connection."); diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index 03ebbc33..7b6a1c5a 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -67,9 +67,10 @@ class OfflineServiceController : public ServiceController { EndpointChannelManager channel_manager_; EndpointManager endpoint_manager_{&channel_manager_}; PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; BwuManager bwu_manager_{ mediums_, endpoint_manager_, channel_manager_, {}, {}}; + PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_, + bwu_manager_}; }; } // namespace connections diff --git a/cpp/core_v2/internal/offline_service_controller.h.orig b/cpp/core_v2/internal/offline_service_controller.h.orig deleted file mode 100644 index 97517fa7..00000000 --- a/cpp/core_v2/internal/offline_service_controller.h.orig +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ -#define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ - -#include -#include -#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/payload_manager.h" -#include "core_v2/internal/pcp_manager.h" -#include "core_v2/internal/service_controller.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/payload.h" -#include "core_v2/status.h" - -namespace location { -namespace nearby { -namespace connections { - -class OfflineServiceController : public ServiceController { - public: - OfflineServiceController() = default; - ~OfflineServiceController() override; - - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const ConnectionRequestInfo& info) override; - void StopAdvertising(ClientProxy* client) override; - - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const DiscoveryListener& listener) override; - void StopDiscovery(ClientProxy* client) override; - - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, - const ConnectionRequestInfo& info, - const ConnectionOptions& options) override; - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, - const PayloadListener& listener) override; - Status RejectConnection(ClientProxy* client, - const std::string& endpoint_id) override; - - void InitiateBandwidthUpgrade(ClientProxy* client, - const std::string& endpoint_id) override; - - void SendPayload(ClientProxy* client, - const std::vector& endpoint_ids, - Payload payload) override; - Status CancelPayload(ClientProxy* client, - Payload::Id payload_id) override; - - void DisconnectFromEndpoint(ClientProxy* client, - const std::string& endpoint_id) override; - - void Stop(); - - private: - // Note that the order of declaration of these is crucial, because we depend - // on the destructors running (strictly) in the reverse order; a deviation - // from that will lead to crashes at runtime. - AtomicBoolean stop_{false}; - Mediums mediums_; - EndpointChannelManager channel_manager_; - EndpointManager endpoint_manager_{&channel_manager_}; - PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; -}; - -} // namespace connections -} // namespace nearby -} // namespace location - -#endif // CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index a4f07de8..116a04fd 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/ble_advertisement.h" #include "core_v2/internal/ble_endpoint_channel.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/mediums/utils.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "core_v2/internal/webrtc_endpoint_channel.h" @@ -23,10 +24,22 @@ ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source, return Utils::Sha256Hash(source, size); } +bool P2pClusterPcpHandler::ShouldAdvertiseBluetoothMacOverBle( + PowerLevel power_level) { + return power_level == PowerLevel::kHighPower; +} + +bool P2pClusterPcpHandler::ShouldAcceptBluetoothConnections( + const ConnectionOptions& options) { + return options.enable_bluetooth_listening; +} + P2pClusterPcpHandler::P2pClusterPcpHandler( Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* endpoint_channel_manager, Pcp pcp) - : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, pcp), + EndpointChannelManager* endpoint_channel_manager, BwuManager* bwu_manager, + Pcp pcp) + : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, + bwu_manager, pcp), bluetooth_radio_(mediums->GetBluetoothRadio()), bluetooth_medium_(mediums->GetBluetoothClassic()), ble_medium_(mediums->GetBle()), @@ -131,10 +144,12 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); webrtc_medium_.StopAcceptingConnections(); wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); return {Status::kSuccess}; } @@ -311,12 +326,11 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( return; } - // Parse the Ble advertisement bytes. + // Parse the BLE advertisement bytes. BleAdvertisement advertisement( - fast_advertisement, - peripheral.GetAdvertisementBytes(service_id)); + fast_advertisement, peripheral.GetAdvertisementBytes(service_id)); - // Make sure the Ble advertisement points to a valid + // Make sure the BLE advertisement points to a valid // endpoint we're discovering. if (!IsRecognizedBleEndpoint(service_id, advertisement)) return; @@ -343,7 +357,34 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( peripheral, })); - // TODO(b/156632928): Check for Bluetooth device with remote mac address. + // Make sure we can connect to this device via Classic Bluetooth. + std::string remote_bluetooth_mac_address = + advertisement.GetBluetoothMacAddress(); + if (remote_bluetooth_mac_address.empty()) { + NEARBY_LOGS(INFO) + << "No Bluetooth Classic MAC address found in advertisement"; + return; + } + + BluetoothDevice remote_bluetooth_device = + bluetooth_medium_.GetRemoteDevice(remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) << "A valid Bluetooth device could not be derived from " + "the MAC address " + << remote_bluetooth_mac_address; + return; + } + + OnEndpointFound(client, + std::make_shared(BluetoothEndpoint{ + { + advertisement.GetEndpointId(), + advertisement.GetEndpointInfo(), + service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + })); }); } @@ -547,7 +588,7 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( .peripheral_lost_cb = absl::bind_front( &P2pClusterPcpHandler::BlePeripheralLostHandler, this, client), }, - client, service_id); + client, service_id, options.fast_advertisement_service_uuid); if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: Ble added"); mediums_started_successfully.push_back(ble_medium); @@ -753,51 +794,90 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, const ConnectionOptions& options) { bool fast_advertisement = !options.fast_advertisement_service_uuid.empty(); + PowerLevel power_level = + options.low_power ? PowerLevel::kLowPower : PowerLevel::kHighPower; // Start listening for connections before advertising in case a connection - // request comes in very quickly. + // request comes in very quickly. BLE allows connecting over BLE itself, as + // well as advertising the Bluetooth MAC address to allow connecting over + // Bluetooth Classic. NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" << service_id << ": start"; - if (ble_medium_.IsAcceptingConnections(service_id)) { - NEARBY_LOGS(ERROR) << "Ble is already accepting connections for service_id=" - << service_id; - return proto::connections::UNKNOWN_MEDIUM; - } + if (!ble_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !ble_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BleSocket socket, + const std::string& service_id) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + service_id, + socket = std::move(socket)]() mutable { + std::string remote_peripheral_name = + socket.GetRemotePeripheral().GetName(); + auto channel = absl::make_unique( + remote_peripheral_name, socket); + ByteArray remote_peripheral_info = + socket.GetRemotePeripheral().GetAdvertisementBytes( + service_id); - NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" - << service_id << ": invoking"; - if (!bluetooth_radio_.Enable() || - !ble_medium_.StartAcceptingConnections( - service_id, - {.accepted_cb = [this, client, local_endpoint_info]( - BleSocket socket, const std::string& service_id) { - if (!socket.IsValid()) { - NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", - std::string(local_endpoint_info).c_str()); - return; - } - RunOnPcpHandlerThread([this, client, local_endpoint_info, - service_id, - socket = std::move(socket)]() mutable { - std::string remote_peripheral_name = - socket.GetRemotePeripheral().GetName(); - auto channel = absl::make_unique( - remote_peripheral_name, socket); - ByteArray remote_peripheral_info = - socket.GetRemotePeripheral().GetAdvertisementBytes( - service_id); - - OnIncomingConnection(client, remote_peripheral_info, - std::move(channel), - proto::connections::Medium::BLE); - }); - }})) { + OnIncomingConnection(client, remote_peripheral_info, + std::move(channel), + proto::connections::Medium::BLE); + }); + }})) { + NEARBY_LOGS(ERROR) + << "Ble failed to start accepting connections for service_id=" + << service_id; + return proto::connections::UNKNOWN_MEDIUM; + } NEARBY_LOGS(ERROR) - << "Ble failed to start accepting connections for service_id=" + << "Ble succeed to start accepting connections for service_id=" << service_id; - return proto::connections::UNKNOWN_MEDIUM; } - // TODO(b/156632928): Should check for Bluetooth connection here + + if (ShouldAdvertiseBluetoothMacOverBle(power_level) || + ShouldAcceptBluetoothConnections(options)) { + if (bluetooth_medium_.IsAvailable() && + !bluetooth_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !bluetooth_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BluetoothSocket socket) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, + "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + socket = std::move(socket)]() mutable { + std::string remote_device_name = + socket.GetRemoteDevice().GetName(); + auto channel = absl::make_unique( + remote_device_name, socket); + ByteArray remote_device_info{remote_device_name}; + + OnIncomingConnection(client, remote_device_info, + std::move(channel), + proto::connections::Medium::BLUETOOTH); + }); + }})) { + NEARBY_LOGS(ERROR) + << "BT failed to start accepting connections for service_id=" + << service_id; + ble_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + NEARBY_LOGS(ERROR) + << "BT succeed to start accepting connections for service_id=" + << service_id; + } + } NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBleAdvertising: service=%s: " @@ -814,8 +894,10 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( } else { const ByteArray service_id_hash = GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); - // TODO(b/156632928): Should advertise Bluetooth MacAddress Over Ble std::string bluetooth_mac_address; + if (bluetooth_medium_.IsAvailable() && + ShouldAdvertiseBluetoothMacOverBle(power_level)) + bluetooth_mac_address = bluetooth_medium_.GetMacAddress(); advertisement_bytes = ByteArray(BleAdvertisement( kBleAdvertisementVersion, GetPcp(), service_id_hash, local_endpoint_id, @@ -852,9 +934,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( proto::connections::Medium P2pClusterPcpHandler::StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id) { + const std::string& service_id, + const std::string& fast_advertisement_service_uuid) { if (bluetooth_radio_.Enable() && - ble_medium_.StartScanning(service_id, std::move(callback))) { + ble_medium_.StartScanning(service_id, fast_advertisement_service_uuid, + std::move(callback))) { NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleScanning: ok"; return proto::connections::BLE; } else { diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index b276a3f9..687075c4 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -7,6 +7,7 @@ #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/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -38,6 +39,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { public: P2pClusterPcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp = Pcp::kP2pCluster); ~P2pClusterPcpHandler() override = default; @@ -117,6 +119,9 @@ class P2pClusterPcpHandler : public BasePcpHandler { WifiLanServiceInfo::Version::kV1; static ByteArray GenerateHash(const std::string& source, size_t size); + static bool ShouldAdvertiseBluetoothMacOverBle(PowerLevel power_level); + static bool ShouldAcceptBluetoothConnections( + const ConnectionOptions& options); // Bluetooth bool IsRecognizedBluetoothEndpoint(const std::string& name_string, @@ -155,7 +160,8 @@ class P2pClusterPcpHandler : public BasePcpHandler { const ByteArray& local_endpoint_info, const ConnectionOptions& options); proto::connections::Medium StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id); + const std::string& service_id, + const std::string& fast_advertisement_service_uuid); BasePcpHandler::ConnectImplResult BleConnectImpl(ClientProxy* client, BleEndpoint* endpoint); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc index 51bce6df..8ac6064b 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -2,6 +2,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/options.h" #include "platform_v2/base/medium_environment.h" #include "platform_v2/public/count_down_latch.h" @@ -61,7 +62,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructOne) { Mediums mediums; EndpointChannelManager ecm; EndpointManager em(&ecm); - P2pClusterPcpHandler handler(&mediums, &em, &ecm); + BwuManager bwu(mediums, em, ecm, {}, {}); + P2pClusterPcpHandler handler(&mediums, &em, &ecm, &bwu); env_.Stop(); } @@ -73,8 +75,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructMultiple) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); env_.Stop(); } @@ -84,7 +88,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanAdvertise) { Mediums mediums_a; EndpointChannelManager ecm_a; EndpointManager em_a(&ecm_a); - P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, {.endpoint_info = ByteArray{endpoint_name}}), @@ -101,8 +106,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanDiscover) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch latch(1); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, @@ -141,8 +148,12 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch discover_latch(1); CountDownLatch connect_latch(2); struct DiscoveredInfo { @@ -207,6 +218,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { }, options_); EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result()); + bwu_a.Shutdown(); + bwu_b.Shutdown(); env_.Stop(); } 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 index c3525bdd..0b09d8bd 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -6,8 +6,9 @@ namespace connections { P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( Mediums& mediums, EndpointManager& endpoint_manager, - EndpointChannelManager& channel_manager, Pcp pcp) - : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp) {} + EndpointChannelManager& channel_manager, BwuManager& bwu_manager, Pcp pcp) + : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, bwu_manager, + pcp) {} std::vector P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { 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 index cd9cb39b..4b09ab3c 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -22,6 +22,7 @@ class P2pPointToPointPcpHandler : public P2pStarPcpHandler { public: P2pPointToPointPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pPointToPoint); protected: diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc index acb45e38..45a20d14 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -9,9 +9,9 @@ namespace connections { P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, - Pcp pcp) - : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, pcp) { -} + BwuManager& bwu_manager, Pcp pcp) + : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, + &bwu_manager, pcp) {} std::vector P2pStarPcpHandler::GetConnectionMediumsByPriority() { diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h index 203bfcf5..c1418ffd 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -25,6 +25,7 @@ class P2pStarPcpHandler : public P2pClusterPcpHandler { public: P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pStar); protected: diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index c3c62aee..3a537547 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -11,14 +11,15 @@ namespace connections { PcpManager::PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager) { + EndpointManager& endpoint_manager, + BwuManager& bwu_manager) { handlers_[Pcp::kP2pCluster] = std::make_unique( - &mediums, &endpoint_manager, &channel_manager); + &mediums, &endpoint_manager, &channel_manager, &bwu_manager); handlers_[Pcp::kP2pStar] = std::make_unique( - mediums, endpoint_manager, channel_manager); + mediums, endpoint_manager, channel_manager, bwu_manager); handlers_[Pcp::kP2pPointToPoint] = std::make_unique(mediums, endpoint_manager, - channel_manager); + channel_manager, bwu_manager); } void PcpManager::DisconnectFromEndpointManager() { diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index ddeb4107..bb4d9991 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_pcp_handler.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -29,7 +30,7 @@ namespace connections { class PcpManager { public: PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager); + EndpointManager& endpoint_manager, BwuManager& bwu_manager); ~PcpManager(); Status StartAdvertising(ClientProxy* client, const string& service_id, diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 4674be0d..2b48353c 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -3,6 +3,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -130,7 +131,8 @@ class SimulationUser { ClientProxy client_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; - PcpManager mgr_{mediums_, ecm_, em_}; + BwuManager bwu_{mediums_, em_, ecm_, {}, {}}; + PcpManager mgr_{mediums_, ecm_, em_, bwu_}; PayloadManager pm_{em_}; }; diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index 6e0b0a66..94c72f39 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -65,6 +65,13 @@ struct MediumSelector { // Feature On/Off switch for mediums. using BooleanMediumSelector = MediumSelector; +// Represents the various power levels that can be used, on mediums that support +// it. +enum class PowerLevel { + kHighPower = 0, + kLowPower = 1, +}; + // Connection Options: used for both Advertising and Discovery. // All fields are mutable, to make the type copy-assignable. struct ConnectionOptions { @@ -72,6 +79,8 @@ struct ConnectionOptions { BooleanMediumSelector allowed{BooleanMediumSelector().SetAll(true)}; bool auto_upgrade_bandwidth; bool enforce_topology_constraints; + bool low_power; + bool enable_bluetooth_listening; ByteArray remote_bluetooth_mac_address; std::string fast_advertisement_service_uuid; // Verify if ConnectionOptions is in a not-initialized (Empty) state. diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 2e9bcb30..1ee1d0df 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -61,7 +61,6 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", - "//platform_v2/base:__pkg__", ], deps = [ "//absl/base", diff --git a/cpp/platform/impl/ios/BUILD b/cpp/platform/impl/ios/BUILD deleted file mode 100644 index a75790f9..00000000 --- a/cpp/platform/impl/ios/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -objc_library( - name = "ios", - visibility = [ - "//googlemac/iPhone/Nearby/HelloSetup:__subpackages__", - ], - deps = [ - "//googlemac/iPhone/Shared/Nearby/Connections:Platform", - ], -) diff --git a/cpp/platform_v2/api/ble.h b/cpp/platform_v2/api/ble.h index 548aeb45..608574cf 100644 --- a/cpp/platform_v2/api/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -75,6 +75,7 @@ class BleMedium { // Returns true once the BLE scan has been initiated. virtual bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) = 0; // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/api/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h index 6dddd606..5b334830 100644 --- a/cpp/platform_v2/api/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -136,7 +136,7 @@ class BluetoothClassicMedium { virtual std::unique_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) = 0; - virtual BluetoothDevice* FindRemoteDevice(const std::string& mac_address) = 0; + virtual BluetoothDevice* GetRemoteDevice(const std::string& mac_address) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index 2b5ca406..eee8bfad 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -42,7 +42,7 @@ class ImplementationPlatform { // - synchronization primitives: // - mutex (regular, and recursive) // - condition variable (must work with regular mutex only) - // - Future : to synchronize on Callable schduled to execute. + // - Future : to synchronize on Callable scheduled to execute. // - CountDownLatch : to ensure at least N threads are waiting. // - file I/O // - Logging @@ -58,8 +58,7 @@ class ImplementationPlatform { // 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 CreateAtomicUint32(std::uint32_t value); static std::unique_ptr CreateCountDownLatch( std::int32_t count); diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 21d72fd7..daa06267 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -340,10 +340,12 @@ void MediumEnvironment::UpdateBleMediumForAdvertising( void MediumEnvironment::UpdateBleMediumForScanning( api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, BleDiscoveredPeripheralCallback callback, bool enabled) { if (!enabled_) return; RunOnMediumEnvironmentThread( - [this, &medium, service_id, callback = std::move(callback), enabled]() { + [this, &medium, service_id, fast_advertisement_service_uuid, + callback = std::move(callback), enabled]() { auto item = ble_mediums_.find(&medium); if (item == ble_mediums_.end()) { NEARBY_LOG(INFO, @@ -353,10 +355,12 @@ void MediumEnvironment::UpdateBleMediumForScanning( } auto& context = item->second; context.discovery_callback = std::move(callback); - NEARBY_LOG(INFO, - "Update Ble medium for scanning: this=%p; medium=%p; " - "service_id=%s; enabled=%d ;", - this, &medium, service_id.c_str(), enabled); + NEARBY_LOG( + INFO, + "Update Ble medium for scanning: this=%p; medium=%p; " + "service_id=%s; fast_advertisement_service_uuid=%s; enabled=%d ;", + this, &medium, service_id.c_str(), + fast_advertisement_service_uuid.c_str(), enabled); for (auto& medium_info : ble_mediums_) { auto& local_medium = medium_info.first; auto& info = medium_info.second; diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 875de81a..826189d1 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -151,10 +151,10 @@ class MediumEnvironment { // This should be called when discoverable state changes. // with user-specified callback when discovery is enabled, and with default // (empty) callback otherwise. - void UpdateBleMediumForScanning(api::BleMedium& medium, - const std::string& service_id, - BleDiscoveredPeripheralCallback callback, - bool enabled); + void UpdateBleMediumForScanning( + api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + BleDiscoveredPeripheralCallback callback, bool enabled); // Updates Accepted connection callback info to allow for dispatch of // advertising events. diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc index c7bfa041..316d64fc 100644 --- a/cpp/platform_v2/impl/g3/ble.cc +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -252,11 +252,15 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return true; } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id; auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, std::move(callback), true); + env.UpdateBleMediumForScanning(*this, service_id, + fast_advertisement_service_uuid, + std::move(callback), true); { absl::MutexLock lock(&mutex_); scanning_info_.service_id = service_id; @@ -277,7 +281,7 @@ bool BleMedium::StopScanning(const std::string& service_id) { } auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, {}, false); + env.UpdateBleMediumForScanning(*this, service_id, {}, {}, false); return true; } diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h index 6bdacb09..9822200d 100644 --- a/cpp/platform_v2/impl/g3/ble.h +++ b/cpp/platform_v2/impl/g3/ble.h @@ -146,6 +146,7 @@ class BleMedium : public api::BleMedium { // Returns true once the Ble scanning has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) override ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc index a0c040d3..36403954 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -240,7 +240,7 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name, return socket; } -api::BluetoothDevice* BluetoothClassicMedium::FindRemoteDevice( +api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( const std::string& mac_address) { auto& env = MediumEnvironment::Instance(); return env.FindBluetoothDevice(mac_address); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h index 8d199863..0aa92c53 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.h +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -207,7 +207,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { const std::string& service_name, const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); - api::BluetoothDevice* FindRemoteDevice( + api::BluetoothDevice* GetRemoteDevice( const std::string& mac_address) override; private: diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 43a81d1b..db8ab6b5 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -17,8 +17,10 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return impl_->StopAdvertising(service_id); } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { { MutexLock lock(&mutex_); discovered_peripheral_callback_ = std::move(callback); @@ -26,6 +28,7 @@ bool BleMedium::StartScanning(const std::string& service_id, } return impl_->StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [this](api::BlePeripheral& peripheral, diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 948903af..ca9bedbb 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -106,6 +106,7 @@ class BleMedium final { // Returns true once the BLE scan has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback); // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index 2af0c3de..41f6d091 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -15,7 +15,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleMediumTest : public ::testing::Test { protected: @@ -59,6 +59,7 @@ TEST_F(BleMediumTest, CanStartAdvertising) { EXPECT_TRUE(ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -85,6 +86,7 @@ TEST_F(BleMediumTest, CanStartScanning) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -119,6 +121,7 @@ TEST_F(BleMediumTest, CanStopDiscovery) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -154,6 +157,7 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral* discovered_peripheral = nullptr; ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h index d8bf989d..8d073f6b 100644 --- a/cpp/platform_v2/public/bluetooth_classic.h +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -188,8 +188,8 @@ class BluetoothClassicMedium final { api::BluetoothClassicMedium& GetImpl() { return *impl_; } BluetoothAdapter& GetAdapter() { return adapter_; } std::string GetMacAddress() const { return adapter_.GetMacAddress(); } - BluetoothDevice FindRemoteDevice(const std::string& mac_address) { - return BluetoothDevice(impl_->FindRemoteDevice(mac_address)); + BluetoothDevice GetRemoteDevice(const std::string& mac_address) { + return BluetoothDevice(impl_->GetRemoteDevice(mac_address)); } private: diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index c7169827..9cd1728d 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -81,8 +81,19 @@ message ConnectionResponseFrame { // // - ConnectionsStatusCodes.STATUS_OK // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. - optional int32 status = 1; + optional int32 status = 1 [deprecated = true]; optional bytes handshake_data = 2; + + // Used to replace the status integer parameter with a meaningful enum item. + // Map ConnectionsStatusCodes.STATUS_OK to ACCEPT and + // ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED to REJECT. + // Flag: connection_replace_status_with_response_connectionResponseFrame + enum ResponseStatus { + UNKNOWN_RESPONSE_STATUS = 0; + ACCEPT = 1; + REJECT = 2; + } + optional ResponseStatus response = 3; } message PayloadTransferFrame { diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index a3ca8c3e..cc095756 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -11,7 +11,7 @@ option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. -// NEXT ID: 132 +// NEXT ID: 133 enum DiscoveryEvent { UNKNOWN_DISCOVERY_EVENT = 0; @@ -395,6 +395,10 @@ enum DiscoveryEvent { // User has seen a low battery notification. FAST_PAIR_LOW_BATTERY_NOTIFICATION_SHOWN = 131; + // Connection Tracker Manager (Baymax) recovered the connection of the + // companion app. + FAST_PAIR_CONNECTION_TRACKER_RECOVER_COMPANION_APP = 132; + // Deprecated. reserved 65, 67 to 72; } diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 9296a49b..6ea1f745 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -408,4 +408,9 @@ enum Description { SOCKET_NOT_BOUND = 141; INVALID_REMOTE_ADDRESS = 142; SOCKET_ALREADY_BOUND = 143; + HOTSPOT_NOT_STARTED = 144; + WEBRTC_ALREADY_INITIALIZED = 145; + INVALID_WEBRTC_STATE = 146; + NULL_DATA_CHANNEL = 147; + CREATE_OFFER_FAILED = 148; } From e7e473b763bdb6b882724aa4908bd90cd3fb9311 Mon Sep 17 00:00:00 2001 From: Josh Nohle Date: Thu, 24 Sep 2020 13:15:16 -0700 Subject: [PATCH 48/52] Roll forward to cl/333580336 Signed-off-by: Josh Nohle --- .../mediums/webrtc/signaling_frames_test.cc | 6 +- cpp/core_v2/internal/base_pcp_handler.cc | 126 ++-- cpp/core_v2/internal/base_pcp_handler.h | 36 +- cpp/core_v2/internal/base_pcp_handler_test.cc | 39 +- .../internal/base_pcp_handler_test.cc.orig | 538 ------------------ cpp/core_v2/internal/bwu_manager.cc | 22 +- cpp/core_v2/internal/bwu_manager.h | 3 +- cpp/core_v2/internal/mediums/ble.cc | 4 +- cpp/core_v2/internal/mediums/ble.h | 1 + cpp/core_v2/internal/mediums/ble_test.cc | 5 +- .../internal/mediums/bluetooth_classic.cc | 4 +- .../internal/mediums/bluetooth_classic.h | 2 +- .../mediums/webrtc/connection_flow.cc | 7 +- .../internal/offline_service_controller.h | 3 +- .../offline_service_controller.h.orig | 81 --- .../internal/p2p_cluster_pcp_handler.cc | 180 ++++-- .../internal/p2p_cluster_pcp_handler.h | 8 +- .../internal/p2p_cluster_pcp_handler_test.cc | 29 +- .../p2p_point_to_point_pcp_handler.cc | 5 +- .../internal/p2p_point_to_point_pcp_handler.h | 1 + cpp/core_v2/internal/p2p_star_pcp_handler.cc | 6 +- cpp/core_v2/internal/p2p_star_pcp_handler.h | 1 + cpp/core_v2/internal/pcp_manager.cc | 9 +- cpp/core_v2/internal/pcp_manager.h | 3 +- cpp/core_v2/internal/simulation_user.h | 4 +- cpp/core_v2/options.h | 9 + cpp/platform/BUILD | 1 - cpp/platform_v2/api/ble.h | 1 + cpp/platform_v2/api/bluetooth_classic.h | 2 +- cpp/platform_v2/api/platform.h | 5 +- cpp/platform_v2/base/medium_environment.cc | 14 +- cpp/platform_v2/base/medium_environment.h | 8 +- cpp/platform_v2/impl/g3/ble.cc | 12 +- cpp/platform_v2/impl/g3/ble.h | 1 + cpp/platform_v2/impl/g3/bluetooth_classic.cc | 2 +- cpp/platform_v2/impl/g3/bluetooth_classic.h | 2 +- cpp/platform_v2/public/ble.cc | 7 +- cpp/platform_v2/public/ble.h | 1 + cpp/platform_v2/public/ble_test.cc | 6 +- cpp/platform_v2/public/bluetooth_classic.h | 4 +- proto/connections/offline_wire_formats.proto | 13 +- proto/discovery_enums.proto | 6 +- proto/error_code_enums.proto | 5 + 43 files changed, 421 insertions(+), 801 deletions(-) delete mode 100644 cpp/core_v2/internal/base_pcp_handler_test.cc.orig delete mode 100644 cpp/core_v2/internal/offline_service_controller.h.orig diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc index 3e468d23..4cc4df2e 100644 --- a/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc +++ b/cpp/core/internal/mediums/webrtc/signaling_frames_test.cc @@ -96,7 +96,7 @@ TEST(SignalingFramesTest, EncodeValidOffer) { TEST(SignalingFramesTest, DecodeValidOffer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kOfferProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kOfferProto, &frame); Ptr decoded_offer = DecodeOffer(frame); EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); @@ -120,7 +120,7 @@ TEST(SignalingFramesTest, EncodeValidAnswer) { TEST(SignalingFramesTest, DecodeValidAnswer) { location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kAnswerProto, &frame); Ptr decoded_answer = DecodeAnswer(frame); EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); @@ -163,7 +163,7 @@ TEST(SignalingFramesTest, DecodeValidIceCandidates) { std::vector encoded_candidates_vec; location::nearby::mediums::WebRtcSignalingFrame frame; - proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + proto2::TextFormat::ParseFromStringPiece(kIceCandidatesProto, &frame); std::vector> decoded_candidates = DecodeIceCandidates(frame); diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 44f6779d..12fdb676 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -9,6 +9,7 @@ #include "core_v2/internal/offline_frames.h" #include "core_v2/internal/pcp_handler.h" #include "core_v2/options.h" +#include "platform_v2/base/bluetooth_utils.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/system_clock.h" #include "securegcm/d2d_connection_context_v1.h" @@ -29,11 +30,13 @@ constexpr absl::Duration BasePcpHandler::kRejectedConnectionCloseDelay; BasePcpHandler::BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp) + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp) : mediums_(mediums), endpoint_manager_(endpoint_manager), channel_manager_(channel_manager), - pcp_(pcp) {} + pcp_(pcp), + bwu_manager_(bwu_manager) {} BasePcpHandler::~BasePcpHandler() { NEARBY_LOGS(INFO) << "BasePcpHandler: going down; strategy=" @@ -63,23 +66,23 @@ Status BasePcpHandler::StartAdvertising(ClientProxy* client, const ConnectionRequestInfo& info) { Future response; ConnectionOptions advertising_options = options.CompatibleOptions(); - RunOnPcpHandlerThread( - [this, client, &service_id, &info, &advertising_options, &response]() { - auto result = StartAdvertisingImpl( - client, service_id, client->GetLocalEndpointId(), - info.endpoint_info, advertising_options); - if (!result.status.Ok()) { - response.Set(result.status); - return; - } + RunOnPcpHandlerThread([this, client, &service_id, &info, &advertising_options, + &response]() { + auto result = + StartAdvertisingImpl(client, service_id, client->GetLocalEndpointId(), + info.endpoint_info, advertising_options); + if (!result.status.Ok()) { + response.Set(result.status); + return; + } - // Now that we've succeeded, mark the client as advertising. - advertising_options_ = advertising_options; - advertising_listener_ = info.listener; - client->StartedAdvertising(service_id, GetStrategy(), info.listener, - absl::MakeSpan(result.mediums)); - response.Set({Status::kSuccess}); - }); + // Now that we've succeeded, mark the client as advertising. + advertising_options_ = advertising_options; + advertising_listener_ = info.listener; + client->StartedAdvertising(service_id, GetStrategy(), info.listener, + absl::MakeSpan(result.mediums)); + response.Set({Status::kSuccess}); + }); return WaitForResult( absl::StrCat("StartAdvertising(", std::string(info.endpoint_info), ")"), client->GetClientId(), &response); @@ -232,8 +235,8 @@ void BasePcpHandler::OnEncryptionSuccessRunnable( .raw_authentication_token = raw_auth_token, .is_incoming_connection = connection_info.is_incoming, }, - connection_info.options, - std::move(connection_info.channel), connection_info.listener); + connection_info.options, std::move(connection_info.channel), + connection_info.listener); if (connection_info.result != nullptr) { NEARBY_LOG(INFO, "Connection established; Finalising future OK"); @@ -318,14 +321,20 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, OnEndpointFound(client, webrtc_endpoint); } - auto endpoints = GetDiscoveredEndpoints(endpoint_id); + auto discovered_endpoints = GetDiscoveredEndpoints(endpoint_id); std::unique_ptr channel; ConnectImplResult connect_impl_result; - // TODO(b/156634369): add GetRemoteBluetoothMacAddressEndpoint here for - // valid remote mac address. + auto remote_bluetooth_mac_address = + BluetoothUtils::ToString(options.remote_bluetooth_mac_address); + if (!remote_bluetooth_mac_address.empty()) { + auto additional_endpoint = GetRemoteBluetoothMacAddressEndpoint( + endpoint_id, remote_bluetooth_mac_address, discovered_endpoints); + if (additional_endpoint != nullptr) + discovered_endpoints.push_back(additional_endpoint.get()); + } - for (auto connect_endpoint : endpoints) { + for (auto connect_endpoint : discovered_endpoints) { connect_impl_result = ConnectImpl(client, connect_endpoint); if (connect_impl_result.status.Ok()) { channel = std::move(connect_impl_result.endpoint_channel); @@ -611,10 +620,6 @@ Status BasePcpHandler::RejectConnection(ClientProxy* client, client->GetClientId(), &response); } -// proto::connections::Medium BasePcpHandler::GetBandwidthUpgradeMedium() { -// return bandwidth_upgrade_medium_.Get(); -//} - void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id, ClientProxy* client, @@ -928,7 +933,7 @@ void BasePcpHandler::ProcessTieBreakLoss( void BasePcpHandler::InitiateBandwidthUpgrade( ClientProxy* client, const std::string& endpoint_id, - const std::vector& supported_mediums) { + const std::vector& supported_mediums) { // When we successfully connect to a remote endpoint and a bandwidth upgrade // medium has not yet been decided, we'll pick the highest bandwidth medium // supported by both us and the remote endpoint. Once we pick a medium, all @@ -938,16 +943,14 @@ void BasePcpHandler::InitiateBandwidthUpgrade( // way to prevent mediums, like Wifi Hotspot, from interfering with active // connections (although it's suboptimal for bandwidth throughput). When all // endpoints disconnect, we reset the bandwidth upgrade medium. - if (bandwidth_upgrade_medium_.Get() == - proto::connections::Medium::UNKNOWN_MEDIUM) { - bandwidth_upgrade_medium_.Set(ChooseBestUpgradeMedium(supported_mediums)); + Medium bwu_medium = bwu_medium_.Get(); + if (bwu_medium == Medium::UNKNOWN_MEDIUM) { + bwu_medium = ChooseBestUpgradeMedium(supported_mediums); + bwu_medium_.Set(bwu_medium); } - if (AutoUpgradeBandwidth() && (bandwidth_upgrade_medium_.Get() != - proto::connections::Medium::UNKNOWN_MEDIUM)) { - // TODO(apolyudov): Bring bandwidth upgrade back, when it is ready. - // bandwidth_upgrade_->InitiateBandwidthUpgradeForEndpoint( - // client, endpoint_id, bandwidth_upgrade_medium_.Get()); + if (AutoUpgradeBandwidth() && bwu_medium != Medium::UNKNOWN_MEDIUM) { + bwu_manager_->InitiateBwuForEndpoint(client, endpoint_id, bwu_medium); } } @@ -975,6 +978,55 @@ proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( return proto::connections::Medium::UNKNOWN_MEDIUM; } +std::unique_ptr +BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints) { + if (!discovery_options_.allowed.bluetooth) { + return nullptr; + } + + if (endpoints.empty()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because endpointId " + << endpoint_id << " has not been discovered"; + return nullptr; + } + + for (auto endpoint : endpoints) { + if (endpoint->medium == proto::connections::Medium::BLUETOOTH) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because the " + "endpoint has already been found over Bluetooth."; + return nullptr; + } + } + + auto remote_bluetooth_device = + mediums_->GetBluetoothClassic().GetRemoteDevice( + remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) + << "Cannot append remote Bluetooth MAC Address, because a valid " + "Bluetooth device could not be derived."; + return nullptr; + } + + auto bluetooth_endpoint = + std::make_unique(BluetoothEndpoint{ + { + endpoint_id, + endpoints[0]->endpoint_info, + endpoints[0]->service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + }); + NEARBY_LOGS(INFO) << "Appended remote Bluetooth device " + << remote_bluetooth_mac_address; + return bluetooth_endpoint; +} + void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, const std::string& endpoint_id, bool can_close_immediately) { diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index d9ee3f92..262d92cb 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -6,6 +6,7 @@ #include #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/endpoint_channel_manager.h" @@ -81,7 +82,8 @@ class BasePcpHandler : public PcpHandler, // TODO(apolyudov): Add SecureRandom. BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* channel_manager, Pcp pcp); + EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp); ~BasePcpHandler() override; BasePcpHandler(BasePcpHandler&&) = delete; BasePcpHandler& operator=(BasePcpHandler&&) = delete; @@ -90,8 +92,7 @@ class BasePcpHandler : public PcpHandler, // Notifies ConnectionListener (info.listener) in case of any event. // See // https://source.corp.google.com/piper///depot/google3/core_v2/listeners.h;l=78 - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, + Status StartAdvertising(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const ConnectionRequestInfo& info) override; @@ -102,8 +103,7 @@ class BasePcpHandler : public PcpHandler, // Starts discovery of endpoints that may be advertising. // Updates ClientProxy state once discovery started. // DiscoveryListener will get called in case of any event. - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, + Status StartDiscovery(ClientProxy* client, const std::string& service_id, const ConnectionOptions& options, const DiscoveryListener& listener) override; @@ -113,16 +113,14 @@ class BasePcpHandler : public PcpHandler, // Requests a newly discovered remote endpoint it to form a connection. // Updates state on ClientProxy. - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, + Status RequestConnection(ClientProxy* client, const std::string& endpoint_id, const ConnectionRequestInfo& info, const ConnectionOptions& options) override; // Called by either party to accept connection on their part. // Until both parties call it, connection will not reach a data phase. // Updates state in ClientProxy. - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, + Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id, const PayloadListener& payload_listener) override; // Called by either party to reject connection on their part. @@ -139,12 +137,12 @@ class BasePcpHandler : public PcpHandler, // Called when an endpoint disconnects while we're waiting for both sides to // approve/reject the connection. // @EndpointManagerThread - void OnEndpointDisconnect(ClientProxy* client, - const std::string& endpoint_id, + void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id, CountDownLatch* barrier) override; Pcp GetPcp() const override { return pcp_; } Strategy GetStrategy() const override { return strategy_; } + Medium GetBwuMedium() const { return bwu_medium_.Get(); } void DisconnectFromEndpointManager(); protected: @@ -227,8 +225,7 @@ class BasePcpHandler : public PcpHandler, std::shared_ptr endpoint); // @PcpHandlerThread - void OnEndpointLost(ClientProxy* client, - const DiscoveredEndpoint& endpoint); + void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint); Exception OnIncomingConnection( ClientProxy* client, const ByteArray& remote_endpoint_info, @@ -270,8 +267,8 @@ class BasePcpHandler : public PcpHandler, // Returns a vector of discovered endpoints, sorted in order of decreasing // preference. - std::vector - GetDiscoveredEndpoints(const std::string& endpoint_id); + std::vector GetDiscoveredEndpoints( + const std::string& endpoint_id); mediums::PeerId CreatePeerIdFromAdvertisement(const string& service_id, const string& endpoint_id, @@ -402,6 +399,11 @@ class BasePcpHandler : public PcpHandler, proto::connections::Medium ChooseBestUpgradeMedium( const std::vector& supported_mediums); + std::unique_ptr + GetRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address, + std::vector endpoints); + void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, EndpointChannel* channel, Status status, @@ -429,8 +431,7 @@ class BasePcpHandler : public PcpHandler, Status WaitForResult(const std::string& method_name, std::int64_t client_id, Future* future); - AtomicReference bandwidth_upgrade_medium_{ - proto::connections::Medium::UNKNOWN_MEDIUM}; + AtomicReference bwu_medium_{Medium::UNKNOWN_MEDIUM}; ScheduledExecutor alarm_executor_; SingleThreadExecutor serial_executor_; @@ -472,6 +473,7 @@ class BasePcpHandler : public PcpHandler, Strategy strategy_{PcpToStrategy(pcp_)}; Prng prng_; EncryptionRunner encryption_runner_; + BwuManager* bwu_manager_; EndpointManager::FrameProcessor::Handle handle_ = nullptr; }; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc b/cpp/core_v2/internal/base_pcp_handler_test.cc index 1a580067..e939fda2 100644 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc +++ b/cpp/core_v2/internal/base_pcp_handler_test.cc @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/encryption_runner.h" #include "core_v2/internal/offline_frames.h" @@ -76,8 +77,9 @@ class MockPcpHandler : public BasePcpHandler { public: using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(m, em, ecm, Pcp::kP2pCluster) {} + MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm, + BwuManager* bwu) + : BasePcpHandler(m, em, ecm, bwu, Pcp::kP2pCluster) {} // Expose protected inner types of a base type for mocking. using BasePcpHandler::ConnectImplResult; @@ -367,7 +369,8 @@ TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); SUCCEED(); } @@ -376,7 +379,8 @@ TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); } @@ -385,7 +389,8 @@ TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartAdvertising(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); EXPECT_TRUE(client.IsAdvertising()); @@ -398,7 +403,8 @@ TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); } @@ -407,7 +413,8 @@ TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); EXPECT_TRUE(client.IsDiscovering()); @@ -421,7 +428,8 @@ TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -444,7 +452,8 @@ TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -471,7 +480,8 @@ TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -494,7 +504,8 @@ TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -530,7 +541,8 @@ TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; @@ -569,7 +581,8 @@ TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) { Mediums m; EndpointChannelManager ecm; EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&m, &em, &ecm); + BwuManager bwu(m, em, ecm, {}, {}); + MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu); StartDiscovery(&client, &pcp_handler); auto mediums = pcp_handler.GetDiscoveryMediums(); auto connect_medium = mediums[mediums.size() - 1]; diff --git a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig b/cpp/core_v2/internal/base_pcp_handler_test.cc.orig deleted file mode 100644 index c9009413..00000000 --- a/cpp/core_v2/internal/base_pcp_handler_test.cc.orig +++ /dev/null @@ -1,538 +0,0 @@ -#include "core_v2/internal/base_pcp_handler.h" - -#include -#include - -#include "core_v2/internal/base_endpoint_channel.h" -#include "core_v2/internal/client_proxy.h" -#include "core_v2/internal/encryption_runner.h" -#include "core_v2/internal/offline_frames.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/params.h" -#include "proto/connections/offline_wire_formats.pb.h" -#include "platform_v2/base/byte_array.h" -#include "platform_v2/public/count_down_latch.h" -#include "platform_v2/public/pipe.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/time/time.h" - -namespace location { -namespace nearby { -namespace connections { -namespace { - -using ::location::nearby::proto::connections::Medium; -using ::testing::_; -using ::testing::AtLeast; -using ::testing::Invoke; -using ::testing::MockFunction; -using ::testing::Return; -using ::testing::StrictMock; - -constexpr BooleanMediumSelector kTestCases[] = { - BooleanMediumSelector{}, - BooleanMediumSelector{ - .bluetooth = true, - }, - BooleanMediumSelector{ - .wifi_lan = true, - }, - BooleanMediumSelector{ - .bluetooth = true, - .wifi_lan = true, - }, -}; - -class MockEndpointChannel : public BaseEndpointChannel { - public: - explicit MockEndpointChannel(Pipe* reader, Pipe* writer) - : BaseEndpointChannel("channel", &reader->GetInputStream(), - &writer->GetOutputStream()) {} - - ExceptionOr DoRead() { return BaseEndpointChannel::Read(); } - Exception DoWrite(const ByteArray& data) { - return BaseEndpointChannel::Write(data); - } - absl::Time DoGetLastReadTimestamp() { - return BaseEndpointChannel::GetLastReadTimestamp(); - } - - MOCK_METHOD(ExceptionOr, Read, (), (override)); - MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); - MOCK_METHOD(void, CloseImpl, (), (override)); - MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override)); - MOCK_METHOD(std::string, GetType, (), (const override)); - MOCK_METHOD(std::string, GetName, (), (const override)); - MOCK_METHOD(bool, IsPaused, (), (const override)); - MOCK_METHOD(void, Pause, (), (override)); - MOCK_METHOD(void, Resume, (), (override)); - MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); -}; - -class MockPcpHandler : public BasePcpHandler { - public: - using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint; - - MockPcpHandler(EndpointManager* em, EndpointChannelManager* ecm) - : BasePcpHandler(em, ecm, Pcp::kP2pCluster) {} - - // Expose protected inner types of a base type for mocking. - using BasePcpHandler::ConnectImplResult; - using BasePcpHandler::DiscoveredEndpoint; - using BasePcpHandler::StartOperationResult; - - MOCK_METHOD(Strategy, GetStrategy, (), (const override)); - MOCK_METHOD(Pcp, GetPcp, (), (const override)); - - MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client), - (const, override)); - MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client), - (const, override)); - - MOCK_METHOD(StartOperationResult, StartAdvertisingImpl, - (ClientProxy * client, const string& service_id, - const string& local_endpoint_id, - const string& local_endpoint_name, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override)); - MOCK_METHOD(StartOperationResult, StartDiscoveryImpl, - (ClientProxy * client, const string& service_id, - const ConnectionOptions& options), - (override)); - MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override)); - MOCK_METHOD(ConnectImplResult, ConnectImpl, - (ClientProxy * client, DiscoveredEndpoint* endpoint), (override)); - MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (), - (override)); - - std::vector GetConnectionMediumsByPriority() - override { - return GetDiscoveryMediums(); - } - - // Mock adapters for protected non-virtual methods of a base class. - void OnEndpointFound(ClientProxy* client, - std::shared_ptr endpoint) { - BasePcpHandler::OnEndpointFound(client, std::move(endpoint)); - } - void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint) { - BasePcpHandler::OnEndpointLost(client, endpoint); - } - - std::vector GetDiscoveryMediums() { - std::vector mediums; - auto allowed = - BasePcpHandler::GetDiscoveryOptions().CompatibleOptions().allowed; - // Mediums are sorted in order of decreasing preference. - if (allowed.wifi_lan) - mediums.push_back(proto::connections::Medium::WIFI_LAN); - if (allowed.web_rtc) mediums.push_back(proto::connections::Medium::WEB_RTC); - if (allowed.bluetooth) - mediums.push_back(proto::connections::Medium::BLUETOOTH); - return mediums; - } - - std::vector GetDiscoveredEndpoints( - const std::string& endpoint_id) { - return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id); - } -}; - -class MockContext { - public: - explicit MockContext(std::atomic_int* destroyed = nullptr) { - destroyed_ = destroyed; - } - MockContext(MockContext&&) = default; - MockContext& operator=(MockContext&&) = default; - - ~MockContext() { - if (destroyed_) (*destroyed_)++; - } - - private: - Swapper destroyed_{nullptr}; -}; - -struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint { - MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context) - : DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {} - - MockContext context; -}; - -class BasePcpHandlerTest - : public ::testing::TestWithParam { - protected: - struct MockConnectionListener { - StrictMock> - initiated_cb; - StrictMock> accepted_cb; - StrictMock> - rejected_cb; - StrictMock> - disconnected_cb; - StrictMock> - bandwidth_changed_cb; - }; - struct MockDiscoveryListener { - StrictMock> - endpoint_found_cb; - StrictMock> - endpoint_lost_cb; - StrictMock< - MockFunction> - endpoint_distance_changed_cb; - }; - - void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - ConnectionRequestInfo info{ - .name = "remote_endpoint_name", - .listener = connection_listener_, - }; - EXPECT_CALL(*pcp_handler, - StartAdvertisingImpl(client, service_id, _, info.name, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id, options, info), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsAdvertising()); - } - - void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler, - BooleanMediumSelector allowed = GetParam()) { - std::string service_id{"service"}; - ConnectionOptions options{ - .strategy = Strategy::kP2pCluster, - .allowed = allowed, - .auto_upgrade_bandwidth = true, - .enforce_topology_constraints = true, - }; - EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _)) - .WillOnce(Return(MockPcpHandler::StartOperationResult{ - .status = {Status::kSuccess}, - .mediums = {Medium::BLE}, - })); - EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, options, - discovery_listener_), - Status{Status::kSuccess}); - EXPECT_TRUE(client->IsDiscovering()); - } - - std::pair, - std::unique_ptr> - SetupConnection(Pipe& pipe_a, Pipe& pipe_b) { // NOLINT - auto channel_a = std::make_unique(&pipe_b, &pipe_a); - auto channel_b = std::make_unique(&pipe_a, &pipe_b); - // On initiator (A) side, we drop the first write, since this is a - // connection establishment packet, and we don't have the peer entity, just - // the peer channel. The rest of the exchange must happen for the benefit of - // DH key exchange. - EXPECT_CALL(*channel_a, Read()) - .WillRepeatedly(Invoke( - [channel = channel_a.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_a, Write(_)) - .WillOnce(Return(Exception{Exception::kSuccess})) - .WillRepeatedly( - Invoke([channel = channel_a.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_a, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false)); - EXPECT_CALL(*channel_b, Read()) - .WillRepeatedly(Invoke( - [channel = channel_b.get()]() { return channel->DoRead(); })); - EXPECT_CALL(*channel_b, Write(_)) - .WillRepeatedly( - Invoke([channel = channel_b.get()](const ByteArray& data) { - return channel->DoWrite(data); - })); - EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(Medium::BLE)); - EXPECT_CALL(*channel_b, GetLastReadTimestamp) - .WillRepeatedly(Return(absl::Now())); - EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false)); - return std::make_pair(std::move(channel_a), std::move(channel_b)); - } - - void RequestConnection(const std::string& endpoint_id, - std::unique_ptr channel_a, - MockEndpointChannel* channel_b, ClientProxy* client, - MockPcpHandler* pcp_handler, - std::atomic_int* flag = nullptr) { - ConnectionRequestInfo info{ - .name = "ABCD", - .listener = connection_listener_, - }; - EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call); - EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*pcp_handler, GetStrategy) - .WillRepeatedly(Return(Strategy::kP2pCluster)); - EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1); - // Simulate successful discovery. - auto encryption_runner = std::make_unique(); - auto allowed_mediums = pcp_handler->GetDiscoveryMediums(); - - EXPECT_CALL(*pcp_handler, ConnectImpl) - .WillOnce(Invoke([&channel_a, medium = allowed_mediums[0]]( - ClientProxy* client, - MockPcpHandler::DiscoveredEndpoint* endpoint) { - return MockPcpHandler::ConnectImplResult{ - .medium = medium, - .status = {Status::kSuccess}, - .endpoint_channel = std::move(channel_a), - }; - })); - - for (const auto& medium : allowed_mediums) { - pcp_handler->OnEndpointFound( - client, - std::make_shared(MockDiscoveredEndpoint{ - { - endpoint_id, - info.name, - "service", - medium, - }, - MockContext{flag}, - })); - } - auto other_client = std::make_unique(); - - // Run peer crypto in advance, if channel_b is provided. - // Otherwise stay in not-encrypted state. - if (channel_b != nullptr) { - encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b, - {}); - } - EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Stopping Encryption Runner"); - } - - Pipe pipe_a_; - Pipe pipe_b_; - MockConnectionListener mock_connection_listener_; - MockDiscoveryListener mock_discovery_listener_; - ConnectionListener connection_listener_{ - .initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(), - .accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(), - .rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(), - .disconnected_cb = - mock_connection_listener_.disconnected_cb.AsStdFunction(), - .bandwidth_changed_cb = - mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(), - }; - DiscoveryListener discovery_listener_{ - .endpoint_found_cb = - mock_discovery_listener_.endpoint_found_cb.AsStdFunction(), - .endpoint_lost_cb = - mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(), - .endpoint_distance_changed_cb = - mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(), - }; -}; - -TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) { - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - SUCCEED(); -} - -TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartAdvertising(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1); - EXPECT_TRUE(client.IsAdvertising()); - pcp_handler.StopAdvertising(&client); - EXPECT_FALSE(client.IsAdvertising()); -} - -TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); -} - -TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) { - ClientProxy client; - EndpointChannelManager ecm; - EndpointManager em(&ecm); - MockPcpHandler pcp_handler(&em, &ecm); - StartDiscovery(&client, &pcp_handler); - EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1); - EXPECT_TRUE(client.IsDiscovering()); - pcp_handler.StopDiscovery(&client); - EXPECT_FALSE(client.IsDiscovering()); -} - -TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "RequestConnection complete"); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) { - 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; - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1); - RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(), - &client, &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id; - EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id), - Status{Status::kSuccess}); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client, - &pcp_handler); - NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id; - EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1); - EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call) - .Times(AtLeast(0)); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str()); - auto frame = - parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess)); - pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client, - Medium::BLE); - NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id; - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); -} - -TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) { - std::atomic_int destroyed_flag = 0; - int mediums_count = 0; - { - 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_a = channel_pair.first; - auto& channel_b = channel_pair.second; - EXPECT_CALL(*channel_a, CloseImpl).Times(1); - EXPECT_CALL(*channel_b, CloseImpl).Times(1); - RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), - &client, &pcp_handler, &destroyed_flag); - mediums_count = pcp_handler.GetDiscoveryMediums().size(); - NEARBY_LOG(INFO, "Attempting to accept connection: id=%s", - endpoint_id.c_str()); - EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}), - Status{Status::kSuccess}); - EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0)); - NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str()); - channel_b->Close(); - pcp_handler.DisconnectFromEndpointManager(); - } - EXPECT_EQ(destroyed_flag.load(), mediums_count); -} - -INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest, - ::testing::ValuesIn(kTestCases)); - -} // namespace -} // namespace connections -} // namespace nearby -} // namespace location diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 4564dce1..9ab910fe 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/bwu_handler.h" #include "core_v2/internal/offline_frames.h" +#include "core_v2/internal/webrtc_bwu_handler.h" #include "platform_v2/base/byte_array.h" #include "platform_v2/public/count_down_latch.h" #include "proto/connections_enums.pb.h" @@ -52,7 +53,11 @@ void BwuManager::InitBwuHandlers() { .incoming_connection_cb = absl::bind_front(&BwuManager::OnIncomingConnection, this), }; - // TODO(apolyudov): inject instances of supported upgrade medium handlers. + if (config_.allow_upgrade_to.web_rtc) { + handlers_.emplace(Medium::WEB_RTC, + std::make_unique( + *mediums_, *channel_manager_, notifications)); + } } void BwuManager::Shutdown() { @@ -90,12 +95,17 @@ void BwuManager::Shutdown() { } // This is the point on the Initiator side where the -// currentBwuMedium is set. +// medium_ is set. void BwuManager::InitiateBwuForEndpoint(ClientProxy* client, - const std::string& endpoint_id) { - RunOnBwuManagerThread([this, client, endpoint_id]() { - auto* handler = SetCurrentBwuHandler(ChooseBestUpgradeMedium( - client->GetUpgradeMediums(endpoint_id).GetMediums(true))); + const std::string& endpoint_id, + Medium new_medium) { + RunOnBwuManagerThread([this, client, endpoint_id, new_medium]() { + Medium proposed_medium = ChooseBestUpgradeMedium( + client->GetUpgradeMediums(endpoint_id).GetMediums(true)); + if (new_medium != Medium::UNKNOWN_MEDIUM) { + proposed_medium = new_medium; + } + auto* handler = SetCurrentBwuHandler(proposed_medium); if (!handler) return; diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index 150f49d5..b97d7138 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -65,7 +65,8 @@ class BwuManager : public EndpointManager::FrameProcessor { // Function initiates the bandwidth upgrade and sends an // UPGRADE_PATH_AVAILABLE OfflineFrame. void InitiateBwuForEndpoint(ClientProxy* client_proxy, - const std::string& endpoint_id); + const std::string& endpoint_id, + Medium new_medium = Medium::UNKNOWN_MEDIUM); // == EndpointManager::FrameProcessor interface ==. // This is the point on the inbound BWU protocol where the handler_ is set. diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index d0ab8b16..f8c7cf8f 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -106,6 +106,7 @@ bool Ble::IsAdvertisingLocked(const std::string& service_id) { } bool Ble::StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) { MutexLock lock(&mutex_); @@ -133,7 +134,8 @@ bool Ble::StartScanning(const std::string& service_id, return false; } - if (!medium_.StartScanning(service_id, callback)) { + if (!medium_.StartScanning(service_id, fast_advertisement_service_uuid, + callback)) { NEARBY_LOGS(INFO) << "Failed to start scan of BLE services."; return false; } diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 42c1cd9c..1a6b7643 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -45,6 +45,7 @@ class Ble { // range through a callback. Returns true, if scanning mode was enabled, // false otherwise. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 6a2d43f0..15e24d8f 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -18,7 +18,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleTest : public ::testing::Test { protected: @@ -61,6 +61,7 @@ TEST_F(BleTest, CanStartAdvertising) { ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -95,6 +96,7 @@ TEST_F(BleTest, CanStartDiscovery) { EXPECT_TRUE(ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&accept_latch]( @@ -139,6 +141,7 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral discovered_peripheral; ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc index b6620e96..15dad66c 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -368,10 +368,10 @@ BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, return socket; } -BluetoothDevice BluetoothClassic::FindRemoteDevice( +BluetoothDevice BluetoothClassic::GetRemoteDevice( const std::string& mac_address) { MutexLock lock(&mutex_); - return medium_.FindRemoteDevice(mac_address); + return medium_.GetRemoteDevice(mac_address); } std::string BluetoothClassic::GetMacAddress() const { diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h index 3ed3a33a..29ae73e5 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_classic.h +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -102,7 +102,7 @@ class BluetoothClassic { std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_); - BluetoothDevice FindRemoteDevice(const std::string& mac_address) + BluetoothDevice GetRemoteDevice(const std::string& mac_address) ABSL_LOCKS_EXCLUDED(mutex_); private: diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 3cb4e4ca..86af87ec 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -233,8 +233,8 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { Future success_future; webrtc_medium.CreatePeerConnection( &peer_connection_observer_, - [this, &success_future]( - rtc::scoped_refptr peer_connection) { + [this, success_future](rtc::scoped_refptr + peer_connection) mutable { if (!peer_connection) { success_future.Set(false); return; @@ -329,8 +329,7 @@ bool ConnectionFlow::CloseLocked() { state_ = State::kEnded; data_channel_future_.SetException({Exception::kInterrupted}); - if (peer_connection_) - peer_connection_->Close(); + if (peer_connection_) peer_connection_->Close(); data_channel_observer_.reset(); NEARBY_LOG(INFO, "Closed WebRTC connection."); diff --git a/cpp/core_v2/internal/offline_service_controller.h b/cpp/core_v2/internal/offline_service_controller.h index 03ebbc33..7b6a1c5a 100644 --- a/cpp/core_v2/internal/offline_service_controller.h +++ b/cpp/core_v2/internal/offline_service_controller.h @@ -67,9 +67,10 @@ class OfflineServiceController : public ServiceController { EndpointChannelManager channel_manager_; EndpointManager endpoint_manager_{&channel_manager_}; PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; BwuManager bwu_manager_{ mediums_, endpoint_manager_, channel_manager_, {}, {}}; + PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_, + bwu_manager_}; }; } // namespace connections diff --git a/cpp/core_v2/internal/offline_service_controller.h.orig b/cpp/core_v2/internal/offline_service_controller.h.orig deleted file mode 100644 index 97517fa7..00000000 --- a/cpp/core_v2/internal/offline_service_controller.h.orig +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ -#define CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ - -#include -#include -#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/payload_manager.h" -#include "core_v2/internal/pcp_manager.h" -#include "core_v2/internal/service_controller.h" -#include "core_v2/listeners.h" -#include "core_v2/options.h" -#include "core_v2/payload.h" -#include "core_v2/status.h" - -namespace location { -namespace nearby { -namespace connections { - -class OfflineServiceController : public ServiceController { - public: - OfflineServiceController() = default; - ~OfflineServiceController() override; - - Status StartAdvertising(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const ConnectionRequestInfo& info) override; - void StopAdvertising(ClientProxy* client) override; - - Status StartDiscovery(ClientProxy* client, - const std::string& service_id, - const ConnectionOptions& options, - const DiscoveryListener& listener) override; - void StopDiscovery(ClientProxy* client) override; - - Status RequestConnection(ClientProxy* client, - const std::string& endpoint_id, - const ConnectionRequestInfo& info, - const ConnectionOptions& options) override; - Status AcceptConnection(ClientProxy* client, - const std::string& endpoint_id, - const PayloadListener& listener) override; - Status RejectConnection(ClientProxy* client, - const std::string& endpoint_id) override; - - void InitiateBandwidthUpgrade(ClientProxy* client, - const std::string& endpoint_id) override; - - void SendPayload(ClientProxy* client, - const std::vector& endpoint_ids, - Payload payload) override; - Status CancelPayload(ClientProxy* client, - Payload::Id payload_id) override; - - void DisconnectFromEndpoint(ClientProxy* client, - const std::string& endpoint_id) override; - - void Stop(); - - private: - // Note that the order of declaration of these is crucial, because we depend - // on the destructors running (strictly) in the reverse order; a deviation - // from that will lead to crashes at runtime. - AtomicBoolean stop_{false}; - Mediums mediums_; - EndpointChannelManager channel_manager_; - EndpointManager endpoint_manager_{&channel_manager_}; - PayloadManager payload_manager_{endpoint_manager_}; - PcpManager pcp_manager_{mediums_, channel_manager_, endpoint_manager_}; -}; - -} // namespace connections -} // namespace nearby -} // namespace location - -#endif // CORE_V2_INTERNAL_OFFLINE_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index a4f07de8..116a04fd 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -4,6 +4,7 @@ #include "core_v2/internal/ble_advertisement.h" #include "core_v2/internal/ble_endpoint_channel.h" #include "core_v2/internal/bluetooth_endpoint_channel.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/mediums/utils.h" #include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h" #include "core_v2/internal/webrtc_endpoint_channel.h" @@ -23,10 +24,22 @@ ByteArray P2pClusterPcpHandler::GenerateHash(const std::string& source, return Utils::Sha256Hash(source, size); } +bool P2pClusterPcpHandler::ShouldAdvertiseBluetoothMacOverBle( + PowerLevel power_level) { + return power_level == PowerLevel::kHighPower; +} + +bool P2pClusterPcpHandler::ShouldAcceptBluetoothConnections( + const ConnectionOptions& options) { + return options.enable_bluetooth_listening; +} + P2pClusterPcpHandler::P2pClusterPcpHandler( Mediums* mediums, EndpointManager* endpoint_manager, - EndpointChannelManager* endpoint_channel_manager, Pcp pcp) - : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, pcp), + EndpointChannelManager* endpoint_channel_manager, BwuManager* bwu_manager, + Pcp pcp) + : BasePcpHandler(mediums, endpoint_manager, endpoint_channel_manager, + bwu_manager, pcp), bluetooth_radio_(mediums->GetBluetoothRadio()), bluetooth_medium_(mediums->GetBluetoothClassic()), ble_medium_(mediums->GetBle()), @@ -131,10 +144,12 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { bluetooth_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); webrtc_medium_.StopAcceptingConnections(); wifi_lan_medium_.StopAdvertising(client->GetAdvertisingServiceId()); + wifi_lan_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); return {Status::kSuccess}; } @@ -311,12 +326,11 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( return; } - // Parse the Ble advertisement bytes. + // Parse the BLE advertisement bytes. BleAdvertisement advertisement( - fast_advertisement, - peripheral.GetAdvertisementBytes(service_id)); + fast_advertisement, peripheral.GetAdvertisementBytes(service_id)); - // Make sure the Ble advertisement points to a valid + // Make sure the BLE advertisement points to a valid // endpoint we're discovering. if (!IsRecognizedBleEndpoint(service_id, advertisement)) return; @@ -343,7 +357,34 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( peripheral, })); - // TODO(b/156632928): Check for Bluetooth device with remote mac address. + // Make sure we can connect to this device via Classic Bluetooth. + std::string remote_bluetooth_mac_address = + advertisement.GetBluetoothMacAddress(); + if (remote_bluetooth_mac_address.empty()) { + NEARBY_LOGS(INFO) + << "No Bluetooth Classic MAC address found in advertisement"; + return; + } + + BluetoothDevice remote_bluetooth_device = + bluetooth_medium_.GetRemoteDevice(remote_bluetooth_mac_address); + if (!remote_bluetooth_device.IsValid()) { + NEARBY_LOGS(INFO) << "A valid Bluetooth device could not be derived from " + "the MAC address " + << remote_bluetooth_mac_address; + return; + } + + OnEndpointFound(client, + std::make_shared(BluetoothEndpoint{ + { + advertisement.GetEndpointId(), + advertisement.GetEndpointInfo(), + service_id, + proto::connections::Medium::BLUETOOTH, + }, + remote_bluetooth_device, + })); }); } @@ -547,7 +588,7 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( .peripheral_lost_cb = absl::bind_front( &P2pClusterPcpHandler::BlePeripheralLostHandler, this, client), }, - client, service_id); + client, service_id, options.fast_advertisement_service_uuid); if (ble_medium != proto::connections::UNKNOWN_MEDIUM) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartDiscoveryImpl: Ble added"); mediums_started_successfully.push_back(ble_medium); @@ -753,51 +794,90 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, const ConnectionOptions& options) { bool fast_advertisement = !options.fast_advertisement_service_uuid.empty(); + PowerLevel power_level = + options.low_power ? PowerLevel::kLowPower : PowerLevel::kHighPower; // Start listening for connections before advertising in case a connection - // request comes in very quickly. + // request comes in very quickly. BLE allows connecting over BLE itself, as + // well as advertising the Bluetooth MAC address to allow connecting over + // Bluetooth Classic. NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" << service_id << ": start"; - if (ble_medium_.IsAcceptingConnections(service_id)) { - NEARBY_LOGS(ERROR) << "Ble is already accepting connections for service_id=" - << service_id; - return proto::connections::UNKNOWN_MEDIUM; - } + if (!ble_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !ble_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BleSocket socket, + const std::string& service_id) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + service_id, + socket = std::move(socket)]() mutable { + std::string remote_peripheral_name = + socket.GetRemotePeripheral().GetName(); + auto channel = absl::make_unique( + remote_peripheral_name, socket); + ByteArray remote_peripheral_info = + socket.GetRemotePeripheral().GetAdvertisementBytes( + service_id); - NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleAdvertising: service_id=" - << service_id << ": invoking"; - if (!bluetooth_radio_.Enable() || - !ble_medium_.StartAcceptingConnections( - service_id, - {.accepted_cb = [this, client, local_endpoint_info]( - BleSocket socket, const std::string& service_id) { - if (!socket.IsValid()) { - NEARBY_LOG(ERROR, "Invalid socket in accept callback: name=%s", - std::string(local_endpoint_info).c_str()); - return; - } - RunOnPcpHandlerThread([this, client, local_endpoint_info, - service_id, - socket = std::move(socket)]() mutable { - std::string remote_peripheral_name = - socket.GetRemotePeripheral().GetName(); - auto channel = absl::make_unique( - remote_peripheral_name, socket); - ByteArray remote_peripheral_info = - socket.GetRemotePeripheral().GetAdvertisementBytes( - service_id); - - OnIncomingConnection(client, remote_peripheral_info, - std::move(channel), - proto::connections::Medium::BLE); - }); - }})) { + OnIncomingConnection(client, remote_peripheral_info, + std::move(channel), + proto::connections::Medium::BLE); + }); + }})) { + NEARBY_LOGS(ERROR) + << "Ble failed to start accepting connections for service_id=" + << service_id; + return proto::connections::UNKNOWN_MEDIUM; + } NEARBY_LOGS(ERROR) - << "Ble failed to start accepting connections for service_id=" + << "Ble succeed to start accepting connections for service_id=" << service_id; - return proto::connections::UNKNOWN_MEDIUM; } - // TODO(b/156632928): Should check for Bluetooth connection here + + if (ShouldAdvertiseBluetoothMacOverBle(power_level) || + ShouldAcceptBluetoothConnections(options)) { + if (bluetooth_medium_.IsAvailable() && + !bluetooth_medium_.IsAcceptingConnections(service_id)) { + if (!bluetooth_radio_.Enable() || + !bluetooth_medium_.StartAcceptingConnections( + service_id, {.accepted_cb = [this, client, local_endpoint_info]( + BluetoothSocket socket) { + if (!socket.IsValid()) { + NEARBY_LOG(ERROR, + "Invalid socket in accept callback: name=%s", + std::string(local_endpoint_info).c_str()); + return; + } + RunOnPcpHandlerThread([this, client, local_endpoint_info, + socket = std::move(socket)]() mutable { + std::string remote_device_name = + socket.GetRemoteDevice().GetName(); + auto channel = absl::make_unique( + remote_device_name, socket); + ByteArray remote_device_info{remote_device_name}; + + OnIncomingConnection(client, remote_device_info, + std::move(channel), + proto::connections::Medium::BLUETOOTH); + }); + }})) { + NEARBY_LOGS(ERROR) + << "BT failed to start accepting connections for service_id=" + << service_id; + ble_medium_.StopAcceptingConnections(service_id); + return proto::connections::UNKNOWN_MEDIUM; + } + NEARBY_LOGS(ERROR) + << "BT succeed to start accepting connections for service_id=" + << service_id; + } + } NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBleAdvertising: service=%s: " @@ -814,8 +894,10 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( } else { const ByteArray service_id_hash = GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); - // TODO(b/156632928): Should advertise Bluetooth MacAddress Over Ble std::string bluetooth_mac_address; + if (bluetooth_medium_.IsAvailable() && + ShouldAdvertiseBluetoothMacOverBle(power_level)) + bluetooth_mac_address = bluetooth_medium_.GetMacAddress(); advertisement_bytes = ByteArray(BleAdvertisement( kBleAdvertisementVersion, GetPcp(), service_id_hash, local_endpoint_id, @@ -852,9 +934,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( proto::connections::Medium P2pClusterPcpHandler::StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id) { + const std::string& service_id, + const std::string& fast_advertisement_service_uuid) { if (bluetooth_radio_.Enable() && - ble_medium_.StartScanning(service_id, std::move(callback))) { + ble_medium_.StartScanning(service_id, fast_advertisement_service_uuid, + std::move(callback))) { NEARBY_LOGS(INFO) << "P2pClusterPcpHandler::StartBleScanning: ok"; return proto::connections::BLE; } else { diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index b276a3f9..687075c4 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -7,6 +7,7 @@ #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/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -38,6 +39,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { public: P2pClusterPcpHandler(Mediums* mediums, EndpointManager* endpoint_manager, EndpointChannelManager* channel_manager, + BwuManager* bwu_manager, Pcp pcp = Pcp::kP2pCluster); ~P2pClusterPcpHandler() override = default; @@ -117,6 +119,9 @@ class P2pClusterPcpHandler : public BasePcpHandler { WifiLanServiceInfo::Version::kV1; static ByteArray GenerateHash(const std::string& source, size_t size); + static bool ShouldAdvertiseBluetoothMacOverBle(PowerLevel power_level); + static bool ShouldAcceptBluetoothConnections( + const ConnectionOptions& options); // Bluetooth bool IsRecognizedBluetoothEndpoint(const std::string& name_string, @@ -155,7 +160,8 @@ class P2pClusterPcpHandler : public BasePcpHandler { const ByteArray& local_endpoint_info, const ConnectionOptions& options); proto::connections::Medium StartBleScanning( BleDiscoveredPeripheralCallback callback, ClientProxy* client, - const std::string& service_id); + const std::string& service_id, + const std::string& fast_advertisement_service_uuid); BasePcpHandler::ConnectImplResult BleConnectImpl(ClientProxy* client, BleEndpoint* endpoint); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc index 51bce6df..8ac6064b 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler_test.cc @@ -2,6 +2,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/options.h" #include "platform_v2/base/medium_environment.h" #include "platform_v2/public/count_down_latch.h" @@ -61,7 +62,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructOne) { Mediums mediums; EndpointChannelManager ecm; EndpointManager em(&ecm); - P2pClusterPcpHandler handler(&mediums, &em, &ecm); + BwuManager bwu(mediums, em, ecm, {}, {}); + P2pClusterPcpHandler handler(&mediums, &em, &ecm, &bwu); env_.Stop(); } @@ -73,8 +75,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanConstructMultiple) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); env_.Stop(); } @@ -84,7 +88,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanAdvertise) { Mediums mediums_a; EndpointChannelManager ecm_a; EndpointManager em_a(&ecm_a); - P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, {.endpoint_info = ByteArray{endpoint_name}}), @@ -101,8 +106,10 @@ TEST_P(P2pClusterPcpHandlerTest, CanDiscover) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, {}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, {}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch latch(1); EXPECT_EQ( handler_a.StartAdvertising(&client_a_, service_id_, options_, @@ -141,8 +148,12 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { 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); + BwuManager bwu_a(mediums_a, em_a, ecm_a, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + BwuManager bwu_b(mediums_b, em_b, ecm_b, {}, + {.allow_upgrade_to = {.bluetooth = true}}); + P2pClusterPcpHandler handler_a(&mediums_a, &em_a, &ecm_a, &bwu_a); + P2pClusterPcpHandler handler_b(&mediums_b, &em_b, &ecm_b, &bwu_b); CountDownLatch discover_latch(1); CountDownLatch connect_latch(2); struct DiscoveredInfo { @@ -207,6 +218,8 @@ TEST_P(P2pClusterPcpHandlerTest, CanConnect) { }, options_); EXPECT_TRUE(connect_latch.Await(absl::Milliseconds(1000)).result()); + bwu_a.Shutdown(); + bwu_b.Shutdown(); env_.Stop(); } 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 index c3525bdd..0b09d8bd 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -6,8 +6,9 @@ namespace connections { P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( Mediums& mediums, EndpointManager& endpoint_manager, - EndpointChannelManager& channel_manager, Pcp pcp) - : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, pcp) {} + EndpointChannelManager& channel_manager, BwuManager& bwu_manager, Pcp pcp) + : P2pStarPcpHandler(mediums, endpoint_manager, channel_manager, bwu_manager, + pcp) {} std::vector P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { 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 index cd9cb39b..4b09ab3c 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.h @@ -22,6 +22,7 @@ class P2pPointToPointPcpHandler : public P2pStarPcpHandler { public: P2pPointToPointPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pPointToPoint); protected: diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc index acb45e38..45a20d14 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -9,9 +9,9 @@ namespace connections { P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, - Pcp pcp) - : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, pcp) { -} + BwuManager& bwu_manager, Pcp pcp) + : P2pClusterPcpHandler(&mediums, &endpoint_manager, &channel_manager, + &bwu_manager, pcp) {} std::vector P2pStarPcpHandler::GetConnectionMediumsByPriority() { diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.h b/cpp/core_v2/internal/p2p_star_pcp_handler.h index 203bfcf5..c1418ffd 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.h @@ -25,6 +25,7 @@ class P2pStarPcpHandler : public P2pClusterPcpHandler { public: P2pStarPcpHandler(Mediums& mediums, EndpointManager& endpoint_manager, EndpointChannelManager& channel_manager, + BwuManager& bwu_manager, Pcp pcp = Pcp::kP2pStar); protected: diff --git a/cpp/core_v2/internal/pcp_manager.cc b/cpp/core_v2/internal/pcp_manager.cc index c3c62aee..3a537547 100644 --- a/cpp/core_v2/internal/pcp_manager.cc +++ b/cpp/core_v2/internal/pcp_manager.cc @@ -11,14 +11,15 @@ namespace connections { PcpManager::PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager) { + EndpointManager& endpoint_manager, + BwuManager& bwu_manager) { handlers_[Pcp::kP2pCluster] = std::make_unique( - &mediums, &endpoint_manager, &channel_manager); + &mediums, &endpoint_manager, &channel_manager, &bwu_manager); handlers_[Pcp::kP2pStar] = std::make_unique( - mediums, endpoint_manager, channel_manager); + mediums, endpoint_manager, channel_manager, bwu_manager); handlers_[Pcp::kP2pPointToPoint] = std::make_unique(mediums, endpoint_manager, - channel_manager); + channel_manager, bwu_manager); } void PcpManager::DisconnectFromEndpointManager() { diff --git a/cpp/core_v2/internal/pcp_manager.h b/cpp/core_v2/internal/pcp_manager.h index ddeb4107..bb4d9991 100644 --- a/cpp/core_v2/internal/pcp_manager.h +++ b/cpp/core_v2/internal/pcp_manager.h @@ -4,6 +4,7 @@ #include #include "core_v2/internal/base_pcp_handler.h" +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -29,7 +30,7 @@ namespace connections { class PcpManager { public: PcpManager(Mediums& mediums, EndpointChannelManager& channel_manager, - EndpointManager& endpoint_manager); + EndpointManager& endpoint_manager, BwuManager& bwu_manager); ~PcpManager(); Status StartAdvertising(ClientProxy* client, const string& service_id, diff --git a/cpp/core_v2/internal/simulation_user.h b/cpp/core_v2/internal/simulation_user.h index 4674be0d..2b48353c 100644 --- a/cpp/core_v2/internal/simulation_user.h +++ b/cpp/core_v2/internal/simulation_user.h @@ -3,6 +3,7 @@ #include +#include "core_v2/internal/bwu_manager.h" #include "core_v2/internal/client_proxy.h" #include "core_v2/internal/endpoint_channel_manager.h" #include "core_v2/internal/endpoint_manager.h" @@ -130,7 +131,8 @@ class SimulationUser { ClientProxy client_; EndpointChannelManager ecm_; EndpointManager em_{&ecm_}; - PcpManager mgr_{mediums_, ecm_, em_}; + BwuManager bwu_{mediums_, em_, ecm_, {}, {}}; + PcpManager mgr_{mediums_, ecm_, em_, bwu_}; PayloadManager pm_{em_}; }; diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h index 6e0b0a66..94c72f39 100644 --- a/cpp/core_v2/options.h +++ b/cpp/core_v2/options.h @@ -65,6 +65,13 @@ struct MediumSelector { // Feature On/Off switch for mediums. using BooleanMediumSelector = MediumSelector; +// Represents the various power levels that can be used, on mediums that support +// it. +enum class PowerLevel { + kHighPower = 0, + kLowPower = 1, +}; + // Connection Options: used for both Advertising and Discovery. // All fields are mutable, to make the type copy-assignable. struct ConnectionOptions { @@ -72,6 +79,8 @@ struct ConnectionOptions { BooleanMediumSelector allowed{BooleanMediumSelector().SetAll(true)}; bool auto_upgrade_bandwidth; bool enforce_topology_constraints; + bool low_power; + bool enable_bluetooth_listening; ByteArray remote_bluetooth_mac_address; std::string fast_advertisement_service_uuid; // Verify if ConnectionOptions is in a not-initialized (Empty) state. diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index 2e9bcb30..1ee1d0df 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -61,7 +61,6 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", - "//platform_v2/base:__pkg__", ], deps = [ "//absl/base", diff --git a/cpp/platform_v2/api/ble.h b/cpp/platform_v2/api/ble.h index 548aeb45..608574cf 100644 --- a/cpp/platform_v2/api/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -75,6 +75,7 @@ class BleMedium { // Returns true once the BLE scan has been initiated. virtual bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) = 0; // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/api/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h index 6dddd606..5b334830 100644 --- a/cpp/platform_v2/api/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -136,7 +136,7 @@ class BluetoothClassicMedium { virtual std::unique_ptr ListenForService( const std::string& service_name, const std::string& service_uuid) = 0; - virtual BluetoothDevice* FindRemoteDevice(const std::string& mac_address) = 0; + virtual BluetoothDevice* GetRemoteDevice(const std::string& mac_address) = 0; }; } // namespace api diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index 2b5ca406..eee8bfad 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -42,7 +42,7 @@ class ImplementationPlatform { // - synchronization primitives: // - mutex (regular, and recursive) // - condition variable (must work with regular mutex only) - // - Future : to synchronize on Callable schduled to execute. + // - Future : to synchronize on Callable scheduled to execute. // - CountDownLatch : to ensure at least N threads are waiting. // - file I/O // - Logging @@ -58,8 +58,7 @@ class ImplementationPlatform { // 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 CreateAtomicUint32(std::uint32_t value); static std::unique_ptr CreateCountDownLatch( std::int32_t count); diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 21d72fd7..daa06267 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -340,10 +340,12 @@ void MediumEnvironment::UpdateBleMediumForAdvertising( void MediumEnvironment::UpdateBleMediumForScanning( api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, BleDiscoveredPeripheralCallback callback, bool enabled) { if (!enabled_) return; RunOnMediumEnvironmentThread( - [this, &medium, service_id, callback = std::move(callback), enabled]() { + [this, &medium, service_id, fast_advertisement_service_uuid, + callback = std::move(callback), enabled]() { auto item = ble_mediums_.find(&medium); if (item == ble_mediums_.end()) { NEARBY_LOG(INFO, @@ -353,10 +355,12 @@ void MediumEnvironment::UpdateBleMediumForScanning( } auto& context = item->second; context.discovery_callback = std::move(callback); - NEARBY_LOG(INFO, - "Update Ble medium for scanning: this=%p; medium=%p; " - "service_id=%s; enabled=%d ;", - this, &medium, service_id.c_str(), enabled); + NEARBY_LOG( + INFO, + "Update Ble medium for scanning: this=%p; medium=%p; " + "service_id=%s; fast_advertisement_service_uuid=%s; enabled=%d ;", + this, &medium, service_id.c_str(), + fast_advertisement_service_uuid.c_str(), enabled); for (auto& medium_info : ble_mediums_) { auto& local_medium = medium_info.first; auto& info = medium_info.second; diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index 875de81a..826189d1 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -151,10 +151,10 @@ class MediumEnvironment { // This should be called when discoverable state changes. // with user-specified callback when discovery is enabled, and with default // (empty) callback otherwise. - void UpdateBleMediumForScanning(api::BleMedium& medium, - const std::string& service_id, - BleDiscoveredPeripheralCallback callback, - bool enabled); + void UpdateBleMediumForScanning( + api::BleMedium& medium, const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + BleDiscoveredPeripheralCallback callback, bool enabled); // Updates Accepted connection callback info to allow for dispatch of // advertising events. diff --git a/cpp/platform_v2/impl/g3/ble.cc b/cpp/platform_v2/impl/g3/ble.cc index c7bfa041..316d64fc 100644 --- a/cpp/platform_v2/impl/g3/ble.cc +++ b/cpp/platform_v2/impl/g3/ble.cc @@ -252,11 +252,15 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return true; } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id; auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, std::move(callback), true); + env.UpdateBleMediumForScanning(*this, service_id, + fast_advertisement_service_uuid, + std::move(callback), true); { absl::MutexLock lock(&mutex_); scanning_info_.service_id = service_id; @@ -277,7 +281,7 @@ bool BleMedium::StopScanning(const std::string& service_id) { } auto& env = MediumEnvironment::Instance(); - env.UpdateBleMediumForScanning(*this, service_id, {}, false); + env.UpdateBleMediumForScanning(*this, service_id, {}, {}, false); return true; } diff --git a/cpp/platform_v2/impl/g3/ble.h b/cpp/platform_v2/impl/g3/ble.h index 6bdacb09..9822200d 100644 --- a/cpp/platform_v2/impl/g3/ble.h +++ b/cpp/platform_v2/impl/g3/ble.h @@ -146,6 +146,7 @@ class BleMedium : public api::BleMedium { // Returns true once the Ble scanning has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback) override ABSL_LOCKS_EXCLUDED(mutex_); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc index a0c040d3..36403954 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -240,7 +240,7 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name, return socket; } -api::BluetoothDevice* BluetoothClassicMedium::FindRemoteDevice( +api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice( const std::string& mac_address) { auto& env = MediumEnvironment::Instance(); return env.FindBluetoothDevice(mac_address); diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h index 8d199863..0aa92c53 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_classic.h +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -207,7 +207,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { const std::string& service_name, const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); - api::BluetoothDevice* FindRemoteDevice( + api::BluetoothDevice* GetRemoteDevice( const std::string& mac_address) override; private: diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index 43a81d1b..db8ab6b5 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -17,8 +17,10 @@ bool BleMedium::StopAdvertising(const std::string& service_id) { return impl_->StopAdvertising(service_id); } -bool BleMedium::StartScanning(const std::string& service_id, - DiscoveredPeripheralCallback callback) { +bool BleMedium::StartScanning( + const std::string& service_id, + const std::string& fast_advertisement_service_uuid, + DiscoveredPeripheralCallback callback) { { MutexLock lock(&mutex_); discovered_peripheral_callback_ = std::move(callback); @@ -26,6 +28,7 @@ bool BleMedium::StartScanning(const std::string& service_id, } return impl_->StartScanning( service_id, + fast_advertisement_service_uuid, { .peripheral_discovered_cb = [this](api::BlePeripheral& peripheral, diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index 948903af..ca9bedbb 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -106,6 +106,7 @@ class BleMedium final { // Returns true once the BLE scan has been initiated. bool StartScanning(const std::string& service_id, + const std::string& fast_advertisement_service_uuid, DiscoveredPeripheralCallback callback); // Returns true once BLE scanning for service_id is well and truly stopped; diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index 2af0c3de..41f6d091 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -15,7 +15,7 @@ namespace { constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"}; constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"}; -constexpr absl::string_view kFastAdvertisementServiceUuid{"\xff\xfe"}; +constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"}; class BleMediumTest : public ::testing::Test { protected: @@ -59,6 +59,7 @@ TEST_F(BleMediumTest, CanStartAdvertising) { EXPECT_TRUE(ble_b.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -85,6 +86,7 @@ TEST_F(BleMediumTest, CanStartScanning) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -119,6 +121,7 @@ TEST_F(BleMediumTest, CanStopDiscovery) { ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( @@ -154,6 +157,7 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral* discovered_peripheral = nullptr; ble_a.StartScanning( service_id, + fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h index d8bf989d..8d073f6b 100644 --- a/cpp/platform_v2/public/bluetooth_classic.h +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -188,8 +188,8 @@ class BluetoothClassicMedium final { api::BluetoothClassicMedium& GetImpl() { return *impl_; } BluetoothAdapter& GetAdapter() { return adapter_; } std::string GetMacAddress() const { return adapter_.GetMacAddress(); } - BluetoothDevice FindRemoteDevice(const std::string& mac_address) { - return BluetoothDevice(impl_->FindRemoteDevice(mac_address)); + BluetoothDevice GetRemoteDevice(const std::string& mac_address) { + return BluetoothDevice(impl_->GetRemoteDevice(mac_address)); } private: diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index c7169827..9cd1728d 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -81,8 +81,19 @@ message ConnectionResponseFrame { // // - ConnectionsStatusCodes.STATUS_OK // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. - optional int32 status = 1; + optional int32 status = 1 [deprecated = true]; optional bytes handshake_data = 2; + + // Used to replace the status integer parameter with a meaningful enum item. + // Map ConnectionsStatusCodes.STATUS_OK to ACCEPT and + // ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED to REJECT. + // Flag: connection_replace_status_with_response_connectionResponseFrame + enum ResponseStatus { + UNKNOWN_RESPONSE_STATUS = 0; + ACCEPT = 1; + REJECT = 2; + } + optional ResponseStatus response = 3; } message PayloadTransferFrame { diff --git a/proto/discovery_enums.proto b/proto/discovery_enums.proto index a3ca8c3e..cc095756 100644 --- a/proto/discovery_enums.proto +++ b/proto/discovery_enums.proto @@ -11,7 +11,7 @@ option java_package = "com.google.location.nearby.proto"; option java_outer_classname = "DiscoveryEnums"; option go_api_flag = "OPEN_TO_OPAQUE_HYBRID"; // See http://go/go-api-flag. -// NEXT ID: 132 +// NEXT ID: 133 enum DiscoveryEvent { UNKNOWN_DISCOVERY_EVENT = 0; @@ -395,6 +395,10 @@ enum DiscoveryEvent { // User has seen a low battery notification. FAST_PAIR_LOW_BATTERY_NOTIFICATION_SHOWN = 131; + // Connection Tracker Manager (Baymax) recovered the connection of the + // companion app. + FAST_PAIR_CONNECTION_TRACKER_RECOVER_COMPANION_APP = 132; + // Deprecated. reserved 65, 67 to 72; } diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto index 9296a49b..6ea1f745 100644 --- a/proto/error_code_enums.proto +++ b/proto/error_code_enums.proto @@ -408,4 +408,9 @@ enum Description { SOCKET_NOT_BOUND = 141; INVALID_REMOTE_ADDRESS = 142; SOCKET_ALREADY_BOUND = 143; + HOTSPOT_NOT_STARTED = 144; + WEBRTC_ALREADY_INITIALIZED = 145; + INVALID_WEBRTC_STATE = 146; + NULL_DATA_CHANNEL = 147; + CREATE_OFFER_FAILED = 148; } From fb9ea31ec00c5cf123380fe5667bffa299f9883e Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 1 Oct 2020 02:27:28 -0700 Subject: [PATCH 49/52] Add .mm support Signed-off-by: Alexey Polyudov Change-Id: I2cc42ef9d05dfbbac6bb252f769682a43ab4c501 --- script/oss.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/oss.py b/script/oss.py index 4b621b0f..21a70028 100755 --- a/script/oss.py +++ b/script/oss.py @@ -96,7 +96,7 @@ def copy_files_to_oss_project(src_root, dst_root): def detect_file_copy_header_options(fname, lines): if not lines: return None # ignore empty file - suffixes = [".cc", ".cpp", ".cxx", ".c", ".h", ".hpp", ".inc", ".proto"] + suffixes = [".cc", ".cpp", ".cxx", ".c", ".h", ".hpp", ".inc", ".mm", ".proto"] for suffix in suffixes: if fname.endswith(suffix): return ("//", 0) From bcc3f21da2e8fecc1225b236c7bec80e1b821a7e Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 1 Oct 2020 02:31:10 -0700 Subject: [PATCH 50/52] Roll forward to cl/334770381 Signed-off-by: Alexey Polyudov Change-Id: I22b685b17c37357d6281cedfa7c228e304de8835 --- cpp/core_v2/internal/bwu_manager.cc | 8 +- cpp/core_v2/internal/endpoint_manager.cc | 3 + cpp/core_v2/internal/offline_frames.cc | 11 ++ .../p2p_point_to_point_pcp_handler.cc | 9 ++ cpp/core_v2/internal/p2p_star_pcp_handler.cc | 9 ++ cpp/platform_v2/impl/ios/BUILD | 56 ++++++++ cpp/platform_v2/impl/ios/atomic_boolean.h | 28 ++++ cpp/platform_v2/impl/ios/atomic_reference.h | 33 +++++ cpp/platform_v2/impl/ios/condition_variable.h | 37 ++++++ cpp/platform_v2/impl/ios/count_down_latch.h | 55 ++++++++ cpp/platform_v2/impl/ios/log_message.h | 28 ++++ cpp/platform_v2/impl/ios/log_message.mm | 56 ++++++++ .../impl/ios/multi_thread_executor.h | 57 ++++++++ cpp/platform_v2/impl/ios/mutex.h | 47 +++++++ cpp/platform_v2/impl/ios/platform.mm | 124 ++++++++++++++++++ cpp/platform_v2/impl/ios/scheduled_executor.h | 43 ++++++ .../impl/ios/scheduled_executor.mm | 65 +++++++++ .../impl/ios/single_thread_executor.h | 20 +++ proto/connections/offline_wire_formats.proto | 27 ++++ proto/connections_enums.proto | 10 +- 20 files changed, 718 insertions(+), 8 deletions(-) create mode 100644 cpp/platform_v2/impl/ios/BUILD create mode 100644 cpp/platform_v2/impl/ios/atomic_boolean.h create mode 100644 cpp/platform_v2/impl/ios/atomic_reference.h create mode 100644 cpp/platform_v2/impl/ios/condition_variable.h create mode 100644 cpp/platform_v2/impl/ios/count_down_latch.h create mode 100644 cpp/platform_v2/impl/ios/log_message.h create mode 100644 cpp/platform_v2/impl/ios/log_message.mm create mode 100644 cpp/platform_v2/impl/ios/multi_thread_executor.h create mode 100644 cpp/platform_v2/impl/ios/mutex.h create mode 100644 cpp/platform_v2/impl/ios/platform.mm create mode 100644 cpp/platform_v2/impl/ios/scheduled_executor.h create mode 100644 cpp/platform_v2/impl/ios/scheduled_executor.mm create mode 100644 cpp/platform_v2/impl/ios/single_thread_executor.h diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index 9ab910fe..ab81e570 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -30,11 +30,11 @@ BwuManager::BwuManager( if (config_.bandwidth_upgrade_retry_delay == absl::ZeroDuration()) { config_.bandwidth_upgrade_retry_delay = absl::Seconds(5); } - if (config_.bandwidth_upgrade_retry_delay == absl::ZeroDuration()) { - config_.bandwidth_upgrade_retry_delay = absl::Seconds(10); + if (config_.bandwidth_upgrade_retry_max_delay == absl::ZeroDuration()) { + config_.bandwidth_upgrade_retry_max_delay = absl::Seconds(10); } if (config_.allow_upgrade_to.All(false)) { - config.allow_upgrade_to.web_rtc = true; + config_.allow_upgrade_to.web_rtc = true; } if (!handlers.empty()) { handlers_ = std::move(handlers); @@ -43,7 +43,7 @@ BwuManager::BwuManager( } // Register the offline frame processor. - endpoint_manager.RegisterFrameProcessor( + endpoint_manager_->RegisterFrameProcessor( V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION, this); } diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 4e3e5d7a..04bf5890 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -136,6 +136,9 @@ ExceptionOr EndpointManager::HandleData( // no explicit handler. if (frame_type == V1Frame::KEEP_ALIVE) { NEARBY_LOG(INFO, "KeepAlive message for: id=%s", endpoint_id.c_str()); + } else if (frame_type == V1Frame::DISCONNECTION) { + NEARBY_LOG(INFO, "Disconnect message for: id=%s", endpoint_id.c_str()); + endpoint_channel->Close(); } else { NEARBY_LOG(ERROR, "Unhandled message: id=%s, type=%d", endpoint_id.c_str(), frame_type); diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index a046486d..9c7f6314 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -254,6 +254,17 @@ ByteArray ForKeepAlive() { return ToBytes(std::move(frame)); } +ByteArray ForDisconnection() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::DISCONNECTION); + v1_frame->mutable_disconnection(); + + return ToBytes(std::move(frame)); +} + UpgradePathInfo::Medium MediumToUpgradePathInfoMedium(Medium medium) { switch (medium) { case Medium::MDNS: 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 index 0b09d8bd..64e05d4a 100644 --- a/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_point_to_point_pcp_handler.cc @@ -13,9 +13,18 @@ P2pPointToPointPcpHandler::P2pPointToPointPcpHandler( std::vector P2pPointToPointPcpHandler::GetConnectionMediumsByPriority() { std::vector mediums; + if (mediums_->GetWifiLan().IsAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } + if (mediums_->GetWebRtc().IsAvailable()) { + mediums.push_back(proto::connections::WEB_RTC); + } if (mediums_->GetBluetoothClassic().IsAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } + if (mediums_->GetBle().IsAvailable()) { + mediums.push_back(proto::connections::BLE); + } return mediums; } diff --git a/cpp/core_v2/internal/p2p_star_pcp_handler.cc b/cpp/core_v2/internal/p2p_star_pcp_handler.cc index 45a20d14..80e773cd 100644 --- a/cpp/core_v2/internal/p2p_star_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_star_pcp_handler.cc @@ -16,9 +16,18 @@ P2pStarPcpHandler::P2pStarPcpHandler(Mediums& mediums, std::vector P2pStarPcpHandler::GetConnectionMediumsByPriority() { std::vector mediums; + if (mediums_->GetWifiLan().IsAvailable()) { + mediums.push_back(proto::connections::WIFI_LAN); + } + if (mediums_->GetWebRtc().IsAvailable()) { + mediums.push_back(proto::connections::WEB_RTC); + } if (mediums_->GetBluetoothClassic().IsAvailable()) { mediums.push_back(proto::connections::BLUETOOTH); } + if (mediums_->GetBle().IsAvailable()) { + mediums.push_back(proto::connections::BLE); + } return mediums; } diff --git a/cpp/platform_v2/impl/ios/BUILD b/cpp/platform_v2/impl/ios/BUILD new file mode 100644 index 00000000..fa133022 --- /dev/null +++ b/cpp/platform_v2/impl/ios/BUILD @@ -0,0 +1,56 @@ +objc_library( + name = "types", + srcs = [ + "log_message.mm", + "scheduled_executor.mm", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "condition_variable.h", + "count_down_latch.h", + "log_message.h", + "multi_thread_executor.h", + "mutex.h", + "scheduled_executor.h", + "single_thread_executor.h", + ], + visibility = [ + "//platform_v2/impl/ios:__pkg__", + ], + deps = [ + "//base", + "//platform_v2/api:platform", + "//platform_v2/api:types", + "//platform_v2/base", + "//platform_v2/base:util", + "//platform_v2/impl/shared:posix_mutex", + "//absl/base:core_headers", + "//absl/synchronization", + "//absl/time", + "//thread", + ], +) + +objc_library( + name = "ios", + srcs = [ + "platform.mm", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":types", + "//platform_v2/api:comm", + "//platform_v2/api:platform", + "//platform_v2/api:types", + "//platform_v2/impl/shared:file", + "//absl/base:core_headers", + "//absl/memory", + "//absl/strings", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/impl/ios/atomic_boolean.h b/cpp/platform_v2/impl/ios/atomic_boolean.h new file mode 100644 index 00000000..37a1d1f1 --- /dev/null +++ b/cpp/platform_v2/impl/ios/atomic_boolean.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace ios { + +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/ios/atomic_reference.h b/cpp/platform_v2/impl/ios/atomic_reference.h new file mode 100644 index 00000000..49bb2849 --- /dev/null +++ b/cpp/platform_v2/impl/ios/atomic_reference.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ + +#include +#include + +#include "platform_v2/api/atomic_reference.h" + +namespace location { +namespace nearby { +namespace ios { + +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 ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/impl/ios/condition_variable.h b/cpp/platform_v2/impl/ios/condition_variable.h new file mode 100644 index 00000000..4df6893a --- /dev/null +++ b/cpp/platform_v2/impl/ios/condition_variable.h @@ -0,0 +1,37 @@ +#ifndef PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/impl/ios/mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(ios::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + Exception Wait(absl::Duration timeout) override { + cond_var_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; + } + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/ios/count_down_latch.h b/cpp/platform_v2/impl/ios/count_down_latch.h new file mode 100644 index 00000000..a06bcc45 --- /dev/null +++ b/cpp/platform_v2/impl/ios/count_down_latch.h @@ -0,0 +1,55 @@ +#ifndef PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ + +#include "platform_v2/api/count_down_latch.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace ios { + +class CountDownLatch final : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + CountDownLatch(CountDownLatch&&) = delete; + CountDownLatch& operator=(CountDownLatch&&) = delete; + ExceptionOr Await(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + absl::Time deadline = absl::Now() + timeout; + while (count_ > 0) { + if (cond_.WaitWithDeadline(&mutex_, deadline)) { + return ExceptionOr(false); + } + } + return ExceptionOr(true); + } + Exception Await() override { + absl::MutexLock lock(&mutex_); + while (count_ > 0) { + cond_.Wait(&mutex_); + } + return {Exception::kSuccess}; + } + void CountDown() override { + absl::MutexLock lock(&mutex_); + if (count_ > 0 && --count_ == 0) { + cond_.SignalAll(); + } + } + + private: + absl::Mutex mutex_; // Mutex to be used with cond_.Wait...() method family. + absl::CondVar cond_; // Condition to synchronize up to N waiting threads. + int count_ + ABSL_GUARDED_BY(mutex_); // When zero, latch should release all waiters. +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.h b/cpp/platform_v2/impl/ios/log_message.h new file mode 100644 index 00000000..dd0a0c2a --- /dev/null +++ b/cpp/platform_v2/impl/ios/log_message.h @@ -0,0 +1,28 @@ +#ifndef PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ +#define PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ + +#include "base/logging.h" +#include "platform_v2/api/log_message.h" + +namespace location { +namespace nearby { +namespace ios { + +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 ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.mm b/cpp/platform_v2/impl/ios/log_message.mm new file mode 100644 index 00000000..0e6ac13c --- /dev/null +++ b/cpp/platform_v2/impl/ios/log_message.mm @@ -0,0 +1,56 @@ +#include "platform_v2/impl/ios/log_message.h" + +#include + +#include "base/stringprintf.h" + +namespace location { +namespace nearby { +namespace ios { + +api::LogMessage::Severity kMinLogSeverity = 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 ios + +namespace api { + +void LogMessage::SetMinLogSeverity(Severity severity) { + ios::kMinLogSeverity = severity; +} + +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + return severity >= ios::kMinLogSeverity; +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/multi_thread_executor.h b/cpp/platform_v2/impl/ios/multi_thread_executor.h new file mode 100644 index 00000000..e665df62 --- /dev/null +++ b/cpp/platform_v2/impl/ios/multi_thread_executor.h @@ -0,0 +1,57 @@ +#ifndef PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ + +#include + +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/impl/ios/count_down_latch.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace ios { + +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_parallelism) + : thread_pool_(max_parallelism) { + thread_pool_.StartWorkers(); + } + void Execute(Runnable&& runnable) override { + if (!shutdown_) { + thread_pool_.Schedule(std::move(runnable)); + } + } + bool DoSubmit(Runnable&& runnable) override { + if (shutdown_) return false; + thread_pool_.Schedule(std::move(runnable)); + return true; + } + void Shutdown() override { DoShutdown(); } + ~MultiThreadExecutor() override { DoShutdown(); } + + int GetTid(int index) const override { + const auto* thread = thread_pool_.thread(index); + return thread ? *(int*)(thread->tid()) : 0; + } + + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { + if (shutdown_) return; + thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); + } + bool InShutdown() const { return shutdown_; } + + private: + void DoShutdown() { + shutdown_ = true; + } + std::atomic_bool shutdown_ = false; + ThreadPool thread_pool_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/mutex.h b/cpp/platform_v2/impl/ios/mutex.h new file mode 100644 index 00000000..2986869f --- /dev/null +++ b/cpp/platform_v2/impl/ios/mutex.h @@ -0,0 +1,47 @@ +#ifndef PLATFORM_V2_IMPL_IOS_MUTEX_H_ +#define PLATFORM_V2_IMPL_IOS_MUTEX_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/impl/shared/posix_mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(bool check) : check_(check) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + if (!check_) mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + bool check_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex { + public: + ~RecursiveMutex() override = default; + RecursiveMutex() = default; + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_MUTEX_H_ diff --git a/cpp/platform_v2/impl/ios/platform.mm b/cpp/platform_v2/impl/ios/platform.mm new file mode 100644 index 00000000..dabd01d4 --- /dev/null +++ b/cpp/platform_v2/impl/ios/platform.mm @@ -0,0 +1,124 @@ +#include "platform_v2/api/platform.h" + +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/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/submittable_executor.h" +#include "platform_v2/impl/ios/atomic_boolean.h" +#include "platform_v2/impl/ios/atomic_reference.h" +#include "platform_v2/impl/ios/condition_variable.h" +#include "platform_v2/impl/ios/count_down_latch.h" +#include "platform_v2/impl/ios/log_message.h" +#include "platform_v2/impl/ios/multi_thread_executor.h" +#include "platform_v2/impl/ios/mutex.h" +#include "platform_v2/impl/ios/scheduled_executor.h" +#include "platform_v2/impl/ios/single_thread_executor.h" +#include "platform_v2/impl/shared/file.h" +#include "absl/memory/memory.h" + +namespace location { +namespace nearby { +namespace api { + +namespace { +std::string GetPayloadPath(PayloadId payload_id) { + return absl::StrCat("/tmp/", payload_id); +} +} // namespace + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean(bool initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicUint32(std::uint32_t value) { + return absl::make_unique(value); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch( + std::int32_t count) { + return absl::make_unique(count); +} + +std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + if (mode == Mutex::Mode::kRecursive) + return absl::make_unique(); + else + return absl::make_unique(mode == Mutex::Mode::kRegular); +} + +std::unique_ptr ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::unique_ptr( + new ios::ConditionVariable(static_cast(mutex))); +} + +std::unique_ptr ImplementationPlatform::CreateInputFile(PayloadId payload_id, + std::int64_t total_size) { + return absl::make_unique(GetPayloadPath(payload_id), total_size); +} + +std::unique_ptr ImplementationPlatform::CreateOutputFile(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::CreateSingleThreadExecutor() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateMultiThreadExecutor( + int max_concurrency) { + return absl::make_unique(max_concurrency); +} + +std::unique_ptr ImplementationPlatform::CreateScheduledExecutor() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateBluetoothAdapter() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateServerSyncMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { + return std::unique_ptr(); +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.h b/cpp/platform_v2/impl/ios/scheduled_executor.h new file mode 100644 index 00000000..6fb08fd7 --- /dev/null +++ b/cpp/platform_v2/impl/ios/scheduled_executor.h @@ -0,0 +1,43 @@ +#ifndef PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/impl/ios/single_thread_executor.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace ios { + +class ScheduledExecutor final : public api::ScheduledExecutor { + public: + ScheduledExecutor() = default; + ~ScheduledExecutor() override { + executor_.Shutdown(); + } + + void Execute(Runnable&& runnable) override { + executor_.Execute(std::move(runnable)); + } + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration delay) override; + void Shutdown() override { executor_.Shutdown(); } + + int GetTid(int index) const override { + return executor_.GetTid(index); + } + private: + SingleThreadExecutor executor_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.mm b/cpp/platform_v2/impl/ios/scheduled_executor.mm new file mode 100644 index 00000000..6d850b06 --- /dev/null +++ b/cpp/platform_v2/impl/ios/scheduled_executor.mm @@ -0,0 +1,65 @@ +#include "platform_v2/impl/ios/scheduled_executor.h" + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace ios { + +namespace { + +class ScheduledCancelable : public api::Cancelable { + public: + bool Cancel() override { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kCanceled)) { + return true; + } + } + return false; + } + bool MarkExecuted() { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kExecuted)) { + return true; + } + } + return false; + } + + private: + enum Status { + kNotRun, + kExecuted, + kCanceled, + }; + std::atomic status_ = kNotRun; +}; + +} // namespace + +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration delay) { + auto scheduled_cancelable = std::make_shared(); + if (executor_.InShutdown()) { + return scheduled_cancelable; + } + executor_.ScheduleAfter( + delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() { + if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) { + runnable(); + } + }); + return scheduled_cancelable; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/ios/single_thread_executor.h b/cpp/platform_v2/impl/ios/single_thread_executor.h new file mode 100644 index 00000000..be5d99e0 --- /dev/null +++ b/cpp/platform_v2/impl/ios/single_thread_executor.h @@ -0,0 +1,20 @@ +#ifndef PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/impl/ios/multi_thread_executor.h" + +namespace location { +namespace nearby { +namespace ios { + +class SingleThreadExecutor final : public MultiThreadExecutor { + public: + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index 9cd1728d..f7c1e6dc 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -207,6 +207,7 @@ message BandwidthUpgradeNegotiationFrame { // Accompanies Medium.WEB_RTC message WebRtcCredentials { optional string peer_id = 1; + optional LocationHint location_hint = 2; } optional Medium medium = 1; @@ -261,3 +262,29 @@ message MediumMetadata { // WiFi Lan BSSID optional string bssid = 2; } + +// LocationHint is used to specify a location as well as format. +message LocationHint { + // Location is the location, provided in the format specified by format. + optional string location = 1; + + // the format of location. + optional LocationStandard.Format format = 2; +} + +// Copy from +// https://source.corp.google.com/piper///depot/google3/media/webrtc/server/tachyon/proto/tachyon_enums.proto;rcl=334271491;l=10242 +// These numbers match must match the original definition. +message LocationStandard { + enum Format { + UNKNOWN = 0; + // E164 country codes: + // https://en.wikipedia.org/wiki/List_of_country_calling_codes + // e.g. +1 for USA + E164_CALLING = 1; + + // ISO 3166-1 alpha-2 country codes: + // https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + ISO_3166_1_ALPHA_2 = 2; + } +} diff --git a/proto/connections_enums.proto b/proto/connections_enums.proto index 3fd7dc8b..c9259767 100644 --- a/proto/connections_enums.proto +++ b/proto/connections_enums.proto @@ -162,7 +162,7 @@ enum PayloadStatus { REMOTE_CANCELLATION = 8; } -// next_id: 17 +// next_id: 18 // Result of an upgrade attempt. enum BandwidthUpgradeResult { UNKNOWN_BANDWIDTH_UPGRADE_RESULT = 0; @@ -191,9 +191,6 @@ enum BandwidthUpgradeResult { // record analytics (e.g. the client disconnected). UNFINISHED_ERROR = 10; - // TODO(b/151833661): add a REMOTE_ERROR when we implement a cancellation - // message, for the case when the remote endpoint had an error on their end. - // Error during setting up Bluetooth. BLUETOOTH_MEDIUM_ERROR = 11; @@ -211,6 +208,9 @@ enum BandwidthUpgradeResult { // Error during setting up WebRTC. WEB_RTC_MEDIUM_ERROR = 16; + + // When the remote endpoint had an error on their end. + RESULT_REMOTE_ERROR = 17; } // next_id: 35 @@ -232,6 +232,8 @@ enum BandwidthUpgradeErrorStage { UPGRADE_UNFINISHED = 7; // Upgrade successfully UPGRADE_SUCCESS = 8; + // Upgrade cancel + UPGRADE_CANCEL = 9; // Medium-specific stages. // TODO(xlythe) Make sure each stage maps to one, and only one, possible From db1248d771d41b2dc4bc74f68f7b1464a4cf884f Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 1 Oct 2020 02:48:58 -0700 Subject: [PATCH 51/52] OSS fixes Signed-off-by: Alexey Polyudov Change-Id: I76049a5776a3d3603e9d2415d3242199bc136a22 --- cpp/platform_v2/impl/ios/BUILD | 14 ++++++++++++++ cpp/platform_v2/impl/ios/atomic_boolean.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/atomic_reference.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/condition_variable.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/count_down_latch.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/log_message.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/log_message.mm | 14 ++++++++++++++ cpp/platform_v2/impl/ios/multi_thread_executor.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/mutex.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/platform.mm | 14 ++++++++++++++ cpp/platform_v2/impl/ios/scheduled_executor.h | 14 ++++++++++++++ cpp/platform_v2/impl/ios/scheduled_executor.mm | 14 ++++++++++++++ cpp/platform_v2/impl/ios/single_thread_executor.h | 14 ++++++++++++++ proto/connections/offline_wire_formats.proto | 3 --- 14 files changed, 182 insertions(+), 3 deletions(-) diff --git a/cpp/platform_v2/impl/ios/BUILD b/cpp/platform_v2/impl/ios/BUILD index fa133022..fe757ec0 100644 --- a/cpp/platform_v2/impl/ios/BUILD +++ b/cpp/platform_v2/impl/ios/BUILD @@ -1,3 +1,17 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + objc_library( name = "types", srcs = [ diff --git a/cpp/platform_v2/impl/ios/atomic_boolean.h b/cpp/platform_v2/impl/ios/atomic_boolean.h index 37a1d1f1..af2de7a7 100644 --- a/cpp/platform_v2/impl/ios/atomic_boolean.h +++ b/cpp/platform_v2/impl/ios/atomic_boolean.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ #define PLATFORM_V2_IMPL_IOS_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/ios/atomic_reference.h b/cpp/platform_v2/impl/ios/atomic_reference.h index 49bb2849..84728d06 100644 --- a/cpp/platform_v2/impl/ios/atomic_reference.h +++ b/cpp/platform_v2/impl/ios/atomic_reference.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ #define PLATFORM_V2_IMPL_IOS_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/impl/ios/condition_variable.h b/cpp/platform_v2/impl/ios/condition_variable.h index 4df6893a..fa571412 100644 --- a/cpp/platform_v2/impl/ios/condition_variable.h +++ b/cpp/platform_v2/impl/ios/condition_variable.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ #define PLATFORM_V2_IMPL_IOS_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/ios/count_down_latch.h b/cpp/platform_v2/impl/ios/count_down_latch.h index a06bcc45..4daef669 100644 --- a/cpp/platform_v2/impl/ios/count_down_latch.h +++ b/cpp/platform_v2/impl/ios/count_down_latch.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ #define PLATFORM_V2_IMPL_IOS_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.h b/cpp/platform_v2/impl/ios/log_message.h index dd0a0c2a..63347dff 100644 --- a/cpp/platform_v2/impl/ios/log_message.h +++ b/cpp/platform_v2/impl/ios/log_message.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ #define PLATFORM_V2_IMPL_IOS_LOG_MESSAGE_H_ diff --git a/cpp/platform_v2/impl/ios/log_message.mm b/cpp/platform_v2/impl/ios/log_message.mm index 0e6ac13c..4c8a0bf8 100644 --- a/cpp/platform_v2/impl/ios/log_message.mm +++ b/cpp/platform_v2/impl/ios/log_message.mm @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/impl/ios/log_message.h" #include diff --git a/cpp/platform_v2/impl/ios/multi_thread_executor.h b/cpp/platform_v2/impl/ios/multi_thread_executor.h index e665df62..6fba2484 100644 --- a/cpp/platform_v2/impl/ios/multi_thread_executor.h +++ b/cpp/platform_v2/impl/ios/multi_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ #define PLATFORM_V2_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/mutex.h b/cpp/platform_v2/impl/ios/mutex.h index 2986869f..a409c702 100644 --- a/cpp/platform_v2/impl/ios/mutex.h +++ b/cpp/platform_v2/impl/ios/mutex.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_MUTEX_H_ #define PLATFORM_V2_IMPL_IOS_MUTEX_H_ diff --git a/cpp/platform_v2/impl/ios/platform.mm b/cpp/platform_v2/impl/ios/platform.mm index dabd01d4..6fa3d9f5 100644 --- a/cpp/platform_v2/impl/ios/platform.mm +++ b/cpp/platform_v2/impl/ios/platform.mm @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/api/platform.h" #include diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.h b/cpp/platform_v2/impl/ios/scheduled_executor.h index 6fb08fd7..1a62f3b7 100644 --- a/cpp/platform_v2/impl/ios/scheduled_executor.h +++ b/cpp/platform_v2/impl/ios/scheduled_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ #define PLATFORM_V2_IMPL_IOS_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/ios/scheduled_executor.mm b/cpp/platform_v2/impl/ios/scheduled_executor.mm index 6d850b06..a1d2f81d 100644 --- a/cpp/platform_v2/impl/ios/scheduled_executor.mm +++ b/cpp/platform_v2/impl/ios/scheduled_executor.mm @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include "platform_v2/impl/ios/scheduled_executor.h" #include diff --git a/cpp/platform_v2/impl/ios/single_thread_executor.h b/cpp/platform_v2/impl/ios/single_thread_executor.h index be5d99e0..810f609d 100644 --- a/cpp/platform_v2/impl/ios/single_thread_executor.h +++ b/cpp/platform_v2/impl/ios/single_thread_executor.h @@ -1,3 +1,17 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ #define PLATFORM_V2_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index c0806f85..49be9e8c 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -286,9 +286,6 @@ message LocationHint { optional LocationStandard.Format format = 2; } -// Copy from -// https://source.corp.google.com/piper///depot/google3/media/webrtc/server/tachyon/proto/tachyon_enums.proto;rcl=334271491;l=10242 -// These numbers match must match the original definition. message LocationStandard { enum Format { UNKNOWN = 0; From 0738d06b0a942d6be2f0732cab22fe428429fbfe Mon Sep 17 00:00:00 2001 From: hai007 Date: Fri, 9 Oct 2020 14:14:41 -0700 Subject: [PATCH 52/52] Roll forward to cl/336363104 Signed-off-by: hai007 --- cpp/core_v2/internal/base_pcp_handler.cc | 70 +++--- cpp/core_v2/internal/base_pcp_handler.h | 16 +- cpp/core_v2/internal/ble_advertisement.cc | 88 ++++++-- cpp/core_v2/internal/ble_advertisement.h | 35 ++- .../internal/ble_advertisement_test.cc | 205 +++++++++++++----- cpp/core_v2/internal/bluetooth_device_name.cc | 62 +++++- cpp/core_v2/internal/bluetooth_device_name.h | 17 +- .../internal/bluetooth_device_name_test.cc | 95 ++++++-- cpp/core_v2/internal/bwu_manager.cc | 10 +- cpp/core_v2/internal/bwu_manager.h | 4 +- cpp/core_v2/internal/mediums/BUILD | 3 + cpp/core_v2/internal/mediums/ble.cc | 67 +++++- cpp/core_v2/internal/mediums/ble.h | 12 +- cpp/core_v2/internal/mediums/ble_test.cc | 12 +- .../mediums/ble_v2/ble_advertisement.cc | 12 +- .../mediums/ble_v2/ble_advertisement.h | 2 - .../mediums/ble_v2/ble_advertisement_test.cc | 12 + .../mediums/webrtc/connection_flow.cc | 9 + cpp/core_v2/internal/offline_frames.cc | 8 + cpp/core_v2/internal/offline_frames_test.cc | 5 +- .../internal/p2p_cluster_pcp_handler.cc | 30 ++- .../internal/p2p_cluster_pcp_handler.h | 1 + cpp/core_v2/internal/wifi_lan_service_info.cc | 64 ++++-- cpp/core_v2/internal/wifi_lan_service_info.h | 26 +-- .../internal/wifi_lan_service_info_test.cc | 75 +++++-- cpp/platform_v2/public/ble.cc | 4 +- cpp/platform_v2/public/ble.h | 7 +- cpp/platform_v2/public/ble_test.cc | 16 +- cpp/platform_v2/public/future.h | 3 + cpp/platform_v2/public/settable_future.h | 10 +- script/oss.py | 2 + 31 files changed, 728 insertions(+), 254 deletions(-) diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index 12fdb676..b2eb73f9 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -321,19 +321,19 @@ Status BasePcpHandler::RequestConnection(ClientProxy* client, OnEndpointFound(client, webrtc_endpoint); } - auto discovered_endpoints = GetDiscoveredEndpoints(endpoint_id); - std::unique_ptr channel; - ConnectImplResult connect_impl_result; - auto remote_bluetooth_mac_address = BluetoothUtils::ToString(options.remote_bluetooth_mac_address); if (!remote_bluetooth_mac_address.empty()) { - auto additional_endpoint = GetRemoteBluetoothMacAddressEndpoint( - endpoint_id, remote_bluetooth_mac_address, discovered_endpoints); - if (additional_endpoint != nullptr) - discovered_endpoints.push_back(additional_endpoint.get()); + if (AddRemoteBluetoothMacAddressEndpoint(endpoint_id, + remote_bluetooth_mac_address)) + NEARBY_LOGS(INFO) << "Appended remote Bluetooth MAC Address endpoint " + << "[" << remote_bluetooth_mac_address << "]"; } + auto discovered_endpoints = GetDiscoveredEndpoints(endpoint_id); + std::unique_ptr channel; + ConnectImplResult connect_impl_result; + for (auto connect_endpoint : discovered_endpoints) { connect_impl_result = ConnectImpl(client, connect_endpoint); if (connect_impl_result.status.Ok()) { @@ -637,7 +637,17 @@ void BasePcpHandler::OnIncomingFrame(OfflineFrame& frame, const ConnectionResponseFrame& connection_response = frame.v1().connection_response(); - if (connection_response.status() == Status::kSuccess) { + // For backward compatible, here still check both status and + // response parameters until the response feature is roll out in all + // supported devices. + bool accepted = false; + if (connection_response.has_response()) { + accepted = + connection_response.response() == ConnectionResponseFrame::ACCEPT; + } else { + accepted = connection_response.status() == Status::kSuccess; + } + if (accepted) { NEARBY_LOG(INFO, "OnConnectionResponse: remote accepted; id=%s", endpoint_id.c_str()); client->RemoteEndpointAcceptedConnection(endpoint_id); @@ -978,27 +988,28 @@ proto::connections::Medium BasePcpHandler::ChooseBestUpgradeMedium( return proto::connections::Medium::UNKNOWN_MEDIUM; } -std::unique_ptr -BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( - std::string endpoint_id, std::string remote_bluetooth_mac_address, - std::vector endpoints) { +bool BasePcpHandler::AddRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address) { if (!discovery_options_.allowed.bluetooth) { - return nullptr; + return false; } + auto endpoints = GetDiscoveredEndpoints(endpoint_id); if (endpoints.empty()) { - NEARBY_LOGS(INFO) - << "Cannot append remote Bluetooth MAC Address, because endpointId " - << endpoint_id << " has not been discovered"; - return nullptr; + NEARBY_LOGS(INFO) << "Cannot append remote Bluetooth MAC Address endpoint, " + "because endpointId " + << endpoint_id << " has not been discovered " + << "[" << remote_bluetooth_mac_address << "]"; + return false; } for (auto endpoint : endpoints) { if (endpoint->medium == proto::connections::Medium::BLUETOOTH) { NEARBY_LOGS(INFO) - << "Cannot append remote Bluetooth MAC Address, because the " - "endpoint has already been found over Bluetooth."; - return nullptr; + << "Cannot append remote Bluetooth MAC Address endpoint, because the " + "endpoint has already been found over Bluetooth " + << "[" << remote_bluetooth_mac_address << "]"; + return false; } } @@ -1006,14 +1017,15 @@ BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( mediums_->GetBluetoothClassic().GetRemoteDevice( remote_bluetooth_mac_address); if (!remote_bluetooth_device.IsValid()) { - NEARBY_LOGS(INFO) - << "Cannot append remote Bluetooth MAC Address, because a valid " - "Bluetooth device could not be derived."; - return nullptr; + NEARBY_LOGS(INFO) << "Cannot append remote Bluetooth MAC Address endpoint, " + "because a valid " + "Bluetooth device could not be derived " + << "[" << remote_bluetooth_mac_address << "]"; + return false; } auto bluetooth_endpoint = - std::make_unique(BluetoothEndpoint{ + std::make_shared(BluetoothEndpoint{ { endpoint_id, endpoints[0]->endpoint_info, @@ -1022,9 +1034,9 @@ BasePcpHandler::GetRemoteBluetoothMacAddressEndpoint( }, remote_bluetooth_device, }); - NEARBY_LOGS(INFO) << "Appended remote Bluetooth device " - << remote_bluetooth_mac_address; - return bluetooth_endpoint; + + discovered_endpoints_.emplace(endpoint_id, std::move(bluetooth_endpoint)); + return true; } void BasePcpHandler::EvaluateConnectionResult(ClientProxy* client, diff --git a/cpp/core_v2/internal/base_pcp_handler.h b/cpp/core_v2/internal/base_pcp_handler.h index 262d92cb..7bc24478 100644 --- a/cpp/core_v2/internal/base_pcp_handler.h +++ b/cpp/core_v2/internal/base_pcp_handler.h @@ -70,6 +70,13 @@ Swapper MakeSwapper(T* value) { return Swapper(value); } +// Represents the WebRtc state that mediums are connectable or not. +enum class WebRtcState { + kUndefined = 0, + kConnectable = 1, + kUnconnectable = 2, +}; + // A base implementation of the PcpHandler interface that takes care of all // bookkeeping and handshake protocols that are common across all PcpHandler // implementations -- thus, every concrete PcpHandler implementation must extend @@ -399,10 +406,11 @@ class BasePcpHandler : public PcpHandler, proto::connections::Medium ChooseBestUpgradeMedium( const std::vector& supported_mediums); - std::unique_ptr - GetRemoteBluetoothMacAddressEndpoint( - std::string endpoint_id, std::string remote_bluetooth_mac_address, - std::vector endpoints); + // Returns true if the bluetooth endpoint based on remote bluetooth mac + // address is created and added into discovered_endpoints_ with key + // endpoint_id. + bool AddRemoteBluetoothMacAddressEndpoint( + std::string endpoint_id, std::string remote_bluetooth_mac_address); void ProcessPreConnectionInitiationFailure(const std::string& endpoint_id, EndpointChannel* channel, diff --git a/cpp/core_v2/internal/ble_advertisement.cc b/cpp/core_v2/internal/ble_advertisement.cc index 1ad5df12..303d2e81 100644 --- a/cpp/core_v2/internal/ble_advertisement.cc +++ b/cpp/core_v2/internal/ble_advertisement.cc @@ -2,6 +2,7 @@ #include +#include "core_v2/internal/base_pcp_handler.h" #include "platform_v2/base/base_input_stream.h" #include "platform_v2/public/logging.h" #include "absl/strings/escaping.h" @@ -14,23 +15,29 @@ BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, const ByteArray& endpoint_info, - const std::string& bluetooth_mac_address) { + const std::string& bluetooth_mac_address, + const ByteArray& uwb_address, + WebRtcState web_rtc_state) { DoInitialize(/*fast_advertisement=*/false, version, pcp, service_id_hash, - endpoint_id, endpoint_info, bluetooth_mac_address); + endpoint_id, endpoint_info, bluetooth_mac_address, uwb_address, + web_rtc_state); } BleAdvertisement::BleAdvertisement(Version version, Pcp pcp, const std::string& endpoint_id, - const ByteArray& endpoint_info) { + const ByteArray& endpoint_info, + const ByteArray& uwb_address) { DoInitialize(/*fast_advertisement=*/true, version, pcp, {}, endpoint_id, - endpoint_info, {}); + endpoint_info, {}, uwb_address, WebRtcState::kUndefined); } void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, const ByteArray& endpoint_info, - const std::string& bluetooth_mac_address) { + const std::string& bluetooth_mac_address, + const ByteArray& uwb_address, + WebRtcState web_rtc_state) { fast_advertisement_ = fast_advertisement; if (!fast_advertisement_) { if (service_id_hash.size() != kServiceIdHashLength) return; @@ -57,10 +64,13 @@ void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version, service_id_hash_ = service_id_hash; endpoint_id_ = endpoint_id; endpoint_info_ = endpoint_info; + uwb_address_ = uwb_address; if (!fast_advertisement_) { if (!BluetoothUtils::FromString(bluetooth_mac_address).Empty()) { bluetooth_mac_address_ = bluetooth_mac_address; } + + web_rtc_state_ = web_rtc_state; } } @@ -120,7 +130,7 @@ BleAdvertisement::BleAdvertisement(bool fast_advertisement, // 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_info. + // The next 1 byte is supposed to be the length of the endpoint_info. std::uint32_t expected_endpoint_info_length = base_input_stream.ReadUint8(); // The next x bytes are the endpoint info. (Max length is 131 bytes or 17 @@ -137,7 +147,7 @@ BleAdvertisement::BleAdvertisement(bool fast_advertisement, fast_advertisement_, expected_endpoint_info_length, endpoint_info_.size()); - // Clear enpoint_id for validadity. + // Clear enpoint_id for validity. endpoint_id_.clear(); return; } @@ -150,6 +160,35 @@ BleAdvertisement::BleAdvertisement(bool fast_advertisement, BluetoothUtils::ToString(bluetooth_mac_address_bytes); } + // The next 1 byte is supposed to be the length of the uwb_address. + std::uint32_t expected_uwb_address_length = base_input_stream.ReadUint8(); + // If the length of uwb_address is not zero, then retrieve it. + if (expected_uwb_address_length != 0) { + uwb_address_ = base_input_stream.ReadBytes(expected_uwb_address_length); + if (uwb_address_.Empty() || + uwb_address_.size() != expected_uwb_address_length) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: " + "expected uwbAddress size to be %d bytes, got %" PRIu64, + expected_uwb_address_length, uwb_address_.size()); + + // Clear enpoint_id for validity. + endpoint_id_.clear(); + return; + } + } + + // The next 1 byte is extra field. + web_rtc_state_ = WebRtcState::kUndefined; + if (!fast_advertisement_) { + if (base_input_stream.IsAvailable(kExtraFieldLength)) { + auto extra_field = static_cast(base_input_stream.ReadUint8()); + web_rtc_state_ = (extra_field & kWebRtcConnectableFlagBitmask) == 1 + ? WebRtcState::kConnectable + : WebRtcState::kUnconnectable; + } + } + base_input_stream.Close(); } @@ -168,21 +207,21 @@ BleAdvertisement::operator ByteArray() const { if (fast_advertisement_) { // clang-format off out = absl::StrCat(std::string(1, version_and_pcp_byte), - endpoint_id_, - std::string(1, endpoint_info_.size()), - std::string(endpoint_info_)); + endpoint_id_, + std::string(1, endpoint_info_.size()), + std::string(endpoint_info_)); // clang-format on } else { // clang-format off out = absl::StrCat(std::string(1, version_and_pcp_byte), - std::string(service_id_hash_), - endpoint_id_, - std::string(1, endpoint_info_.size()), - std::string(endpoint_info_)); + std::string(service_id_hash_), + endpoint_id_, + std::string(1, endpoint_info_.size()), + std::string(endpoint_info_)); // 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. + // is invalid or empty, we get back a empty byte array. auto bluetooth_mac_address_bytes{ BluetoothUtils::FromString(bluetooth_mac_address_)}; if (!bluetooth_mac_address_bytes.Empty()) { @@ -190,6 +229,25 @@ BleAdvertisement::operator ByteArray() const { } } + // The next bytes are UWB address field. + if (!uwb_address_.Empty()) { + absl::StrAppend(&out, std::string(1, uwb_address_.size())); + absl::StrAppend(&out, std::string(uwb_address_)); + } else { + // Write UWB address with length 0 to be able to read the next field when + // decode. + absl::StrAppend(&out, std::string(1, uwb_address_.size())); + } + + // The next 1 byte is extra field. + if (!fast_advertisement_) { + int web_rtc_connectable_flag = + (web_rtc_state_ == WebRtcState::kConnectable) ? 1 : 0; + char extra_field_byte = static_cast(web_rtc_connectable_flag) & + kWebRtcConnectableFlagBitmask; + absl::StrAppend(&out, std::string(1, extra_field_byte)); + } + return ByteArray(std::move(out)); } diff --git a/cpp/core_v2/internal/ble_advertisement.h b/cpp/core_v2/internal/ble_advertisement.h index 3f1d04f3..1e7edcdb 100644 --- a/cpp/core_v2/internal/ble_advertisement.h +++ b/cpp/core_v2/internal/ble_advertisement.h @@ -1,6 +1,7 @@ #ifndef CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ #define CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ +#include "core_v2/internal/base_pcp_handler.h" #include "core_v2/internal/pcp.h" #include "platform_v2/base/bluetooth_utils.h" #include "platform_v2/base/byte_array.h" @@ -13,7 +14,7 @@ namespace connections { // Advertising + Discovery. // //

[VERSION][PCP][SERVICE_ID_HASH][ENDPOINT_ID][ENDPOINT_INFO_SIZE] -// [ENDPOINT_INFO][BLUETOOTH_MAC] +// [ENDPOINT_INFO][BLUETOOTH_MAC][UWB_ADDRESS_SIZE][UWB_ADDRESS][EXTRA_FIELD] // //

The fast version of this advertisement simply omits SERVICE_ID_HASH and // the Bluetooth MAC address. @@ -35,27 +36,35 @@ class BleAdvertisement { static constexpr int kServiceIdHashLength = 3; static constexpr int kEndpointIdLength = 4; static constexpr int kEndpointInfoSizeLength = 1; + static constexpr int kBluetoothMacAddressLength = + BluetoothUtils::kBluetoothMacAddressLength; + static constexpr int kUwbAddressSizeLength = 1; + static constexpr int kExtraFieldLength = 1; static constexpr int kEndpointInfoLengthBitmask = 0x0FF; + static constexpr int kWebRtcConnectableFlagBitmask = 0x01; static constexpr int kMinAdvertisementLength = kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + - kEndpointInfoSizeLength + BluetoothUtils::kBluetoothMacAddressLength; + kEndpointInfoSizeLength + kBluetoothMacAddressLength; // The difference between normal and fast advertisements is that the fast one // omits the SERVICE_ID_HASH and Bluetooth MAC address. This is done to save // space. - static constexpr int kMinFastAdvertisementLength = - kMinAdvertisementLength - kServiceIdHashLength - - BluetoothUtils::kBluetoothMacAddressLength; + static constexpr int kMinFastAdvertisementLength = kMinAdvertisementLength - + kServiceIdHashLength - + kBluetoothMacAddressLength; static constexpr int kMaxEndpointInfoLength = 131; static constexpr int kMaxFastEndpointInfoLength = 17; BleAdvertisement() = default; BleAdvertisement(Version version, Pcp pcp, const std::string& endpoint_id, - const ByteArray& endpoint_info); + const ByteArray& endpoint_info, + const ByteArray& uwb_address); BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, const ByteArray& endpoint_info, - const std::string& bluetooth_mac_address); + const std::string& bluetooth_mac_address, + const ByteArray& uwb_address, + WebRtcState web_rtc_state); BleAdvertisement(bool fast_advertisement, const ByteArray& ble_advertisement_bytes); BleAdvertisement(const BleAdvertisement&) = default; @@ -74,21 +83,27 @@ class BleAdvertisement { std::string GetEndpointId() const { return endpoint_id_; } ByteArray GetEndpointInfo() const { return endpoint_info_; } std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; } + ByteArray GetUwbAddress() const { return uwb_address_; } + WebRtcState GetWebRtcState() const { return web_rtc_state_; } private: void DoInitialize(bool fast_advertisement, Version version, Pcp pcp, const ByteArray& service_id_hash, const std::string& endpoint_id, const ByteArray& endpoint_info, - const std::string& bluetooth_mac_address); + const std::string& bluetooth_mac_address, + const ByteArray& uwb_address, WebRtcState web_rtc_state); bool fast_advertisement_ = false; - Version version_ = Version::kUndefined; - Pcp pcp_ = Pcp::kUnknown; + Version version_{Version::kUndefined}; + Pcp pcp_{Pcp::kUnknown}; ByteArray service_id_hash_; std::string endpoint_id_; ByteArray endpoint_info_; std::string bluetooth_mac_address_; + // TODO(b/169550050): Define UWB address field. + ByteArray uwb_address_; + WebRtcState web_rtc_state_{WebRtcState::kUndefined}; }; } // namespace connections diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc index 7ad1d374..42e4b978 100644 --- a/cpp/core_v2/internal/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -1,5 +1,6 @@ #include "core_v2/internal/ble_advertisement.h" +#include "core_v2/internal/base_pcp_handler.h" #include "gtest/gtest.h" namespace location { @@ -15,14 +16,20 @@ constexpr absl::string_view kEndpointName{ "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"}; constexpr absl::string_view kFastAdvertisementEndpointName{"Fast Advertise"}; constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"}; +constexpr WebRtcState kWebRtcState = WebRtcState::kConnectable; +// TODO(b/169550050): Implement UWBAddress. TEST(BleAdvertisementTest, ConstructionWorks) { ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); @@ -32,12 +39,16 @@ TEST(BleAdvertisementTest, ConstructionWorks) { EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionWorksForFastAdvertisement) { ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - fast_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); @@ -45,6 +56,7 @@ TEST(BleAdvertisementTest, ConstructionWorksForFastAdvertisement) { EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo()); + EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfo) { @@ -56,7 +68,9 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfo) { service_id_hash, std::string(kEndpointId), empty_endpoint_info, - std::string(kBluetoothMacAddress)}; + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); @@ -66,14 +80,18 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfo) { EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfoForFastAdvertisement) { ByteArray empty_endpoint_info; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - empty_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + empty_endpoint_info, + ByteArray{}}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); @@ -81,6 +99,7 @@ TEST(BleAdvertisementTest, EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo()); + EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfo) { @@ -92,7 +111,9 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfo) { service_id_hash, std::string(kEndpointId), emoji_endpoint_info, - std::string(kBluetoothMacAddress)}; + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_FALSE(ble_advertisement.IsFastAdvertisement()); @@ -102,14 +123,18 @@ TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfo) { EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfoForFastAdvertisement) { ByteArray emoji_endpoint_info{std::string("\u0001F450 \u0001F450")}; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - emoji_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + emoji_endpoint_info, + ByteArray{}}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_TRUE(ble_advertisement.IsFastAdvertisement()); @@ -117,6 +142,7 @@ TEST(BleAdvertisementTest, EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo()); + EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointInfo) { @@ -125,10 +151,14 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointInfo) { ByteArray long_endpoint_info{long_endpoint_name}; ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - long_endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + long_endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -139,8 +169,11 @@ TEST(BleAdvertisementTest, BleAdvertisement::kMaxFastEndpointInfoLength + 1, 'x'); ByteArray long_endpoint_info{long_endpoint_name}; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - long_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + long_endpoint_info, + ByteArray{}}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -150,10 +183,14 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - bad_version, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{bad_version, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -163,8 +200,11 @@ TEST(BleAdvertisementTest, auto bad_version = static_cast(666); ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement ble_advertisement{ - bad_version, kPcp, std::string(kEndpointId), fast_endpoint_info}; + BleAdvertisement ble_advertisement{bad_version, + kPcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -174,10 +214,14 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, bad_pcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + bad_pcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -186,8 +230,11 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadPCPForFastAdvertisement) { auto bad_pcp = static_cast(666); ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, bad_pcp, std::string(kEndpointId), fast_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + bad_pcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; EXPECT_FALSE(ble_advertisement.IsValid()); } @@ -197,10 +244,14 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, empty_bluetooth_mac_address}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + empty_bluetooth_mac_address, + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(ble_advertisement.IsValid()); } @@ -210,10 +261,14 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, bad_bluetooth_mac_address}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + bad_bluetooth_mac_address, + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(ble_advertisement.IsValid()); EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); @@ -222,16 +277,21 @@ TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { // Serialize good data into a good Ble Advertisement. ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement org_ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement org_ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; ByteArray ble_advertisement_bytes(org_ble_advertisement); BleAdvertisement ble_advertisement{false, ble_advertisement_bytes}; @@ -244,13 +304,17 @@ TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionFromBytesWorksForFastAdvertisement) { // Serialize good data into a good Ble Advertisement. ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement org_ble_advertisement{ - kVersion, kPcp, std::string(kEndpointId), fast_endpoint_info}; + BleAdvertisement org_ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; ByteArray ble_advertisement_bytes(org_ble_advertisement); BleAdvertisement ble_advertisement{true, ble_advertisement_bytes}; @@ -261,6 +325,7 @@ TEST(BleAdvertisementTest, ConstructionFromBytesWorksForFastAdvertisement) { EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId()); EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo()); + EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState()); } // Bytes at the end should be ignored so that they can be used as reserve bytes @@ -269,10 +334,14 @@ TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { // Serialize good data into a good Ble Advertisement. ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; ByteArray ble_advertisement_bytes(ble_advertisement); // Add bytes to the end of the valid Ble advertisement. @@ -293,6 +362,7 @@ TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { EXPECT_EQ(endpoint_info, long_ble_advertisement.GetEndpointInfo()); EXPECT_EQ(kBluetoothMacAddress, long_ble_advertisement.GetBluetoothMacAddress()); + EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState()); } TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { @@ -311,10 +381,14 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { // Serialize good data into a good Ble Advertisement. ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; ByteArray ble_advertisement_bytes(ble_advertisement); // Shorten the valid Ble Advertisement. @@ -327,12 +401,16 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { EXPECT_FALSE(short_ble_advertisement.IsValid()); } + TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFailsForFastAdvertisement) { // Serialize good data into a good Ble Advertisement. ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - fast_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; ByteArray ble_advertisement_bytes(ble_advertisement); // Shorten the valid Ble Advertisement. @@ -350,10 +428,14 @@ TEST(BleAdvertisementTest, // Serialize good data into a good Ble Advertisement. ByteArray service_id_hash{std::string(kServiceIdHashBytes)}; ByteArray endpoint_info{std::string(kEndpointName)}; - BleAdvertisement ble_advertisement{ - kVersion, kPcp, - service_id_hash, std::string(kEndpointId), - endpoint_info, std::string(kBluetoothMacAddress)}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + service_id_hash, + std::string(kEndpointId), + endpoint_info, + std::string(kBluetoothMacAddress), + ByteArray{}, + kWebRtcState}; ByteArray ble_advertisement_bytes(ble_advertisement); // Corrupt the EndpointNameLength bits. @@ -371,8 +453,11 @@ TEST(BleAdvertisementTest, ConstructionFromByesWithWrongEndpointInfoLengthFailsForFastAdvertisement) { // Serialize good data into a good Ble Advertisement. ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)}; - BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId), - fast_endpoint_info}; + BleAdvertisement ble_advertisement{kVersion, + kPcp, + std::string(kEndpointId), + fast_endpoint_info, + ByteArray{}}; ByteArray ble_advertisement_bytes = ByteArray(ble_advertisement); // Corrupt the EndpointInfoLength bits. diff --git a/cpp/core_v2/internal/bluetooth_device_name.cc b/cpp/core_v2/internal/bluetooth_device_name.cc index 48897dc9..374fd186 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.cc +++ b/cpp/core_v2/internal/bluetooth_device_name.cc @@ -18,7 +18,9 @@ namespace connections { BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - const ByteArray& endpoint_info) { + const ByteArray& endpoint_info, + const ByteArray& uwb_address, + WebRtcState web_rtc_state) { if (version != Version::kV1 || endpoint_id.empty() || endpoint_id.length() != kEndpointIdLength || service_id_hash.size() != kServiceIdHashLength) { @@ -38,6 +40,8 @@ BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp, endpoint_id_ = std::string(endpoint_id); service_id_hash_ = service_id_hash; endpoint_info_ = endpoint_info; + uwb_address_ = uwb_address; + web_rtc_state_ = web_rtc_state; } BluetoothDeviceName::BluetoothDeviceName( @@ -53,15 +57,6 @@ BluetoothDeviceName::BluetoothDeviceName( return; } - if (bluetooth_device_name_bytes.size() > kMaxBluetoothDeviceNameLength) { - NEARBY_LOG(INFO, - "Cannot deserialize BluetoothDeviceName: expecting max %d raw " - "bytes, got %" PRIu64, - kMaxBluetoothDeviceNameLength, - bluetooth_device_name_bytes.size()); - return; - } - if (bluetooth_device_name_bytes.size() < kMinBluetoothDeviceNameLength) { NEARBY_LOG(INFO, "Cannot deserialize BluetoothDeviceName: expecting min %d raw " @@ -103,11 +98,18 @@ BluetoothDeviceName::BluetoothDeviceName( // 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 + + // The next 1 byte is field containning WebRtc state. + auto field_byte = static_cast(base_input_stream.ReadUint8()); + web_rtc_state_ = (field_byte & kWebRtcConnectableFlagBitmask) == 1 + ? WebRtcState::kConnectable + : WebRtcState::kUnconnectable; + + // The next 6 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_info. + // The next 1 byte is supposed to be the length of the endpoint_info. std::uint32_t expected_endpoint_info_length = base_input_stream.ReadUint8(); // The rest bytes are supposed to be the endpoint_info @@ -123,6 +125,29 @@ BluetoothDeviceName::BluetoothDeviceName( endpoint_id_.clear(); return; } + + // If the input stream has extra bytes, it's for UWB address. The first byte + // is the address length. It can be 2-byte short address or 8-byte extended + // address. + if (base_input_stream.IsAvailable(1)) { + // The next 1 byte is supposed to be the length of the uwb_address. + std::uint32_t expected_uwb_address_length = base_input_stream.ReadUint8(); + // If the length of usb_address is not zero, then retrieve it. + if (expected_uwb_address_length != 0) { + uwb_address_ = base_input_stream.ReadBytes(expected_uwb_address_length); + if (uwb_address_.Empty() || + uwb_address_.size() != expected_uwb_address_length) { + NEARBY_LOG(INFO, + "Cannot deserialize BluetoothDeviceName: " + "expected uwbAddress size to be %d bytes, got %" PRIu64, + expected_uwb_address_length, uwb_address_.size()); + + // Clear enpoint_id for validadity. + endpoint_id_.clear(); + return; + } + } + } } BluetoothDeviceName::operator std::string() const { @@ -137,6 +162,12 @@ BluetoothDeviceName::operator std::string() const { version_and_pcp_byte |= static_cast(static_cast(pcp_) & kPcpBitmask); + // A byte contains WebRtcState state. + int web_rtc_connectable_flag = + (web_rtc_state_ == WebRtcState::kConnectable) ? 1 : 0; + char field_byte = static_cast(web_rtc_connectable_flag) & + kWebRtcConnectableFlagBitmask; + ByteArray reserved_bytes{kReservedLength}; ByteArray usable_endpoint_info(endpoint_info_); @@ -153,11 +184,18 @@ BluetoothDeviceName::operator std::string() const { std::string out = absl::StrCat(std::string(1, version_and_pcp_byte), endpoint_id_, std::string(service_id_hash_), + std::string(1, field_byte), std::string(reserved_bytes), std::string(1, usable_endpoint_info.size()), std::string(usable_endpoint_info)); // clang-format on + // If UWB address is available, attach it at the end. + if (!uwb_address_.Empty()) { + absl::StrAppend(&out, std::string(1, uwb_address_.size())); + absl::StrAppend(&out, std::string(uwb_address_)); + } + return Base64Utils::Encode(ByteArray{std::move(out)}); } diff --git a/cpp/core_v2/internal/bluetooth_device_name.h b/cpp/core_v2/internal/bluetooth_device_name.h index c5c3f652..77de4f53 100644 --- a/cpp/core_v2/internal/bluetooth_device_name.h +++ b/cpp/core_v2/internal/bluetooth_device_name.h @@ -3,6 +3,7 @@ #include +#include "core_v2/internal/base_pcp_handler.h" #include "core_v2/internal/pcp.h" #include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" @@ -30,7 +31,9 @@ class BluetoothDeviceName { BluetoothDeviceName() = default; BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - const ByteArray& endpoint_info); + const ByteArray& endpoint_info, + const ByteArray& uwb_address, + WebRtcState web_rtc_state); explicit BluetoothDeviceName(absl::string_view bluetooth_device_name_string); BluetoothDeviceName(const BluetoothDeviceName&) = default; BluetoothDeviceName& operator=(const BluetoothDeviceName&) = default; @@ -46,24 +49,28 @@ class BluetoothDeviceName { std::string GetEndpointId() const { return endpoint_id_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } ByteArray GetEndpointInfo() const { return endpoint_info_; } + ByteArray GetUwbAddress() const { return uwb_address_; } + WebRtcState GetWebRtcState() const { return web_rtc_state_; } private: - static constexpr int kMaxBluetoothDeviceNameLength = 147; static constexpr int kEndpointIdLength = 4; - static constexpr int kReservedLength = 7; + static constexpr int kReservedLength = 6; static constexpr int kMaxEndpointInfoLength = 131; - static constexpr int kMinBluetoothDeviceNameLength = - kMaxBluetoothDeviceNameLength - kMaxEndpointInfoLength; + static constexpr int kMinBluetoothDeviceNameLength = 16; static constexpr int kVersionBitmask = 0x0E0; static constexpr int kPcpBitmask = 0x01F; static constexpr int kEndpointNameLengthBitmask = 0x0FF; + static constexpr int kWebRtcConnectableFlagBitmask = 0x01; Version version_{Version::kUndefined}; Pcp pcp_{Pcp::kUnknown}; std::string endpoint_id_; ByteArray service_id_hash_; ByteArray endpoint_info_; + // TODO(b/169550050): Define UWB address field. + ByteArray uwb_address_; + WebRtcState web_rtc_state_{WebRtcState::kUndefined}; }; } // namespace connections diff --git a/cpp/core_v2/internal/bluetooth_device_name_test.cc b/cpp/core_v2/internal/bluetooth_device_name_test.cc index d957bb63..a509170e 100644 --- a/cpp/core_v2/internal/bluetooth_device_name_test.cc +++ b/cpp/core_v2/internal/bluetooth_device_name_test.cc @@ -17,12 +17,19 @@ 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!"}; +constexpr WebRtcState kWebRtcState = WebRtcState::kConnectable; +// TODO(b/169550050): Implement UWBAddress. TEST(BluetoothDeviceNameTest, ConstructionWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(bluetooth_device_name.IsValid()); EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); @@ -30,14 +37,20 @@ TEST(BluetoothDeviceNameTest, ConstructionWorks) { EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); EXPECT_EQ(endpoint_info, bluetooth_device_name.GetEndpointInfo()); + EXPECT_EQ(kWebRtcState, bluetooth_device_name.GetWebRtcState()); } TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { ByteArray empty_endpoint_info; ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; - BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, service_id_hash, empty_endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + kEndPointID, + service_id_hash, + empty_endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(bluetooth_device_name.IsValid()); EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion()); @@ -45,6 +58,7 @@ TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) { EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId()); EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash()); EXPECT_EQ(empty_endpoint_info, bluetooth_device_name.GetEndpointInfo()); + EXPECT_EQ(kWebRtcState, bluetooth_device_name.GetWebRtcState()); } TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { @@ -52,8 +66,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{bad_version, kPcp, kEndPointID, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{bad_version, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -63,8 +82,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{kVersion, bad_pcp, kEndPointID, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + bad_pcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -74,8 +98,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, short_endpoint_id, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + short_endpoint_id, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -85,8 +114,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, long_endpoint_id, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + long_endpoint_id, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -96,8 +130,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortServiceIdHash) { ByteArray short_service_id_hash{short_service_id_hash_bytes}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, short_service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + kEndPointID, + short_service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -107,8 +146,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongServiceIdHash) { ByteArray long_service_id_hash{long_service_id_hash_bytes}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{ - kVersion, kPcp, kEndPointID, long_service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + kEndPointID, + long_service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(bluetooth_device_name.IsValid()); } @@ -127,8 +171,13 @@ TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) { // Serialize good data into a good Bluetooth Device Name. ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - BluetoothDeviceName bluetooth_device_name{kVersion, kPcp, kEndPointID, - service_id_hash, endpoint_info}; + BluetoothDeviceName bluetooth_device_name{kVersion, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; auto bluetooth_device_name_string = std::string(bluetooth_device_name); // Base64-decode the good Bluetooth Device Name. @@ -155,8 +204,13 @@ TEST(BluetoothDeviceNameTest, CanParseGeneratedName) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; // Build name1 from scratch. - BluetoothDeviceName name1{kVersion, kPcp, kEndPointID, service_id_hash, - endpoint_info}; + BluetoothDeviceName name1{kVersion, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; // Build name2 from string composed from name1. BluetoothDeviceName name2{std::string(name1)}; EXPECT_TRUE(name1.IsValid()); @@ -166,6 +220,7 @@ TEST(BluetoothDeviceNameTest, CanParseGeneratedName) { EXPECT_EQ(name1.GetEndpointId(), name2.GetEndpointId()); EXPECT_EQ(name1.GetServiceIdHash(), name2.GetServiceIdHash()); EXPECT_EQ(name1.GetEndpointInfo(), name2.GetEndpointInfo()); + EXPECT_EQ(name1.GetWebRtcState(), name2.GetWebRtcState()); } } // namespace diff --git a/cpp/core_v2/internal/bwu_manager.cc b/cpp/core_v2/internal/bwu_manager.cc index ab81e570..cd4c9c38 100644 --- a/cpp/core_v2/internal/bwu_manager.cc +++ b/cpp/core_v2/internal/bwu_manager.cc @@ -1,6 +1,7 @@ #include "core_v2/internal/bwu_manager.h" #include +#include #include "core_v2/internal/bwu_handler.h" #include "core_v2/internal/offline_frames.h" @@ -192,7 +193,7 @@ void BwuManager::OnEndpointDisconnect(ClientProxy* client, handler_->OnEndpointDisconnect(client, endpoint_id); } - auto item = old_channels_.extract(endpoint_id); + auto item = previous_endpoint_channels_.extract(endpoint_id); if (!item.empty()) { auto old_channel = item.mapped(); @@ -260,7 +261,10 @@ void BwuManager::OnBwuNegotiationFrame(ClientProxy* client, } void BwuManager::OnIncomingConnection( - ClientProxy* client, BwuHandler::IncomingSocketConnection* connection) { + ClientProxy* client, + BwuHandler::IncomingSocketConnection* mutable_connection) { + auto connection = std::make_shared( + std::move(*mutable_connection)); RunOnBwuManagerThread([this, client, connection]() { EndpointChannel* channel = connection->channel.get(); if (channel == nullptr) { @@ -329,7 +333,7 @@ void BwuManager::RunUpgradeProtocol( // continue when we receive a corresponding // BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL OfflineFrame from // the remote device, so for now, just store that previous EndpointChannel. - old_channels_.emplace(endpoint_id, old_channel); + previous_endpoint_channels_.emplace(endpoint_id, old_channel); // If we already read LAST_WRITE on the old endpoint channel, then we can // safely close it now. diff --git a/cpp/core_v2/internal/bwu_manager.h b/cpp/core_v2/internal/bwu_manager.h index b97d7138..ed0252dd 100644 --- a/cpp/core_v2/internal/bwu_manager.h +++ b/cpp/core_v2/internal/bwu_manager.h @@ -156,10 +156,8 @@ class BwuManager : public EndpointManager::FrameProcessor { // Stores each upgraded endpoint's previous EndpointChannel (that was // displaced in favor of a new EndpointChannel) temporarily, until it can // safely be shut down for good in processLastWriteToPriorChannelEvent(). - absl::flat_hash_map> - previous_endpoint_channels_; absl::flat_hash_map> - old_channels_; + previous_endpoint_channels_; absl::flat_hash_set successfully_upgraded_endpoints_; // Maps endpointId -> ClientProxy for which // initiateBwuForEndpoint() has been called but which have not diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index b2125c5c..49ae79f4 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -25,7 +25,9 @@ cc_library( "//core_v2/internal:__subpackages__", ], deps = [ + ":utils", "//core_v2:core_types", + "//core_v2/internal/mediums/ble_v2", "//core_v2/internal/mediums/webrtc", "//platform_v2/base", "//platform_v2/public:comm", @@ -49,6 +51,7 @@ cc_library( hdrs = ["utils.h"], visibility = [ "//core_v2/internal:__pkg__", + "//core_v2/internal/mediums:__pkg__", "//core_v2/internal/mediums/ble_v2:__pkg__", "//core_v2/internal/mediums/webrtc:__pkg__", ], diff --git a/cpp/core_v2/internal/mediums/ble.cc b/cpp/core_v2/internal/mediums/ble.cc index f8c7cf8f..80622a97 100644 --- a/cpp/core_v2/internal/mediums/ble.cc +++ b/cpp/core_v2/internal/mediums/ble.cc @@ -4,6 +4,9 @@ #include #include +#include "core_v2/internal/mediums/ble_v2/ble_advertisement.h" +#include "core_v2/internal/mediums/utils.h" +#include "platform_v2/base/prng.h" #include "platform_v2/public/logging.h" #include "platform_v2/public/mutex_lock.h" @@ -11,6 +14,15 @@ namespace location { namespace nearby { namespace connections { +ByteArray Ble::GenerateHash(const std::string& source, size_t size) { + return Utils::Sha256Hash(source, size); +} + +ByteArray Ble::GenerateDeviceToken() { + return Utils::Sha256Hash(std::to_string(Prng().NextUint32()), + mediums::BleAdvertisement::kDeviceTokenLength); +} + Ble::Ble(BluetoothRadio& radio) : radio_(radio) {} bool Ble::IsAvailable() const { @@ -63,7 +75,23 @@ bool Ble::StartAdvertising(const std::string& service_id, << ", service id=" << service_id << ", fast advertisement service uuid=" << fast_advertisement_service_uuid; - if (!medium_.StartAdvertising(service_id, advertisement_bytes, + + // Wrap the connections advertisement to the medium advertisement. + const bool fast_advertisement = !fast_advertisement_service_uuid.empty(); + ByteArray service_id_hash{GenerateHash( + service_id, mediums::BleAdvertisement::kServiceIdHashLength)}; + ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{ + mediums::BleAdvertisement::Version::kV2, + mediums::BleAdvertisement::SocketVersion::kV2, + fast_advertisement ? ByteArray{} : service_id_hash, advertisement_bytes, + GenerateDeviceToken()}}; + if (medium_advertisement_bytes.Empty()) { + NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not " + "create a medium advertisement."; + return false; + } + + if (!medium_.StartAdvertising(service_id, medium_advertisement_bytes, fast_advertisement_service_uuid)) { NEARBY_LOGS(INFO) << "Failed to turn on BLE advertising with advertisement bytes=" @@ -110,6 +138,8 @@ bool Ble::StartScanning(const std::string& service_id, DiscoveredPeripheralCallback callback) { MutexLock lock(&mutex_); + discovered_peripheral_callback_ = std::move(callback); + if (service_id.empty()) { NEARBY_LOGS(INFO) << "Refusing to start BLE scanning with empty service id."; @@ -134,8 +164,29 @@ bool Ble::StartScanning(const std::string& service_id, return false; } - if (!medium_.StartScanning(service_id, fast_advertisement_service_uuid, - callback)) { + if (!medium_.StartScanning( + service_id, fast_advertisement_service_uuid, + { + .peripheral_discovered_cb = + [this](BlePeripheral& peripheral, + const std::string& service_id, + const ByteArray& medium_advertisement_bytes, + bool fast_advertisement) { + // Unwrap connection BleAdvertisement from medium + // BleAdvertisement. + auto connection_advertisement_bytes = + UnwrapAdvertisementBytes(medium_advertisement_bytes); + discovered_peripheral_callback_.peripheral_discovered_cb( + peripheral, service_id, connection_advertisement_bytes, + fast_advertisement); + }, + .peripheral_lost_cb = + [this](BlePeripheral& peripheral, + const std::string& service_id) { + discovered_peripheral_callback_.peripheral_lost_cb( + peripheral, service_id); + }, + })) { NEARBY_LOGS(INFO) << "Failed to start scan of BLE services."; return false; } @@ -272,6 +323,16 @@ BleSocket Ble::Connect(BlePeripheral& peripheral, return socket; } +ByteArray Ble::UnwrapAdvertisementBytes( + const ByteArray& medium_advertisement_data) { + mediums::BleAdvertisement medium_ble_advertisement{medium_advertisement_data}; + if (!medium_ble_advertisement.IsValid()) { + return ByteArray{}; + } + + return medium_ble_advertisement.GetData(); +} + } // namespace connections } // namespace nearby } // namespace location diff --git a/cpp/core_v2/internal/mediums/ble.h b/cpp/core_v2/internal/mediums/ble.h index 1a6b7643..b99c07c0 100644 --- a/cpp/core_v2/internal/mediums/ble.h +++ b/cpp/core_v2/internal/mediums/ble.h @@ -88,8 +88,6 @@ class Ble { ABSL_LOCKS_EXCLUDED(mutex_); private: - static constexpr int kMaxAdvertisementLength = 512; - struct AdvertisingInfo { bool Empty() const { return service_ids.empty(); } void Clear() { service_ids.clear(); } @@ -132,6 +130,11 @@ class Ble { absl::flat_hash_set service_ids; }; + static constexpr int kMaxAdvertisementLength = 512; + + static ByteArray GenerateHash(const std::string& source, size_t size); + static ByteArray GenerateDeviceToken(); + // Same as IsAvailable(), but must be called with mutex_ held. bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -147,6 +150,10 @@ class Ble { bool IsAcceptingConnectionsLocked(const std::string& service_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Extract connection advertisement from medium advertisement. + ByteArray UnwrapAdvertisementBytes( + const ByteArray& medium_advertisement_data); + mutable Mutex mutex_; BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_); BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){ @@ -154,6 +161,7 @@ class Ble { BleMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_}; AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); ScanningInfo scanning_info_ ABSL_GUARDED_BY(mutex_); + DiscoveredPeripheralCallback discovered_peripheral_callback_; AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_); }; diff --git a/cpp/core_v2/internal/mediums/ble_test.cc b/cpp/core_v2/internal/mediums/ble_test.cc index 15e24d8f..9977d6ac 100644 --- a/cpp/core_v2/internal/mediums/ble_test.cc +++ b/cpp/core_v2/internal/mediums/ble_test.cc @@ -60,12 +60,12 @@ TEST_F(BleTest, CanStartAdvertising) { CountDownLatch found_latch(1); ble_b.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { found_latch.CountDown(); }, }); @@ -95,12 +95,12 @@ TEST_F(BleTest, CanStartDiscovery) { fast_advertisement_service_uuid); EXPECT_TRUE(ble_a.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&accept_latch]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { accept_latch.CountDown(); }, .peripheral_lost_cb = [&lost_latch](BlePeripheral& peripheral, @@ -140,12 +140,12 @@ TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) { }); BlePeripheral discovered_peripheral; ble_b.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, { .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { discovered_peripheral = peripheral; NEARBY_LOG( diff --git a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc index d988a869..2011d925 100644 --- a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.cc @@ -16,16 +16,8 @@ BleAdvertisement::BleAdvertisement(Version version, const ByteArray &service_id_hash, const ByteArray &data, const ByteArray &device_token) { - DoInitialize(/*fast_advertisement=*/false, version, socket_version, - service_id_hash, data, device_token); -} - -BleAdvertisement::BleAdvertisement(Version version, - SocketVersion socket_version, - const ByteArray &data, - const ByteArray &device_token) { - DoInitialize(/*fast_advertisement=*/true, version, socket_version, - {}, data, device_token); + DoInitialize(/*fast_advertisement=*/service_id_hash.Empty(), version, + socket_version, service_id_hash, data, device_token); } void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version, diff --git a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h index 203b3614..3b8ad37d 100644 --- a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement.h @@ -47,8 +47,6 @@ class BleAdvertisement { BleAdvertisement(Version version, SocketVersion socket_version, const ByteArray &service_id_hash, const ByteArray &data, const ByteArray &device_token); - BleAdvertisement(Version version, SocketVersion socket_version, - const ByteArray &data, const ByteArray &device_token); explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes); BleAdvertisement(const BleAdvertisement &) = default; BleAdvertisement &operator=(const BleAdvertisement &) = default; diff --git a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc index 46a18850..6bbbd196 100644 --- a/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc +++ b/cpp/core_v2/internal/mediums/ble_v2/ble_advertisement_test.cc @@ -53,6 +53,7 @@ TEST(BleAdvertisementTest, ConstructionWorksV1ForFastAdvertisement) { BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, BleAdvertisement::SocketVersion::kV1, + ByteArray{}, fast_data, device_token}; @@ -83,6 +84,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { BleAdvertisement fast_ble_advertisement{bad_version, kSocketVersion, + ByteArray{}, data, device_token}; EXPECT_FALSE(fast_ble_advertisement.IsValid()); @@ -105,6 +107,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { BleAdvertisement fast_ble_advertisement{kVersion, bad_socket_version, + ByteArray{}, data, device_token}; EXPECT_FALSE(fast_ble_advertisement.IsValid()); @@ -160,6 +163,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { BleAdvertisement fast_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, bad_data, device_token}; EXPECT_FALSE(fast_ble_advertisement.IsValid()); @@ -191,6 +195,7 @@ TEST(BleAdvertisementTest, BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, fast_data, ByteArray{}}; @@ -228,12 +233,14 @@ TEST(BleAdvertisementTest, ConstructionFailsWithWrongSizeofDeviceToken) { BleAdvertisement fast_ble_advertisement_1{kVersion, kSocketVersion, + ByteArray{}, data, bad_device_token_1}; EXPECT_FALSE(fast_ble_advertisement_1.IsValid()); BleAdvertisement fast_ble_advertisement_2{kVersion, kSocketVersion, + ByteArray{}, data, bad_device_token_2}; EXPECT_FALSE(fast_ble_advertisement_2.IsValid()); @@ -270,6 +277,7 @@ TEST(BleAdvertisementTest, BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, fast_data, device_token}; @@ -312,6 +320,7 @@ TEST(BleAdvertisementTest, BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, ByteArray(), device_token}; ByteArray ble_advertisement_bytes{org_ble_advertisement}; @@ -366,6 +375,7 @@ TEST(BleAdvertisementTest, BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, fast_data, device_token}; ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; @@ -424,6 +434,7 @@ TEST(BleAdvertisementTest, BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, fast_data, device_token}; ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; @@ -475,6 +486,7 @@ TEST(BleAdvertisementTest, BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + ByteArray{}, fast_data, device_token}; ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc index 86af87ec..3950f8c4 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.cc @@ -231,6 +231,10 @@ bool ConnectionFlow::Close() { bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { Future success_future; + // CreatePeerConnection callback may be invoked after ConnectionFlow lifetime + // has ended, in case of a timeout. Future is captured by value, and is safe + // to access, but it is not safe to access ConnectionFlow member variables + // unless the Future::Set() returns true. webrtc_medium.CreatePeerConnection( &peer_connection_observer_, [this, success_future](rtc::scoped_refptr @@ -240,6 +244,11 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) { return; } + // If this fails, means we have already assigned something to + // success_future; it is either: + // 1) this is the 2nd call of this callback (and this is a bug), or + // 2) Get(timeout) has set the future value as exception already. + if (success_future.IsSet()) return; peer_connection_ = peer_connection; success_future.Set(true); }); diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index 9c7f6314..fd9a6527 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -4,6 +4,7 @@ #include #include "core/internal/message_lite.h" +#include "core_v2/status.h" #include "proto/connections/offline_wire_formats.pb.h" #include "platform_v2/base/byte_array.h" @@ -71,7 +72,14 @@ ByteArray ForConnectionResponse(std::int32_t status) { auto* v1_frame = frame.mutable_v1(); v1_frame->set_type(V1Frame::CONNECTION_RESPONSE); auto* sub_frame = v1_frame->mutable_connection_response(); + + // For backward compatiblility, here still sets both status and response + // parameters until the response feature is roll out in all supported + // devices. sub_frame->set_status(status); + sub_frame->set_response(status == Status::kSuccess + ? ConnectionResponseFrame::ACCEPT + : ConnectionResponseFrame::REJECT); return ToBytes(std::move(frame)); } diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc index 42dce1f3..8d0a402c 100644 --- a/cpp/core_v2/internal/offline_frames_test.cc +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -93,7 +93,10 @@ TEST(OfflineFramesTest, CanGenerateConnectionResponse) { version: V1 v1: < type: CONNECTION_RESPONSE - connection_response: < status: 1 > + connection_response: < + status: 1 + response: REJECT + > >)pb"; ByteArray bytes = ForConnectionResponse(1); auto response = FromBytes(bytes); diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc index 116a04fd..039b7bdc 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.cc @@ -314,9 +314,10 @@ bool P2pClusterPcpHandler::IsRecognizedBleEndpoint( void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( ClientProxy* client, BlePeripheral& peripheral, - const std::string& service_id, bool fast_advertisement) { + const std::string& service_id, const ByteArray& advertisement_bytes, + bool fast_advertisement) { RunOnPcpHandlerThread([this, client, &peripheral, service_id, - fast_advertisement]() { + advertisement_bytes, fast_advertisement]() { // Make sure we are still discovering before proceeding. if (!client->IsDiscovering()) { NEARBY_LOG(INFO, @@ -327,8 +328,7 @@ void P2pClusterPcpHandler::BlePeripheralDiscoveredHandler( } // Parse the BLE advertisement bytes. - BleAdvertisement advertisement( - fast_advertisement, peripheral.GetAdvertisementBytes(service_id)); + BleAdvertisement advertisement(fast_advertisement, advertisement_bytes); // Make sure the BLE advertisement points to a valid // endpoint we're discovering. @@ -568,6 +568,9 @@ BasePcpHandler::StartOperationResult P2pClusterPcpHandler::StartDiscoveryImpl( .device_discovered_cb = absl::bind_front( &P2pClusterPcpHandler::BluetoothDeviceDiscoveredHandler, this, client, service_id), + .device_name_changed_cb = absl::bind_front( + &P2pClusterPcpHandler::BluetoothDeviceDiscoveredHandler, this, + client, service_id), .device_lost_cb = absl::bind_front( &P2pClusterPcpHandler::BluetoothDeviceLostHandler, this, client, service_id), @@ -714,9 +717,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBluetoothAdvertising( absl::BytesToHexString(service_id_hash.data()).c_str(), absl::BytesToHexString(local_endpoint_info.data()).c_str()); // Generate a BluetoothDeviceName with which to become Bluetooth discoverable. + // TODO(b/169550050): Implement UWBAddress. + // TODO(b/169303359): Implement WebRtcState. std::string device_name(BluetoothDeviceName( kBluetoothDeviceNameVersion, GetPcp(), local_endpoint_id, service_id_hash, - local_endpoint_info)); + local_endpoint_info, ByteArray{}, WebRtcState::kUnconnectable)); if (device_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartBluetoothAdvertising: generate " @@ -887,10 +892,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( // Generate a BleAdvertisement. If a fast advertisement service UUID was // provided, create a fast BleAdvertisement. ByteArray advertisement_bytes; + // TODO(b/169550050): Implement UWBAddress. if (fast_advertisement) { - advertisement_bytes = - ByteArray(BleAdvertisement(kBleAdvertisementVersion, GetPcp(), - local_endpoint_id, local_endpoint_info)); + advertisement_bytes = ByteArray( + BleAdvertisement(kBleAdvertisementVersion, GetPcp(), local_endpoint_id, + local_endpoint_info, ByteArray{})); } else { const ByteArray service_id_hash = GenerateHash(service_id, BleAdvertisement::kServiceIdHashLength); @@ -899,9 +905,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartBleAdvertising( ShouldAdvertiseBluetoothMacOverBle(power_level)) bluetooth_mac_address = bluetooth_medium_.GetMacAddress(); + // TODO(b/169303359): Implement WebRtcState. advertisement_bytes = ByteArray(BleAdvertisement( kBleAdvertisementVersion, GetPcp(), service_id_hash, local_endpoint_id, - local_endpoint_info, bluetooth_mac_address)); + local_endpoint_info, bluetooth_mac_address, ByteArray{}, + WebRtcState::kUnconnectable)); } if (advertisement_bytes.Empty()) { NEARBY_LOG(INFO, @@ -1022,9 +1030,11 @@ proto::connections::Medium P2pClusterPcpHandler::StartWifiLanAdvertising( absl::BytesToHexString(service_id_hash.data()).c_str(), absl::BytesToHexString(local_endpoint_info.data()).c_str()); // Generate a WifiLanServiceInfo with which to become WifiLan discoverable. + // TODO(b/169550050): Implement UWBAddress. + // TODO(b/169303359): Implement WebRtcState. std::string service_info_name(WifiLanServiceInfo( kWifiLanServiceInfoVersion, GetPcp(), local_endpoint_id, service_id_hash, - local_endpoint_info)); + local_endpoint_info, ByteArray{}, WebRtcState::kUnconnectable)); if (service_info_name.empty()) { NEARBY_LOG(INFO, "P2pClusterPcpHandler::StartWifiLanAdvertising: generate " diff --git a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h index 687075c4..50ea90ef 100644 --- a/cpp/core_v2/internal/p2p_cluster_pcp_handler.h +++ b/cpp/core_v2/internal/p2p_cluster_pcp_handler.h @@ -151,6 +151,7 @@ class P2pClusterPcpHandler : public BasePcpHandler { void BlePeripheralDiscoveredHandler(ClientProxy* client, BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement); void BlePeripheralLostHandler(ClientProxy* client, BlePeripheral& peripheral, const std::string& service_id); diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc index 566cf40e..b982a89c 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -17,7 +17,9 @@ namespace connections { WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - const ByteArray& endpoint_info) { + const ByteArray& endpoint_info, + const ByteArray& uwb_address, + WebRtcState web_rtc_state) { if (version != Version::kV1 || endpoint_id.empty() || endpoint_id.length() != kEndpointIdLength || service_id_hash.size() != kServiceIdHashLength) { @@ -37,6 +39,8 @@ WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, service_id_hash_ = service_id_hash; endpoint_id_ = std::string(endpoint_id); endpoint_info_ = endpoint_info; + uwb_address_ = uwb_address; + web_rtc_state_ = web_rtc_state; } WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { @@ -50,14 +54,6 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { return; } - if (service_info_bytes.size() > kMaxLanServiceNameLength) { - NEARBY_LOG(INFO, - "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " - "bytes, got %" PRIu64, - kMaxLanServiceNameLength, service_info_bytes.size()); - return; - } - if (service_info_bytes.size() < kMinLanServiceNameLength) { NEARBY_LOG(INFO, "Cannot deserialize WifiLanServiceInfo: expecting min %d raw " @@ -105,6 +101,20 @@ WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { // 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 UWB address. + std::uint32_t expected_uwb_address_length = base_input_stream.ReadUint8(); + + // The next bytes are supposed to be UWB address if length is not zero. + if (expected_uwb_address_length != 0) { + uwb_address_ = base_input_stream.ReadBytes(expected_uwb_address_length); + } + + // The next 1 byte is extra field. + auto extra_field = static_cast(base_input_stream.ReadUint8()); + web_rtc_state_ = (extra_field & kWebRtcConnectableFlagBitmask) == 1 + ? WebRtcState::kConnectable + : WebRtcState::kUnconnectable; + // The next 1 byte are supposed to be the length of the endpoint_info. std::uint32_t expected_endpoint_info_length = base_input_stream.ReadUint8(); @@ -135,6 +145,12 @@ WifiLanServiceInfo::operator std::string() const { version_and_pcp_byte |= static_cast(static_cast(pcp_) & kPcpBitmask); + // A byte contains WebRtcState state. + int web_rtc_connectable_flag = + (web_rtc_state_ == WebRtcState::kConnectable) ? 1 : 0; + char field_byte = static_cast(web_rtc_connectable_flag) & + kWebRtcConnectableFlagBitmask; + ByteArray usable_endpoint_info(endpoint_info_); if (endpoint_info_.size() > kMaxEndpointInfoLength) { NEARBY_LOG( @@ -146,13 +162,29 @@ WifiLanServiceInfo::operator std::string() const { usable_endpoint_info.SetData(endpoint_info_.data(), kMaxEndpointInfoLength); } - // 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_info.size()), - std::string(usable_endpoint_info)); - // clang-format on + std::string out; + if (!uwb_address_.Empty()) { + // clang-format off + out = absl::StrCat(std::string(1, version_and_pcp_byte), + endpoint_id_, + std::string(service_id_hash_), + std::string(1, uwb_address_.size()), + std::string(uwb_address_), + std::string(1, field_byte), + std::string(1, usable_endpoint_info.size()), + std::string(usable_endpoint_info)); + // clang-format on + } else { + // clang-format off + out = absl::StrCat(std::string(1, version_and_pcp_byte), + endpoint_id_, + std::string(service_id_hash_), + std::string(1, uwb_address_.size()), + std::string(1, field_byte), + std::string(1, usable_endpoint_info.size()), + std::string(usable_endpoint_info)); + // 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 4b6b3897..bc422f08 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info.h +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -3,6 +3,7 @@ #include +#include "core_v2/internal/base_pcp_handler.h" #include "core_v2/internal/pcp.h" #include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" @@ -28,7 +29,9 @@ class WifiLanServiceInfo { WifiLanServiceInfo() = default; WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, const ByteArray& service_id_hash, - const ByteArray& endpoint_info); + const ByteArray& endpoint_info, + const ByteArray& uwb_address, + WebRtcState web_rtc_state); explicit WifiLanServiceInfo(absl::string_view service_info_string); WifiLanServiceInfo(const WifiLanServiceInfo&) = default; WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; @@ -44,31 +47,28 @@ class WifiLanServiceInfo { std::string GetEndpointId() const { return endpoint_id_; } ByteArray GetEndpointInfo() const { return endpoint_info_; } ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray GetUwbAddress() const { return uwb_address_; } + WebRtcState GetWebRtcState() const { return web_rtc_state_; } private: - // The maximum length of encrypted WifiLanServiceInfo string. - static constexpr int kMaxLanServiceNameLength = 47; - // The minimum length of encrypted WifiLanServiceInfo string. static constexpr int kMinLanServiceNameLength = 9; - // The length for endpoint id in encrypted WifiLanServiceInfo string. static constexpr int kEndpointIdLength = 4; - // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. static constexpr int kMaxEndpointInfoLength = 131; + static constexpr int kUwbAddressLengthSize = 1; static constexpr int kVersionBitmask = 0x0E0; static constexpr int kPcpBitmask = 0x01F; static constexpr int kVersionShift = 5; + static constexpr int kWebRtcConnectableFlagBitmask = 0x01; - // WifiLanServiceInfo version. - Version version_ = Version::kUndefined; - // Pre-Connection Protocols version. - Pcp pcp_ = Pcp::kUnknown; - // Connected endpoint id. + Version version_{Version::kUndefined}; + Pcp pcp_{Pcp::kUnknown}; std::string endpoint_id_; - // Connected hash service id. ByteArray service_id_hash_; - // Connected endpoint info. ByteArray endpoint_info_; + // TODO(b/169550050): Define UWB address field. + ByteArray uwb_address_; + WebRtcState web_rtc_state_{WebRtcState::kUndefined}; }; } // namespace connections 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 90215c06..4eabc78e 100644 --- a/cpp/core_v2/internal/wifi_lan_service_info_test.cc +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -17,12 +17,19 @@ 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!"}; +constexpr WebRtcState kWebRtcState = WebRtcState::kConnectable; +// TODO(b/169550050): Implement UWBAddress. TEST(WifiLanServiceInfoTest, ConstructionWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{ - kVersion, kPcp, kEndPointID, service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_TRUE(wifi_lan_service_info.IsValid()); EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); @@ -35,8 +42,13 @@ TEST(WifiLanServiceInfoTest, ConstructionWorks) { TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo org_wifi_lan_service_info{kVersion, kPcp, kEndPointID, - service_id_hash, endpoint_info}; + WifiLanServiceInfo org_wifi_lan_service_info{kVersion, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; std::string wifi_lan_service_info_string{org_wifi_lan_service_info}; WifiLanServiceInfo wifi_lan_service_info{wifi_lan_service_info_string}; @@ -47,6 +59,7 @@ TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); EXPECT_EQ(endpoint_info, wifi_lan_service_info.GetEndpointInfo()); + EXPECT_EQ(kWebRtcState, wifi_lan_service_info.GetWebRtcState()); } TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { @@ -54,8 +67,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{bad_version, kPcp, kEndPointID, - service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{bad_version, + kPcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -65,8 +83,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{kVersion, bad_pcp, kEndPointID, - service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + bad_pcp, + kEndPointID, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -76,8 +99,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, short_endpoint_id, - service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + kPcp, + short_endpoint_id, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -87,8 +115,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { ByteArray service_id_hash{std::string(kServiceIDHashBytes)}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, long_endpoint_id, - service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + kPcp, + long_endpoint_id, + service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -98,8 +131,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { ByteArray short_service_id_hash{short_service_id_hash_bytes}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{ - kVersion, kPcp, kEndPointID, short_service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + kPcp, + kEndPointID, + short_service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } @@ -109,8 +147,13 @@ TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { ByteArray long_service_id_hash{long_service_id_hash_bytes}; ByteArray endpoint_info{std::string(kEndPointName)}; - WifiLanServiceInfo wifi_lan_service_info{kVersion, kPcp, kEndPointID, - long_service_id_hash, endpoint_info}; + WifiLanServiceInfo wifi_lan_service_info{kVersion, + kPcp, + kEndPointID, + long_service_id_hash, + endpoint_info, + ByteArray{}, + kWebRtcState}; EXPECT_FALSE(wifi_lan_service_info.IsValid()); } diff --git a/cpp/platform_v2/public/ble.cc b/cpp/platform_v2/public/ble.cc index db8ab6b5..40d8777b 100644 --- a/cpp/platform_v2/public/ble.cc +++ b/cpp/platform_v2/public/ble.cc @@ -51,7 +51,9 @@ bool BleMedium::StartScanning( &context.peripheral, &peripheral, peripheral.GetName().c_str()); discovered_peripheral_callback_.peripheral_discovered_cb( - context.peripheral, service_id, fast_advertisement); + context.peripheral, service_id, + context.peripheral.GetAdvertisementBytes(service_id), + fast_advertisement); } }, .peripheral_lost_cb = diff --git a/cpp/platform_v2/public/ble.h b/cpp/platform_v2/public/ble.h index ca9bedbb..41f5b1b9 100644 --- a/cpp/platform_v2/public/ble.h +++ b/cpp/platform_v2/public/ble.h @@ -71,11 +71,12 @@ class BleMedium final { public: using Platform = api::ImplementationPlatform; struct DiscoveredPeripheralCallback { - std::function peripheral_discovered_cb = - DefaultCallback(); + DefaultCallback(); std::function peripheral_lost_cb = diff --git a/cpp/platform_v2/public/ble_test.cc b/cpp/platform_v2/public/ble_test.cc index 41f6d091..94a5a2b5 100644 --- a/cpp/platform_v2/public/ble_test.cc +++ b/cpp/platform_v2/public/ble_test.cc @@ -58,12 +58,12 @@ TEST_F(BleMediumTest, CanStartAdvertising) { fast_advertisement_service_uuid); EXPECT_TRUE(ble_b.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { found_latch.CountDown(); }, })); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); @@ -85,12 +85,12 @@ TEST_F(BleMediumTest, CanStartScanning) { CountDownLatch lost_latch(1); ble_a.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { found_latch.CountDown(); }, .peripheral_lost_cb = [&lost_latch](BlePeripheral& peripheral, @@ -120,12 +120,12 @@ TEST_F(BleMediumTest, CanStopDiscovery) { CountDownLatch lost_latch(1); ble_a.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { found_latch.CountDown(); }, .peripheral_lost_cb = [&lost_latch](BlePeripheral& peripheral, @@ -156,12 +156,12 @@ TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) { BlePeripheral* discovered_peripheral = nullptr; ble_a.StartScanning( - service_id, - fast_advertisement_service_uuid, + service_id, fast_advertisement_service_uuid, DiscoveredPeripheralCallback{ .peripheral_discovered_cb = [&found_latch, &discovered_peripheral]( BlePeripheral& peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, bool fast_advertisement) { NEARBY_LOG( INFO, diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h index df9fcae8..80babc9c 100644 --- a/cpp/platform_v2/public/future.h +++ b/cpp/platform_v2/public/future.h @@ -20,6 +20,9 @@ class Future final { void AddListener(Runnable runnable, api::Executor* executor) { impl_->AddListener(std::move(runnable), executor); } + bool IsSet() const { + return impl_->IsSet(); + } private: // Instance of future implementation is wrapped in shared_ptr<> to make diff --git a/cpp/platform_v2/public/settable_future.h b/cpp/platform_v2/public/settable_future.h index 7649df07..4a45aab0 100644 --- a/cpp/platform_v2/public/settable_future.h +++ b/cpp/platform_v2/public/settable_future.h @@ -25,8 +25,9 @@ class SettableFuture : public api::SettableFuture { exception_ = {Exception::kSuccess}; completed_.Notify(); InvokeAllLocked(); + return true; } - return true; + return false; } void AddListener(Runnable runnable, api::Executor* executor) override { @@ -38,6 +39,11 @@ class SettableFuture : public api::SettableFuture { } } + bool IsSet() const { + MutexLock lock(&mutex_); + return done_; + } + bool SetException(Exception exception) override { MutexLock lock(&mutex_); return SetExceptionLocked(exception); @@ -94,7 +100,7 @@ class SettableFuture : public api::SettableFuture { listeners_.clear(); } - Mutex mutex_; + mutable Mutex mutex_; ConditionVariable completed_{&mutex_}; std::vector>> listeners_; bool done_{false}; diff --git a/script/oss.py b/script/oss.py index 21a70028..b75e78ef 100755 --- a/script/oss.py +++ b/script/oss.py @@ -19,6 +19,7 @@ import argparse import os import re import shutil +import stat import sys copy_header="""Copyright 2020 Google LLC @@ -200,6 +201,7 @@ def post_process_oss_files(path, args): modified = True if modified: + os.chmod(fname, os.stat(fname).st_mode | stat.S_IWRITE) with open(fname, "w") as f: for line in lines: f.write(line)

See go/connections-ble-advertisement for more information. +class BleAdvertisement { + public: + // Versions of the BleAdvertisement. + enum class Version { + kUndefined = 0, + kV1 = 1, + // Version is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + static constexpr int kVersionAndPcpLength = 1; + // Should be defined as EndpointManager::kEndpointIdLength, but that + // involves making BleAdvertisement templatized on Platform just for + // that one little thing, so forget it (at least for now). + static constexpr int kEndpointIdLength = 4; + static constexpr int kEndpointNameSizeLength = 1; + static constexpr int kBluetoothMacAddressLength = 6; + static constexpr int kMinAdvertisementLength = + kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength + + kEndpointNameSizeLength + kBluetoothMacAddressLength; + static constexpr int kMaxEndpointNameLength = 131; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kEndpointNameLengthBitmask = 0x0FF; + + BleAdvertisement() = default; + BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash, + const std::string& endpoint_id, + const std::string& endpoint_name, + const std::string& bluetooth_mac_address); + explicit BleAdvertisement(const ByteArray& ble_advertisement_bytes); + ~BleAdvertisement() = default; + + BleAdvertisement(const BleAdvertisement&) = default; + BleAdvertisement& operator=(const BleAdvertisement&) = default; + BleAdvertisement(BleAdvertisement&&) = default; + BleAdvertisement& operator=(BleAdvertisement&&) = default; + + explicit operator ByteArray() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline ByteArray GetServiceIdHash() const{ return service_id_hash_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline std::string GetBluetoothMacAddress() const { + return bluetooth_mac_address_; + } + + private: + std::uint32_t ComputeEndpointNameLength( + const ByteArray& ble_advertisement_bytes) const; + ByteArray BluetoothMacAddressHexStringToBytes( + const std::string& bluetooth_mac_address) const; + std::string HexBytesToColonDelimitedString(const ByteArray& hex_bytes) const; + bool IsBluetoothMacAddressUnset( + const ByteArray& bluetooth_mac_address_bytes) const; + + Version version_ = Version::kUndefined; + Pcp pcp_ = Pcp::kUnknown; + ByteArray service_id_hash_; + std::string endpoint_id_; + std::string endpoint_name_; + std::string bluetooth_mac_address_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/ble_advertisement_test.cc b/cpp/core_v2/internal/ble_advertisement_test.cc new file mode 100644 index 00000000..ec899a15 --- /dev/null +++ b/cpp/core_v2/internal/ble_advertisement_test.cc @@ -0,0 +1,272 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/ble_advertisement.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +const char kEndPointID[] = "AB12"; +const char kEndpointName[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +const char kBluetoothMacAddress[] = "00:00:E6:88:64:13"; + +TEST(BleAdvertisementTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointName) { + std::string empty_endpoint_name; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + empty_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(empty_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointName) { + std::string emoji_endpoint_name("\u0001F450 \u0001F450"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + emoji_endpoint_name, kBluetoothMacAddress); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(emoji_endpoint_name, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointName) { + std::string long_endpoint_name(BleAdvertisement::kMaxEndpointNameLength + 1, + 'x'); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + long_endpoint_name, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(bad_version, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, bad_pcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) { + std::string empty_bluetooth_mac_address = ""; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, empty_bluetooth_mac_address); + + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) { + std::string bad_bluetooth_mac_address = "022:00"; + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, bad_bluetooth_mac_address); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty()); +} + +TEST(BleAdvertisementTest, ConstructionFromBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(org_ble_advertisement); + + auto ble_advertisement = BleAdvertisement(ble_advertisement_bytes); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress()); +} + +// Bytes at the end should be ignored so that they can be used as reserve bytes +// in the future. +TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Add bytes to the end of the valid Ble advertisement. + auto long_ble_advertisement_bytes = + ByteArray(BleAdvertisement::kMinAdvertisementLength + 1000); + ASSERT_LE(ble_advertisement_bytes.size(), + long_ble_advertisement_bytes.size()); + memcpy(long_ble_advertisement_bytes.data(), + ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + + auto long_ble_advertisement = BleAdvertisement(long_ble_advertisement_bytes); + auto is_valid = long_ble_advertisement.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(kEndPointID, long_ble_advertisement.GetEndpointId()); + EXPECT_EQ(kEndpointName, long_ble_advertisement.GetEndpointName()); + EXPECT_EQ(kBluetoothMacAddress, + long_ble_advertisement.GetBluetoothMacAddress()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + auto ble_advertisement = BleAdvertisement(ByteArray()); + auto is_valid = ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Shorten the valid Ble Advertisement. + auto short_ble_advertisement_bytes( + ByteArray(ble_advertisement_bytes.data(), + BleAdvertisement::kMinAdvertisementLength - 1)); + + auto short_ble_advertisement = + BleAdvertisement(short_ble_advertisement_bytes); + auto is_valid = short_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(BleAdvertisementTest, + ConstructionFromByesWithWrongEndpointNameLengthFails) { + // Serialize good data into a good Ble Advertisement. + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto ble_advertisement = + BleAdvertisement(kVersion, kPcp, service_id_hash, kEndPointID, + kEndpointName, kBluetoothMacAddress); + auto ble_advertisement_bytes = ByteArray(ble_advertisement); + + // Corrupt the EndpointNameLength bits. + std::string corrupt_ble_advertisement_string(ble_advertisement_bytes.data(), + ble_advertisement_bytes.size()); + corrupt_ble_advertisement_string[8] ^= 0x0FF; + auto corrupt_ble_advertisement_bytes = + ByteArray(corrupt_ble_advertisement_string); + + auto corrupt_ble_advertisement = + BleAdvertisement(corrupt_ble_advertisement_bytes); + auto is_valid = corrupt_ble_advertisement.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.cc b/cpp/core_v2/internal/client_proxy.cc new file mode 100644 index 00000000..5eda67d7 --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.cc @@ -0,0 +1,475 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/client_proxy.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/str_cat.h" + +namespace location { +namespace nearby { +namespace connections { + +ClientProxy::ClientProxy() : client_id_(Prng().NextInt64()) {} + +ClientProxy::~ClientProxy() { Reset(); } + +std::int64_t ClientProxy::GetClientId() const { return client_id_; } + +std::string ClientProxy::GenerateLocalEndpointId() { + // 1) Concatenate the DeviceID with this ClientID. + // 2) Compute a hash of that concatenation. + // 3) Base64-encode that hash, to make it human-readable. + // 4) Use only the first 4 bytes of that Base64 encoding. + ByteArray id_hash(Crypto::Sha256( + absl::StrCat(api::ImplementationPlatform::GetDeviceId(), GetClientId()))); + + return Base64Utils::Encode(id_hash).substr(0, kEndpointIdLength); +} + +void ClientProxy::Reset() { + MutexLock lock(&mutex_); + + StoppedAdvertising(); + StoppedDiscovery(); + RemoveAllEndpoints(); +} + +void ClientProxy::StartedAdvertising( + const std::string& service_id, Strategy strategy, + const ConnectionListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + advertising_info_ = {service_id, listener}; +} + +void ClientProxy::StoppedAdvertising() { + MutexLock lock(&mutex_); + + if (IsAdvertising()) { + advertising_info_.Clear(); + } +} + +bool ClientProxy::IsAdvertising() const { + MutexLock lock(&mutex_); + + return !advertising_info_.IsEmpty(); +} + +std::string ClientProxy::GetAdvertisingServiceId() const { + MutexLock lock(&mutex_); + return advertising_info_.service_id; +} + +void ClientProxy::StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& listener, + absl::Span mediums) { + MutexLock lock(&mutex_); + + discovery_info_ = DiscoveryInfo{service_id, listener}; +} + +void ClientProxy::StoppedDiscovery() { + MutexLock lock(&mutex_); + + if (IsDiscovering()) { + discovered_endpoint_ids_.clear(); + discovery_info_.Clear(); + } +} + +bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const { + MutexLock lock(&mutex_); + + return IsDiscovering() && service_id == discovery_info_.service_id; +} + +bool ClientProxy::IsDiscovering() const { + MutexLock lock(&mutex_); + + return !discovery_info_.IsEmpty(); +} + +std::string ClientProxy::GetDiscoveryServiceId() const { + MutexLock lock(&mutex_); + + return discovery_info_.service_id; +} + +void ClientProxy::OnEndpointFound(const std::string& service_id, + const std::string& endpoint_id, + const std::string& endpoint_name, + proto::connections::Medium medium) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + if (discovered_endpoint_ids_.count(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + discovered_endpoint_ids_.insert(endpoint_id); + discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_name, + service_id); +} + +void ClientProxy::OnEndpointLost(const std::string& service_id, + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!IsDiscoveringServiceId(service_id)) return; + const auto it = discovered_endpoint_ids_.find(endpoint_id); + if (it == discovered_endpoint_ids_.end()) return; + discovered_endpoint_ids_.erase(it); + discovery_info_.listener.endpoint_lost_cb(endpoint_id); +} + +void ClientProxy::OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener) { + MutexLock lock(&mutex_); + + // Whether this is incoming or outgoing, the local and remote endpoints both + // still need to accept this connection, so set its establishment status to + // PENDING. + auto result = connections_.emplace( + endpoint_id, Connection{ + .is_incoming = info.is_incoming_connection, + .connection_listener = listener, + }); + // Instead of using structured binding which is nice, but banned + // (can not use c++17 features, until chromium does) we unpack manually. + auto& pair_iter = result.first; + bool& inserted = result.second; + DCHECK(inserted); + const Connection& item = pair_iter->second; + // Notify the client. + // + // Note: we allow devices to connect to an advertiser even after it stops + // advertising, so no need to check IsAdvertising() here. + item.connection_listener.initiated_cb(endpoint_id, info); +} + +void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + // Notify the client. + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.accepted_cb(endpoint_id); + item->status = Connection::kConnected; + } +} + +void ClientProxy::OnConnectionRejected(const std::string& endpoint_id, + const Status& status) { + MutexLock lock(&mutex_); + + if (!HasPendingConnectionToEndpoint(endpoint_id)) { + NEARBY_LOG(INFO, "ClientProxy [Rejected]: no pending connection; id=%s", + endpoint_id.c_str()); + return; + } + + // Notify the client. + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.rejected_cb(endpoint_id, status); + OnDisconnected(endpoint_id, false /* notify */); + } +} + +void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id, + std::int32_t quality) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->connection_listener.bandwidth_changed_cb(endpoint_id, quality); + } +} + +void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + if (notify) { + item->connection_listener.disconnected_cb({endpoint_id}); + } + connections_.erase(endpoint_id); + } +} + +bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status == status; + } + return false; +} + +bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const { + return ConnectionStatusMatches(endpoint_id, Connection::kConnected); +} + +std::vector ClientProxy::GetMatchingEndpoints( + std::function pred) const { + MutexLock lock(&mutex_); + + std::vector connected_endpoints; + + for (const auto& pair : connections_) { + const auto& endpoint_id = pair.first; + const auto& connection = pair.second; + if (pred(connection)) { + connected_endpoints.push_back(endpoint_id); + } + } + return connected_endpoints; +} + +std::vector ClientProxy::GetPendingConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status != Connection::kConnected; + }); +} + +std::vector ClientProxy::GetConnectedEndpoints() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected; + }); +} + +std::int32_t ClientProxy::GetNumOutgoingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + !connection.is_incoming; + }) + .size(); +} + +std::int32_t ClientProxy::GetNumIncomingConnections() const { + return GetMatchingEndpoints([](const Connection& connection) { + return connection.status == Connection::kConnected && + connection.is_incoming; + }) + .size(); +} + +bool ClientProxy::HasPendingConnectionToEndpoint( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return item->status != Connection::kConnected; + } + return false; +} + +bool ClientProxy::HasLocalEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointAccepted | + Connection::kLocalEndpointRejected)); +} + +bool ClientProxy::HasRemoteEndpointResponded( + const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kRemoteEndpointAccepted | + Connection::kRemoteEndpointRejected)); +} + +void ClientProxy::LocalEndpointAcceptedConnection( + const std::string& endpoint_id, const PayloadListener& listener) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointAccepted); + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener = listener; + } +} + +void ClientProxy::LocalEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointRejected); +} + +void ClientProxy::RemoteEndpointAcceptedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::RemoteEndpointRejectedConnection( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (HasRemoteEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): Add logging. + return; + } + + AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointRejected); +} + +bool ClientProxy::IsConnectionAccepted(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains(endpoint_id, + Connection::kLocalEndpointAccepted) && + ConnectionStatusesContains(endpoint_id, + Connection::kRemoteEndpointAccepted); +} + +bool ClientProxy::IsConnectionRejected(const std::string& endpoint_id) const { + MutexLock lock(&mutex_); + + return ConnectionStatusesContains( + endpoint_id, + static_cast(Connection::kLocalEndpointRejected | + Connection::kRemoteEndpointRejected)); +} + +bool ClientProxy::LocalConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kLocalEndpointAccepted); +} + +bool ClientProxy::RemoteConnectionIsAccepted(std::string endpoint_id) const { + return ConnectionStatusesContains( + endpoint_id, ClientProxy::Connection::kRemoteEndpointAccepted); +} + +void ClientProxy::OnPayload(const std::string& endpoint_id, Payload payload) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_cb(endpoint_id, std::move(payload)); + } + } +} + +const ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) const { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +ClientProxy::Connection* ClientProxy::LookupConnection( + const std::string& endpoint_id) { + auto item = connections_.find(endpoint_id); + return item != connections_.end() ? &item->second : nullptr; +} + +void ClientProxy::OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info) { + MutexLock lock(&mutex_); + + if (IsConnectedToEndpoint(endpoint_id)) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->payload_listener.payload_progress_cb(endpoint_id, info); + } + } +} + +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() == rhs.GetClientId(); +} + +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs) { + return lhs.GetClientId() < rhs.GetClientId(); +} + +void ClientProxy::RemoveAllEndpoints() { + MutexLock lock(&mutex_); + + // Note: we may want to notify the client of onDisconnected() for each + // endpoint, in the case when this is called from stopAllEndpoints(). For now, + // just remove without notifying. + connections_.clear(); +} + +bool ClientProxy::ConnectionStatusesContains( + const std::string& endpoint_id, Connection::Status status_to_match) const { + const Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + return (item->status & status_to_match) != 0; + } + return false; +} + +void ClientProxy::AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append) { + Connection* item = LookupConnection(endpoint_id); + if (item != nullptr) { + item->status = + static_cast(item->status | status_to_append); + } +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/client_proxy.h b/cpp/core_v2/internal/client_proxy.h new file mode 100644 index 00000000..4b13bb28 --- /dev/null +++ b/cpp/core_v2/internal/client_proxy.h @@ -0,0 +1,231 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_CLIENT_PROXY_H_ +#define CORE_V2_INTERNAL_CLIENT_PROXY_H_ + +#include +#include +#include + +#include "core_v2/listeners.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "proto/connections_enums.pb.h" +// Prefer using absl:: versions of a set and a map; they tend to be more +// efficient: implementation is using open-addressing hash tables. +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// CLientProxy is tracking state of client's connection, and serves as +// a proxy for notifications sent to this client. +class ClientProxy final { + public: + static constexpr int kEndpointIdLength = 4; + + ClientProxy(); + ~ClientProxy(); + ClientProxy(ClientProxy&&) = default; + ClientProxy& operator=(ClientProxy&&) = default; + + std::int64_t GetClientId() const; + + std::string GenerateLocalEndpointId(); + + // Clears all the runtime state of this client. + void Reset(); + + // Marks this client as advertising with the given callbacks. + void StartedAdvertising( + const std::string& service_id, Strategy strategy, + const ConnectionListener& connection_lifecycle_listener, + absl::Span mediums); + // Marks this client as not advertising. + void StoppedAdvertising(); + bool IsAdvertising() const; + std::string GetAdvertisingServiceId() const; + + // Marks this client as discovering with the given callback. + void StartedDiscovery( + const std::string& service_id, Strategy strategy, + const DiscoveryListener& discovery_listener, + absl::Span mediums); + // Marks this client as not discovering at all. + void StoppedDiscovery(); + bool IsDiscoveringServiceId(const std::string& service_id) const; + bool IsDiscovering() const; + std::string GetDiscoveryServiceId() const; + + // Proxies to the client's DiscoveryListener::OnEndpointFound() callback. + void OnEndpointFound(const std::string& service_id, + const std::string& endpoint_id, + const std::string& endpoint_name, + proto::connections::Medium medium); + // Proxies to the client's DiscoveryListener::OnEndpointLost() callback. + void OnEndpointLost(const std::string& service_id, + const std::string& endpoint_id); + + // Proxies to the client's ConnectionListener::OnInitiated() callback. + void OnConnectionInitiated(const std::string& endpoint_id, + const ConnectionResponseInfo& info, + const ConnectionListener& listener); + + // Proxies to the client's ConnectionListener::OnAccepted() callback. + void OnConnectionAccepted(const std::string& endpoint_id); + // Proxies to the client's ConnectionListener::OnRejected() callback. + void OnConnectionRejected(const std::string& endpoint_id, + const Status& status); + + void OnBandwidthChanged(const std::string& endpoint_id, std::int32_t quality); + + // Removes the endpoint from this client's list of connected endpoints. If + // notify is true, also calls the client's + // ConnectionListener.disconnected_cb() callback. + void OnDisconnected(const std::string& endpoint_id, bool notify); + + // Returns true if it's safe to send payloads to this endpoint. + bool IsConnectedToEndpoint(const std::string& endpoint_id) const; + // Returns all endpoints that can safely be sent payloads. + std::vector GetConnectedEndpoints() const; + // Returns all endpoints that are still awaiting acceptance. + std::vector GetPendingConnectedEndpoints() const; + // Returns the number of endpoints that are connected and outgoing. + std::int32_t GetNumOutgoingConnections() const; + // Returns the number of endpoints that are connected and incoming. + std::int32_t GetNumIncomingConnections() const; + // If true, then we're in the process of approving (or rejecting) a + // connection. No payloads should be sent until isConnectedToEndpoint() + // returns true. + bool HasPendingConnectionToEndpoint(const std::string& endpoint_id) const; + // Returns true if the local endpoint has already marked itself as + // accepted/rejected. + bool HasLocalEndpointResponded(const std::string& endpoint_id) const; + // Returns true if the remote endpoint has already marked themselves as + // accepted/rejected. + bool HasRemoteEndpointResponded(const std::string& endpoint_id) const; + // Marks the local endpoint as having accepted the connection. + void LocalEndpointAcceptedConnection(const std::string& endpoint_id, + const PayloadListener& listener); + // Marks the local endpoint as having rejected the connection. + void LocalEndpointRejectedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having accepted the connection. + void RemoteEndpointAcceptedConnection(const std::string& endpoint_id); + // Marks the remote endpoint as having rejected the connection. + void RemoteEndpointRejectedConnection(const std::string& endpoint_id); + // Returns true if both the local endpoint and the remote endpoint have + // accepted the connection. + bool IsConnectionAccepted(const std::string& endpoint_id) const; + // Returns true if either the local endpoint or the remote endpoint has + // rejected the connection. + bool IsConnectionRejected(const std::string& endpoint_id) const; + + // Proxies to the client's PayloadListener::OnPayload() callback. + void OnPayload(const std::string& endpoint_id, Payload payload); + // Proxies to the client's PayloadListener::OnPayloadProgress() callback. + void OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info); + bool LocalConnectionIsAccepted(std::string endpoint_id) const; + bool RemoteConnectionIsAccepted(std::string endpoint_id) const; + + private: + struct Connection { + // Status: may be either: + // Connection::PENDING, or combination of + // Connection::LOCAL_ENDPOINT_ACCEPTED: + // Connection::LOCAL_ENDPOINT_REJECTED and + // Connection::REMOTE_ENDPOINT_ACCEPTED: + // Connection::REMOTE_ENDPOINT_REJECTED, or + // Connection::CONNECTED. + // Only when this is set to CONNECTED should you allow payload transfers. + // + // We want this enum to be implicitly convertible to int, because + // we perform bit operations on it. + enum Status : uint8_t { + kPending = 0, + kLocalEndpointAccepted = 1 << 0, + kLocalEndpointRejected = 1 << 1, + kRemoteEndpointAccepted = 1 << 2, + kRemoteEndpointRejected = 1 << 3, + kConnected = 1 << 4, + }; + bool is_incoming{false}; + Status status{kPending}; + ConnectionListener connection_listener; + PayloadListener payload_listener; + }; + + struct AdvertisingInfo { + std::string service_id; + ConnectionListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + struct DiscoveryInfo { + std::string service_id; + DiscoveryListener listener; + void Clear() { service_id.clear(); } + bool IsEmpty() const { return service_id.empty(); } + }; + + void RemoveAllEndpoints(); + bool ConnectionStatusesContains(const std::string& endpoint_id, + Connection::Status status_to_match) const; + void AppendConnectionStatus(const std::string& endpoint_id, + Connection::Status status_to_append); + + const Connection* LookupConnection(const std::string& endpoint_id) const; + Connection* LookupConnection(const std::string& endpoint_id); + bool ConnectionStatusMatches(const std::string& endpoint_id, + Connection::Status status) const; + std::vector GetMatchingEndpoints( + std::function pred) const; + + mutable RecursiveMutex mutex_; + std::int64_t client_id_; + + // If not empty, we are currently advertising and accepting connection + // requests for the given service_id. + AdvertisingInfo advertising_info_; + + // If not empty, we are currently discovering for the given service_id. + DiscoveryInfo discovery_info_; + + // Maps endpoint_id to endpoint connection state. + absl::flat_hash_map connections_; + + // A cache of endpoint ids that we've already notified the discoverer of. We + // check this cache before calling onEndpointFound() so that we don't notify + // the client multiple times for the same endpoint. This would otherwise + // happen because some mediums (like Bluetooth) repeatedly give us the same + // endpoints after each scan. + absl::flat_hash_set discovered_endpoint_ids_; +}; + +// Operator overloads when comparing Ptr. +bool operator==(const ClientProxy& lhs, const ClientProxy& rhs); +bool operator<(const ClientProxy& lhs, const ClientProxy& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_CLIENT_PROXY_H_ diff --git a/cpp/core_v2/internal/client_proxy_test.cc b/cpp/core_v2/internal/client_proxy_test.cc new file mode 100644 index 00000000..7adc6488 --- /dev/null +++ b/cpp/core_v2/internal/client_proxy_test.cc @@ -0,0 +1,371 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/client_proxy.h" + +#include + +#include "core_v2/listeners.h" +#include "core_v2/strategy.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::testing::MockFunction; +using ::testing::StrictMock; + +class ClientProxyTest : public testing::Test { + protected: + struct MockDiscoveryListener { + StrictMock> + endpoint_found_cb; + StrictMock> + endpoint_lost_cb; + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + }; + struct MockPayloadListener { + StrictMock< + MockFunction> + payload_cb; + StrictMock> + payload_progress_cb; + }; + + struct Endpoint { + std::string name; + std::string id; + }; + + Endpoint StartAdvertising(ClientProxy* client, ConnectionListener listener) { + Endpoint endpoint{ + .name = "advertising endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedAdvertising(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + Endpoint StartDiscovery(ClientProxy* client, DiscoveryListener listener) { + Endpoint endpoint{ + .name = "discovery endpoint name", + .id = client->GenerateLocalEndpointId(), + }; + client->StartedDiscovery(service_id_, strategy_, listener, + absl::MakeSpan(mediums_)); + return endpoint; + } + + void OnDiscoveryEndpointFound(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_found_cb, Call).Times(1); + client->OnEndpointFound(service_id_, endpoint.id, endpoint.name, medium_); + } + + void OnDiscoveryEndpointLost(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_.endpoint_lost_cb, Call).Times(1); + client->OnEndpointLost(service_id_, endpoint.id); + } + + void OnDiscoveryConnectionInitiated(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1); + const std::string auth_token{"auth_token"}; + const ByteArray raw_auth_token{auth_token}; + advertising_connection_info_.remote_endpoint_name = endpoint.name; + client->OnConnectionInitiated(endpoint.id, advertising_connection_info_, + discovery_connection_listener_); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + } + + void OnDiscoveryConnectionLocalAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointAcceptedConnection(endpoint.id, payload_listener_); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointAcceptedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_TRUE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionLocalRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id)); + client->LocalEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->LocalConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionRemoteRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id)); + EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id)); + client->RemoteEndpointRejectedConnection(endpoint.id); + EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id)); + EXPECT_FALSE(client->RemoteConnectionIsAccepted(endpoint.id)); + } + + void OnDiscoveryConnectionAccepted(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.accepted_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint.id)); + client->OnConnectionAccepted(endpoint.id); + } + + void OnDiscoveryConnectionRejected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.rejected_cb, Call).Times(1); + EXPECT_TRUE(client->IsConnectionRejected(endpoint.id)); + client->OnConnectionRejected(endpoint.id, {Status::kConnectionRejected}); + } + + void OnDiscoveryBandwidthChanged(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.bandwidth_changed_cb, Call).Times(1); + client->OnBandwidthChanged(endpoint.id, 1); + } + + void OnDiscoveryConnectionDisconnected(ClientProxy* client, + const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_connection_.disconnected_cb, Call).Times(1); + client->OnDisconnected(endpoint.id, true); + } + + void OnPayload(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_cb, Call).Times(1); + client->OnPayload(endpoint.id, Payload(payload_bytes_)); + } + + void OnPayloadProgress(ClientProxy* client, const Endpoint& endpoint) { + EXPECT_CALL(mock_discovery_payload_.payload_progress_cb, Call).Times(1); + client->OnPayloadProgress(endpoint.id, {}); + } + + MockDiscoveryListener mock_discovery_; + MockConnectionListener mock_discovery_connection_; + MockPayloadListener mock_discovery_payload_; + + proto::connections::Medium medium_{proto::connections::Medium::BLUETOOTH}; + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH, + }; + Strategy strategy_{Strategy::kP2pPointToPoint}; + const std::string service_id_{"service"}; + ClientProxy client1_; + ClientProxy client2_; + std::string auth_token_ = "auth_token"; + ByteArray raw_auth_token_ = ByteArray(auth_token_); + ByteArray payload_bytes_{"bytes"}; + ConnectionResponseInfo advertising_connection_info_{ + .authentication_token = auth_token_, + .raw_authentication_token = raw_auth_token_, + .is_incoming_connection = true, + }; + ConnectionListener advertising_connection_listener_; + ConnectionListener discovery_connection_listener_{ + .initiated_cb = mock_discovery_connection_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_discovery_connection_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_discovery_connection_.rejected_cb.AsStdFunction(), + .disconnected_cb = + mock_discovery_connection_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_discovery_connection_.bandwidth_changed_cb.AsStdFunction(), + }; + DiscoveryListener discovery_listener_{ + .endpoint_found_cb = mock_discovery_.endpoint_found_cb.AsStdFunction(), + .endpoint_lost_cb = mock_discovery_.endpoint_lost_cb.AsStdFunction(), + }; + PayloadListener payload_listener_{ + .payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(), + .payload_progress_cb = + mock_discovery_payload_.payload_progress_cb.AsStdFunction(), + }; +}; + +TEST_F(ClientProxyTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(ClientProxyTest, ClientIdIsUnique) { + EXPECT_NE(client1_.GetClientId(), client2_.GetClientId()); +} + +TEST_F(ClientProxyTest, GeneratedEndpointIdIsUnique) { + EXPECT_NE(client1_.GenerateLocalEndpointId(), + client2_.GenerateLocalEndpointId()); +} + +TEST_F(ClientProxyTest, ResetClearsState) { + client1_.Reset(); + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedAdvertisingChangesStateFromIdle) { + client1_.StartedAdvertising(service_id_, strategy_, {}, {}); + + EXPECT_TRUE(client1_.IsAdvertising()); + EXPECT_FALSE(client1_.IsDiscovering()); + EXPECT_EQ(client1_.GetAdvertisingServiceId(), service_id_); + EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty()); +} + +TEST_F(ClientProxyTest, StartedDiscoveryChangesStateFromIdle) { + client1_.StartedDiscovery(service_id_, strategy_, {}, {}); + + EXPECT_FALSE(client1_.IsAdvertising()); + EXPECT_TRUE(client1_.IsDiscovering()); + EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty()); + EXPECT_EQ(client1_.GetDiscoveryServiceId(), service_id_); +} + +TEST_F(ClientProxyTest, OnEndpointFoundFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnEndpointLostFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryEndpointLost(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnConnectionInitiatedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnBandwidthChangedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnDiscoveryBandwidthChanged(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnDisconnectedFiresNotificationInDiscovery) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionDisconnected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, LocalEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointAcceptedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, RemoteEndpointRejectedConnectionChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteRejected(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayload(&client2_, advertising_endpoint); +} + +TEST_F(ClientProxyTest, OnPayloadProgressChangesState) { + Endpoint advertising_endpoint = + StartAdvertising(&client1_, advertising_connection_listener_); + StartDiscovery(&client2_, discovery_listener_); + OnDiscoveryEndpointFound(&client2_, advertising_endpoint); + OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint); + OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint); + OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint); + OnPayloadProgress(&client2_, advertising_endpoint); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.cc b/cpp/core_v2/internal/encryption_runner.cc new file mode 100644 index 00000000..383f95b2 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.cc @@ -0,0 +1,382 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/encryption_runner.h" + +#include +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/cancelable_alarm.h" +#include "platform_v2/public/logging.h" +#include "securegcm/ukey2_handshake.h" +#include "absl/strings/ascii.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::Duration kTimeout = absl::Seconds(15); +constexpr std::int32_t kMaxUkey2VerificationStringLength = 32; +constexpr std::int32_t kTokenLength = 5; +constexpr securegcm::UKey2Handshake::HandshakeCipher kCipher = + securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512; + +// Transforms a raw UKEY2 token (which is a random ByteArray that's +// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only +// uses [A-Z], [0-9], '_', '-' for each character. +std::string ToHumanReadableString(const ByteArray& token) { + std::string result = Base64Utils::Encode(token).substr(0, kTokenLength); + absl::AsciiStrToUpper(&result); + return result; +} + +bool HandleEncryptionSuccess(const std::string& endpoint_id, + std::unique_ptr ukey2, + const EncryptionRunner::ResultListener& listener) { + std::unique_ptr verification_string = + ukey2->GetVerificationString(kMaxUkey2VerificationStringLength); + if (verification_string == nullptr) { + return false; + } + + ByteArray raw_authentication_token(*verification_string); + + listener.on_success_cb(endpoint_id, std::move(ukey2), + ToHumanReadableString(raw_authentication_token), + raw_authentication_token); + + return true; +} + +void CancelableAlarmRunnable(ClientProxy* client_proxy, + const std::string& endpoint_id, + EndpointChannel* endpoint_channel) { + NEARBY_LOG(INFO, + "Timing out encryption for client %" PRId64 + " to endpoint %s after %" PRId64 " ms", + client_proxy->GetClientId(), endpoint_id.c_str(), + static_cast(absl::ToInt64Milliseconds(kTimeout))); + endpoint_channel->Close(); +} + +class ServerRunnable final { + public: + ServerRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startServer() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr server = + securegcm::UKey2Handshake::ForResponder(kCipher); + if (server == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + ExceptionOr client_init = channel_->Read(); + if (!client_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + server->ParseHandshakeMessage(std::string(client_init.result())); + + // Java code throws a HandshakeException / AlertException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 1 from endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + std::unique_ptr server_init = + server->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (server_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_exception = + channel_->Write(ByteArray(std::move(*server_init))); + if (!write_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), wrote UKEY2 Message 2 to endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + ExceptionOr client_finish = channel_->Read(); + + if (!client_finish.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + parse_result = + server->ParseHandshakeMessage(std::string(client_finish.result())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startServer(), read UKEY2 Message 3 from endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.Cancel(); + + if (!HandleEncryptionSuccess(endpoint_id_, std::move(server), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startServer(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startServer(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +class ClientRunnable final { + public: + ClientRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor, + const std::string& endpoint_id, EndpointChannel* channel, + EncryptionRunner::ResultListener&& listener) + : client_(client), + alarm_executor_(alarm_executor), + endpoint_id_(endpoint_id), + channel_(channel), + listener_(std::move(listener)) {} + + void operator()() const { + CancelableAlarm timeout_alarm( + "EncryptionRunner.startClient() timeout", + [this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); }, + kTimeout, alarm_executor_); + + std::unique_ptr crypto = + securegcm::UKey2Handshake::ForInitiator(kCipher); + + // Java code throws a HandshakeException. + if (crypto == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + // Message 1 (Client Init) + std::unique_ptr client_init = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_init == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_init_exception = channel_->Write(ByteArray(*client_init)); + if (!write_init_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 1 to endpoint %s", + endpoint_id_.c_str()); + + // Message 2 (Server Init) + ExceptionOr server_init = channel_->Read(); + + if (!server_init.ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + securegcm::UKey2Handshake::ParseResult parse_result = + crypto->ParseHandshakeMessage(std::string(server_init.result())); + + // Java code throws an AlertException or a HandshakeException. + if (!parse_result.success) { + LogException(); + if (parse_result.alert_to_send != nullptr) { + HandleAlertException(parse_result); + } + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), read UKEY2 Message 2 from endpoint %s", + endpoint_id_.c_str()); + + // Message 3 (Client Finish) + std::unique_ptr client_finish = + crypto->GetNextHandshakeMessage(); + + // Java code throws a HandshakeException. + if (client_finish == nullptr) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + Exception write_finish_exception = + channel_->Write(ByteArray(*client_finish)); + if (!write_finish_exception.Ok()) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + + NEARBY_LOG(INFO, "In startClient(), wrote UKEY2 Message 3 to endpoint %s", + endpoint_id_.c_str()); + + timeout_alarm.Cancel(); + + if (!HandleEncryptionSuccess(endpoint_id_, std::move(crypto), listener_)) { + LogException(); + HandleHandshakeOrIoException(&timeout_alarm); + return; + } + } + + private: + void LogException() const { + NEARBY_LOG(ERROR, "In startClient(), UKEY2 failed with endpoint %s", + endpoint_id_.c_str()); + } + + void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const { + timeout_alarm->Cancel(); + listener_.on_failure_cb(endpoint_id_, channel_); + } + + void HandleAlertException( + const securegcm::UKey2Handshake::ParseResult& parse_result) const { + Exception write_exception = + channel_->Write(ByteArray(*parse_result.alert_to_send)); + if (!write_exception.Ok()) { + NEARBY_LOG(WARNING, + "In startClient(), client %" PRId64 + " failed to pass the alert error message to endpoint %s", + client_->GetClientId(), endpoint_id_.c_str()); + } + } + + ClientProxy* client_; + ScheduledExecutor* alarm_executor_; + const std::string endpoint_id_; + EndpointChannel* channel_; + EncryptionRunner::ResultListener listener_; +}; + +} // namespace + +EncryptionRunner::~EncryptionRunner() { + // Stop all the ongoing Runnables (as gracefully as possible). + client_executor_.Shutdown(); + server_executor_.Shutdown(); + alarm_executor_.Shutdown(); +} + +void EncryptionRunner::StartServer( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + server_executor_.Execute( + [runnable{ServerRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +void EncryptionRunner::StartClient( + ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + EncryptionRunner::ResultListener&& listener) { + client_executor_.Execute( + [runnable{ClientRunnable(client_proxy, &alarm_executor_, endpoint_id, + endpoint_channel, std::move(listener))}]() { + runnable(); + }); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/encryption_runner.h b/cpp/core_v2/internal/encryption_runner.h new file mode 100644 index 00000000..8dc5c126 --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner.h @@ -0,0 +1,86 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ +#define CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/scheduled_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "securegcm/ukey2_handshake.h" + +namespace location { +namespace nearby { +namespace connections { + +// Encrypts a connection over UKEY2. +// +// NOTE: Stalled EndpointChannels will be disconnected after kTimeout. +// This is to prevent unverified endpoints from maintaining an +// indefinite connection to us. +class EncryptionRunner { + public: + EncryptionRunner() = default; + ~EncryptionRunner(); + + struct ResultListener { + // @EncryptionRunnerThread + std::function ukey2, + const std::string& auth_token, + const ByteArray& raw_auth_token)> + on_success_cb = + DefaultCallback, + const std::string&, const ByteArray&>(); + + // Encryption has failed. The remote_endpoint_id and channel are given so + // that any pending state can be cleaned up. + // + // We return the EndpointChannel because, at this stage, simultaneous + // connections are a possibility. Use this channel to verify that the state + // you're cleaning up is for this EndpointChannel, and not state for another + // channel to the same endpoint. + // + // @EncryptionRunnerThread + std::function + on_failure_cb = DefaultCallback(); + }; + + // @AnyThread + void StartServer(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + // @AnyThread + void StartClient(ClientProxy* client_proxy, const std::string& endpoint_id, + EndpointChannel* endpoint_channel, + ResultListener&& result_listener); + + private: + ScheduledExecutor alarm_executor_; + SingleThreadExecutor server_executor_; + SingleThreadExecutor client_executor_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENCRYPTION_RUNNER_H_ diff --git a/cpp/core_v2/internal/encryption_runner_test.cc b/cpp/core_v2/internal/encryption_runner_test.cc new file mode 100644 index 00000000..c72dddac --- /dev/null +++ b/cpp/core_v2/internal/encryption_runner_test.cc @@ -0,0 +1,142 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/encryption_runner.h" + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/pipe.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::Medium; + +class FakeEndpointChannel : public EndpointChannel { + public: + FakeEndpointChannel(InputStream* in, OutputStream* out) + : in_(in), out_(out) {} + ExceptionOr Read() override { + read_timestamp_ = SystemClock::ElapsedRealtime(); + return in_ ? in_->Read(Pipe::kChunkSize) + : ExceptionOr{Exception::kIo}; + } + Exception Write(const ByteArray& data) override { + return out_ ? out_->Write(data) : Exception{Exception::kIo}; + } + void Close() override { + if (in_) in_->Close(); + if (out_) out_->Close(); + } + void Close(proto::connections::DisconnectionReason reason) override { + Close(); + } + std::string GetType() const override { return "fake-channel-type"; } + std::string GetName() const override { return "fake-channel"; } + Medium GetMedium() const override { return Medium::BLE; } + void EnableEncryption( + securegcm::D2DConnectionContextV1* connection_context) override {} + bool IsPaused() const override { return false; } + void Pause() override {} + void Resume() override {} + absl::Time GetLastReadTimestamp() const override { return read_timestamp_; } + + private: + InputStream* in_ = nullptr; + OutputStream* out_ = nullptr; + absl::Time read_timestamp_ = absl::InfinitePast(); +}; + +struct User { + User(Pipe* reader, Pipe* writer) + : channel(&reader->GetInputStream(), &writer->GetOutputStream()) {} + + FakeEndpointChannel channel; + EncryptionRunner crypto; + ClientProxy client; +}; + +struct Response { + enum class Status { + kUnknown = 0, + kDone = 1, + kFailed = 2, + }; + + CountDownLatch latch{2}; + Status server_status = Status::kUnknown; + Status client_status = Status::kUnknown; +}; + +TEST(EncryptionRunnerTest, ConstructorDestructorWorks) { EncryptionRunner enc; } + +TEST(EncryptionRunnerTest, ReadWrite) { + Pipe from_a_to_b; + Pipe from_b_to_a; + User user_a(/*reader=*/&from_b_to_a, /*writer=*/&from_a_to_b); + User user_b(/*reader=*/&from_a_to_b, /*writer=*/&from_b_to_a); + Response response; + + user_a.crypto.StartServer( + &user_a.client, "endpoint_id", &user_a.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.server_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.server_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + user_b.crypto.StartClient( + &user_b.client, "endpoint_id", &user_b.channel, + { + .on_success_cb = + [&response](const string& endpoint_id, + std::unique_ptr ukey2, + const string& auth_token, + const ByteArray& raw_auth_token) { + response.client_status = Response::Status::kDone; + response.latch.CountDown(); + }, + .on_failure_cb = + [&response](const string& endpoint_id, EndpointChannel* channel) { + response.client_status = Response::Status::kFailed; + response.latch.CountDown(); + }, + }); + EXPECT_TRUE(response.latch.Await(absl::Milliseconds(5000)).result()); + EXPECT_EQ(response.server_status, Response::Status::kDone); + EXPECT_EQ(response.client_status, Response::Status::kDone); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel.h b/cpp/core_v2/internal/endpoint_channel.h new file mode 100644 index 00000000..bb33736a --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel.h @@ -0,0 +1,88 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +class EndpointChannel { + public: + virtual ~EndpointChannel() = default; + + virtual ExceptionOr + Read() = 0; // throws Exception::IO, Exception::INTERRUPTED + + virtual Exception Write(const ByteArray& data) = 0; // throws Exception::IO + + // Closes this EndpointChannel, without tracking the closure in analytics. + virtual void Close() = 0; + + // Closes this EndpointChannel and records the closure with the given reason. + virtual void Close(proto::connections::DisconnectionReason reason) = 0; + + // Returns a one-word type descriptor for the concrete EndpointChannel + // implementation that can be used in log messages; eg: BLUETOOTH, BLE, WIFI. + virtual std::string GetType() const = 0; + + // Returns the name of the EndpointChannel. + virtual std::string GetName() const = 0; + + // Returns the analytics enum representing the medium of this EndpointChannel. + virtual proto::connections::Medium GetMedium() const = 0; + + // Enables encryption on the EndpointChannel. + virtual void EnableEncryption( + securegcm::D2DConnectionContextV1* context) = 0; + + // True if the EndpointChannel is currently pausing all writes. + virtual bool IsPaused() const = 0; + + // Pauses all writes on this EndpointChannel until resume() is called. + virtual void Pause() = 0; + + // Resumes any writes on this EndpointChannel that were suspended when pause() + // was called. + virtual void Resume() = 0; + + // Returns the timestamp of the last read from this endpoint, or -1 if no + // reads have occurred. + virtual absl::Time GetLastReadTimestamp() const = 0; +}; + +inline bool operator==(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return (lhs.GetType() == rhs.GetType()) && (lhs.GetName() == rhs.GetName()) && + (lhs.GetMedium() == rhs.GetMedium()); +} + +inline bool operator!=(const EndpointChannel& lhs, const EndpointChannel& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager.cc b/cpp/core_v2/internal/endpoint_channel_manager.cc new file mode 100644 index 00000000..efec56e8 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.cc @@ -0,0 +1,151 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/endpoint_channel_manager.h" + +#include + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +EndpointChannelManager::~EndpointChannelManager() { + MutexLock lock(&mutex_); + channel_state_.DestroyAll(); +} + +void EndpointChannelManager::RegisterChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); + + NEARBY_LOG(INFO, "Registered channel: id=%s", endpoint_id.c_str()); +} + +void EndpointChannelManager::ReplaceChannelForEndpoint( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint != nullptr && endpoint->channel == nullptr) { + NEARBY_LOG(INFO, "Channel is missing while trying to update: id=%s", + endpoint_id.c_str()); + } + + SetActiveEndpointChannel(client, endpoint_id, std::move(channel)); +} + +bool EndpointChannelManager::EncryptChannelForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + MutexLock lock(&mutex_); + + channel_state_.UpdateEncryptionContextForEndpoint(endpoint_id, + std::move(context)); + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + return channel_state_.EncryptChannel(endpoint); +} + +std::shared_ptr EndpointChannelManager::GetChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint == nullptr) { + NEARBY_LOG(INFO, "No channel info: id=%s", endpoint_id.c_str()); + return {}; + } + + return endpoint->channel; +} + +void EndpointChannelManager::SetActiveEndpointChannel( + ClientProxy* client, const std::string& endpoint_id, + std::unique_ptr channel) { + + // Update the channel first, then encrypt this new channel, if + // crypto context is present. + channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel)); + + auto* endpoint = channel_state_.LookupEndpointData(endpoint_id); + if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint); +} + +// endpoint - channel endpoint to encrypt +bool EndpointChannelManager::ChannelState::EncryptChannel( + EndpointChannelManager::ChannelState::EndpointData* endpoint) { + if (endpoint != nullptr && endpoint->channel != nullptr && + endpoint->context != nullptr) { + endpoint->channel->EnableEncryption(endpoint->context.get()); + return true; + } + return false; +} + +///////////////////////////////// ChannelState ///////////////////////////////// +EndpointChannelManager::ChannelState::EndpointData* +EndpointChannelManager::ChannelState::LookupEndpointData( + const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + return item != endpoints_.end() ? &item->second : nullptr; +} + +void EndpointChannelManager::ChannelState::UpdateChannelForEndpoint( + const std::string& endpoint_id, std::unique_ptr channel) { + // Create EndpointData instance, if necessary, and populate channel. + endpoints_[endpoint_id].channel = std::move(channel); +} + +void EndpointChannelManager::ChannelState::UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context) { + // Create EndpointData instance, if necessary, and populate crypto context. + endpoints_[endpoint_id].context = std::move(context); +} + +bool EndpointChannelManager::ChannelState::RemoveEndpoint( + const std::string& endpoint_id, + proto::connections::DisconnectionReason reason) { + auto item = endpoints_.find(endpoint_id); + if (item == endpoints_.end()) return false; + item->second.disconnect_reason = reason; + endpoints_.erase(item); + return true; +} + +bool EndpointChannelManager::UnregisterChannelForEndpoint( + const std::string& endpoint_id) { + MutexLock lock(&mutex_); + + if (!channel_state_.RemoveEndpoint( + endpoint_id, + proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) { + return false; + } + + NEARBY_LOG(INFO, "Unregistered channel: id=%s", endpoint_id.c_str()); + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_channel_manager.h b/cpp/core_v2/internal/endpoint_channel_manager.h new file mode 100644 index 00000000..a1c72c59 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager.h @@ -0,0 +1,169 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex.h" +#include "securegcm/d2d_connection_context_v1.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +using EncryptionContext = ::securegcm::D2DConnectionContextV1; + +// NOTE(std::string): +// All the strings in internal class public interfaces should be exchanged as +// const std::string& if they are immutable, and as std::string +// it they are mutable. +// This is to keep all the internal classes compatible with each other, +// and minimize resources spent on the type conversion. +// Project-wide, strings are either passed around as reference (which has +// zero maintenance costs, and sizeof(void*) memory usage => passed around in a +// CPU register), and whenever lifetime etension is required, it must be copied +// to std::string instance (which will again propagate as a const reference +// within it's lifetime domain). + +// Manages the communication channels to all the remote endpoints with which we +// are interacting. +class EndpointChannelManager final { + public: + ~EndpointChannelManager(); + + // Registers the initial EndpointChannel to be associated with an endpoint; + // if there already exists a previously-associated EndpointChannel, that will + // be closed before continuing the registration. + void RegisterChannelForEndpoint(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Replaces the EndpointChannel to be associated with an endpoint from here on + // in, transferring the encryption context from the previous EndpointChannel + // to the newly-provided EndpointChannel. + void ReplaceChannelForEndpoint(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_LOCKS_EXCLUDED(mutex_); + + bool EncryptChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr context) + ABSL_LOCKS_EXCLUDED(mutex_); + + // NOTE(shared_ptr<> usage): + // + // EndpointChannelManager is holding an EndpointChannel instance; + // GetChannelForEndpoint() is passing ownership over to a worker thread. + // It is not a pointer passing but an ownership passing, to guarantee that + // channel instance will not disappear underneath the feet of a worker thread + // inside EndpointManager [ EndpointManager::EndpointChannelLoopRunnable() ]. + // If it is just a pointer, Channel will get destroyed while in use by a + // worker thread. shared_ptr is a simple and reliable tool to avoid that. + // + // The reason why it can not be std::unique_ptr<> is: there are other code + // paths that expect to be able to read the pointer value multiple times, from + // multiple places (each of them needs "ownership" for the duration of their + // use). EndpointManager::SendTransferFrameBytes() is another such place. + // If EndpointChannelManager replaces the current channel, and any (or both) + // EndpointManager methods that use a channel are running, it is better to + // have a shared ownership. + std::shared_ptr GetChannelForEndpoint( + const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if 'endpoint_id' actually had a registered EndpointChannel. + // IOW, a return of false signifies a no-op. + bool UnregisterChannelForEndpoint(const std::string& endpoint_id) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Tracks channel state for all endpoints. This includes what EndpointChannel + // the endpoint is currently using and whether or not the EndpointChannel has + // been encrypted yet. + class ChannelState { + public: + struct EndpointData { + EndpointData() = default; + EndpointData(EndpointData&&) = default; + EndpointData& operator=(EndpointData&&) = default; + ~EndpointData() { + if (channel != nullptr) { + channel->Close(disconnect_reason); + } + } + + // True if we have a 'context' for the endpoint. + bool IsEncrypted() const { return context != nullptr; } + + std::shared_ptr channel; + std::unique_ptr context; + proto::connections::DisconnectionReason disconnect_reason = + proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON; + }; + + ChannelState() = default; + ~ChannelState() { DestroyAll(); } + ChannelState(ChannelState&&) = default; + ChannelState& operator=(ChannelState&&) = default; + + // Provides a way to destroy contents of a container, while holding a lock. + void DestroyAll() { endpoints_.clear(); } + // Return pointer to endpoint data, or nullptr, it not found. + EndpointData* LookupEndpointData(const std::string& endpoint_id); + + // Stores a new EndpointChannel for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateChannelForEndpoint(const std::string& endpoint_id, + std::unique_ptr channel); + + // Stores a new EncryptionContext for the endpoint. + // Prevoius one is destroyed, if it existed. + void UpdateEncryptionContextForEndpoint( + const std::string& endpoint_id, + std::unique_ptr context); + + // Removes all knowledge of this endpoint, cleaning up as necessary. + // Returns false if the endpoint was not found. + bool RemoveEndpoint(const std::string& endpoint_id, + proto::connections::DisconnectionReason reason); + + bool EncryptChannel(EndpointData* endpoint); + + private: + // Endpoint ID -> EndpointData. Contains everything we know about the + // endpoint. + absl::flat_hash_map endpoints_; + }; + + void SetActiveEndpointChannel(ClientProxy* client, + const std::string& endpoint_id, + std::unique_ptr channel) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + Mutex mutex_; + ChannelState channel_state_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_channel_manager_test.cc b/cpp/core_v2/internal/endpoint_channel_manager_test.cc new file mode 100644 index 00000000..d1d42c96 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_channel_manager_test.cc @@ -0,0 +1,31 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/endpoint_channel_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(EndpointChannelManagerTest, ConstructorDestructorWorks) { + EndpointChannelManager mgr; + SUCCEED(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc new file mode 100644 index 00000000..f5dbf43c --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -0,0 +1,491 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +using ::location::nearby::proto::connections::Medium; + +// A Runnable that continuously grabs the most recent EndpointChannel available +// for an endpoint. +// +// handler - Called whenever an EndpointChannel is available for endpointId. +// Implementations are expected to read/write freely to the +// EndpointChannel until an Exception::IO is thrown. Once an +// Exception::IO occurs, a check will be performed to see if another +// EndpointChannel is available for the given endpoint and, if so, +// handler(EndpointChannel) will be called again. Return false to exit +// the loop. +void EndpointManager::EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler) { + // EndpointChannelManager will not let multiple channels exist simultaneously + // for the same endpoint_id; it will be closing "old" channels as new ones + // come. (There will be a short overlap). + // Closed channel will return Exception::kIo for any Read, and loop (below) + // will retry and attempt to pick another channel. + // If channel is deleted (no mapping), or it is still the same channel + // (same Medium) on which we got the Exception::kIo, we terminate the loop. + Medium last_failed_medium = Medium::UNKNOWN_MEDIUM; + while (true) { + // It's important to keep re-fetching the EndpointChannel for an endpoint + // because it can be changed out from under us (for example, when we + // upgrade from Bluetooth to Wifi). + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + if (channel == nullptr) { + // TODO(tracyzhou): Add logging. + break; + } + + // If we're looping back around after a failure, and there's not a new + // EndpointChannel for this endpoint, there's nothing more to do here. + if ((last_failed_medium != Medium::UNKNOWN_MEDIUM) && + (channel->GetMedium() == last_failed_medium)) { + // TODO(tracyzhou): Add logging. + break; + } + + ExceptionOr keep_using_channel = handler(channel.get()); + + if (!keep_using_channel.ok()) { + Exception exception = keep_using_channel.GetException(); + if (exception.Raised(Exception::kIo)) { + last_failed_medium = channel->GetMedium(); + // TODO(tracyzhou): Add logging. + continue; + } + if (exception.Raised(Exception::kInterrupted)) { + break; + } + } + + if (!keep_using_channel.result()) { + // TODO(tracyzhou): Add logging. + break; + } + } + // Indicate we're out of the loop and it is ok to schedule another instance + // if needed. + NEARBY_LOG(INFO, "Worker going down; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); + barrier->CountDown(); + + // Always clear out all state related to this endpoint before terminating + // this thread. + DiscardEndpoint(client, endpoint_id); + NEARBY_LOG(INFO, "Worker done; name=%s; id=%s", runnable_name.c_str(), + endpoint_id.c_str()); +} + +ExceptionOr EndpointManager::HandleData( + const std::string& endpoint_id, ClientProxy* client, + EndpointChannel* endpoint_channel) { + // Read as much as we can from the healthy EndpointChannel - when it is no + // longer in good shape (i.e. our read from it throws an Exception), our + // super class will loop back around and try our luck in case there's been + // a replacement for this endpoint since we last checked with the + // EndpointChannelManager. + while (true) { + ExceptionOr bytes = endpoint_channel->Read(); + if (!bytes.ok()) { + NEARBY_LOG(INFO, "Stop reading on read-time exception: %d", + bytes.exception()); + return ExceptionOr(bytes.exception()); + } + ExceptionOr wrapped_frame = parser::FromBytes(bytes.result()); + if (!wrapped_frame.ok()) { + if (wrapped_frame.GetException().Raised( + Exception::kInvalidProtocolBuffer)) { + NEARBY_LOG(INFO, "failed to decode; endpoint=%s; channel=%s; skip", + endpoint_id.c_str(), endpoint_channel->GetType().c_str()); + continue; + } else { + NEARBY_LOG(INFO, "Stop reading on parse-time exception: %d", + wrapped_frame.exception()); + return ExceptionOr(wrapped_frame.exception()); + } + } + OfflineFrame& frame = wrapped_frame.result(); + + // Route the incoming offlineFrame to its registered processor. + V1Frame::FrameType frame_type = parser::GetFrameType(frame); + EndpointManager::FrameProcessor* frame_processor = + GetFrameProcessor(frame_type); + if (frame_processor == nullptr) { + NEARBY_LOG(ERROR, "Unhandled message: type=%d", frame_type); + continue; + } + + frame_processor->OnIncomingFrame(frame, endpoint_id, client, + endpoint_channel->GetMedium()); + } +} + +ExceptionOr EndpointManager::HandleKeepAlive( + EndpointChannel* endpoint_channel) { + // Check if it has been too long since we received a frame from our + // endpoint. + if ((endpoint_channel->GetLastReadTimestamp() != kInvalidTimestamp) && + ((endpoint_channel->GetLastReadTimestamp() + + EndpointManager::kKeepAliveReadTimeout) < + SystemClock::ElapsedRealtime())) { + // TODO(tracyzhou): Add logging. + return ExceptionOr(false); + } + + // Attempt to send the KeepAlive frame over the endpoint channel - if the + // write fails, our super class will loop back around and try our luck again + // in case there's been a replacement for this endpoint. + Exception write_exception = endpoint_channel->Write(parser::ForKeepAlive()); + if (!write_exception.Ok()) { + return ExceptionOr(write_exception); + } + + // We sleep as the very last step because we want to minimize the caching of + // the EndpointChannel. If we do hold on to the EndpointChannel, and it's + // switched out from under us in BandwidthUpgradeManager, our write will + // trigger an erroneous write to the encryption context that will cascade + // into all our remote endpoint's future reads failing. + Exception sleep_exception = + SystemClock::Sleep(EndpointManager::kKeepAliveWriteInterval); + if (!sleep_exception.Ok()) { + return ExceptionOr(sleep_exception); + } + + return ExceptionOr(true); +} + +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs == &rhs; +} + +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs) { + // We're comparing addresses because these objects are callbacks which need to + // be matched by exact instances. + return &lhs < &rhs; +} + +EndpointManager::EndpointManager(EndpointChannelManager* manager) + : channel_manager_(manager) {} + +EndpointManager::~EndpointManager() { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, &latch]() { + NEARBY_LOG(INFO, "Bringing down endpoints"); + for (auto& item : endpoints_) { + const std::string& endpoint_id = item.first; + EndpointState& state = item.second; + // This will close the channel; all workers will sense that and + // terminate. + NEARBY_LOG(INFO, "Bringing down endpoint channels: id=%s", + endpoint_id.c_str()); + WaitForEndpointDisconnectionProcessing(state.client, endpoint_id); + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + } + latch.CountDown(); + }); + latch.Await(); + NEARBY_LOG(INFO, "Bringing down worker threads"); + + // Stop all the ongoing Runnables (as gracefully as possible). + // Order matters: bring worker pools down first; serial_executor_ thread + // should go last, since workers schedule jobs there even during shutdown. + handlers_executor_.Shutdown(); + keep_alive_executor_.Shutdown(); + NEARBY_LOG(INFO, "Bringing down control thread"); + serial_executor_.Shutdown(); + NEARBY_LOG(INFO, "EndpointManager is down"); +} + +const EndpointManager::FrameProcessor::Handle +EndpointManager::RegisterFrameProcessor( + V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) { + const FrameProcessor::Handle handle = processor; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &latch, processor]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + // TODO(tracyzhou): Add logging. + it->second = processor; + } else { + frame_processors_.emplace(frame_type, processor); + } + latch.CountDown(); + }); + latch.Await(); + return handle; +} + +void EndpointManager::UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle) { + RunOnEndpointManagerThread([this, frame_type, handle]() { + auto it = frame_processors_.find(frame_type); + if (it == frame_processors_.end()) return; + if (it->second != handle) { + NEARBY_LOG(INFO, + "Failed to unregister: type=%d; handle mismatch: passed=%p, " + "expected=%p", + frame_type, handle, it->second); + return; + } + + frame_processors_.erase(it); + NEARBY_LOG(INFO, "unregistered: type=%d", frame_type); + }); +} + +EndpointManager::FrameProcessor* EndpointManager::GetFrameProcessor( + V1Frame::FrameType frame_type) { + EndpointManager::FrameProcessor* processor = nullptr; + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, frame_type, &processor, &latch]() { + auto it = frame_processors_.find(frame_type); + if (it != frame_processors_.end()) { + processor = it->second; + } + latch.CountDown(); + }); + latch.Await(); + return processor; +} + +void EndpointManager::EnsureWorkersTerminated(const std::string& endpoint_id) { + auto item = endpoints_.find(endpoint_id); + if (item != endpoints_.end()) { + // If another instance of data and keep-alive handlers is running, it will + // terminate soon; we should block until it happens. + EndpointState& endpoint_state = item->second; + NEARBY_LOG(INFO, "Waiting for workers to terminate for endpoint_id='%s'", + endpoint_id.c_str()); + endpoint_state.barrier.Await(); + endpoints_.erase(item); + } +} + +void EndpointManager::RegisterEndpoint(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& listener) { + CountDownLatch latch(1); + + // NOTE (unique_ptr<> capture): + // std::unique_ptr<> is not copyable, so we can not pass it to + // lambda capture, because lambda eventually is converted to std::function<>. + // Instead, we release() a pointer, and pass a raw pointer, which is copyalbe. + // We ignore the risk of job not scheduled (and an associated risk of memory + // leak), because this may only happen during service shutdown. + RunOnEndpointManagerThread([this, client, channel = channel.release(), + &endpoint_id, &info, &listener, &latch]() { + // Pass ownership of channel to EndpointChannelManager + NEARBY_LOG(INFO, "Registering endpoint with channel manager: id=%s", + endpoint_id.c_str()); + channel_manager_->RegisterChannelForEndpoint( + client, endpoint_id, std::unique_ptr(channel)); + + EnsureWorkersTerminated(endpoint_id); + EndpointState& endpoint_state = + endpoints_.emplace(endpoint_id, EndpointState()).first->second; + endpoint_state.client = client; + + NEARBY_LOG(INFO, "Starting workers: id=%s", endpoint_id.c_str()); + // For every endpoint, there's normally only one Read handler instance + // running on the handlers_executor_ pool. This instance reads data from the + // endpoint and delegates incoming frames to various FrameProcessors. + // Once the frame has been properly handled, it starts reading again for + // the next frame. If the handler fails its read and no other + // EndpointChannels are available for this endpoint, a disconnection + // will be initiated. + StartEndpointReader( + [this, client, endpoint_id, barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable( + "Read", client, endpoint_id, barrier, + [this, client, endpoint_id](EndpointChannel* channel) { + return HandleData(endpoint_id, client, channel); + }); + }); + + // For every endpoint, there's only one KeepAliveManager instance + // running on the keep_alive_executor_ pool. This instance will + // periodically send out a ping* to the endpoint while listening for an + // incoming pong**. If it fails to send the ping, or if no pong is heard + // within kKeepAliveReadTimeoutMillis milliseconds, it initiates a + // disconnection. + // + // (*) Bluetooth requires a constant outgoing stream of messages. If + // there's silence, Android will break the socket. This is why we ping. + // (**) Wifi Hotspots can fail to notice a connection has been lost, and + // they will happily keep writing to /dev/null. This is why we listen + // for the pong. + StartEndpointKeepAliveManager([this, client, endpoint_id, + barrier = &endpoint_state.barrier]() { + EndpointChannelLoopRunnable("KeepAliveManager", client, endpoint_id, + barrier, [this](EndpointChannel* channel) { + return HandleKeepAlive(channel); + }); + }); + // TODO(tracyzhou): Add logging. + + // It's now time to let the client know of this new connection so that + // they can accept or reject it. + client->OnConnectionInitiated(endpoint_id, info, listener); + latch.CountDown(); + }); + latch.Await(); +} + +void EndpointManager::UnregisterEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + CountDownLatch latch(1); + RunOnEndpointManagerThread([this, client, endpoint_id, &latch]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, /*notify=*/false); + latch.CountDown(); + }); + latch.Await(); +} + +// Designed to run asynchronously. It is called from IO thread pools, and +// jobs in these pools may be waited for from the EndpointManager thread. If we +// allow synchronous behavior here it will cause a live lock. +void EndpointManager::DiscardEndpoint(ClientProxy* client, + const std::string& endpoint_id) { + RunOnEndpointManagerThread([this, client, endpoint_id]() { + channel_manager_->UnregisterChannelForEndpoint(endpoint_id); + RemoveEndpoint(client, endpoint_id, + /*notify=*/ + client->IsConnectedToEndpoint(endpoint_id)); + }); +} + +std::vector EndpointManager::SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids) { + ByteArray bytes = + parser::ForDataPayloadTransfer(payload_header, payload_chunk); + + return SendTransferFrameBytes(endpoint_ids, bytes, payload_header.id(), + /*offset=*/payload_chunk.offset(), + /*packet_type=*/"DATA"); +} + +std::vector EndpointManager::SendControlMessage( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control, + const std::vector& endpoint_ids) { + ByteArray bytes = parser::ForControlPayloadTransfer(header, control); + + return SendTransferFrameBytes(endpoint_ids, bytes, header.id(), + /*offset=*/control.offset(), + /*packet_type=*/"CONTROL"); +} + +// @EndpointManagerThread +void EndpointManager::RemoveEndpoint(ClientProxy* client, + const std::string& endpoint_id, + bool notify) { + // Unregistering from channel_manager_ will also serve to terminate + // the dedicated handler and KeepAlive threads we started when we registered + // this endpoint. + if (channel_manager_->UnregisterChannelForEndpoint(endpoint_id)) { + // Notify all frame processors of the disconnection immediately and wait + // for them to clean up state. Only once all processors are done cleaning + // up, we can remove the endpoint from ClientProxy after which there + // should be no further interactions with the endpoint. + // (See b/37352254 for history) + WaitForEndpointDisconnectionProcessing(client, endpoint_id); + EnsureWorkersTerminated(endpoint_id); + + client->OnDisconnected(endpoint_id, notify); + // TODO(tracyzhou): Add logging. + } +} + +// @EndpointManagerThread +void EndpointManager::WaitForEndpointDisconnectionProcessing( + ClientProxy* client, const std::string& endpoint_id) { + CountDownLatch barrier(frame_processors_.size()); + + for (auto& item : frame_processors_) { + auto& processor = item.second; + processor->OnEndpointDisconnect(client, endpoint_id, &barrier); + } + + barrier.Await(kProcessEndpointDisconnectionTimeout); +} + +std::vector EndpointManager::SendTransferFrameBytes( + const std::vector& endpoint_ids, const ByteArray& bytes, + std::int64_t payload_id, std::int64_t offset, + const std::string& packet_type) { + std::vector failed_endpoint_ids; + for (const std::string& endpoint_id : endpoint_ids) { + std::shared_ptr channel = + channel_manager_->GetChannelForEndpoint(endpoint_id); + + if (channel == nullptr) { + // We no longer know about this endpoint (it was either explicitly + // unregistered, or a read/write error made us unregister it internally). + NEARBY_LOG(INFO, "Channel not available; id=%s", endpoint_id.c_str()); + failed_endpoint_ids.push_back(endpoint_id); + continue; + } + + Exception write_exception = channel->Write(bytes); + if (!write_exception.Ok()) { + failed_endpoint_ids.push_back(endpoint_id); + NEARBY_LOG(INFO, "Failed to send packet; endpoint_id=%s", + endpoint_id.c_str()); + continue; + } + } + + return failed_endpoint_ids; +} + +void EndpointManager::StartEndpointReader(Runnable runnable) { + handlers_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::StartEndpointKeepAliveManager(Runnable runnable) { + keep_alive_executor_.Execute(std::move(runnable)); +} + +void EndpointManager::RunOnEndpointManagerThread(Runnable runnable) { + serial_executor_.Execute(std::move(runnable)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/endpoint_manager.h b/cpp/core_v2/internal/endpoint_manager.h new file mode 100644 index 00000000..65834dd7 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager.h @@ -0,0 +1,232 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ +#define CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/listeners.h" +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/single_thread_executor.h" +#include "platform_v2/public/system_clock.h" +#include "proto/connections_enums.pb.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { + +// Manages all operations related to the remote endpoints with which we are +// interacting. +// +// All processing of incoming and outgoing payloads is spread across this and +// the PayloadManager as described below. +// +// The sending of outgoing payloads originates in +// PayloadManager::SendPayload() before control is transferred over to +// EndpointManager::SendPayloadChunk(). This work happens on one of three +// dedicated writer threads belonging to the PayloadManager. The writer thread +// that is used depends on the Payload::Type. +// +// The EndpointManager has one dedicated reader thread for each registered +// endpoint, and the receiving of every incoming payload (and its subsequent +// chunks) originates on one of those threads before control is transferred over +// to PayloadManager::ProcessFrame() (still running on that +// same dedicated reader thread). + +class EndpointManager { + public: + class FrameProcessor { + public: + using Handle = void*; + + virtual ~FrameProcessor() = default; + + // @EndpointManagerReaderThread + virtual void OnIncomingFrame(const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, + ClientProxy* to_client, + proto::connections::Medium current_medium) = 0; + + // Implementations must call barrier.CountDown() once + // they're done. This parallelizes the disconnection event across all frame + // processors. + // + // @EndpointManagerThread + virtual void OnEndpointDisconnect(ClientProxy* client, + const std::string& endpoint_id, + CountDownLatch* barrier) = 0; + }; + + explicit EndpointManager(EndpointChannelManager* manager); + ~EndpointManager(); + + // Invoked from the constructors of the various *Manager components that make + // up the OfflineServiceController implementation. + // FrameProcessor* instances are of dynamic duration and survive all sessions. + // returns unique handle to be used for unregistering. + // Blocks until registration is complete. + const FrameProcessor::Handle RegisterFrameProcessor( + V1Frame::FrameType frame_type, FrameProcessor* processor); + void UnregisterFrameProcessor(V1Frame::FrameType frame_type, + const void* handle); + + // Invoked from the different PcpHandler implementations (of which there can + // be only one at a time). + // Blocks until registration is complete. + void RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id, + const ConnectionResponseInfo& info, + std::unique_ptr channel, + const ConnectionListener& listener); + // Called when a client explicitly asks to disconnect from this endpoint. In + // this case, we do not notify the client of onDisconnected(). + void UnregisterEndpoint(ClientProxy* client, const std::string& endpoint_id); + + // Returns the list of endpoints to which sending this chunk failed. + // + // Invoked from the PayloadManager's sendPayload() method. + std::vector SendPayloadChunk( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::PayloadChunk& payload_chunk, + const std::vector& endpoint_ids); + std::vector SendControlMessage( + const PayloadTransferFrame::PayloadHeader& payload_header, + const PayloadTransferFrame::ControlMessage& control_message, + const std::vector& endpoint_ids); + + // Called when we internally want to get rid of the endpoint, without the + // client directly telling us to. For example... + // a) We failed to read from the endpoint in its dedicated reader thread. + // b) We failed to write to the endpoint in PayloadManager. + // c) The connection was rejected in PCPHandler. + // d) The dedicated KeepAlive thread exceeded its period of inactivity. + // Or in the numerous other cases where a failure occurred and we no longer + // believe the endpoint is in a healthy state. + // + // Note: This must not block. Otherwise we can get into a deadlock where we + // ask everyone who's registered an FrameProcessor to + // processEndpointDisconnection() while the caller of DiscardEndpoint() is + // blocked here. + void DiscardEndpoint(ClientProxy* client, const std::string& endpoint_id); + + private: + struct EndpointState { + // ClientProxy object associated with this endpoint. + ClientProxy* client; + // Execution barrier, used to ensure that all workers associated with an + // endpoint on handlers_executor_ and keep_alive_executor_ are terminated. + CountDownLatch barrier{2}; + }; + + FrameProcessor* GetFrameProcessor(V1Frame::FrameType frame_type); + + ExceptionOr HandleData(const std::string& endpoint_id, + ClientProxy* client_proxy, + EndpointChannel* endpoint_channel); + + ExceptionOr HandleKeepAlive(EndpointChannel* endpoint_channel); + + // Waits for a given endpoint EndpointChannelLoopRunnable() workers to + // terminate. + // Is called from RegisterEndpoint to avoid races; also called from + // RemoveEndpoint as part of proper endpoint shutdown sequence. + // @EndpointManagerThread + void EnsureWorkersTerminated(const std::string& endpoint_id); + + void EndpointChannelLoopRunnable( + const std::string& runnable_name, ClientProxy* client_proxy, + const std::string& endpoint_id, CountDownLatch* barrier, + std::function(EndpointChannel*)> handler); + + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch); + static void WaitForLatch(const std::string& method_name, + CountDownLatch* latch, std::int32_t timeout_millis); + + static constexpr absl::Duration kKeepAliveWriteInterval = + absl::Milliseconds(5000); + static constexpr absl::Duration kKeepAliveReadTimeout = + absl::Milliseconds(30000); + static constexpr absl::Duration kProcessEndpointDisconnectionTimeout = + absl::Milliseconds(2000); + static constexpr std::int32_t kMaxConcurrentEndpoints = 50; + static constexpr absl::Time kInvalidTimestamp = absl::InfinitePast(); + + // It should be noted that this method may be called multiple times (because + // invoking this method closes the endpoint channel, which causes the + // dedicated reader and KeepAlive threads to terminate, which in turn leads to + // this method being called), but that's alright because the implementation of + // this method is idempotent. + // @EndpointManagerThread + void RemoveEndpoint(ClientProxy* client, const std::string& endpoint_id, + bool notify); + + void WaitForEndpointDisconnectionProcessing(ClientProxy* client, + const std::string& endpoint_id); + + std::vector SendTransferFrameBytes( + const std::vector& endpoint_ids, + const ByteArray& payload_transfer_frame_bytes, std::int64_t payload_id, + std::int64_t offset, const std::string& packet_type); + + // Executes data-handing jobs on a separate thread for each endpoint, on a + // handlers_executor_. + // If amount of concurrent connections is less the pool capacity, it is + // possible that while a channel is being replaced, two jobs are trying to + // run for the same endpoint (for a short time). + // TODO (apolyudov): do not let extra job start. + void StartEndpointReader(Runnable runnable); + + // Executes keep-alive jobs on a separate thread for each endpoint on a + // keep_alive_executor_. + void StartEndpointKeepAliveManager(Runnable runnable); + + // Executes all jobs sequentially, on a serial_executor_. + void RunOnEndpointManagerThread(Runnable runnable); + + EndpointChannelManager* channel_manager_; + + absl::flat_hash_map + frame_processors_; + + // We keep track of all registered channel endpoints here. + absl::flat_hash_map endpoints_; + + MultiThreadExecutor keep_alive_executor_{kMaxConcurrentEndpoints}; + MultiThreadExecutor handlers_executor_{kMaxConcurrentEndpoints}; + SingleThreadExecutor serial_executor_; +}; + +// Operator overloads when comparing FrameProcessor*. +bool operator==(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); +bool operator<(const EndpointManager::FrameProcessor& lhs, + const EndpointManager::FrameProcessor& rhs); + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_ENDPOINT_MANAGER_H_ diff --git a/cpp/core_v2/internal/endpoint_manager_test.cc b/cpp/core_v2/internal/endpoint_manager_test.cc new file mode 100644 index 00000000..4d3cd641 --- /dev/null +++ b/cpp/core_v2/internal/endpoint_manager_test.cc @@ -0,0 +1,256 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/endpoint_manager.h" + +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/endpoint_channel_manager.h" +#include "core_v2/internal/offline_frames.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/pipe.h" +#include "proto/connections_enums.pb.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +using ::location::nearby::proto::connections::DisconnectionReason; +using ::location::nearby::proto::connections::Medium; +using ::securegcm::D2DConnectionContextV1; +using ::testing::_; +using ::testing::MockFunction; +using ::testing::Return; +using ::testing::StrictMock; + +class MockEndpointChannel : public EndpointChannel { + public: + MOCK_METHOD(ExceptionOr, Read, (), (override)); + MOCK_METHOD(Exception, Write, (const ByteArray& data), (override)); + MOCK_METHOD(void, Close, (), (override)); + MOCK_METHOD(void, Close, (DisconnectionReason reason), (override)); + MOCK_METHOD(std::string, GetType, (), (const override)); + MOCK_METHOD(std::string, GetName, (), (const override)); + MOCK_METHOD(Medium, GetMedium, (), (const override)); + MOCK_METHOD(void, EnableEncryption, + (D2DConnectionContextV1 * connection_context), + (override)); + MOCK_METHOD(bool, IsPaused, (), (const override)); + MOCK_METHOD(void, Pause, (), (override)); + MOCK_METHOD(void, Resume, (), (override)); + MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override)); + + bool IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; + } + void DoClose() { + absl::MutexLock lock(&mutex_); + closed_ = true; + } + + private: + mutable absl::Mutex mutex_; + bool closed_ = false; +}; + +class MockFrameProcessor : public EndpointManager::FrameProcessor { + public: + MOCK_METHOD(void, OnIncomingFrame, + (const OfflineFrame& offline_frame, + const std::string& from_endpoint_id, ClientProxy* to_client, + Medium current_medium), + (override)); + + MOCK_METHOD(void, OnEndpointDisconnect, + (ClientProxy * client, const std::string& endpoint_id, + CountDownLatch* barrier), + (override)); +}; + +class EndpointManagerTest : public ::testing::Test { + protected: + void RegisterEndpoint(std::unique_ptr channel, + bool should_close = true) { + CountDownLatch done(1); + if (should_close) { + ON_CALL(*channel, Close(_)) + .WillByDefault( + [&done](DisconnectionReason reason) { done.CountDown(); }); + } + EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE)); + EXPECT_CALL(*channel, GetLastReadTimestamp()) + .WillRepeatedly(Return(start_time_)); + EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1); + em_.RegisterEndpoint(&client_, endpoint_id_, info_, std::move(channel), + listener_); + if (should_close) { + EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result()); + } + } + + ClientProxy client_; + std::vector> processors_; + EndpointChannelManager ecm_; + EndpointManager em_{&ecm_}; + std::string endpoint_id_ = "endpoint_id"; + ConnectionResponseInfo info_ = { + .remote_endpoint_name = "name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + struct MockConnectionListener { + StrictMock> + initiated_cb; + StrictMock> accepted_cb; + StrictMock> + rejected_cb; + StrictMock> + disconnected_cb; + StrictMock> + bandwidth_changed_cb; + } mock_listener_; + ConnectionListener listener_{ + .initiated_cb = mock_listener_.initiated_cb.AsStdFunction(), + .accepted_cb = mock_listener_.accepted_cb.AsStdFunction(), + .rejected_cb = mock_listener_.rejected_cb.AsStdFunction(), + .disconnected_cb = mock_listener_.disconnected_cb.AsStdFunction(), + .bandwidth_changed_cb = + mock_listener_.bandwidth_changed_cb.AsStdFunction(), + }; + absl::Time start_time_{absl::Now()}; +}; + +TEST_F(EndpointManagerTest, ConstructorDestructorWorks) { SUCCEED(); } + +TEST_F(EndpointManagerTest, RegisterEndpointCallsOnConnectionInitiated) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Close(_)).Times(1); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + RegisterEndpoint(std::make_unique()); + // NOTE: disconnect_cb is not called, because we did not reach fully connected + // state. On top of that, UnregisterEndpoint is suppressing this notification. + // (IMO, it should be called as long as any connection callback was called + // before. (in this case initiated_cb is called)). + // Test captures current protocol behavior. + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + auto connect_request = std::make_unique(); + auto read_data = parser::ForConnectionRequest("endpoint_id", "endpoint_name", + 1234, std::vector{Medium::BLE}); + EXPECT_CALL(*connect_request, OnIncomingFrame); + EXPECT_CALL(*connect_request, OnEndpointDisconnect); + EXPECT_CALL(*endpoint_channel, Read()) + .WillOnce(Return(ExceptionOr(read_data))) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + // Register frame processor, then register endpoint. + // Endpoint will read one frame, then fail to read more and terminate. + // On disconnection, it will notify frame processor and we verify that. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + RegisterEndpoint(std::move(endpoint_channel)); +} + +TEST_F(EndpointManagerTest, UnregisterFrameProcessorWorks) { + auto endpoint_channel = std::make_unique(); + EXPECT_CALL(*endpoint_channel, Read()) + .WillRepeatedly(Return(ExceptionOr(Exception::kIo))); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + // We should not receive any notifications to frame processor. + auto connect_request = std::make_unique>(); + + // Register frame processor and immediately unregister it. + const void* handle = em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST, + connect_request.get()); + processors_.emplace_back(std::move(connect_request)); + EXPECT_NE(handle, nullptr); + em_.UnregisterFrameProcessor(V1Frame::CONNECTION_REQUEST, handle); + // Endpoint will not send OnDisconnect notification to frame processor. + RegisterEndpoint(std::move(endpoint_channel), false); + em_.UnregisterEndpoint(&client_, endpoint_id_); +} + +TEST_F(EndpointManagerTest, SendControlMessageWorks) { + auto endpoint_channel = std::make_unique(); + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_offset(150); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + + ON_CALL(*endpoint_channel, Read()) + .WillByDefault([channel = endpoint_channel.get()]() { + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + NEARBY_LOG(INFO, "Simulate read delay: wait"); + absl::SleepFor(absl::Milliseconds(100)); + NEARBY_LOG(INFO, "Simulate read delay: done"); + if (channel->IsClosed()) return ExceptionOr(Exception::kIo); + return ExceptionOr(ByteArray{}); + }); + ON_CALL(*endpoint_channel, Close(_)) + .WillByDefault( + [channel = endpoint_channel.get()](DisconnectionReason reason) { + channel->DoClose(); + NEARBY_LOG(INFO, "Channel closed"); + }); + EXPECT_CALL(*endpoint_channel, Write(_)) + .WillRepeatedly(Return(Exception{Exception::kSuccess})); + + RegisterEndpoint(std::move(endpoint_channel), false); + auto failed_ids = + em_.SendControlMessage(header, control, std::vector{endpoint_id_}); + EXPECT_EQ(failed_ids, std::vector{}); + NEARBY_LOG(INFO, "Will unregister endpoint now"); + em_.UnregisterEndpoint(&client_, endpoint_id_); + NEARBY_LOG(INFO, "Will call destructors now"); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD new file mode 100644 index 00000000..3e89765c --- /dev/null +++ b/cpp/core_v2/internal/mediums/BUILD @@ -0,0 +1,84 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "mediums", + srcs = [ + "advertisement_read_result.cc", + "ble_advertisement.cc", + "ble_advertisement_header.cc", + "ble_packet.cc", + "bluetooth_radio.cc", + "uuid.cc", + ], + hdrs = [ + "advertisement_read_result.h", + "ble_advertisement.h", + "ble_advertisement_header.h", + "ble_packet.h", + "ble_peripheral.h", + "bluetooth_radio.h", + "lost_entity_tracker.h", + "uuid.h", + ], + visibility = [ + "//core_v2/internal:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "utils", + srcs = ["utils.cc"], + hdrs = ["utils.h"], + visibility = [ + "//core_v2/internal/mediums/webrtc:__pkg__", + ], + deps = [ + "//platform_v2/base", + "//platform_v2/public", + ], +) + +cc_test( + name = "core_v2_internal_mediums_test", + srcs = [ + "advertisement_read_result_test.cc", + "ble_advertisement_header_test.cc", + "ble_advertisement_test.cc", + "ble_packet_test.cc", + "ble_peripheral_test.cc", + "bluetooth_radio_test.cc", + "lost_entity_tracker_test.cc", + "uuid_test.cc", + ], + shard_count = 16, + deps = [ + ":mediums", + "//platform_v2/base", + "//platform_v2/impl/g3", # build_cleaner: keep + "//platform_v2/public", + "//platform_v2/public:logging", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.cc b/cpp/core_v2/internal/mediums/advertisement_read_result.cc new file mode 100644 index 00000000..7abadd81 --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.cc @@ -0,0 +1,139 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include +#include + +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{ + .backoff_multiplier = 2.0, + .base_backoff_duration = absl::Seconds(1), + .max_backoff_duration = absl::Minutes(5), +}; + +// Adds a successfully read advertisement for the specified slot to this read +// result. This is fundamentally different from RecordLastReadStatus() because +// we can report a read failure, but still manage to read some advertisements. +void AdvertisementReadResult::AddAdvertisement(std::int32_t slot, + const ByteArray& advertisement) { + MutexLock lock(&mutex_); + + // Blindly remove from the advertisements map to make sure any existing + // key-value pair is destroyed. + advertisements_.emplace(slot, advertisement); +} + +// Determines whether or not an advertisement was successfully read at the +// specified slot. +bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const { + MutexLock lock(&mutex_); + + return advertisements_.contains(slot); +} + +// Retrieves all raw advertisements that were successfully read. +std::vector AdvertisementReadResult::GetAdvertisements() + const { + MutexLock lock(&mutex_); + + std::vector all_advertisements; + all_advertisements.reserve(advertisements_.size()); + for (const auto& item : advertisements_) { + all_advertisements.emplace_back(&item.second); + } + + return all_advertisements; +} + +// Determines what stage we're in for retrying a read from an advertisement +// GATT server. +AdvertisementReadResult::RetryStatus +AdvertisementReadResult::EvaluateRetryStatus() const { + MutexLock lock(&mutex_); + + // Check if we have already succeeded reading this advertisement. + if (status_ == Status::kSuccess) { + return RetryStatus::kPreviouslySucceeded; + } + + // Check if we have recently failed to read this advertisement. + if (GetDurationSinceReadLocked() < backoff_duration_) { + return RetryStatus::kTooSoon; + } + + return RetryStatus::kRetry; +} + +// Records the status of the latest read, and updates the next backoff +// duration for subsequent reads. Be sure to also call +// AddAdvertisement() if any advertisements were read. +void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { + MutexLock lock(&mutex_); + + // Update the last read timestamp. + last_read_timestamp_ = SystemClock::ElapsedRealtime(); + + // Update the backoff duration. + if (is_success) { + // Reset the backoff duration now that we had a successful read. + backoff_duration_ = config_.base_backoff_duration; + } else { + // Determine whether or not we were already failing before. If we were, we + // should increase the backoff duration. + if (status_ == Status::kFailure) { + // Use exponential backoff to determine the next backoff duration. This + // simply involves multiplying our current backoff duration by some + // multiplier. + absl::Duration next_backoff_duration = + config_.backoff_multiplier * backoff_duration_; + // Update the backoff duration, making sure not to blow past the + // ceiling. + backoff_duration_ = + std::min(next_backoff_duration, config_.max_backoff_duration); + } else { + // This is our first time failing, so we should only backoff for the + // initial duration. + backoff_duration_ = config_.base_backoff_duration; + } + } + + // Update the internal result. + status_ = is_success ? Status::kSuccess : Status::kFailure; +} + +// Returns how much time has passed since we last tried reading from an +// advertisement GATT server. +absl::Duration AdvertisementReadResult::GetDurationSinceRead() const { + MutexLock lock(&mutex_); + return GetDurationSinceReadLocked(); +} + +absl::Duration AdvertisementReadResult::GetDurationSinceReadLocked() const { + return SystemClock::ElapsedRealtime() - last_read_timestamp_; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result.h b/cpp/core_v2/internal/mediums/advertisement_read_result.h new file mode 100644 index 00000000..938c11ee --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result.h @@ -0,0 +1,104 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/system_clock.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Representation of a GATT advertisement read result. This object helps us +// determine whether or not we need to retry GATT reads. +class AdvertisementReadResult { + public: + // We need a long enough duration such that we always trigger a read + // retry AND we always connect to it without delay. The former case + // helps us initialize an AdvertisementReadResult so that we + // unconditionally try reading on the first sighting. And the latter + // case helps us connect immediately when we initialize a dummy read + // result for fast advertisements (which don't use the GATT server). + + struct Config { + // How much to multiply the backoff duration by with every failure to read + // from the advertisement GATT server. This should never be below 1! + float backoff_multiplier; + // The initial backoff duration when we fail to read from an advertisement + // GATT server. + absl::Duration base_backoff_duration; + // The maximum backoff duration allowed between advertisement GATT server + // reads. + absl::Duration max_backoff_duration; + }; + + static const Config kDefaultConfig; + explicit AdvertisementReadResult(const Config& config = kDefaultConfig) + : config_(config) {} + ~AdvertisementReadResult() = default; + + enum class RetryStatus { + kUnknown = 0, + kRetry = 1, + kPreviouslySucceeded = 2, + kTooSoon = 3, + }; + + void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement) + ABSL_LOCKS_EXCLUDED(mutex_); + bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_); + std::vector GetAdvertisements() const + ABSL_LOCKS_EXCLUDED(mutex_); + RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_); + void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_); + absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + enum class Status { + kUnknown = 0, + kSuccess = 1, + kFailure = 2, + }; + + absl::Duration GetDurationSinceReadLocked() const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + + // Maps slot numbers to the GATT advertisement found in that slot. + absl::flat_hash_map advertisements_ + ABSL_GUARDED_BY(mutex_); + + Config config_; + absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_); + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_); + Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_ diff --git a/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc new file mode 100644 index 00000000..c11ca67a --- /dev/null +++ b/cpp/core_v2/internal/mediums/advertisement_read_result_test.cc @@ -0,0 +1,143 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/advertisement_read_result.h" + +#include "gtest/gtest.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +constexpr char kAdvertisementBytes[] = "\x0A\x0B\x0C"; + +// Default values may be too big and impractical to wait for in the test. +// For the test platform, we redefine them to some reasonable values. +const absl::Duration kAdvertisementBaseBackoffDuration = absl::Seconds(1); +const absl::Duration kAdvertisementMaxBackoffDuration = absl::Seconds(6); + +const AdvertisementReadResult::Config test_config{ + .backoff_multiplier = + AdvertisementReadResult::kDefaultConfig.backoff_multiplier, + .base_backoff_duration = kAdvertisementBaseBackoffDuration, + .max_backoff_duration = kAdvertisementMaxBackoffDuration, +}; + +TEST(AdvertisementReadResultTest, AdvertisementExists) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + advertisement_read_result.AddAdvertisement(slot, + ByteArray(kAdvertisementBytes)); + + EXPECT_TRUE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + std::int32_t slot = 6; + + EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot)); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) { + AdvertisementReadResult advertisement_read_result(test_config); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kPreviouslySucceeded); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep for some time, but not long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration / 2); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep long enough to warrant a retry. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Record an additional failure so our backoff duration increases. + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Sleep for the backoff duration. We shouldn't trigger a retry because the + // backoff should have increased from failing a second time. + absl::SleepFor(kAdvertisementBaseBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kTooSoon); +} + +TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + + // Record an absurd amount of failures so we hit the maximum backoff duration. + for (std::int32_t i = 0; i < 1000; i++) { + advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); + } + + // Sleep for the maximum backoff duration. This should be enough to warrant a + // retry. + absl::SleepFor(kAdvertisementMaxBackoffDuration); + + EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(), + AdvertisementReadResult::RetryStatus::kRetry); +} + +TEST(AdvertisementReadResultTest, GetDurationSinceRead) { + AdvertisementReadResult advertisement_read_result(test_config); + advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); + + absl::Duration sleepTime = absl::Milliseconds(420); + absl::SleepFor(sleepTime); + + EXPECT_GE(advertisement_read_result.GetDurationSinceRead(), sleepTime); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.cc b/cpp/core_v2/internal/mediums/ble_advertisement.cc new file mode 100644 index 00000000..1fc8a6b7 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.cc @@ -0,0 +1,215 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisement::BleAdvertisement(Version version, + SocketVersion socket_version, + const ByteArray &service_id_hash, + const ByteArray &data) { + // Check that the given input is valid. + if (!IsSupportedVersion(version) || + !IsSupportedSocketVersion(socket_version) || + service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + + version_ = version; + socket_version_ = socket_version; + service_id_hash_ = service_id_hash; + data_ = data; +} + +BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) { + if (ble_advertisement_bytes.Empty()) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: null bytes passed in."); + return; + } + + if (ble_advertisement_bytes.size() < kMinAdvertisementLength) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: expecting min %d raw " + "bytes, got %" PRIu64, + kMinAdvertisementLength, ble_advertisement_bytes.size()); + return; + } + + // Now, time to read the bytes! + const auto *read_ptr = ble_advertisement_bytes.data(); + + // 1. Version. + version_ = static_cast((*read_ptr & kVersionBitmask) >> 5); + if (!IsSupportedVersion(version_)) { + NEARBY_LOG(INFO, + "Cannot deserialize BleAdvertisement: unsupported Version %u", + version_); + return; + } + + // 2. Socket Version. + socket_version_ = + static_cast((*read_ptr & kSocketVersionBitmask) >> 2); + if (!IsSupportedSocketVersion(socket_version_)) { + NEARBY_LOG( + INFO, + "Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u", + socket_version_); + version_ = Version::kUndefined; + return; + } + read_ptr += kVersionLength; + + // 3. Service ID hash. + service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength); + read_ptr += kServiceIdHashLength; + + // 4.1. Data size. + size_t expected_data_size = DeserializeDataSize(read_ptr); + if (expected_data_size < 0) { + NEARBY_LOG( + INFO, + "Cannot deserialize BleAdvertisement: negative data size %" PRIu64, + expected_data_size); + version_ = Version::kUndefined; + return; + } + read_ptr += kDataSizeLength; + + // Check that the stated data size is the same as what we received. + size_t actual_data_size = ComputeDataSize(ble_advertisement_bytes); + if (actual_data_size < expected_data_size) { + NEARBY_LOG(INFO, + "Cannot deserialize BLEAdvertisement: expected data to be %zu " + "bytes, got %" PRIu64 " bytes", + expected_data_size, actual_data_size); + version_ = Version::kUndefined; + return; + } + + // 4.2. Data. + data_ = ByteArray(read_ptr, expected_data_size); + read_ptr += expected_data_size; +} + +BleAdvertisement::operator ByteArray() const { + if (!IsValid()) { + return ByteArray{}; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_socket_version_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 3 bits are the Socket version. 2 bits left are reserved. + version_and_socket_version_byte |= + (static_cast(socket_version_) << 2) & kSocketVersionBitmask; + // Serialize Data size bytes(4). + ByteArray data_size_bytes{kDataSizeLength}; + auto *data_size_bytes_write_ptr = data_size_bytes.data(); + SerializeDataSize(data_size_bytes_write_ptr, data_.size()); + + out.reserve(1 + service_id_hash_.size() + 1 + data_.size()); + out.append(1, version_and_socket_version_byte); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_size_bytes)); + out.append(std::string(data_)); + + return ByteArray{std::move(out)}; +} + +bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const { + return this->GetVersion() == rhs.GetVersion() && + this->GetSocketVersion() == rhs.GetSocketVersion() && + this->GetServiceIdHash() == rhs.GetServiceIdHash() && + this->GetData() == rhs.GetData(); +} + +bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const { + if (this->GetVersion() != rhs.GetVersion()) { + return this->GetVersion() < rhs.GetVersion(); + } + if (this->GetSocketVersion() != rhs.GetSocketVersion()) { + return this->GetSocketVersion() < rhs.GetSocketVersion(); + } + if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) { + return this->GetServiceIdHash() < rhs.GetServiceIdHash(); + } + return this->GetData() < rhs.GetData(); +} + +bool BleAdvertisement::IsSupportedVersion(Version version) const { + return version >= Version::kV1 && version <= Version::kV2; +} + +bool BleAdvertisement::IsSupportedSocketVersion( + SocketVersion socket_version) const { + return socket_version >= SocketVersion::kV1 && + socket_version <= SocketVersion::kV2; +} + +void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const { + // Get a raw representation of the data size bytes in memory. + char *data_size_bytes = reinterpret_cast(&data_size); + + // Append these raw bytes to advertisement bytes, keeping in mind that we need + // to convert from Little Endian to Big Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1]; + } +} + +size_t BleAdvertisement::DeserializeDataSize( + const char *data_size_bytes_read_ptr) const { + // Allocate a chunk of memory to store our deserialized size. + char data_size_bytes[kDataSizeLength]; + + // Assign the bits of our size from the given raw bytes, keeping in mind that + // we need to convert from Big Endian to Little Endian in the process. + for (int i = 0; i < kDataSizeLength; ++i) { + data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1]; + } + + // Interpret the char array as a single int. + return static_cast( + *(reinterpret_cast(&data_size_bytes))); +} + +size_t BleAdvertisement::ComputeDataSize( + const ByteArray &ble_advertisement_bytes) const { + return ble_advertisement_bytes.size() - kMinAdvertisementLength; +} + +size_t BleAdvertisement::ComputeAdvertisementLength( + const ByteArray &data) const { + // The advertisement length is the minimum length + the length of the data. + return kMinAdvertisementLength + data.size(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement.h b/cpp/core_v2/internal/mediums/ble_advertisement.h new file mode 100644 index 00000000..a64de73d --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement.h @@ -0,0 +1,114 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums Ble Advertisement used in advertising +// and discovery. +// +// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA] +// +// See go/nearby-ble-design for more information. +class BleAdvertisement { + public: + // Versions of the BleAdvertisement. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // Version is only allocated 3 bits in the BleAdvertisement, so this can + // never go beyond V7. + }; + + // Versions of the BLESocket. + enum class SocketVersion { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // SocketVersion is only allocated 3 bits in the BleAdvertisement, so this + // can never go beyond V7. + }; + + static constexpr int kServiceIdHashLength = 3; + + BleAdvertisement() = default; + BleAdvertisement(Version version, SocketVersion socket_version, + const ByteArray &service_id_hash, const ByteArray &data); + explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes); + BleAdvertisement(const BleAdvertisement &) = default; + BleAdvertisement &operator=(const BleAdvertisement &) = default; + BleAdvertisement(BleAdvertisement &&) = default; + BleAdvertisement &operator=(BleAdvertisement &&) = default; + ~BleAdvertisement() = default; + + explicit operator ByteArray() const; + // Operator overloads when comparing BleAdvertisement. + bool operator==(const BleAdvertisement &rhs) const; + bool operator<(const BleAdvertisement &rhs) const; + + bool IsValid() const { return IsSupportedVersion(version_); } + Version GetVersion() const { return version_; } + SocketVersion GetSocketVersion() const { return socket_version_; } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray &GetData() & { return data_; } + const ByteArray &GetData() const & { return data_; } + ByteArray &&GetData() && { return std::move(data_); } + const ByteArray &&GetData() const && { return std::move(data_); } + + private: + bool IsSupportedVersion(Version version) const; + bool IsSupportedSocketVersion(SocketVersion socket_version) const; + void SerializeDataSize(char *data_size_bytes_write_ptr, + size_t data_size) const; + size_t DeserializeDataSize(const char *data_size_bytes_read_ptr) const; + size_t ComputeDataSize(const ByteArray &ble_advertisement_bytes) const; + size_t ComputeAdvertisementLength(const ByteArray &data) const; + + static constexpr int kVersionLength = 1; + // Length of one int. Be sure to re-evaluate how we compute data size in this + // class if this constant ever changes! + static constexpr int kDataSizeLength = 4; + static constexpr int kMinAdvertisementLength = + kVersionLength + kServiceIdHashLength + kDataSizeLength; + // The maximum length for a Gatt characteristic value is 512 bytes, so make + // sure the entire advertisement is less than that. The data can take up + // whatever space is remaining after the bytes preceding it. + static constexpr int kMaxGattCharacteristicValueSize = 512; + static constexpr int kMaxDataSize = + kMaxGattCharacteristicValueSize - kMinAdvertisementLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kSocketVersionBitmask = 0x01C; + + Version version_{Version::kUndefined}; + SocketVersion socket_version_{SocketVersion::kUndefined}; + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc new file mode 100644 index 00000000..768fd00d --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.cc @@ -0,0 +1,132 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BleAdvertisementHeader::BleAdvertisementHeader( + Version version, int num_slots, const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash) { + // TODO(edwinwu): Checks if num_slots needs to be >= 0 + if (version != Version::kV2 || + service_id_bloom_filter.size() != kServiceIdBloomFilterLength || + advertisement_hash.size() != kAdvertisementHashLength) { + return; + } + + version_ = version; + num_slots_ = num_slots; + service_id_bloom_filter_ = service_id_bloom_filter; + advertisement_hash_ = advertisement_hash; +} + +BleAdvertisementHeader::BleAdvertisementHeader( + const std::string &ble_advertisement_header_string) { + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + + if (ble_advertisement_header_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding"); + return; + } + + if (ble_advertisement_header_bytes.size() < kMinAdvertisementHeaderLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize BleAdvertisementHeader: expecting min %u " + "raw bytes, got %" PRIu64 " instead", + kMinAdvertisementHeaderLength, + ble_advertisement_header_bytes.size()); + return; + } + + // Start reading the bytes. + auto *ble_advertisement_header_read_ptr = + ble_advertisement_header_bytes.data(); + + // The first 3 bits are supposed to be the version. + version_ = static_cast( + (*ble_advertisement_header_read_ptr & kVersionBitmask) >> 5); + if (version_ != Version::kV2) { + NEARBY_LOG( + ERROR, + "Cannot deserialize BleAdvertisementHeader: unsupported Version %d", + version_); + return; + } + // The last 5 bits of the first byte represent the number of slots. + num_slots_ = static_cast(*ble_advertisement_header_read_ptr & + kNumSlotsBitmask); + ble_advertisement_header_read_ptr++; + + // Service ID bloom filter. + service_id_bloom_filter_ = + ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength); + ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength; + + // Advertisement hash. + advertisement_hash_ = + ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength); + ble_advertisement_header_read_ptr += kAdvertisementHashLength; +} + +BleAdvertisementHeader::operator std::string() const { + if (!IsValid()) { + return ""; + } + + std::string out; + + // The first 3 bits are the Version. + char version_and_num_slots_byte = + (static_cast(version_) << 5) & kVersionBitmask; + // The next 5 bits are the number of slots. + version_and_num_slots_byte |= + static_cast(num_slots_) & kNumSlotsBitmask; + out.reserve(1 + service_id_bloom_filter_.size() + advertisement_hash_.size()); + out.append(1, version_and_num_slots_byte); + out.append(std::string(service_id_bloom_filter_)); + out.append(std::string(advertisement_hash_)); + + return Base64Utils::Encode(ByteArray(std::move(out))); +} + +bool BleAdvertisementHeader::operator<( + const BleAdvertisementHeader &rhs) const { + if (this->GetVersion() != rhs.GetVersion()) { + return this->GetVersion() < rhs.GetVersion(); + } + if (this->GetNumSlots() != rhs.GetNumSlots()) { + return this->GetNumSlots() < rhs.GetNumSlots(); + } + if (this->GetServiceIdBloomFilter() != rhs.GetServiceIdBloomFilter()) { + return this->GetServiceIdBloomFilter() < rhs.GetServiceIdBloomFilter(); + } + return this->GetAdvertisementHash() < rhs.GetAdvertisementHash(); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header.h b/cpp/core_v2/internal/mediums/ble_advertisement_header.h new file mode 100644 index 00000000..18f34608 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header.h @@ -0,0 +1,98 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of the Mediums BLE Advertisement Header used in +// Advertising + Discovery. +// +// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH] +// +// See go/nearby-ble-design for more information. +// +// Note. The object constructed by default constructor or the parameterized +// constructor with invalid value(s) is treated as invalid instance. Caller +// should be responsible to call IsValid() to check the instance is invalid in +// advance before continue on. +class BleAdvertisementHeader { + public: + // Versions of the BleAdvertisementHeader. + enum class Version { + kUndefined = 0, + kV1 = 1, + kV2 = 2, + // Version is only allocated 3 bits in the BleAdvertisementHeader, so this + // can never go beyond V7. + // + // V1 is not present because it's an old format used in Nearby Connections + // before this logic was pushed down into Nearby Mediums. V1 put + // everything in the service data, while V2 puts the data inside a GATT + // characteristic so the two are not compatible. + }; + + BleAdvertisementHeader() = default; + BleAdvertisementHeader(Version version, int num_slots, + const ByteArray &service_id_bloom_filter, + const ByteArray &advertisement_hash); + explicit BleAdvertisementHeader( + const std::string &ble_advertisement_header_string); + ~BleAdvertisementHeader() = default; + + BleAdvertisementHeader(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default; + BleAdvertisementHeader(BleAdvertisementHeader &&) = default; + BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default; + + // Produces an encoded binary string which can be decoded by the explicit + // constructor. The returned string is empty if BleAdvertisementHeader is not + // valid - false on IsValid(). + explicit operator std::string() const; + bool operator<(const BleAdvertisementHeader &rhs) const; + + bool IsValid() const { return version_ == Version::kV2; } + Version GetVersion() const { return version_; } + int GetNumSlots() const { return num_slots_; } + ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; } + ByteArray GetAdvertisementHash() const { return advertisement_hash_; } + + private: + static constexpr int kServiceIdBloomFilterLength = 10; + static constexpr int kAdvertisementHashLength = 4; + static constexpr int kMinAdvertisementHeaderLength = + 1 + kServiceIdBloomFilterLength + kAdvertisementHashLength; + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kNumSlotsBitmask = 0x01F; + + Version version_ = Version::kUndefined; + int num_slots_; + ByteArray service_id_bloom_filter_; + ByteArray advertisement_hash_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_ diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc new file mode 100644 index 00000000..77b39520 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_header_test.cc @@ -0,0 +1,190 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_advertisement_header.h" + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { +constexpr BleAdvertisementHeader::Version kVersion = + BleAdvertisementHeader::Version::kV2; +constexpr int kNumSlots = 2; +constexpr char kServiceIDBloomFilter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"; +constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d"; + +TEST(BleAdvertisementHeaderTest, ConstructionWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithShortServiceIdBloomFilter) { + char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09"; + + ByteArray short_service_id_bloom_filter_bytes(short_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, short_service_id_bloom_filter_bytes, + advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, + ConstructionFailsWithLongServiceIdBloomFilter) { + char long_service_id_bloom_filter[] = + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b"; + + ByteArray service_id_bloom_filter(long_service_id_bloom_filter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) { + char short_advertisement_hash[] = "\x0a\x0b\x0c"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(short_advertisement_hash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) { + char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\0x0e"; + + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(long_advertisement_hash, + sizeof(long_advertisement_hash) / sizeof(char)); + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + + EXPECT_FALSE(ble_advertisement_header.IsValid()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader org_ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = + std::string(org_ble_advertisement_header); + + auto ble_advertisement_header = + BleAdvertisementHeader(ble_advertisement_header_string); + + EXPECT_TRUE(ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, add a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray long_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() + 1); + long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes); + std::string long_ble_advertisement_header_string = + Base64Utils::Encode(long_ble_advertisement_header_bytes); + + auto long_ble_advertisement_header = + BleAdvertisementHeader(long_ble_advertisement_header_string); + + EXPECT_TRUE(long_ble_advertisement_header.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion()); + EXPECT_EQ(kNumSlots, long_ble_advertisement_header.GetNumSlots()); + EXPECT_EQ(service_id_bloom_filter, + long_ble_advertisement_header.GetServiceIdBloomFilter()); + EXPECT_EQ(advertisement_hash, + long_ble_advertisement_header.GetAdvertisementHash()); +} + +TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { + ByteArray service_id_bloom_filter(kServiceIDBloomFilter); + ByteArray advertisement_hash(kAdvertisementHash); + + BleAdvertisementHeader ble_advertisement_header( + kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash); + auto ble_advertisement_header_string = std::string(ble_advertisement_header); + + // Base64 decode the string, remove a character, and then re-encode it. + ByteArray ble_advertisement_header_bytes = + Base64Utils::Decode(ble_advertisement_header_string); + ByteArray short_ble_advertisement_header_bytes( + ble_advertisement_header_bytes.size() - 1); + short_ble_advertisement_header_bytes.CopyAt(0, + ble_advertisement_header_bytes); + std::string short_ble_advertisement_header_string = + Base64Utils::Encode(short_ble_advertisement_header_bytes); + + auto short_ble_advertisement_header = + BleAdvertisementHeader(short_ble_advertisement_header_string); + + EXPECT_FALSE(short_ble_advertisement_header.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_advertisement_test.cc b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc new file mode 100644 index 00000000..4d75137f --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_advertisement_test.cc @@ -0,0 +1,237 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_advertisement.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2; +const BleAdvertisement::SocketVersion kSocketVersion = + BleAdvertisement::SocketVersion::kV2; +const char kServiceIDHashBytes[] = "\x0a\x0b\x0c"; +const char kData[] = + "How much wood can a woodchuck chuck if a wood chuck would chuck wood?"; +// This corresponds to the length of a specific BleAdvertisement packed with the +// kData given above. Be sure to update this if kData ever changes. +const size_t kAdvertisementLength = 77; +const size_t kLongAdvertisementLength = kAdvertisementLength + 1000; + +TEST(BleAdvertisementTest, ConstructionWorksV1) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1, + BleAdvertisement::SocketVersion::kV1, + service_id_hash, data}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion()); + EXPECT_EQ(BleAdvertisement::SocketVersion::kV1, + ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) { + BleAdvertisement::Version bad_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{bad_version, kSocketVersion, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) { + BleAdvertisement::SocketVersion bad_socket_version = + static_cast(666); + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, bad_socket_version, + service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = "\x0a\x0b"; + + ByteArray bad_service_id_hash{short_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d"; + + ByteArray bad_service_id_hash{long_service_id_hash_bytes}; + ByteArray data{kData}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, + bad_service_id_hash, data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFailsWithLongData) { + // BleAdvertisement shouldn't be able to support data with the max GATT + // attribute length because it needs some room for the preceding fields. + char long_data[512]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray bad_data{long_data, 512}; + + BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash, + bad_data}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) { + char empty_data[0]{}; + + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{empty_data}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray ble_advertisement_bytes{org_ble_advertisement}; + BleAdvertisement ble_advertisement{ble_advertisement_bytes}; + + EXPECT_TRUE(ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), ble_advertisement.GetData().size()); + EXPECT_EQ(data, ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Copy the bytes into a new array with extra bytes. We must explicitly + // define how long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kLongAdvertisementLength]{}; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + std::min(sizeof(raw_ble_advertisement_bytes), + org_ble_advertisement_bytes.size())); + + // Re-parse the Ble advertisement using our extra long advertisement bytes. + ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kLongAdvertisementLength}; + BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes}; + + EXPECT_TRUE(long_ble_advertisement.IsValid()); + EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion()); + EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion()); + EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash()); + EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size()); + EXPECT_EQ(data, long_ble_advertisement.GetData()); +} + +TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) { + BleAdvertisement ble_advertisement{ByteArray{}}; + + EXPECT_FALSE(ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Cut off the advertisement so that it's too short. + ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(), + 7}; + BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes}; + + EXPECT_FALSE(short_ble_advertisement.IsValid()); +} + +TEST(BleAdvertisementTest, + ConstructionFromSerializedBytesWithInvalidDataLengthFails) { + ByteArray service_id_hash{kServiceIDHashBytes}; + ByteArray data{kData}; + + BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, + service_id_hash, data}; + ByteArray org_ble_advertisement_bytes{org_ble_advertisement}; + + // Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble + // advertisement bytes so we can modify it. We must explicitly define how + // long our array is because we can't use variable length arrays. + char raw_ble_advertisement_bytes[kAdvertisementLength]; + memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(), + kAdvertisementLength); + + // The data size field lives in indices 4-7. Corrupt it. + memset(raw_ble_advertisement_bytes + 4, 0xFF, 4); + + // Try to parse the Ble advertisement using our corrupted advertisement bytes. + ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes, + kAdvertisementLength}; + BleAdvertisement corrupted_ble_advertisement{ + corrupted_ble_advertisement_bytes}; + + EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.cc b/cpp/core_v2/internal/mediums/ble_packet.cc new file mode 100644 index 00000000..fce1a855 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.cc @@ -0,0 +1,73 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_packet.h" + +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) { + if (service_id_hash.size() != kServiceIdHashLength || + data.size() > kMaxDataSize) { + return; + } + service_id_hash_ = service_id_hash; + data_ = data; +} + +BlePacket::BlePacket(const ByteArray& ble_packet_bytes) { + if (ble_packet_bytes.Empty()) { + NEARBY_LOG(ERROR, "Cannot deserialize BlePacket: null bytes passed in"); + return; + } + + if (ble_packet_bytes.size() < kServiceIdHashLength) { + NEARBY_LOG( + INFO, + "Cannot deserialize BlePacket: expecting min %u raw bytes, got %zu", + kServiceIdHashLength, ble_packet_bytes.size()); + return; + } + + const char *ble_packet_bytes_read_ptr = ble_packet_bytes.data(); + service_id_hash_ = + ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength); + ble_packet_bytes_read_ptr += kServiceIdHashLength; + + data_ = ByteArray(ble_packet_bytes_read_ptr, + ble_packet_bytes.size() - kServiceIdHashLength); +} + +BlePacket::operator ByteArray() const { + if (!IsValid()) { + return ByteArray(); + } + + std::string out; + + out.reserve(service_id_hash_.size() + data_.size()); + out.append(std::string(service_id_hash_)); + out.append(std::string(data_)); + + return ByteArray(std::move(out)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_packet.h b/cpp/core_v2/internal/mediums/ble_packet.h new file mode 100644 index 00000000..363220c4 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet.h @@ -0,0 +1,65 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Represents the format of data sent over Ble sockets. +// +// [SERVICE_ID_HASH][DATA] +// +// See go/nearby-ble-design for more information. +class BlePacket { + public: + static const std::uint32_t kServiceIdHashLength = 3; + + BlePacket() = default; + BlePacket(const ByteArray& service_id_hash, const ByteArray& data); + explicit BlePacket(const ByteArray& ble_packet_byte); + ~BlePacket() = default; + + BlePacket(const BlePacket&) = default; + BlePacket& operator=(const BlePacket&) = default; + BlePacket(BlePacket&&) = default; + BlePacket& operator=(BlePacket&&) = default; + + explicit operator ByteArray() const; + + bool IsValid() const { return !service_id_hash_.Empty(); } + ByteArray GetServiceIdHash() const { return service_id_hash_; } + ByteArray GetData() const { return data_; } + + private: + static const std::uint32_t kMaxDataSize = + std::numeric_limits::max() - kServiceIdHashLength; + + ByteArray service_id_hash_; + ByteArray data_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_ diff --git a/cpp/core_v2/internal/mediums/ble_packet_test.cc b/cpp/core_v2/internal/mediums/ble_packet_test.cc new file mode 100644 index 00000000..c37775ae --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_packet_test.cc @@ -0,0 +1,111 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_packet.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +constexpr char kServiceIDHash[] = "\x0a\x0b\x0c"; +constexpr char kData[] = "\x01\x02\x03\x04\x05"; + +TEST(BlePacketTest, ConstructionWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionWorksWithEmptyData) { + char empty_data[] = {}; + + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(empty_data); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash[] = "\x0a\x0b"; + + ByteArray service_id_hash(short_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash[] = "\x0a\x0b\x0c\x0d"; + + ByteArray service_id_hash(long_service_id_hash); + ByteArray data(kData); + + BlePacket ble_packet(service_id_hash, data); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray ble_packet_bytes(org_ble_packet); + + BlePacket ble_packet(ble_packet_bytes); + + EXPECT_TRUE(ble_packet.IsValid()); + EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash()); + EXPECT_EQ(data, ble_packet.GetData()); +} + +TEST(BlePacketTest, ConstructionFromNullBytesFails) { + BlePacket ble_packet(ByteArray{}); + + EXPECT_FALSE(ble_packet.IsValid()); +} + +TEST(BlePacketTest, ConstructionFromShortLengthDataFails) { + ByteArray service_id_hash(kServiceIDHash); + ByteArray data(kData); + + BlePacket org_ble_packet(service_id_hash, data); + ByteArray org_ble_packet_bytes(org_ble_packet); + + // Cut off the packet so that it's too short + ByteArray short_ble_packet_bytes(ByteArray(org_ble_packet_bytes.data(), 2)); + + BlePacket short_ble_packet(short_ble_packet_bytes); + + EXPECT_FALSE(short_ble_packet.IsValid()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/ble_peripheral.h b/cpp/core_v2/internal/mediums/ble_peripheral.h new file mode 100644 index 00000000..f5af9807 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +class BlePeripheral { + public: + BlePeripheral() = default; + explicit BlePeripheral(const ByteArray& id) : id_(id) {} + ~BlePeripheral() = default; + + BlePeripheral(const BlePeripheral&) = default; + BlePeripheral& operator=(const BlePeripheral&) = default; + BlePeripheral(BlePeripheral&&) = default; + BlePeripheral& operator=(BlePeripheral&&) = default; + + bool IsValid() const { return !id_.Empty(); } + ByteArray GetId() const { return id_; } + + private: + // A unique identifier for this peripheral. It can be the BLE advertisement it + // was found on, or even simply the BLE MAC address. + ByteArray id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_ diff --git a/cpp/core_v2/internal/mediums/ble_peripheral_test.cc b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc new file mode 100644 index 00000000..026ab3b4 --- /dev/null +++ b/cpp/core_v2/internal/mediums/ble_peripheral_test.cc @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/ble_peripheral.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +const char kId[] = "AB12"; + +TEST(BlePeripheralTest, ConstructionWorks) { + ByteArray id(kId); + + BlePeripheral ble_peripheral(id); + + EXPECT_TRUE(ble_peripheral.IsValid()); + EXPECT_EQ(id, ble_peripheral.GetId()); +} + +TEST(BlePeripheralTest, ConstructionEmptyFails) { + BlePeripheral ble_peripheral; + + EXPECT_FALSE(ble_peripheral.IsValid()); + EXPECT_TRUE(ble_peripheral.GetId().Empty()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.cc b/cpp/core_v2/internal/mediums/bluetooth_radio.cc new file mode 100644 index 00000000..e7ec394e --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.cc @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "platform_v2/base/exception.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothRadio::BluetoothRadio() { + if (!IsAdapterValid()) { + NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported"); + } +} + +BluetoothRadio::~BluetoothRadio() { + // We never enabled Bluetooth, nothing to do. + if (!ever_saved_state_.Get()) { + NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW."); + return; + } + + // Toggle Bluetooth regardless of our original state. Some devices/chips can + // start to freak out after some time (e.g. b/37775337), and this helps to + // ensure BT resets properly. + NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter."); + Toggle(); + + NEARBY_LOG(INFO, "Bring BT adapter to original state"); + if (!SetBluetoothState(originally_enabled_.Get())) { + NEARBY_LOG(INFO, "Failed to restore BT adapter original state."); + } +} + +bool BluetoothRadio::Enable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(true); +} + +bool BluetoothRadio::Disable() { + if (!SaveOriginalState()) { + return false; + } + + return SetBluetoothState(false); +} + +bool BluetoothRadio::IsEnabled() const { + return IsAdapterValid() && IsInDesiredState(true); +} + +bool BluetoothRadio::Toggle() { + if (!SaveOriginalState()) { + return false; + } + + if (!SetBluetoothState(false)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off."); + return false; + } + + if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) { + NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on."); + return false; + } + + if (!SetBluetoothState(true)) { + NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on."); + return false; + } + + return true; +} + +bool BluetoothRadio::SetBluetoothState(bool enable) { + return bluetooth_adapter_.SetStatus( + enable ? BluetoothAdapter::Status::kEnabled + : BluetoothAdapter::Status::kDisabled); +} + +bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const { + return bluetooth_adapter_.IsEnabled() == should_be_enabled; +} + +bool BluetoothRadio::SaveOriginalState() { + if (!IsAdapterValid()) { + return false; + } + + // If we haven't saved the original state of the radio, save it. + if (!ever_saved_state_.Set(true)) { + originally_enabled_.Set(bluetooth_adapter_.IsEnabled()); + } + + return true; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.h b/cpp/core_v2/internal/mediums/bluetooth_radio.h new file mode 100644 index 00000000..ad43d18f --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.h @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ + +#include + +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +// Provides the operations that can be performed on the Bluetooth radio. +class BluetoothRadio { + public: + BluetoothRadio(); + BluetoothRadio(BluetoothRadio&&) = default; + BluetoothRadio& operator=(BluetoothRadio&&) = default; + + // Reverts the Bluetooth radio to its original state. + ~BluetoothRadio(); + + // Enables Bluetooth. + // + // This must be called before attempting to invoke any other methods of + // this class. + // + // Returns true if enabled successfully. + bool Enable(); + + // Disables Bluetooth. + // + // Returns true if disabled successfully. + bool Disable(); + + // Returns true if the Bluetooth radio is currently enabled. + bool IsEnabled() const; + + // Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On. + // This will block calling thread for at least kPauseBetweenToggle duration. + bool Toggle(); + + // Returns result of BluetoothAdapter::IsValid() for private adapter instance. + bool IsAdapterValid() const { + return bluetooth_adapter_.IsValid(); + } + + BluetoothAdapter& GetBluetoothAdapter() { + return bluetooth_adapter_; + } + + private: + static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3); + + bool SetBluetoothState(bool enable); + bool IsInDesiredState(bool should_be_enabled) const; + // To be called in enable(), disable(), and toggle(). This will remember the + // original state of the radio before any radio state has been modified. + // Returns false if Bluetooth doesn't exist on the device and the state cannot + // be obtained. + bool SaveOriginalState(); + + // BluetoothAdapter::IsValid() will return false if BT is not supported. + BluetoothAdapter bluetooth_adapter_; + + // The Bluetooth radio's original state, before we modified it. True if + // originally enabled, false if originally disabled. + // We restore the radio to its original state in the destructor. + + AtomicBoolean originally_enabled_{false}; + // false if we never modified the radio state, true otherwise. + AtomicBoolean ever_saved_state_{false}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_ diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc new file mode 100644 index 00000000..415f71ff --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_radio_test.cc @@ -0,0 +1,59 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/bluetooth_radio.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(BluetoothRadioTest, ConstructorDestructorWorks) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); +} + +TEST(BluetoothRadioTest, CanEnable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanDisable) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Enable()); + EXPECT_TRUE(radio.IsEnabled()); + EXPECT_TRUE(radio.Disable()); + EXPECT_FALSE(radio.IsEnabled()); +} + +TEST(BluetoothRadioTest, CanToggle) { + BluetoothRadio radio; + EXPECT_TRUE(radio.IsAdapterValid()); + EXPECT_FALSE(radio.IsEnabled()); + EXPECT_TRUE(radio.Toggle()); + EXPECT_TRUE(radio.IsEnabled()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker.h b/cpp/core_v2/internal/mediums/lost_entity_tracker.h new file mode 100644 index 00000000..34298b51 --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker.h @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ +#define CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ + +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/container/flat_hash_set.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Tracks "lost" entities based on a manual update/compute model. Used by +// mediums that only report found devices. Lost entities are computed based off +// of whether a specific entity was rediscovered since the last call to +// ComputeLostEntities. +// +// Note: Entity must overload the < and == operators. +template +class LostEntityTracker { + public: + using EntitySet = absl::flat_hash_set; + + LostEntityTracker(); + ~LostEntityTracker(); + + // Records the given entity as being recently found, whether or not this is + // our first time discovering the entity. + void RecordFoundEntity(const Entity& entity) ABSL_LOCKS_EXCLUDED(mutex_); + + // Computes and returns the set of entities considered lost since the last + // time this method was called. + EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Mutex mutex_; + EntitySet current_entities_ ABSL_GUARDED_BY(mutex_); + EntitySet previously_found_entities_ ABSL_GUARDED_BY(mutex_); +}; + +template +LostEntityTracker::LostEntityTracker() + : current_entities_{}, previously_found_entities_{} {} + +template +LostEntityTracker::~LostEntityTracker() { + previously_found_entities_.clear(); + current_entities_.clear(); +} + +template +void LostEntityTracker::RecordFoundEntity(const Entity& entity) { + MutexLock lock(&mutex_); + + current_entities_.insert(entity); +} + +template +typename LostEntityTracker::EntitySet +LostEntityTracker::ComputeLostEntities() { + MutexLock lock(&mutex_); + + // The set of lost entities is the previously found set MINUS the currently + // found set. + for (const auto& item : current_entities_) { + previously_found_entities_.erase(item); + } + auto lost_entities = std::move(previously_found_entities_); + previously_found_entities_ = std::move(current_entities_); + current_entities_ = {}; + + return lost_entities; +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_ diff --git a/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc new file mode 100644 index 00000000..3f373f52 --- /dev/null +++ b/cpp/core_v2/internal/mediums/lost_entity_tracker_test.cc @@ -0,0 +1,137 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/lost_entity_tracker.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace { + +struct TestEntity { + int id; + + template + friend H AbslHashValue(H h, const TestEntity& test_entity) { + return H::combine(std::move(h), test_entity.id); + } + + bool operator==(const TestEntity& other) const { return id == other.id; } + bool operator<(const TestEntity& other) const { return id < other.id; } +}; + +TEST(LostEntityTrackerTest, NoEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure none are lost on the first round. + ASSERT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Rediscover the same entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure we still didn't lose any entities. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); +} + +TEST(LostEntityTrackerTest, AllEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + lost_entity_tracker.RecordFoundEntity(entity_3); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through a round without rediscovering any entities. + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) != lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SomeEntitiesLost) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_2{2}; + TestEntity entity_3{3}; + + // Discover some entities. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_2); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through the next round only rediscovering one of our entities and + // discovering an additional entity as well. Then, verify that only one entity + // was lost after the check. + lost_entity_tracker.RecordFoundEntity(entity_1); + lost_entity_tracker.RecordFoundEntity(entity_3); + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_TRUE(lost_entities.find(entity_1) == lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_3) == lost_entities.end()); +} + +TEST(LostEntityTrackerTest, SameEntityMultipleCopies) { + LostEntityTracker lost_entity_tracker; + TestEntity entity_1{1}; + TestEntity entity_1_copy{1}; + + // Discover an entity. + lost_entity_tracker.RecordFoundEntity(entity_1); + + // Make sure none are lost on the first round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Rediscover the same entity, but through a copy of it. + lost_entity_tracker.RecordFoundEntity(entity_1_copy); + + // Make sure none are lost on the second round. + EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty()); + + // Go through a round without rediscovering any entities and verify that we + // lost an entity equivalent to both copies of it. + typename LostEntityTracker::EntitySet lost_entities = + lost_entity_tracker.ComputeLostEntities(); + EXPECT_EQ(lost_entities.size(), 1); + EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end()); + EXPECT_TRUE(lost_entities.find(entity_1_copy) != lost_entities.end()); +} + +} // namespace +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.cc b/cpp/core_v2/internal/mediums/utils.cc new file mode 100644 index 00000000..60d05268 --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.cc @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/utils.h" + +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { + +ByteArray Utils::GenerateRandomBytes(size_t length) { + Prng rng; + std::string data; + data.reserve(length); + + // Adds 4 random bytes per iteration. + while (length > 0) { + std::uint32_t val = rng.NextUint32(); + for (int i = 0; i < 4; i++) { + data += val & 0xFF; + val >>= 8; + length--; + + if (!length) break; + } + } + + return ByteArray(data); +} + +ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) { + ByteArray full_hash(length); + full_hash.CopyAt(0, Crypto::Sha256(std::string(source))); + return full_hash; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/utils.h b/cpp/core_v2/internal/mediums/utils.h new file mode 100644 index 00000000..0337fc1d --- /dev/null +++ b/cpp/core_v2/internal/mediums/utils.h @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ + +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +class Utils { + public: + static ByteArray GenerateRandomBytes(size_t length); + static ByteArray Sha256Hash(const ByteArray& source, size_t length); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UTILS_H_ diff --git a/cpp/core_v2/internal/mediums/uuid.cc b/cpp/core_v2/internal/mediums/uuid.cc new file mode 100644 index 00000000..0230fe30 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.cc @@ -0,0 +1,89 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/uuid.h" + +#include +#include + +#include "platform_v2/public/crypto.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { +std::ostream& write_hex(std::ostream& os, absl::string_view data) { + for (const auto b : data) { + os << std::setfill('0') + << std::setw(2) + << std::hex + << (static_cast(b) & 0x0ff); + } + return os; +} +} // namespace + +Uuid::Uuid(absl::string_view data) : data_(Crypto::Md5(data)) { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162. + data_[6] &= 0x0f; // Clear version. + data_[6] |= 0x30; // Set to version 3. + data_[8] &= 0x3f; // Clear variant. + data_[8] |= 0x80; // Set to IETF variant. +} + +Uuid::Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits) { + // Base on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104. + data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits)); + + data_[0] = static_cast((most_sig_bits >> 56) & 0x0ff); + data_[1] = static_cast((most_sig_bits >> 48) & 0x0ff); + data_[2] = static_cast((most_sig_bits >> 40) & 0x0ff); + data_[3] = static_cast((most_sig_bits >> 32) & 0x0ff); + data_[4] = static_cast((most_sig_bits >> 24) & 0x0ff); + data_[5] = static_cast((most_sig_bits >> 16) & 0x0ff); + data_[6] = static_cast((most_sig_bits >> 8) & 0x0ff); + data_[7] = static_cast((most_sig_bits >> 0) & 0x0ff); + + data_[8] = static_cast((least_sig_bits >> 56) & 0x0ff); + data_[9] = static_cast((least_sig_bits >> 48) & 0x0ff); + data_[10] = static_cast((least_sig_bits >> 40) & 0x0ff); + data_[11] = static_cast((least_sig_bits >> 32) & 0x0ff); + data_[12] = static_cast((least_sig_bits >> 24) & 0x0ff); + data_[13] = static_cast((least_sig_bits >> 16) & 0x0ff); + data_[14] = static_cast((least_sig_bits >> 8) & 0x0ff); + data_[15] = static_cast((least_sig_bits >> 0) & 0x0ff); +} + +Uuid::operator std::string() const { + // Based on the Java counterpart at + // http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375. + std::ostringstream md5_hex; + write_hex(md5_hex, absl::string_view(&data_[0], 4)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[4], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[6], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[8], 2)); + md5_hex << "-"; + write_hex(md5_hex, absl::string_view(&data_[10], 6)); + + return md5_hex.str(); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/uuid.h b/cpp/core_v2/internal/mediums/uuid.h new file mode 100644 index 00000000..6c432d29 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid.h @@ -0,0 +1,59 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_UUID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_UUID_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// A type 3 name-based +// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) +// UUID. +// +// https://developer.android.com/reference/java/util/UUID.html +class Uuid final { + public: + Uuid() : Uuid("uuid") {} + explicit Uuid(absl::string_view data); + Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits); + Uuid(const Uuid&) = default; + Uuid& operator=(const Uuid&) = default; + Uuid(Uuid&&) = default; + Uuid& operator=(Uuid&&) = default; + ~Uuid() = default; + + // Returns the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the + // UUID. + explicit operator std::string() const; + std::string data() const { + return data_; + } + + private: + std::string data_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_UUID_H_ diff --git a/cpp/core_v2/internal/mediums/uuid_test.cc b/cpp/core_v2/internal/mediums/uuid_test.cc new file mode 100644 index 00000000..993311e9 --- /dev/null +++ b/cpp/core_v2/internal/mediums/uuid_test.cc @@ -0,0 +1,70 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/uuid.h" + +#include "platform_v2/public/crypto.h" +#include "platform_v2/public/logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr char kString[] = "some string"; +constexpr std::uint64_t kNum1 = 0x123456789abcdef0; +constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f; + +TEST(UuidTest, CreateFromStringWithMd5) { + Uuid uuid(kString); + std::string uuid_str(uuid); + std::string uuid_data(uuid.data()); + std::string md5_data(Crypto::Md5(kString)); + NEARBY_LOG(INFO, "MD5-based UUID: '%s'", uuid_str.c_str()); + uuid_data[6] = 0; + uuid_data[8] = 0; + md5_data[6] = 0; + md5_data[8] = 0; + EXPECT_EQ(md5_data, uuid_data); +} + +TEST(UuidTest, CreateFromBinary) { + Uuid uuid(kNum1, kNum2); + std::string uuid_data(uuid.data()); + std::string uuid_str(uuid); + NEARBY_LOG(INFO, "UUID: '%s'", uuid_str.c_str()); + EXPECT_EQ(uuid_data[0], (kNum1 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[1], (kNum1 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[2], (kNum1 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[3], (kNum1 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[4], (kNum1 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[5], (kNum1 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[6], (kNum1 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[7], (kNum1 >> 0) & 0xFF); + EXPECT_EQ(uuid_data[8], (kNum2 >> 56) & 0xFF); + EXPECT_EQ(uuid_data[9], (kNum2 >> 48) & 0xFF); + EXPECT_EQ(uuid_data[10], (kNum2 >> 40) & 0xFF); + EXPECT_EQ(uuid_data[11], (kNum2 >> 32) & 0xFF); + EXPECT_EQ(uuid_data[12], (kNum2 >> 24) & 0xFF); + EXPECT_EQ(uuid_data[13], (kNum2 >> 16) & 0xFF); + EXPECT_EQ(uuid_data[14], (kNum2 >> 8) & 0xFF); + EXPECT_EQ(uuid_data[15], (kNum2 >> 0) & 0xFF); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD new file mode 100644 index 00000000..99a04d47 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -0,0 +1,90 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "webrtc", + srcs = [ + "webrtc_socket.cc", + ], + hdrs = [ + "webrtc_socket.h", + ], + deps = [ + "//core_v2:core_types", + "//platform_v2/base", + "//platform_v2/public", + "//platform_v2/public:logging", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "webrtc_test", + srcs = ["webrtc_socket_test.cc"], + deps = [ + ":webrtc", + "//platform_v2/base", + "//platform_v2/impl/g3", # buildcleaner: keep + "//testing/base/public:gunit_main", + "//webrtc/api:libjingle_peerconnection_api", + ], +) + +cc_test( + name = "peer_id_test", + srcs = ["peer_id_test.cc"], + deps = [ + ":peer_id", + "//platform_v2/base", + "//platform_v2/impl/g3", #buildcleaner: keep + "//platform_v2/public", + "//testing/base/public:gunit_main", + ], +) + +cc_test( + name = "signaling_frames_test", + srcs = ["signaling_frames_test.cc"], + deps = [ + ":peer_id", + ":signaling_frames", + "//platform_v2/impl/g3", # buildcleaner: keep + "//net/proto2/public:proto2", + "//testing/base/public:gunit_main", + "//webrtc/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/api:libjingle_peerconnection_api", + ], +) diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc new file mode 100644 index 00000000..6a830627 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.cc @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "core_v2/internal/mediums/utils.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { +constexpr int kPeerIdLength = 64; + +std::string BytesToStringUppercase(const ByteArray& bytes) { + std::string hex_string( + absl::BytesToHexString(std::string(bytes.data(), bytes.size()))); + absl::AsciiStrToUpper(&hex_string); + return hex_string; +} +} // namespace + +PeerId PeerId::FromRandom() { + return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength)); +} + +PeerId PeerId::FromSeed(const ByteArray& seed) { + ByteArray full_hash(Utils::Sha256Hash(seed, kPeerIdLength)); + ByteArray hashed_seed(full_hash.data(), kPeerIdLength / 2); + return PeerId(BytesToStringUppercase(hashed_seed)); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id.h b/cpp/core_v2/internal/mediums/webrtc/peer_id.h new file mode 100644 index 00000000..307724af --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id.h @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ + +#include +#include + +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// PeerId is used as an identifier to exchange SDP messages to establish WebRTC +// p2p connection. +class PeerId { + public: + explicit PeerId(const string& id) : id_(id) {} + ~PeerId() = default; + + static PeerId FromRandom(); + static PeerId FromSeed(const ByteArray& seed); + + const string& GetId() const { return id_; } + + private: + const string id_; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc new file mode 100644 index 00000000..7227ae92 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/peer_id_test.cc @@ -0,0 +1,56 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/peer_id.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/crypto.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +TEST(PeerIdTest, GenerateRandomPeerId) { + PeerId peer_id = PeerId::FromRandom(); + EXPECT_EQ(64, peer_id.GetId().size()); +} + +TEST(PeerIdTest, GenerateFromSeed) { + // Values calculated by running actual SHA-256 hash on |seed|. + std::string seed = "seed"; + std::string expected_peer_id = + "19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B"; + + ByteArray seed_bytes(seed); + PeerId peer_id = PeerId::FromSeed(seed_bytes); + + EXPECT_EQ(64, peer_id.GetId().size()); + EXPECT_EQ(expected_peer_id, peer_id.GetId()); +} + +TEST(PeerIdTest, GetId) { + const std::string id = "this_is_a_test"; + PeerId peer_id(id); + EXPECT_EQ(id, peer_id.GetId()); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc new file mode 100644 index 00000000..ab9f9607 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.cc @@ -0,0 +1,134 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { +using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame; + +namespace { + +ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) { + std::string message; + signaling_frame.SerializeToString(&message); + return ByteArray(message.c_str(), message.size()); +} + +void SetSenderId(const PeerId& sender_id, WebRtcSignalingFrame& frame) { + frame.mutable_sender_id()->set_id(sender_id.GetId()); +} + +std::unique_ptr DecodeIceCandidate( + location::nearby::mediums::IceCandidate ice_candidate_proto) { + webrtc::SdpParseError error; + return std::unique_ptr( + webrtc::CreateIceCandidate(ice_candidate_proto.sdp_mid(), + ice_candidate_proto.sdp_m_line_index(), + ice_candidate_proto.sdp(), &error)); +} + +} // namespace + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE); + SetSenderId(sender_id, signaling_frame); + signaling_frame.set_allocated_ready_for_signaling_poke( + new location::nearby::mediums::ReadyForSignalingPoke()); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string offer_str; + offer.ToString(&offer_str); + signaling_frame.mutable_offer() + ->mutable_session_description() + ->set_description(offer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE); + SetSenderId(sender_id, signaling_frame); + std::string answer_str; + answer.ToString(&answer_str); + signaling_frame.mutable_answer() + ->mutable_session_description() + ->set_description(answer_str); + return FrameToByteArray(std::move(signaling_frame)); +} + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& + ice_candidates) { + WebRtcSignalingFrame signaling_frame; + signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE); + SetSenderId(sender_id, signaling_frame); + for (const auto& ice_candidate : ice_candidates) { + *signaling_frame.mutable_ice_candidates()->add_ice_candidates() = + ice_candidate; + } + return FrameToByteArray(std::move(signaling_frame)); +} + +std::unique_ptr DecodeOffer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kOffer, + frame.offer().session_description().description()); +} + +std::unique_ptr DecodeAnswer( + const WebRtcSignalingFrame& frame) { + return webrtc::CreateSessionDescription( + webrtc::SdpType::kAnswer, + frame.answer().session_description().description()); +} + +std::vector> DecodeIceCandidates( + const WebRtcSignalingFrame& frame) { + std::vector> ice_candidates; + for (const auto& candidate : frame.ice_candidates().ice_candidates()) { + ice_candidates.push_back(DecodeIceCandidate(candidate)); + } + return ice_candidates; +} + +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate) { + std::string sdp; + ice_candidate.ToString(&sdp); + location::nearby::mediums::IceCandidate ice_candidate_proto; + ice_candidate_proto.set_sdp(sdp); + ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid()); + ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index()); + return ice_candidate_proto; +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h new file mode 100644 index 00000000..e034e414 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h @@ -0,0 +1,58 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "platform_v2/base/byte_array.h" +#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h" +#include "webrtc/api/peer_connection_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id); + +ByteArray EncodeOffer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& offer); +ByteArray EncodeAnswer(const PeerId& sender_id, + const webrtc::SessionDescriptionInterface& answer); + +ByteArray EncodeIceCandidates( + const PeerId& sender_id, + const std::vector& ice_candidates); +location::nearby::mediums::IceCandidate EncodeIceCandidate( + const webrtc::IceCandidateInterface& ice_candidate); + +std::unique_ptr DecodeOffer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); +std::unique_ptr DecodeAnswer( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +std::vector> DecodeIceCandidates( + const location::nearby::mediums::WebRtcSignalingFrame& frame); + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc new file mode 100644 index 00000000..5e9c4002 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/signaling_frames_test.cc @@ -0,0 +1,196 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/signaling_frames.h" + +#include + +#include "core_v2/internal/mediums/webrtc/peer_id.h" +#include "net/proto2/public/text_format.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace webrtc_frames { + +namespace { + +const char kSampleSdp[] = + "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 " + "0\r\na=msid-semantic: WMS\r\n"; + +const char kIceCandidateSdp1[] = + "a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host"; +const char kIceCandidateSdp2[] = + "a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr"; + +const char kIceSdpMid[] = "data"; +const int kIceSdpMLineIndex = 0; + +const char kOfferProto[] = R"( + sender_id { id: "abc" } + type: OFFER_TYPE + offer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kAnswerProto[] = R"( + sender_id { id: "abc" } + type: ANSWER_TYPE + answer { + session_description { + description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n" + } + } + )"; + +const char kIceCandidatesProto[] = R"( + sender_id { id: "abc" } + type: ICE_CANDIDATES_TYPE + ice_candidates { + ice_candidates { + sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + ice_candidates { + sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0" + sdp_mid: "data" + sdp_m_line_index: 0 + } + } + )"; +} // namespace + +TEST(SignalingFramesTest, SignalingPoke) { + PeerId sender_id("abc"); + ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size())); + + EXPECT_THAT(frame, testing::EqualsProto(R"( + sender_id { id: "abc" } + type: READY_FOR_SIGNALING_POKE_TYPE + ready_for_signaling_poke {} + )")); +} + +TEST(SignalingFramesTest, EncodeValidOffer) { + PeerId sender_id("abc"); + std::unique_ptr offer = + webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp); + ByteArray encoded_offer = EncodeOffer(sender_id, *offer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_offer.data(), encoded_offer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kOfferProto)); +} + +TEST(SignaingFramesTest, DecodeValidOffer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kOfferProto, &frame); + std::unique_ptr decoded_offer = + DecodeOffer(frame); + + EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType()); + std::string description; + decoded_offer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidAnswer) { + PeerId sender_id("abc"); + std::unique_ptr answer( + webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp)); + ByteArray encoded_answer = EncodeAnswer(sender_id, *answer); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_answer.data(), encoded_answer.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto)); +} + +TEST(SignalingFramesTest, DecodeValidAnswer) { + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kAnswerProto, &frame); + std::unique_ptr decoded_answer = + DecodeAnswer(frame); + + EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType()); + std::string description; + decoded_answer->ToString(&description); + EXPECT_EQ(kSampleSdp, description); +} + +TEST(SignalingFramesTest, EncodeValidIceCandidates) { + PeerId sender_id("abc"); + webrtc::SdpParseError error; + + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + std::vector encoded_candidates_vec; + for (const auto& ice_candidate : ice_candidates) { + encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate)); + } + ByteArray encoded_candidates = + EncodeIceCandidates(sender_id, encoded_candidates_vec); + + location::nearby::mediums::WebRtcSignalingFrame frame; + frame.ParseFromString( + std::string(encoded_candidates.data(), encoded_candidates.size())); + + EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto)); +} + +TEST(SignalingFramesTest, DecodeValidIceCandidates) { + webrtc::SdpParseError error; + std::vector> ice_candidates; + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error)); + ice_candidates.emplace_back(webrtc::CreateIceCandidate( + kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error)); + + location::nearby::mediums::WebRtcSignalingFrame frame; + proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame); + std::vector> + decoded_candidates = DecodeIceCandidates(frame); + + ASSERT_EQ(2u, decoded_candidates.size()); + for (int i = 0; i < static_cast(decoded_candidates.size()); i++) { + EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent( + decoded_candidates[i]->candidate())); + EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid()); + EXPECT_EQ(ice_candidates[i]->sdp_mline_index(), + decoded_candidates[i]->sdp_mline_index()); + } +} + +} // namespace webrtc_frames +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc new file mode 100644 index 00000000..11f89236 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.cc @@ -0,0 +1,115 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// OutputStreamImpl +Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) { + if (data.size() > kMaxDataSize) { + NEARBY_LOG(WARNING, "Sending data larger than 1MB"); + return {Exception::kIo}; + } + + socket_->BlockUntilSufficientSpaceInBuffer(data.size()); + + if (socket_->IsClosed()) { + NEARBY_LOG(WARNING, "Tried sending message while socket is closed"); + return {Exception::kIo}; + } + + if (!socket_->SendMessage(data)) { + return {Exception::kIo}; + } + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Flush() { + // Java implementation is empty. + return {Exception::kSuccess}; +} + +Exception WebRtcSocket::OutputStreamImpl::Close() { + socket_->Close(); + return {Exception::kSuccess}; +} + +// WebRtcSocket +WebRtcSocket::WebRtcSocket( + const string& name, + rtc::scoped_refptr data_channel) + : name_(name), data_channel_(std::move(data_channel)) {} + +InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); } + +OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; } + +void WebRtcSocket::Close() { + if (IsClosed()) return; + + closed_.Set(true); + pipe_.GetInputStream().Close(); + pipe_.GetOutputStream().Close(); + data_channel_->Close(); + WakeUpWriter(); + socket_closed_listener_.socket_closed_cb(); +} + +void WebRtcSocket::NotifyDataChannelMsgReceived(const ByteArray& message) { + if (!pipe_.GetOutputStream().Write(message).Ok()) { + Close(); + return; + } + + if (!pipe_.GetOutputStream().Flush().Ok()) Close(); +} + +void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { WakeUpWriter(); } + +bool WebRtcSocket::SendMessage(const ByteArray& data) { + return data_channel_->Send( + webrtc::DataBuffer(std::string(data.data(), data.size()))); +} + +bool WebRtcSocket::IsClosed() { return closed_.Get(); } + +void WebRtcSocket::WakeUpWriter() { + MutexLock lock(&backpressure_mutex_); + buffer_variable_.Notify(); +} + +void WebRtcSocket::SetOnSocketClosedListener(SocketClosedListener&& listener) { + socket_closed_listener_ = std::move(listener); +} + +void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) { + MutexLock lock(&backpressure_mutex_); + while (!IsClosed() && + (data_channel_->buffered_amount() + length > kMaxDataSize)) { + // TODO(himanshujaju): Add wait with timeout. + buffer_variable_.Wait(); + } +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h new file mode 100644 index 00000000..91bff312 --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h @@ -0,0 +1,115 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ +#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ + +#include + +#include "core_v2/listeners.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/base/socket.h" +#include "platform_v2/public/atomic_boolean.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/pipe.h" +#include "webrtc/api/data_channel_interface.h" +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +// Maximum data size: 1 MB +constexpr int kMaxDataSize = 1 * 1024 * 1024; + +// Defines the Socket implementation specific to WebRTC, which uses the WebRTC +// data channel to send and receive messages. +// +// Messages are buffered here to prevent the data channel from overflowing, +// which could lead to data loss. +class WebRtcSocket : public Socket { + public: + WebRtcSocket(const string& name, + rtc::scoped_refptr data_channel); + ~WebRtcSocket() override = default; + + WebRtcSocket(const WebRtcSocket& other) = delete; + WebRtcSocket& operator=(const WebRtcSocket& other) = delete; + + // Overrides for location::nearby::Socket: + InputStream& GetInputStream() override; + OutputStream& GetOutputStream() override; + void Close() override; + + // Callback from WebRTC data channel when new message has been received from + // the remote. + void NotifyDataChannelMsgReceived(const ByteArray& message); + + // Callback from WebRTC data channel that the buffered data amount has + // changed. + void NotifyDataChannelBufferedAmountChanged(); + + // Listener class the gets called when the socket is closed. + struct SocketClosedListener { + std::function socket_closed_cb = DefaultCallback<>(); + }; + + void SetOnSocketClosedListener(SocketClosedListener&& listener); + + private: + class OutputStreamImpl : public OutputStream { + public: + explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {} + ~OutputStreamImpl() override = default; + + OutputStreamImpl(const OutputStreamImpl& other) = delete; + OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete; + + // OutputStream: + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + // |this| OutputStreamImpl is owned by |socket_|. + WebRtcSocket* const socket_; + }; + + void WakeUpWriter(); + bool IsClosed(); + bool SendMessage(const ByteArray& data); + void BlockUntilSufficientSpaceInBuffer(int length); + + string name_; + rtc::scoped_refptr data_channel_; + + Pipe pipe_; + + OutputStreamImpl output_stream_{this}; + + AtomicBoolean closed_{false}; + + SocketClosedListener socket_closed_listener_; + + mutable Mutex backpressure_mutex_; + ConditionVariable buffer_variable_{&backpressure_mutex_}; +}; + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc new file mode 100644 index 00000000..5184c75e --- /dev/null +++ b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket_test.cc @@ -0,0 +1,168 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/mediums/webrtc/webrtc_socket.h" + +#include + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "webrtc/api/data_channel_interface.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { + +namespace { + +// using TestPlatform = platform::ImplementationPlatform; + +const char kSocketName[] = "TestSocket"; + +class MockDataChannel + : public rtc::RefCountedObject { + public: + MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*)); + MOCK_METHOD(void, UnregisterObserver, ()); + + MOCK_METHOD(std::string, label, (), (const)); + + MOCK_METHOD(bool, reliable, (), (const)); + MOCK_METHOD(int, id, (), (const)); + MOCK_METHOD(DataState, state, (), (const)); + MOCK_METHOD(uint32_t, messages_sent, (), (const)); + MOCK_METHOD(uint64_t, bytes_sent, (), (const)); + MOCK_METHOD(uint32_t, messages_received, (), (const)); + MOCK_METHOD(uint64_t, bytes_received, (), (const)); + + MOCK_METHOD(uint64_t, buffered_amount, (), (const)); + + MOCK_METHOD(void, Close, ()); + + MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&)); +}; + +} // namespace + +TEST(WebRtcSocketTest, ReadFromSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(kMessage); + ExceptionOr result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), kMessage); +} + +TEST(WebRtcSocketTest, ReadMultipleMessages) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"Me"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ssa"}); + webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ge"}); + + ExceptionOr result; + + // This behaviour is different from the Java code + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"Me"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ssa"}); + + result = webrtc_socket.GetInputStream().Read(7); + EXPECT_TRUE(result.ok()); + EXPECT_EQ(result.result(), ByteArray{"ge"}); +} + +TEST(WebRtcSocketTest, WriteToSocket) { + const ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)) + .WillRepeatedly(testing::Return(true)); + EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok()); +} + +TEST(WebRtcSocketTest, SendDataBiggerThanMax) { + const ByteArray kMessage{kMaxDataSize + 1}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, WriteToDataChannelFails) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(false)); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, Close) { + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + + EXPECT_CALL(*mock_data_channel, Close()); + + int socket_closed_cb_called = 0; + + webrtc_socket.SetOnSocketClosedListener( + {.socket_closed_cb = [&]() { socket_closed_cb_called++; }}); + webrtc_socket.Close(); + + EXPECT_EQ(socket_closed_cb_called, 1); +} + +TEST(WebRtcSocketTest, WriteOnClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + webrtc_socket.Close(); + + EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0); + EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage), + Exception{Exception::kIo}); +} + +TEST(WebRtcSocketTest, ReadFromClosedChannel) { + ByteArray kMessage{"Message"}; + rtc::scoped_refptr mock_data_channel = new MockDataChannel(); + WebRtcSocket webrtc_socket(kSocketName, mock_data_channel); + ON_CALL(*mock_data_channel, Send(testing::_)) + .WillByDefault(testing::Return(true)); + + webrtc_socket.GetOutputStream().Write(kMessage); + webrtc_socket.Close(); + + EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo); +} + +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mock_service_controller.h b/cpp/core_v2/internal/mock_service_controller.h new file mode 100644 index 00000000..6bcf700b --- /dev/null +++ b/cpp/core_v2/internal/mock_service_controller.h @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ + +#include "core_v2/internal/service_controller.h" +#include "gmock/gmock.h" + +namespace location { +namespace nearby { +namespace connections { + +/* Mock implementation for ServiceController: + * All methods execute asynchronously (in a private executor thread). + * To synchronise, two approaches may be used: + * 1. For methods that have result callback, we use it to unblock main thread. + * 2. For methods that do not have callbacks, we provide a mock implementation + * that unblocks main thread. + */ +class MockServiceController : public ServiceController { + public: + MOCK_METHOD(Status, StartAdvertising, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(void, StopAdvertising, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, StartDiscovery, + (ClientProxy * client, const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener), + (override)); + + MOCK_METHOD(void, StopDiscovery, (ClientProxy * client), (override)); + + MOCK_METHOD(Status, RequestConnection, + (ClientProxy * client, const std::string& endpoint_id, + const ConnectionRequestInfo& info), + (override)); + + MOCK_METHOD(Status, AcceptConnection, + (ClientProxy * client, const std::string& endpoint_id, + const PayloadListener& listener), + (override)); + + MOCK_METHOD(Status, RejectConnection, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, InitiateBandwidthUpgrade, + (ClientProxy * client, const std::string& endpoint_id), + (override)); + + MOCK_METHOD(void, SendPayload, + (ClientProxy * client, + const std::vector& endpoint_ids, Payload payload), + (override)); + + MOCK_METHOD(Status, CancelPayload, + (ClientProxy * client, std::int64_t payload_id), (override)); + + MOCK_METHOD(void, DisconnectFromEndpoint, + (ClientProxy * client, const std::string& endpoint_id), + (override)); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MOCK_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc new file mode 100644 index 00000000..84106507 --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.cc @@ -0,0 +1,265 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/offline_frames.h" + +#include +#include + +#include "core/internal/message_lite.h" +#include "platform_v2/base/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using ExceptionOrOfflineFrame = ExceptionOr; +using Medium = proto::connections::Medium; +using MessageLite = ::google3_proto_compat::MessageLite; + +ByteArray ToBytes(OfflineFrame&& frame) { + ByteArray bytes(frame.ByteSizeLong()); + frame.set_version(OfflineFrame::V1); + frame.SerializeToArray(bytes.data(), bytes.size()); + return bytes; +} + +} // namespace + +ExceptionOrOfflineFrame FromBytes(const ByteArray& bytes) { + OfflineFrame frame; + + if (frame.ParseFromString(std::string(bytes))) { + return ExceptionOrOfflineFrame(std::move(frame)); + } else { + return ExceptionOrOfflineFrame(Exception::kInvalidProtocolBuffer); + } +} + +V1Frame::FrameType GetFrameType(const OfflineFrame& frame) { + if ((frame.version() == OfflineFrame::V1) && frame.has_v1()) { + return frame.v1().type(); + } + + return V1Frame::UNKNOWN_FRAME_TYPE; +} + +ByteArray ForConnectionRequest(const std::string& endpoint_id, + const std::string& endpoint_name, + std::int32_t nonce, + const std::vector& mediums) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + auto* connection_request = v1_frame->mutable_connection_request(); + connection_request->set_endpoint_id(endpoint_id); + connection_request->set_endpoint_name(endpoint_name); + connection_request->set_endpoint_info(endpoint_name); + connection_request->set_nonce(nonce); + for (const auto& medium : mediums) { + connection_request->add_mediums(MediumToConnectionRequestMedium(medium)); + } + + return ToBytes(std::move(frame)); +} + +ByteArray ForConnectionResponse(std::int32_t status) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::CONNECTION_RESPONSE); + auto* sub_frame = v1_frame->mutable_connection_response(); + sub_frame->set_status(status); + + return ToBytes(std::move(frame)); +} + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::DATA); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_payload_chunk() = chunk; + + return ToBytes(std::move(frame)); +} + +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAYLOAD_TRANSFER); + auto* sub_frame = v1_frame->mutable_payload_transfer(); + sub_frame->set_packet_type(PayloadTransferFrame::CONTROL); + *sub_frame->mutable_payload_header() = header; + *sub_frame->mutable_control_message() = control; + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeWifiHotspot(const std::string& ssid, + const std::string& password, + std::int32_t port) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::UPGRADE_PATH_AVAILABLE); + auto* upgrade_path_info = sub_frame->mutable_upgrade_path_info(); + upgrade_path_info->set_medium( + BandwidthUpgradeNegotiationFrame::UpgradePathInfo::WIFI_HOTSPOT); + auto* wifi_hotspot_credentials = + upgrade_path_info->mutable_wifi_hotspot_credentials(); + wifi_hotspot_credentials->set_ssid(ssid); + wifi_hotspot_credentials->set_password(password); + wifi_hotspot_credentials->set_port(port); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeLastWrite() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::LAST_WRITE_TO_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeSafeToClose() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::SAFE_TO_CLOSE_PRIOR_CHANNEL); + + return ToBytes(std::move(frame)); +} + +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id) { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION); + auto* sub_frame = v1_frame->mutable_bandwidth_upgrade_negotiation(); + sub_frame->set_event_type( + BandwidthUpgradeNegotiationFrame::CLIENT_INTRODUCTION); + auto* client_introduction = sub_frame->mutable_client_introduction(); + client_introduction->set_endpoint_id(endpoint_id); + + return ToBytes(std::move(frame)); +} + +ByteArray ForKeepAlive() { + OfflineFrame frame; + + frame.set_version(OfflineFrame::V1); + auto* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::KEEP_ALIVE); + v1_frame->mutable_keep_alive(); + + return ToBytes(std::move(frame)); +} + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium) { + switch (medium) { + case Medium::MDNS: + return ConnectionRequestFrame::MDNS; + case Medium::BLUETOOTH: + return ConnectionRequestFrame::BLUETOOTH; + case Medium::WIFI_HOTSPOT: + return ConnectionRequestFrame::WIFI_HOTSPOT; + case Medium::BLE: + return ConnectionRequestFrame::BLE; + case Medium::WIFI_LAN: + return ConnectionRequestFrame::WIFI_LAN; + case Medium::WIFI_AWARE: + return ConnectionRequestFrame::WIFI_AWARE; + case Medium::NFC: + return ConnectionRequestFrame::NFC; + case Medium::WIFI_DIRECT: + return ConnectionRequestFrame::WIFI_DIRECT; + case Medium::WEB_RTC: + return ConnectionRequestFrame::WEB_RTC; + default: + return ConnectionRequestFrame::UNKNOWN_MEDIUM; + } +} + +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium) { + switch (medium) { + case ConnectionRequestFrame::MDNS: + return Medium::MDNS; + case ConnectionRequestFrame::BLUETOOTH: + return Medium::BLUETOOTH; + case ConnectionRequestFrame::WIFI_HOTSPOT: + return Medium::WIFI_HOTSPOT; + case ConnectionRequestFrame::BLE: + return Medium::BLE; + case ConnectionRequestFrame::WIFI_LAN: + return Medium::WIFI_LAN; + case ConnectionRequestFrame::WIFI_AWARE: + return Medium::WIFI_AWARE; + case ConnectionRequestFrame::NFC: + return Medium::NFC; + case ConnectionRequestFrame::WIFI_DIRECT: + return Medium::WIFI_DIRECT; + case ConnectionRequestFrame::WEB_RTC: + return Medium::WEB_RTC; + default: + return Medium::UNKNOWN_MEDIUM; + } +} + +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& frame) { + std::vector result; + for (const auto& int_medium : frame.mediums()) { + result.push_back(ConnectionRequestMediumToMedium( + static_cast(int_medium))); + } + return result; +} + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/offline_frames.h b/cpp/core_v2/internal/offline_frames.h new file mode 100644 index 00000000..3cd4403f --- /dev/null +++ b/cpp/core_v2/internal/offline_frames.h @@ -0,0 +1,75 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ +#define CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ + +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { + +// Serialize/Deserialize Nearby Connections Protocol messages. + +// Parses incoming message. +// Returns OfflineFrame if parser was able to understand it, or +// Exception::kInvalidProtocolBuffer, if parser failed. +ExceptionOr FromBytes(const ByteArray& offline_frame_bytes); + +// Returns FrameType of a parsed message, or +// V1Frame::UNKNOWN_FRAME_TYPE, if frame contents is not recognized. +V1Frame::FrameType GetFrameType(const OfflineFrame& offline_frame); + +// Build ConnectionRequest message. +ByteArray ForConnectionRequest( + const std::string& endpoint_id, const std::string& endpoint_name, + std::int32_t nonce, const std::vector& mediums); +ByteArray ForConnectionResponse(std::int32_t status); + +ByteArray ForDataPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::PayloadChunk& chunk); +ByteArray ForControlPayloadTransfer( + const PayloadTransferFrame::PayloadHeader& header, + const PayloadTransferFrame::ControlMessage& control); + +ByteArray ForBandwidthUpgradeWifiHotspot( + const std::string& ssid, const std::string& password, std::int32_t port); +ByteArray ForBandwidthUpgradeLastWrite(); +ByteArray ForBandwidthUpgradeSafeToClose(); +ByteArray ForBandwidthUpgradeIntroduction(const std::string& endpoint_id); + +ByteArray ForKeepAlive(); + +ConnectionRequestFrame::Medium MediumToConnectionRequestMedium( + proto::connections::Medium medium); +proto::connections::Medium ConnectionRequestMediumToMedium( + ConnectionRequestFrame::Medium medium); +std::vector ConnectionRequestMediumsToMediums( + const ConnectionRequestFrame& connection_request_frame); + +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_OFFLINE_FRAMES_H_ diff --git a/cpp/core_v2/internal/offline_frames_test.cc b/cpp/core_v2/internal/offline_frames_test.cc new file mode 100644 index 00000000..46e6fbfa --- /dev/null +++ b/cpp/core_v2/internal/offline_frames_test.cc @@ -0,0 +1,266 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/offline_frames.h" + +#include +#include +#include +#include + +#include "proto/connections/offline_wire_formats.pb.h" +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace parser { +namespace { + +using Medium = proto::connections::Medium; +using ::testing::EqualsProto; + +constexpr char kEndpointId[] = "ABC"; +constexpr char kEndpointName[] = "XYZ"; +constexpr int kNonce = 1234; +constexpr std::array kMediums = { + Medium::MDNS, Medium::BLUETOOTH, Medium::WIFI_HOTSPOT, + Medium::BLE, Medium::WIFI_LAN, Medium::WIFI_AWARE, + Medium::NFC, Medium::WIFI_DIRECT, Medium::WEB_RTC, +}; + +TEST(OfflineFramesTest, CanParseMessageFromBytes) { + OfflineFrame tx_message; + + { + tx_message.set_version(OfflineFrame::V1); + auto* v1_frame = tx_message.mutable_v1(); + auto* sub_frame = v1_frame->mutable_connection_request(); + + v1_frame->set_type(V1Frame::CONNECTION_REQUEST); + sub_frame->set_endpoint_id(kEndpointId); + sub_frame->set_endpoint_name(kEndpointName); + sub_frame->set_endpoint_info(kEndpointName); + sub_frame->set_nonce(kNonce); + for (auto& medium : kMediums) { + sub_frame->add_mediums(MediumToConnectionRequestMedium(medium)); + } + } + auto serialized_bytes = ByteArray(tx_message.SerializeAsString()); + auto ret_value = FromBytes(serialized_bytes); + ASSERT_TRUE(ret_value.ok()); + const auto& rx_message = ret_value.result(); + EXPECT_THAT(rx_message, EqualsProto(tx_message)); + EXPECT_EQ(GetFrameType(rx_message), V1Frame::CONNECTION_REQUEST); + EXPECT_EQ( + ConnectionRequestMediumsToMediums(rx_message.v1().connection_request()), + std::vector(kMediums.begin(), kMediums.end())); +} + +TEST(OfflineFramesTest, CanGenerateConnectionRequest) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_REQUEST + connection_request: < + endpoint_id: "ABC" + endpoint_name: "XYZ" + endpoint_info: "XYZ" + nonce: 1234 + mediums: MDNS + mediums: BLUETOOTH + mediums: WIFI_HOTSPOT + mediums: BLE + mediums: WIFI_LAN + mediums: WIFI_AWARE + mediums: NFC + mediums: WIFI_DIRECT + mediums: WEB_RTC + > + >)pb"; + ByteArray bytes = + ForConnectionRequest(kEndpointId, kEndpointName, kNonce, + std::vector(kMediums.begin(), kMediums.end())); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateConnectionResponse) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: CONNECTION_RESPONSE + connection_response: < status: 1 > + >)pb"; + ByteArray bytes = ForConnectionResponse(1); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateControlPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::ControlMessage control; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED); + control.set_offset(150); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: CONTROL, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + control_message: < event: PAYLOAD_CANCELED offset: 150 > + > + >)pb"; + ByteArray bytes = ForControlPayloadTransfer(header, control); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateDataPayloadTransfer) { + PayloadTransferFrame::PayloadHeader header; + PayloadTransferFrame::PayloadChunk chunk; + header.set_id(12345); + header.set_type(PayloadTransferFrame::PayloadHeader::BYTES); + header.set_total_size(1024); + chunk.set_body("payload data"); + chunk.set_offset(150); + chunk.set_flags(1); + + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: PAYLOAD_TRANSFER + payload_transfer: < + packet_type: DATA, + payload_header: < type: BYTES id: 12345 total_size: 1024 > + payload_chunk: < flags: 1 offset: 150 body: "payload data" > + > + >)pb"; + ByteArray bytes = ForDataPayloadTransfer(header, chunk); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeWifiHotspot) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: UPGRADE_PATH_AVAILABLE + upgrade_path_info: < + medium: WIFI_HOTSPOT + wifi_hotspot_credentials: < + ssid: "ssid" + password: "password" + port: 1234 + > + > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeWifiHotspot("ssid", "password", 1234); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeLastWrite) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: LAST_WRITE_TO_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeLastWrite(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeSafeToClose) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < event_type: SAFE_TO_CLOSE_PRIOR_CHANNEL > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeSafeToClose(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateBandwidthUpgradeIntroduction) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: BANDWIDTH_UPGRADE_NEGOTIATION + bandwidth_upgrade_negotiation: < + event_type: CLIENT_INTRODUCTION + client_introduction: < endpoint_id: "ABC" > + > + >)pb"; + ByteArray bytes = ForBandwidthUpgradeIntroduction(kEndpointId); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +TEST(OfflineFramesTest, CanGenerateKeepAlive) { + constexpr char kExpected[] = + R"pb( + version: V1 + v1: < + type: KEEP_ALIVE + keep_alive: <> + >)pb"; + ByteArray bytes = ForKeepAlive(); + auto response = FromBytes(bytes); + ASSERT_TRUE(response.ok()); + OfflineFrame message = FromBytes(bytes).result(); + EXPECT_THAT(message, EqualsProto(kExpected)); +} + +} // namespace +} // namespace parser +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/pcp.h b/cpp/core_v2/internal/pcp.h new file mode 100644 index 00000000..772a88c5 --- /dev/null +++ b/cpp/core_v2/internal/pcp.h @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_PCP_H_ +#define CORE_V2_INTERNAL_PCP_H_ + +namespace location { +namespace nearby { +namespace connections { + +// The PreConnectionProtocol (PCP) defines the combinations of interactions +// between the techniques (ultrasound audio, Bluetooth device names, BLE +// advertisements) used for offline Advertisement + Discovery, and identifies +// the steps to go through on each device. +// +// See go/nearby-offline-data-interchange-formats for more. +enum class Pcp { + kUnknown = 0, + kP2pStar = 1, + kP2pCluster = 2, + kP2pPointToPoint = 3, + // PCP is only allocated 5 bits in our data interchange formats, so there can + // never be more than 31 PCP values. +}; +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_H_ diff --git a/cpp/core_v2/internal/pcp_handler.h b/cpp/core_v2/internal/pcp_handler.h new file mode 100644 index 00000000..b4d807a8 --- /dev/null +++ b/cpp/core_v2/internal/pcp_handler.h @@ -0,0 +1,102 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_PCP_HANDLER_H_ +#define CORE_V2_INTERNAL_PCP_HANDLER_H_ + +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/pcp.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/status.h" +#include "core_v2/strategy.h" +#include "proto/connections_enums.pb.h" + +namespace location { +namespace nearby { +namespace connections { + +// Defines the set of methods that need to be implemented to handle the +// per-PCP-specific operations in the OfflineServiceController. +// +// These methods are all meant to be synchronous, and should return only after +// knowing they've done what they were supposed to do (or unequivocally failed +// to do so). +// +// See details here: +// cpp/core_v2/core.h +class PcpHandler { + public: + virtual ~PcpHandler() = default; + + // Return strategy supported by this protocol. + virtual Strategy GetStrategy() = 0; + + // Return concrete variant of protocol. + virtual Pcp GetPcp() = 0; + + // We have been asked by the client to start advertising. Once we successfully + // start advertising, we'll change the ClientProxy's state. + // ConnectionListener (info.listener) will be notified in case of any event. + // See for details + // cpp/core_v2/listeners.h + virtual Status StartAdvertising(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + + // If Advertising is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopAdvertising(ClientProxy* client) = 0; + + // Start discovery of endpoints that may be advertising. + // Update ClientProxy state once discovery started. + // DiscoveryListener will get called in case of any event. + virtual Status StartDiscovery(ClientProxy* client, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + + // If Discovery is active, stop it, and change CLientProxy state, + // otherwise do nothing. + virtual void StopDiscovery(ClientProxy* client) = 0; + + // If remote endpoint has been successfully discovered, request it to form a + // connection, update state on ClientProxy. + virtual Status RequestConnection(ClientProxy* client, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + + // Either party may call this to accept connection on their part. + // Until both parties call it, connection will not reach a data phase. + // Update state in ClientProxy. + virtual Status AcceptConnection(ClientProxy* clientProxy, + const std::string& endpoint_id, + const PayloadListener& payload_listener) = 0; + + // Either party may call this to reject connection on their part before + // connection reaches data phase. If either party does call it, connection + // will terminate. Update state in ClientProxy. + virtual Status RejectConnection(ClientProxy* client, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_PCP_HANDLER_H_ diff --git a/cpp/core_v2/internal/service_controller.h b/cpp/core_v2/internal/service_controller.h new file mode 100644 index 00000000..9d8bf669 --- /dev/null +++ b/cpp/core_v2/internal/service_controller.h @@ -0,0 +1,91 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "core_v2/status.h" + +namespace location { +namespace nearby { +namespace connections { + +// Interface defines the core functionality of Nearby Connections Service. +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// ResultCallback arguments are not provided for this class, because all methods +// are called synchronously. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// See details here: +// cpp/core_v2/core.h +class ServiceController { + public: + virtual ~ServiceController() = default; + ServiceController() = default; + ServiceController(const ServiceController&) = delete; + ServiceController& operator=(const ServiceController&) = delete; + + // Starts advertising an endpoint for a local app. + virtual Status StartAdvertising(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info) = 0; + virtual void StopAdvertising(ClientProxy* client_proxy) = 0; + + virtual Status StartDiscovery(ClientProxy* client_proxy, + const std::string& service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener) = 0; + virtual void StopDiscovery(ClientProxy* client_proxy) = 0; + + virtual Status RequestConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const ConnectionRequestInfo& info) = 0; + virtual Status AcceptConnection(ClientProxy* client_proxy, + const std::string& endpoint_id, + const PayloadListener& listener) = 0; + virtual Status RejectConnection(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void InitiateBandwidthUpgrade(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; + + virtual void SendPayload(ClientProxy* client_proxy, + const std::vector& endpoint_ids, + Payload payload) = 0; + + virtual Status CancelPayload(ClientProxy* client_proxy, + std::int64_t payload_id) = 0; + + virtual void DisconnectFromEndpoint(ClientProxy* client_proxy, + const std::string& endpoint_id) = 0; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_H_ diff --git a/cpp/core_v2/internal/service_controller_router.cc b/cpp/core_v2/internal/service_controller_router.cc new file mode 100644 index 00000000..7a22daf7 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.cc @@ -0,0 +1,397 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "core_v2/payload.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace connections { + +ServiceControllerRouter::~ServiceControllerRouter() { + // TODO(tracyzhou): Add logging. + + // And make sure that cleanup is the last thing we do. + serializer_.Shutdown(); +} + +void ServiceControllerRouter::StartAdvertising( + ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, const ConnectionRequestInfo& info, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, info, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsAdvertising()) { + callback.result_cb({Status::kAlreadyAdvertising}); + return; + } + + status = service_controller_->StartAdvertising(client, service_id, options, + info); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopAdvertising(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsAdvertising()) { + service_controller_->StopAdvertising(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::StartDiscovery(ClientProxy* client, + absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, service_id = std::string(service_id), + options, listener, callback]() { + Status status = AcquireServiceControllerForClient(client, options.strategy); + if (!status.Ok()) { + callback.result_cb(status); + return; + } + + if (client->IsDiscovering()) { + callback.result_cb({Status::kAlreadyDiscovering}); + return; + } + + status = service_controller_->StartDiscovery(client, service_id, options, + listener); + callback.result_cb(status); + }); +} + +void ServiceControllerRouter::StopDiscovery(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client) && client->IsDiscovering()) { + service_controller_->StopDiscovery(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::RequestConnection( + ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), info, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->HasPendingConnectionToEndpoint(endpoint_id) || + client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + callback.result_cb( + service_controller_->RequestConnection(client, endpoint_id, info)); + }); +} + +void ServiceControllerRouter::AcceptConnection(ClientProxy* client, + absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + RouteToServiceController([this, client, + endpoint_id = std::string(endpoint_id), listener, + callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->AcceptConnection(client, endpoint_id, listener)); + }); +} + +void ServiceControllerRouter::RejectConnection(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kAlreadyConnectedToEndpoint}); + return; + } + + if (client->HasLocalEndpointResponded(endpoint_id)) { + // TODO(tracyzhou): logging + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb( + service_controller_->RejectConnection(client, endpoint_id)); + }); +} + +void ServiceControllerRouter::InitiateBandwidthUpgrade( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (!ClientHasAcquiredServiceController(client) || + !client->IsConnectedToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + service_controller_->InitiateBandwidthUpgrade(client, endpoint_id); + + // Operation is triggered; the caller can listen to + // ConnectionListener::OnBandwidthChanged() to determine its success. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::SendPayload( + ClientProxy* client, absl::Span endpoint_ids, + Payload payload, const ResultCallback& callback) { + // Payload is a move-only type. + // We have to capture it by value inside the lambda, and pass it over to + // the executor as an std::function instance. + // Lambda must be copyable, in order ot satisfy std::function<> requirements. + // To make it so, we need Payload wrapped by a copyable wrapper. + // std::shared_ptr<> is used, because it is copyable. + auto shared_payload = std::make_shared(std::move(payload)); + RouteToServiceController( + [this, client, shared_payload, + endpoint_ids = std::vector(endpoint_ids.begin(), endpoint_ids.end()), + &callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + if (!ClientHasConnectionToAtLeastOneEndpoint(client, endpoint_ids)) { + callback.result_cb({Status::kEndpointUnknown}); + return; + } + + service_controller_->SendPayload(client, endpoint_ids, + std::move(*shared_payload)); + + // At this point, we've queued up the send Payload request with the + // ServiceController; any further failures (e.g. one of the endpoints is + // unknown, goes away, or otherwise fails) will be returned to the + // client as a PayloadTransferUpdate. + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::CancelPayload(ClientProxy* client, + std::uint64_t payload_id, + const ResultCallback& callback) { + RouteToServiceController([this, client, payload_id, callback]() { + if (!ClientHasAcquiredServiceController(client)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + + callback.result_cb(service_controller_->CancelPayload(client, payload_id)); + }); +} + +void ServiceControllerRouter::DisconnectFromEndpoint( + ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback) { + RouteToServiceController( + [this, client, endpoint_id = std::string(endpoint_id), callback]() { + if (ClientHasAcquiredServiceController(client)) { + if (!client->IsConnectedToEndpoint(endpoint_id) && + !client->HasPendingConnectionToEndpoint(endpoint_id)) { + callback.result_cb({Status::kOutOfOrderApiCall}); + return; + } + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + callback.result_cb({Status::kSuccess}); + } + }); +} + +void ServiceControllerRouter::StopAllEndpoints(ClientProxy* client, + const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + } + callback.result_cb({Status::kSuccess}); + }); +} + +void ServiceControllerRouter::ClientDisconnecting( + ClientProxy* client, const ResultCallback& callback) { + RouteToServiceController([this, client, callback]() { + if (ClientHasAcquiredServiceController(client)) { + DoneWithStrategySessionForClient(client); + // Log the completion of this client's connection. + // TODO(tracyzhou): Add logging. + } + callback.result_cb({Status::kSuccess}); + }); +} + +Status ServiceControllerRouter::AcquireServiceControllerForClient( + ClientProxy* client, Strategy strategy) { + if (current_strategy_.IsNone()) { + // Case 1: There is no existing Strategy at all. + + // Set everything up for the first time. + Status status = UpdateCurrentServiceControllerAndStrategy(strategy); + if (!status.Ok()) { + return status; + } + clients_.insert(client); + return {Status::kSuccess}; + } else if (strategy == current_strategy_) { + // Case 2: The existing Strategy matches. + + // The new client just needs to be added to the set of clients using the + // current ServiceController. + clients_.insert(client); + return {Status::kSuccess}; + } else { + // Case 3: The existing Strategy doesn't match. + + // It's only safe for a client to cause a switch if it's the only client + // using the current ServiceController. + bool is_the_only_client_of_service_controller = + clients_.size() == 1 && ClientHasAcquiredServiceController(client); + if (!is_the_only_client_of_service_controller) { + // TODO(tracyzhou): logging + return {Status::kAlreadyHaveActiveStrategy}; + } + + // If the client still has connected endpoints, they must disconnect before + // they can switch. + if (!client->GetConnectedEndpoints().empty()) { + // TODO(tracyzhou): logging + return {Status::kOutOfOrderApiCall}; + } + + // By this point, it's safe to switch the Strategy and ServiceController + // (and since it's the only client, there's no need to add it to the set of + // clients using the current ServiceController). + return UpdateCurrentServiceControllerAndStrategy(strategy); + } +} + +bool ServiceControllerRouter::ClientHasAcquiredServiceController( + ClientProxy* client) const { + return clients_.contains(client); +} + +void ServiceControllerRouter::ReleaseServiceControllerForClient( + ClientProxy* client) { + clients_.erase(client); + + if (clients_.empty()) { + service_controller_.reset(); + current_strategy_ = Strategy{}; + } +} + +/** Clean up all state for this client. The client is now free to switch + * strategies. */ +void ServiceControllerRouter::DoneWithStrategySessionForClient( + ClientProxy* client) { + // Disconnect from all the connected endpoints tied to this clientProxy. + for (auto& endpoint_id : client->GetPendingConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + for (auto& endpoint_id : client->GetConnectedEndpoints()) { + service_controller_->DisconnectFromEndpoint(client, endpoint_id); + } + + // Stop any advertising and discovery that may be underway due to this + // clientProxy. + service_controller_->StopAdvertising(client); + service_controller_->StopDiscovery(client); + + ReleaseServiceControllerForClient(client); +} + +void ServiceControllerRouter::RouteToServiceController(Runnable runnable) { + serializer_.Execute(std::move(runnable)); +} + +bool ServiceControllerRouter::ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids) { + for (auto& endpoint_id : remote_endpoint_ids) { + if (client->IsConnectedToEndpoint(endpoint_id)) { + return true; + } + } + return false; +} + +Status ServiceControllerRouter::UpdateCurrentServiceControllerAndStrategy( + Strategy strategy) { + if (!strategy.IsValid()) { + // TODO(tracyzhou): logging + return {Status::kError}; + } + + service_controller_.reset(service_controller_factory_()); + current_strategy_ = strategy; + + return {Status::kSuccess}; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/service_controller_router.h b/cpp/core_v2/internal/service_controller_router.h new file mode 100644 index 00000000..d573c378 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router.h @@ -0,0 +1,125 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ +#define CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +// ServiceControllerRouter: this class is an implementation detail of a +// location::nearby::Core class. The latter delegates all of its activities to +// the former. +// +// All the activities are documented in the public API class: +// cpp/core_v2/core.h +// +// In every method, ClientProxy* represents the client app which receives +// notifications from Nearby Connections service and forwards them to the app. +// The rest of arguments have the same meaning as the corresponding +// methods in the definition of location::nearby::Core API. +// +// Every activity is handled the same way: +// 1) all the arguments to the call are captured by value; +// 2) the actual processing is scheduled on a private single-threaded executor, +// which makes locking unnecessary, when internal data is being manipulated. +// 3) activity handlers are delegating much of their work to an implementation +// of a ServiceController interface, which does the actual job. +class ServiceControllerRouter { + public: + explicit ServiceControllerRouter(std::function factory) + : service_controller_factory_(std::move(factory)) {} + ~ServiceControllerRouter(); + ServiceControllerRouter(ServiceControllerRouter&&) = default; + ServiceControllerRouter& operator=(ServiceControllerRouter&&) = default; + + void StartAdvertising(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void StopAdvertising(ClientProxy* client, const ResultCallback& callback); + + void StartDiscovery(ClientProxy* client, absl::string_view service_id, + const ConnectionOptions& options, + const DiscoveryListener& listener, + const ResultCallback& callback); + void StopDiscovery(ClientProxy* client, const ResultCallback& callback); + + void RequestConnection(ClientProxy* client, absl::string_view endpoint_id, + const ConnectionRequestInfo& info, + const ResultCallback& callback); + void AcceptConnection(ClientProxy* client, absl::string_view endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback); + void RejectConnection(ClientProxy* client, absl::string_view endpoint_id, + const ResultCallback& callback); + + void InitiateBandwidthUpgrade(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + + void SendPayload(ClientProxy* client, + absl::Span endpoint_ids, Payload payload, + const ResultCallback& callback); + void CancelPayload(ClientProxy* client, std::uint64_t payload_id, + const ResultCallback& callback); + + void DisconnectFromEndpoint(ClientProxy* client, + absl::string_view endpoint_id, + const ResultCallback& callback); + void StopAllEndpoints(ClientProxy* client, const ResultCallback& callback); + + void ClientDisconnecting(ClientProxy* client, const ResultCallback& callback); + + private: + friend class ServiceControllerRouterTest; + static bool ClientHasConnectionToAtLeastOneEndpoint( + ClientProxy* client, const std::vector& remote_endpoint_ids); + + void RouteToServiceController(Runnable runnable); + + Status AcquireServiceControllerForClient(ClientProxy* client, + Strategy strategy); + bool ClientHasAcquiredServiceController(ClientProxy* client) const; + void ReleaseServiceControllerForClient(ClientProxy* client); + void DoneWithStrategySessionForClient(ClientProxy* client); + Status UpdateCurrentServiceControllerAndStrategy(Strategy strategy); + + absl::flat_hash_set clients_; + std::function service_controller_factory_; + std::unique_ptr service_controller_; + Strategy current_strategy_; + SingleThreadExecutor serializer_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_SERVICE_CONTROLLER_ROUTER_H_ diff --git a/cpp/core_v2/internal/service_controller_router_test.cc b/cpp/core_v2/internal/service_controller_router_test.cc new file mode 100644 index 00000000..9c565677 --- /dev/null +++ b/cpp/core_v2/internal/service_controller_router_test.cc @@ -0,0 +1,390 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/service_controller_router.h" + +#include +#include +#include + +#include "core_v2/internal/client_proxy.h" +#include "core_v2/internal/mock_service_controller.h" +#include "core_v2/internal/service_controller.h" +#include "core_v2/listeners.h" +#include "core_v2/options.h" +#include "core_v2/params.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/container/flat_hash_set.h" +#include "absl/time/clock.h" +#include "absl/types/span.h" + +namespace location { +namespace nearby { +namespace connections { + +namespace { +using ::testing::Return; +} // namespace + +// This class must be in the same namespace as ServiceControllerRouter for +// friend class to work. +class ServiceControllerRouterTest : public testing::Test { + public: + ServiceControllerRouterTest() = default; + ~ServiceControllerRouterTest() override { + router_.service_controller_.release(); + } + + void StartAdvertising(ClientProxy* client, std::string service_id, + ConnectionOptions options, ConnectionRequestInfo info, + ResultCallback callback) { + EXPECT_CALL(mock_, StartAdvertising) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartAdvertising(client, service_id, options, info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedAdvertising(kServiceId, options.strategy, info.listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsAdvertising()); + } + + void StopAdvertising(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopAdvertising).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopAdvertising(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedAdvertising(); + EXPECT_FALSE(client->IsAdvertising()); + } + + void StartDiscovery(ClientProxy* client, std::string service_id, + ConnectionOptions options, + const DiscoveryListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, StartDiscovery) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StartDiscovery(client, kServiceId, options, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->StartedDiscovery(service_id, options.strategy, listener, + absl::MakeSpan(mediums_)); + EXPECT_TRUE(client->IsDiscovering()); + } + + void StopDiscovery(ClientProxy* client, ResultCallback callback) { + EXPECT_CALL(mock_, StopDiscovery).Times(1); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.StopDiscovery(client, callback); + while (!complete_) cond_.Wait(); + } + client->StoppedDiscovery(); + EXPECT_FALSE(client->IsDiscovering()); + } + + void RequestConnection(ClientProxy* client, const std::string& endpoint_id, + const ConnectionRequestInfo& request_info, + ResultCallback callback) { + EXPECT_CALL(mock_, RequestConnection) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RequestConnection(client, endpoint_id, request_info, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + ConnectionResponseInfo response_info{ + .remote_endpoint_name = "endpoint_name", + .authentication_token = "auth_token", + .raw_authentication_token = ByteArray("auth_token"), + .is_incoming_connection = true, + }; + client->OnConnectionInitiated(endpoint_id, response_info, + request_info.listener); + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + } + + void AcceptConnection(ClientProxy* client, const std::string endpoint_id, + const PayloadListener& listener, + const ResultCallback& callback) { + EXPECT_CALL(mock_, AcceptConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.AcceptConnection(client, endpoint_id, listener, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointAcceptedConnection(endpoint_id, listener); + client->RemoteEndpointAcceptedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionAccepted(endpoint_id)); + client->OnConnectionAccepted(endpoint_id); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + } + + void RejectConnection(ClientProxy* client, const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, RejectConnection) + .WillOnce(Return(Status{Status::kSuccess})); + // Pre-condition for successful Accept is: connection must exist. + EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.RejectConnection(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + client->LocalEndpointRejectedConnection(endpoint_id); + EXPECT_TRUE(client->IsConnectionRejected(endpoint_id)); + } + + void InitiateBandwidthUpgrade(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, InitiateBandwidthUpgrade).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.InitiateBandwidthUpgrade(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void SendPayload(ClientProxy* client, + const std::vector& endpoint_ids, + Payload payload, ResultCallback callback) { + EXPECT_CALL(mock_, SendPayload).Times(1); + + bool connected = false; + for (const auto& endpoint_id : endpoint_ids) { + connected = connected || client->IsConnectedToEndpoint(endpoint_id); + } + EXPECT_TRUE(connected); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.SendPayload(client, absl::MakeSpan(endpoint_ids), + std::move(payload), callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void CancelPayload(ClientProxy* client, std::int64_t payload_id, + ResultCallback callback) { + EXPECT_CALL(mock_, CancelPayload) + .WillOnce(Return(Status{Status::kSuccess})); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.CancelPayload(client, payload_id, callback); + while (!complete_) cond_.Wait(); + EXPECT_EQ(result_, Status{Status::kSuccess}); + } + } + + void DisconnectFromEndpoint(ClientProxy* client, + const std::string endpoint_id, + ResultCallback callback) { + EXPECT_CALL(mock_, DisconnectFromEndpoint).Times(1); + EXPECT_TRUE(client->IsConnectedToEndpoint(endpoint_id)); + { + MutexLock lock(&mutex_); + complete_ = false; + router_.DisconnectFromEndpoint(client, endpoint_id, callback); + while (!complete_) cond_.Wait(); + } + client->OnDisconnected(endpoint_id, false); + EXPECT_FALSE(client->IsConnectedToEndpoint(endpoint_id)); + } + + protected: + const ResultCallback kCallback{ + .result_cb = + [this](Status status) { + MutexLock lock(&mutex_); + result_ = status; + complete_ = true; + cond_.Notify(); + }, + }; + const std::string kServiceId = "service id"; + const std::string kRequestorName = "requestor name"; + const std::string kRemoteEndpointId = "remote endpoint id"; + const std::int64_t kPayloadId = UINT64_C(0x123456789ABCDEF0); + const ConnectionOptions kConnectionOptions{ + .strategy = Strategy::kP2pPointToPoint, + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }; + + std::vector mediums_{ + proto::connections::Medium::BLUETOOTH}; + const ConnectionRequestInfo kConnectionRequestInfo{ + .name = kRequestorName, + .listener = ConnectionListener(), + }; + + DiscoveryListener discovery_listener_; + PayloadListener payload_listener_; + + Mutex mutex_; + ConditionVariable cond_{&mutex_}; + Status result_ ABSL_GUARDED_BY(mutex_) = {Status::kError}; + bool complete_ ABSL_GUARDED_BY(mutex_) = false; + MockServiceController mock_; + ClientProxy client_; + + ServiceControllerRouter router_{ + [this]() -> ServiceController* { return &mock_; }}; +}; + +namespace { +TEST_F(ServiceControllerRouterTest, CostructorDestructorWorks) { SUCCEED(); } + +TEST_F(ServiceControllerRouterTest, StartAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopAdvertisingCalled) { + StartAdvertising(&client_, kServiceId, kConnectionOptions, + kConnectionRequestInfo, kCallback); + StopAdvertising(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, StartDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, StopDiscoveryCalled) { + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + StopDiscovery(&client_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RequestConnectionCalled) { + // Either Advertising, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); +} + +TEST_F(ServiceControllerRouterTest, AcceptConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); +} + +TEST_F(ServiceControllerRouterTest, RejectConnectionCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can reject connection. + RejectConnection(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, InitiateBandwidthUpgradeCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can change connection bandwidth. + InitiateBandwidthUpgrade(&client_, kRemoteEndpointId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, SendPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // Now we can send payload. + SendPayload(&client_, std::vector{kRemoteEndpointId}, + Payload{ByteArray("data")}, kCallback); +} + +TEST_F(ServiceControllerRouterTest, CancelPayloadCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We have to know payload id, before we can cancel payload transfer. + // It is either after a call to SendPayload, or after receiving + // PayloadProgress callback. Let's assume we have it, and proceed. + CancelPayload(&client_, kPayloadId, kCallback); +} + +TEST_F(ServiceControllerRouterTest, DisconnectFromEndpointCalled) { + // Either Adviertisng, or Discovery should be ongoing. + StartDiscovery(&client_, kServiceId, kConnectionOptions, discovery_listener_, + kCallback); + // Establish connection. + RequestConnection(&client_, kRemoteEndpointId, kConnectionRequestInfo, + kCallback); + // Now, we can accept connection. + AcceptConnection(&client_, kRemoteEndpointId, payload_listener_, kCallback); + // We can disconnect at any time after RequestConnection. + DisconnectFromEndpoint(&client_, kRemoteEndpointId, kCallback); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.cc b/cpp/core_v2/internal/wifi_lan_service_info.cc new file mode 100644 index 00000000..65929ea9 --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.cc @@ -0,0 +1,194 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/wifi_lan_service_info.h" + +#include + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "platform_v2/public/logging.h" + +namespace location { +namespace nearby { +namespace connections { + +WifiLanServiceInfo::WifiLanServiceInfo(Version version, Pcp pcp, + absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name) { + if (version != Version::kV1 || endpoint_id.empty() || + endpoint_id.length() != kEndpointIdLength || + service_id_hash.size() != kServiceIdHashLength) { + return; + } + switch (pcp) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + break; + default: + return; + } + + version_ = version; + pcp_ = pcp; + service_id_hash_ = service_id_hash; + endpoint_id_ = endpoint_id; +} + +WifiLanServiceInfo::WifiLanServiceInfo(absl::string_view service_info_string) { + ByteArray service_info_bytes = Base64Utils::Decode(service_info_string); + + if (service_info_bytes.Empty()) { + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: failed Base64 decoding of %s", + std::string(service_info_string).c_str()); + return; + } + + if (service_info_bytes.size() > kMaxLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting max %d raw " + "bytes, got %" PRIu64, + kMaxLanServiceNameLength, service_info_bytes.size()); + return; + } + + if (service_info_bytes.size() < kMinLanServiceNameLength) { + NEARBY_LOG(ERROR, + "Cannot deserialize WifiLanServiceInfo: expecting min %d raw " + "bytes, got %" PRIu64, + kMinLanServiceNameLength, service_info_bytes.size()); + return; + } + + // The upper 3 bits are supposed to be the version. + version_ = static_cast( + (service_info_bytes.data()[0] & kVersionBitmask) >> kVersionShift); + const char* service_info_bytes_read_ptr = service_info_bytes.data(); + switch (version_) { + case Version::kV1: + // The lower 5 bits of the V1 payload are supposed to be the Pcp. + pcp_ = static_cast(*service_info_bytes_read_ptr & kPcpBitmask); + service_info_bytes_read_ptr++; + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are supposed to be the endpoint_id. + endpoint_id_ = + std::string(service_info_bytes_read_ptr, kEndpointIdLength); + service_info_bytes_read_ptr += kEndpointIdLength; + + // The next 24 bits are supposed to be the service_id_hash. + service_id_hash_ = + ByteArray(service_info_bytes_read_ptr, kServiceIdHashLength); + service_info_bytes_read_ptr += kServiceIdHashLength; + + // The next bits are supposed to be endpoint_name. + // TODO(edwinwu): Implements it. Temp to set "found_device". + endpoint_name_ = "found_device"; + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer + // ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + break; + } + break; + + default: + // TODO(edwinwu): [ANALYTICIZE] This either represents corruption over + // the air, or older versions of GmsCore intermingling with newer ones. + NEARBY_LOG( + ERROR, + "Cannot deserialize WifiLanServiceInfo: unsupported Version %d", + version_); + break; + } +} + +WifiLanServiceInfo::operator std::string() const { + if (!IsValid()) { + return ""; + } + + ByteArray wifi_lan_service_info_name_bytes(kMinLanServiceNameLength); + auto* wifi_lan_service_info_name_bytes_write_ptr = + wifi_lan_service_info_name_bytes.data(); + + // The upper 3 bits are the Version. + auto version_and_pcp_byte = static_cast( + (static_cast(Version::kV1) << 5) & kVersionBitmask); + // The lower 5 bits are the PCP. + version_and_pcp_byte |= + static_cast(static_cast(pcp_) & kPcpBitmask); + *wifi_lan_service_info_name_bytes_write_ptr = version_and_pcp_byte; + wifi_lan_service_info_name_bytes_write_ptr++; + + switch (pcp_) { + case Pcp::kP2pCluster: // Fall through + case Pcp::kP2pStar: // Fall through + case Pcp::kP2pPointToPoint: + // The next 32 bits are the endpoint_id. + if (endpoint_id_.size() != kEndpointIdLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 Endpoint ID %s (%" PRIu64 + " bytes) should be exactly %d bytes", + endpoint_id_.c_str(), endpoint_id_.size(), kEndpointIdLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, endpoint_id_.data(), + kEndpointIdLength); + wifi_lan_service_info_name_bytes_write_ptr += kEndpointIdLength; + + // The next 24 bits are the service_id_hash. + if (service_id_hash_.size() != kServiceIdHashLength) { + NEARBY_LOG( + ERROR, + "Cannot serialize WifiLanServiceInfo: V1 ServiceID hash (%" PRIu64 + " bytes) should be exactly %d bytes", + service_id_hash_.size(), kServiceIdHashLength); + return ""; + } + memcpy(wifi_lan_service_info_name_bytes_write_ptr, + service_id_hash_.data(), kServiceIdHashLength); + wifi_lan_service_info_name_bytes_write_ptr += kServiceIdHashLength; + + // The next bits are the endpoint_name. + // TODO(edwinwu): Implements to parse endpoint_name. + break; + default: + NEARBY_LOG(ERROR, + "Cannot serialize WifiLanServiceInfo: unsupported V1 PCP %d", + pcp_); + return ""; + } + + return Base64Utils::Encode(wifi_lan_service_info_name_bytes); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/wifi_lan_service_info.h b/cpp/core_v2/internal/wifi_lan_service_info.h new file mode 100644 index 00000000..694652ae --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info.h @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ +#define CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ + +#include + +#include "core_v2/internal/pcp.h" +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace connections { + +// Represents the format of the WifiLan service info used in Advertising + +// Discovery. +// +// See go/nearby-offline-data-interchange-formats for the specification. +class WifiLanServiceInfo { + public: + // Versions of the WifiLanServiceInfo. + enum class Version { + kUndefined = 0, + kV1 = 1, + }; + + static constexpr std::uint32_t kServiceIdHashLength = 3; + + WifiLanServiceInfo() = default; + WifiLanServiceInfo(Version version, Pcp pcp, absl::string_view endpoint_id, + const ByteArray& service_id_hash, + absl::string_view endpoint_name); + explicit WifiLanServiceInfo(absl::string_view service_info_string); + ~WifiLanServiceInfo() = default; + + WifiLanServiceInfo(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo& operator=(const WifiLanServiceInfo&) = default; + WifiLanServiceInfo(WifiLanServiceInfo&&) = default; + WifiLanServiceInfo& operator=(WifiLanServiceInfo&&) = default; + + explicit operator std::string() const; + + inline bool IsValid() const { return !endpoint_id_.empty(); } + inline Version GetVersion() const { return version_; } + inline Pcp GetPcp() const { return pcp_; } + inline std::string GetEndpointId() const { return endpoint_id_; } + inline std::string GetEndpointName() const { return endpoint_name_; } + inline ByteArray GetServiceIdHash() const { return service_id_hash_; } + + private: + // The maximum length of encrypted WifiLanServiceInfo string. + static constexpr int kMaxLanServiceNameLength = 47; + // The minimum length of encrypted WifiLanServiceInfo string. + static constexpr int kMinLanServiceNameLength = 9; + // The length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kEndpointIdLength = 4; + // The maximum length for endpoint id in encrypted WifiLanServiceInfo string. + static constexpr int kMaxEndpointNameLength = 131; + + static constexpr int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int kVersionShift = 5; + + // WifiLanServiceInfo version. + Version version_ = Version::kUndefined; + // Pre-Connection Protocols version. + Pcp pcp_ = Pcp::kUnknown; + // Connected endpoint id. + std::string endpoint_id_; + // Connected hash service id. + ByteArray service_id_hash_; + // TODO(edwinwu): Replaces endpointName as endPointInfo eventually; + // it is not in this version yet for endpointName. + // Connected endpoint name. + std::string endpoint_name_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_WIFI_LAN_SERVICE_INFO_H_ diff --git a/cpp/core_v2/internal/wifi_lan_service_info_test.cc b/cpp/core_v2/internal/wifi_lan_service_info_test.cc new file mode 100644 index 00000000..de1e0f03 --- /dev/null +++ b/cpp/core_v2/internal/wifi_lan_service_info_test.cc @@ -0,0 +1,157 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/internal/wifi_lan_service_info.h" + +#include +#include + +#include "platform_v2/base/base64_utils.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +const WifiLanServiceInfo::Version kVersion = WifiLanServiceInfo::Version::kV1; +const Pcp kPcp = Pcp::kP2pCluster; +const char kEndPointID[] = "AB12"; +const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C}; +// TODO(edwinwu): Temp to set empty string for endpoint_name. +const char kEndPointName[] = ""; + +TEST(WifiLanServiceInfoTest, ConstructionWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFromSerializedStringWorks) { + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto org_wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, service_id_hash, kEndPointName); + auto wifi_lan_service_info_string = std::string(org_wifi_lan_service_info); + + auto wifi_lan_service_info = WifiLanServiceInfo(wifi_lan_service_info_string); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_TRUE(is_valid); + EXPECT_EQ(kPcp, wifi_lan_service_info.GetPcp()); + EXPECT_EQ(kVersion, wifi_lan_service_info.GetVersion()); + EXPECT_EQ(kEndPointID, wifi_lan_service_info.GetEndpointId()); + EXPECT_EQ(service_id_hash, wifi_lan_service_info.GetServiceIdHash()); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadVersion) { + auto bad_version = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + bad_version, kPcp, kEndPointID, service_id_hash, kEndPointName); + + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithBadPCP) { + auto bad_pcp = static_cast(666); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, bad_pcp, kEndPointID, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortEndpointId) { + std::string short_endpoint_id("AB1"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, short_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongEndpointId) { + std::string long_endpoint_id("AB12X"); + + auto service_id_hash = ByteArray(kServiceIDHashBytes, + sizeof(kServiceIDHashBytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, long_endpoint_id, service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortServiceIdHash) { + char short_service_id_hash_bytes[] = {0x0A, 0x0B}; + + auto short_service_id_hash = + ByteArray(short_service_id_hash_bytes, + sizeof(short_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, short_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithLongServiceIdHash) { + char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D}; + + auto long_service_id_hash = + ByteArray(long_service_id_hash_bytes, + sizeof(long_service_id_hash_bytes) / sizeof(char)); + auto wifi_lan_service_info = WifiLanServiceInfo( + kVersion, kPcp, kEndPointID, long_service_id_hash, kEndPointName); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +TEST(WifiLanServiceInfoTest, ConstructionFailsWithShortStringLength) { + char wifi_lan_service_info_string[] = {'X'}; + + auto wifi_lan_service_info_bytes = + ByteArray(wifi_lan_service_info_string, + sizeof(wifi_lan_service_info_string) / sizeof(char)); + auto wifi_lan_service_info = + WifiLanServiceInfo(Base64Utils::Encode(wifi_lan_service_info_bytes)); + auto is_valid = wifi_lan_service_info.IsValid(); + + EXPECT_FALSE(is_valid); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/listeners.h b/cpp/core_v2/listeners.h new file mode 100644 index 00000000..7263ffcf --- /dev/null +++ b/cpp/core_v2/listeners.h @@ -0,0 +1,194 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_LISTENERS_H_ +#define CORE_V2_LISTENERS_H_ + +#include +#include +#include +#include + +// This file defines all the protocol listeners and their parameter structures. +// Listeners are defined as collections of std::function instances, which is +// more flexible than a virtual function: +// - a subset of listener callbacks may be overridden, while others may remain +// default-initialized. +// - callbacks may be initialized with lambdas; lambda definitions are concize. + +#include "core_v2/payload.h" +#include "core_v2/status.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Common callback for asynchronously invoked methods. +// Called after a job scheduled for execution is completed. +// This is not the same as completion of the associated process, +// which may have many states, and multiple async jobs, and be still ongoing. +// Progress on the overall process is reported by the associated listener. +struct ResultCallback { + // Callback to access the status of the operation when available. + // status - result of job execution; + // Status::kSuccess, if successful; anything else indicates failure. + std::function result_cb = DefaultCallback(); +}; + +struct ConnectionResponseInfo { + std::string remote_endpoint_name; + std::string authentication_token; + ByteArray raw_authentication_token; + ByteArray endpoint_info; + bool is_incoming_connection; + bool is_connection_verified; +}; + +struct PayloadProgressInfo { + std::int64_t payload_id; + enum class Status { + kSuccess, + kFailure, + kInProgress, + kCanceled, + } status; + std::int64_t total_bytes; + std::int64_t bytes_transferred; +}; + +enum class DistanceInfo { + kUnknown = 1, + kVeryClose = 2, + kClose = 3, + kFar = 4, +}; + +struct ConnectionListener { + // A basic encrypted channel has been created between you and the endpoint. + // Both sides are now asked if they wish to accept or reject the connection + // before any data can be sent over this channel. + // + // This is your chance, before you accept the connection, to confirm that you + // connected to the correct device. Both devices are given an identical token; + // it's up to you to decide how to verify it before proceeding. Typically this + // involves showing the token on both devices and having the users manually + // compare and confirm; however, this is only required if you desire a secure + // connection between the devices. + // + // Whichever route you decide to take (including not authenticating the other + // device), call Core::AcceptConnection() when you're ready to talk, or + // Core::RejectConnection() to close the connection. + // + // endpoint_id - The identifier for the remote endpoint. + // info - Other relevant information about the connection. + std::function + initiated_cb = + DefaultCallback(); + + // Called after both sides have accepted the connection. + // Both sides may now send Payloads to each other. + // Call Core::SendPayload() or wait for incoming PayloadListener::OnPayload(). + // + // endpoint_id - The identifier for the remote endpoint. + std::function accepted_cb = + DefaultCallback(); + + // Called when either side rejected the connection. + // Payloads can not be exchaged. Call Core::DisconnectFromEndpoint() + // to terminate connection. + // + // endpoint_id - The identifier for the remote endpoint. + std::function + rejected_cb = DefaultCallback(); + + // Called when a remote endpoint is disconnected or has become unreachable. + // At this point service (re-)discovery may start again. + // + // endpoint_id - The identifier for the remote endpoint. + std::function disconnected_cb = + DefaultCallback(); + + // Called when the connection's available bandwidth has changed. + // + // endpoint_id - The identifier for the remote endpoint. + // quality - TODO(apolyudov): document. + std::function + bandwidth_changed_cb = + DefaultCallback(); +}; + +struct DiscoveryListener { + // Called when a remote endpoint is discovered. + // + // endpoint_id - The ID of the remote endpoint that was discovered. + // endpoint_name - The human readable name of the remote endpoint. + // service_id - The ID of the service advertised by the remote endpoint. + std::function + endpoint_found_cb = + DefaultCallback(); + + // Called when a remote endpoint is no longer discoverable; only called for + // endpoints that previously had been passed to {@link + // #onEndpointFound(String, DiscoveredEndpointInfo)}. + // + // endpoint_id - The ID of the remote endpoint that was lost. + std::function endpoint_lost_cb = + DefaultCallback(); + + // Called when a remote endpoint is found with an updated distance. + // + // arguments: + // endpoint_id - The ID of the remote endpoint that was lost. + // info - The distance info, encoded as enum value. + std::function + endpoint_distance_changed_cb = + DefaultCallback(); +}; + +struct PayloadListener { + // Called when a Payload is received from a remote endpoint. Depending + // on the type of the Payload, all of the data may or may not have been + // received at the time of this call. Use OnPayloadProgress() to + // get updates on the status of the data received. + // + // endpoint_id - The identifier for the remote endpoint that sent the + // payload. + // payload - The Payload object received. + std::function + payload_cb = DefaultCallback(); + + // Called with progress information about an active Payload transfer, either + // incoming or outgoing. + // + // endpoint_id - The identifier for the remote endpoint that is sending or + // receiving this payload. + // info - The PayloadProgressInfo structure describing the status of + // the transfer. + std::function + payload_progress_cb = + DefaultCallback(); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_LISTENERS_H_ diff --git a/cpp/core_v2/listeners_test.cc b/cpp/core_v2/listeners_test.cc new file mode 100644 index 00000000..d91d7c56 --- /dev/null +++ b/cpp/core_v2/listeners_test.cc @@ -0,0 +1,59 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/listeners.h" + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +TEST(ListenersTest, EnsureDefaultInitializedIsCallable) { + ConnectionListener listener; + std::string endpoint_id("endpoint_id"); + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + SUCCEED(); +} + +TEST(ListenersTest, EnsurePartiallyInitializedIsCallable) { + std::string endpoint_id = {"endpoint_id"}; + bool initiated_cb_called = false; + ConnectionListener listener{ + .initiated_cb = + [&](std::string, ConnectionResponseInfo) { + initiated_cb_called = true; + }, + }; + listener.initiated_cb(endpoint_id, ConnectionResponseInfo()); + listener.accepted_cb(endpoint_id); + listener.rejected_cb(endpoint_id, {Status::kError}); + listener.disconnected_cb(endpoint_id); + listener.bandwidth_changed_cb(endpoint_id, int()); + EXPECT_TRUE(initiated_cb_called); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/options.h b/cpp/core_v2/options.h new file mode 100644 index 00000000..064ba2b6 --- /dev/null +++ b/cpp/core_v2/options.h @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_OPTIONS_H_ +#define CORE_V2_OPTIONS_H_ + +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +// Connection Options: used for both Advertising and Discovery. +// All fields are mutable, to make the type copy-assignable. +struct ConnectionOptions { + Strategy strategy; + bool auto_upgrade_bandwidth; + bool enforce_topology_constraints; + // Verify if ConnectionOptions is in a not-initialized (Empty) state. + bool Empty() const { + return strategy.IsNone(); + } + // Bring ConnectionOptions to a not-initialized (Empty) state. + void Clear() { + strategy.Clear(); + } +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_OPTIONS_H_ diff --git a/cpp/core_v2/params.h b/cpp/core_v2/params.h new file mode 100644 index 00000000..d71c14e7 --- /dev/null +++ b/cpp/core_v2/params.h @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_PARAMS_H_ +#define CORE_V2_PARAMS_H_ + +#include + +#include "core_v2/listeners.h" + +namespace location { +namespace nearby { +namespace connections { + +// Used by Discovery in Core::RequestConnection(). +// Used by Advertising in Core::StartAdvertising(). +struct ConnectionRequestInfo { + // name - A human readable name for this endpoint, to appear on + // other devices. + // listener - A set of callbacks notified when remote endpoints request a + // connection to this endpoint. + std::string name; + ConnectionListener listener; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PARAMS_H_ diff --git a/cpp/core_v2/payload.h b/cpp/core_v2/payload.h new file mode 100644 index 00000000..ffe4d0d8 --- /dev/null +++ b/cpp/core_v2/payload.h @@ -0,0 +1,99 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_PAYLOAD_H_ +#define CORE_V2_PAYLOAD_H_ + +#include +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/prng.h" +#include "platform_v2/public/file.h" +#include "absl/types/variant.h" + +namespace location { +namespace nearby { +namespace connections { + +// Payload is default-constructible, and moveable, but not copyable container +// that holds at most one instance of one of: +// ByteArray, InputStream, or InputFile. +class Payload { + public: + // Order of types in variant, and values in Type enum is important. + // Enum values must match respective variant types. + using Content = + absl::variant, + std::unique_ptr>; + enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; + + Payload(Payload&& other) = default; + ~Payload() = default; + Payload& operator=(Payload&& other) = default; + + // Create Payload from bytes, steam, or file. Payload is immutable. + Payload() : content_(absl::monostate()) {} + explicit Payload(ByteArray&& bytes) : content_(std::move(bytes)) {} + explicit Payload(const ByteArray& bytes) : content_(bytes) {} + explicit Payload(std::unique_ptr stream) + : content_(std::move(stream)) {} + explicit Payload(std::unique_ptr file) + : content_(std::move(file)) {} + + // Returns ByteArray payload, if it has been defined, or empty ByteArray. + const ByteArray& AsBytes() const & { + static const ByteArray empty; // NOLINT: function-level static is OK. + auto* result = absl::get_if(&content_); + return result ? *result : empty; + } + ByteArray&& AsBytes() && { + auto* result = absl::get_if(&content_); + return result ? std::move(*result) : std::move(ByteArray()); + } + // Returns InputStream* payload, if it has been defined, or nullptr. + InputStream* AsStream() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + // Returns InputFile* payload, if it has been defined, or nullptr. + InputFile* AsFile() const { + auto* result = absl::get_if>(&content_); + return result ? result->get() : nullptr; + } + + // Returns Payload unique ID. + std::int64_t GetId() const { return id_; } + + // Returns Payload type. + Type GetType() const { return type_; } + + private: + static std::int64_t GenerateId() { return Prng().NextInt64(); } + Type FindType(const Content& content) const { + return static_cast(content_.index()); + } + + Content content_; + std::int64_t id_{GenerateId()}; + Type type_{FindType(content_)}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_PAYLOAD_H_ diff --git a/cpp/core_v2/payload_test.cc b/cpp/core_v2/payload_test.cc new file mode 100644 index 00000000..5b3c0068 --- /dev/null +++ b/cpp/core_v2/payload_test.cc @@ -0,0 +1,90 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/payload.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/public/file.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(PayloadTest, DefaultPayloadHasUnknownType) { + Payload payload; + EXPECT_EQ(payload.GetType(), Payload::Type::kUnknown); +} + +TEST(PayloadTest, SupportsByteArrayType) { + const ByteArray bytes("bytes"); + Payload payload(bytes); + EXPECT_EQ(payload.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), bytes); +} + +TEST(PayloadTest, SupportsFileType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr file(raw_file); + Payload payload(std::move(file)); + EXPECT_EQ(payload.GetType(), Payload::Type::kFile); + EXPECT_EQ(payload.AsStream(), nullptr); + EXPECT_EQ(payload.AsFile(), raw_file); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, SupportsStreamType) { + InputFile* raw_file = new InputFile("/path/to/file", 0); + std::unique_ptr stream(raw_file); + Payload payload(std::move(stream)); + EXPECT_EQ(payload.GetType(), Payload::Type::kStream); + EXPECT_EQ(payload.AsStream(), raw_file); + EXPECT_EQ(payload.AsFile(), nullptr); + EXPECT_EQ(payload.AsBytes(), ByteArray{}); +} + +TEST(PayloadTest, PayloadIsMoveable) { + Payload payload1; + Payload payload2(ByteArray("bytes")); + auto id = payload2.GetId(); + ByteArray bytes = payload2.AsBytes(); + EXPECT_EQ(payload1.GetType(), Payload::Type::kUnknown); + EXPECT_EQ(payload2.GetType(), Payload::Type::kBytes); + payload1 = std::move(payload2); + EXPECT_EQ(payload1.GetType(), Payload::Type::kBytes); + EXPECT_EQ(payload1.AsBytes(), bytes); + EXPECT_EQ(payload1.GetId(), id); +} + +TEST(PayloadTest, PayloadHasUniqueId) { + Payload payload1; + Payload payload2; + EXPECT_NE(payload1.GetId(), payload2.GetId()); +} + +TEST(PayloadTest, PayloadIsNotCopyable) { + EXPECT_FALSE(std::is_copy_constructible_v); + EXPECT_FALSE(std::is_copy_assignable_v); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/status.h b/cpp/core_v2/status.h new file mode 100644 index 00000000..36db8d69 --- /dev/null +++ b/cpp/core_v2/status.h @@ -0,0 +1,59 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_STATUS_H_ +#define CORE_V2_STATUS_H_ + +namespace location { +namespace nearby { +namespace connections { + +// Protocol operation result: kSuccess, if operation was successful; +// descriptive error code otherwise. +struct Status { + // Status is a struct, so it is possible to pass some context about failure, + // by adding extra fields to it when necessary, and not change any of the + // method signatures. + enum Value { + kSuccess, + kError, + kOutOfOrderApiCall, + kAlreadyHaveActiveStrategy, + kAlreadyAdvertising, + kAlreadyDiscovering, + kEndpointIoError, + kEndpointUnknown, + kConnectionRejected, + kAlreadyConnectedToEndpoint, + kNotConnectedToEndpoint, + kBluetoothError, + kPayloadUnknown, + }; + Value value {kError}; + bool Ok() const { return value == kSuccess; } +}; + +inline bool operator==(const Status& a, const Status& b) { + return a.value == b.value; +} + +inline bool operator!=(const Status& a, const Status& b) { + return !(a == b); +} + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STATUS_H_ diff --git a/cpp/core_v2/status_test.cc b/cpp/core_v2/status_test.cc new file mode 100644 index 00000000..0643c8b8 --- /dev/null +++ b/cpp/core_v2/status_test.cc @@ -0,0 +1,58 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/status.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StatusTest, DefaultIsError) { + Status status; + EXPECT_FALSE(status.Ok()); + EXPECT_EQ(status, Status{Status::kError}); +} + +TEST(StatusTest, DefaultEquals) { + Status status1; + Status status2; + EXPECT_EQ(status1, status2); +} + +TEST(StatusTest, ExplicitInitEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kSuccess}; + EXPECT_EQ(status1, status2); + EXPECT_TRUE(status1.Ok()); +} + +TEST(StatusTest, ExplicitInitNotEquals) { + Status status1 = {Status::kSuccess}; + Status status2 = {Status::kAlreadyAdvertising}; + EXPECT_NE(status1, status2); +} + +TEST(StatusTest, CopyInitEquals) { + Status status1 = {Status::kAlreadyAdvertising}; + Status status2 = {status1}; + + EXPECT_EQ(status1, status2); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.cc b/cpp/core_v2/strategy.cc new file mode 100644 index 00000000..9f373b93 --- /dev/null +++ b/cpp/core_v2/strategy.cc @@ -0,0 +1,61 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/strategy.h" + +namespace location { +namespace nearby { +namespace connections { + +const Strategy Strategy::kNone = {Strategy::ConnectionType::kNone, + Strategy::TopologyType::kUnknown}; +const Strategy Strategy::kP2pCluster{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kManyToMany}; +const Strategy Strategy::kP2pStar{Strategy::ConnectionType::kPointToPoint, + Strategy::TopologyType::kOneToMany}; +const Strategy Strategy::kP2pPointToPoint{ + Strategy::ConnectionType::kPointToPoint, Strategy::TopologyType::kOneToOne}; + +bool Strategy::IsNone() const { + return *this == kNone; +} + +bool Strategy::IsValid() const { + return *this == kP2pStar || *this == kP2pCluster || *this ==kP2pPointToPoint; +} + +std::string Strategy::GetName() const { + if (*this == Strategy::kP2pCluster) { + return "P2P_CLUSTER"; + } else if (*this == Strategy::kP2pStar) { + return "P2P_STAR"; + } else if (*this == Strategy::kP2pPointToPoint) { + return "P2P_POINT_TO_POINT"; + } else { + return "UNKNOWN"; + } +} + +bool operator==(const Strategy& lhs, const Strategy& rhs) { + return lhs.connection_type_ == rhs.connection_type_ && + lhs.topology_type_ == rhs.topology_type_; +} + +bool operator!=(const Strategy& lhs, const Strategy& rhs) { + return !(lhs == rhs); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/strategy.h b/cpp/core_v2/strategy.h new file mode 100644 index 00000000..24da7a37 --- /dev/null +++ b/cpp/core_v2/strategy.h @@ -0,0 +1,76 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_V2_STRATEGY_H_ +#define CORE_V2_STRATEGY_H_ + +#include + +namespace location { +namespace nearby { +namespace connections { + +// Defines a copyable, comparable connection strategy type. +// It is one of: kP2pCluster, kP2pStar, kP2pPointToPoint. +class Strategy { + public: + static const Strategy kNone; + static const Strategy kP2pCluster; + static const Strategy kP2pStar; + static const Strategy kP2pPointToPoint; + + Strategy() : Strategy(kNone) {} + + constexpr Strategy(const Strategy& other) + : connection_type_(other.connection_type_), + topology_type_(other.topology_type_) {} + + // Returns true, if strategy is kNone, false otherwise. + bool IsNone() const; + // Returns true, if a strategy is one of the supported strategies, + // false otherwise. + bool IsValid() const; + // Returns a string representing given strategy, for every valid strategy. + std::string GetName() const; + // Undefine strategy. + void Clear() { + *this = kNone; + } + + friend bool operator==(const Strategy& lhs, const Strategy& rhs); + friend bool operator!=(const Strategy& lhs, const Strategy& rhs); + + private: + enum class ConnectionType { + kNone = 0, + kPointToPoint = 1, + }; + enum class TopologyType { + kUnknown = 0, + kOneToOne = 1, + kOneToMany = 2, + kManyToMany = 3, + }; + Strategy(ConnectionType connection_type, TopologyType topology_type) + : connection_type_(connection_type), topology_type_(topology_type) {} + + ConnectionType connection_type_; + TopologyType topology_type_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_STRATEGY_H_ diff --git a/cpp/core_v2/strategy_test.cc b/cpp/core_v2/strategy_test.cc new file mode 100644 index 00000000..57776920 --- /dev/null +++ b/cpp/core_v2/strategy_test.cc @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "core_v2/strategy.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { + +TEST(StrategyTest, IsValidWorks) { + EXPECT_FALSE(Strategy().IsValid()); + EXPECT_TRUE(Strategy::kP2pCluster.IsValid()); + EXPECT_TRUE(Strategy::kP2pStar.IsValid()); + EXPECT_TRUE(Strategy::kP2pPointToPoint.IsValid()); +} + +TEST(StrategyTest, IsNoneWorks) { + EXPECT_TRUE(Strategy().IsNone()); + EXPECT_FALSE(Strategy::kP2pCluster.IsNone()); + EXPECT_FALSE(Strategy::kP2pStar.IsNone()); + EXPECT_FALSE(Strategy::kP2pPointToPoint.IsNone()); +} + +TEST(StrategyTest, CompareWorks) { + EXPECT_EQ(Strategy::kP2pCluster, Strategy::kP2pCluster); + EXPECT_EQ(Strategy::kP2pStar, Strategy::kP2pStar); + EXPECT_EQ(Strategy::kP2pPointToPoint, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pStar); + EXPECT_NE(Strategy::kP2pCluster, Strategy::kP2pPointToPoint); + EXPECT_NE(Strategy::kP2pStar, Strategy::kP2pPointToPoint); +} + +TEST(StrategyTest, GetNameWorks) { + EXPECT_EQ(Strategy().GetName(), "UNKNOWN"); + EXPECT_EQ(Strategy::kP2pCluster.GetName(), "P2P_CLUSTER"); + EXPECT_EQ(Strategy::kP2pStar.GetName(), "P2P_STAR"); + EXPECT_EQ(Strategy::kP2pPointToPoint.GetName(), "P2P_POINT_TO_POINT"); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/platform/BUILD b/cpp/platform/BUILD index fea39046..ebcc7fcb 100644 --- a/cpp/platform/BUILD +++ b/cpp/platform/BUILD @@ -16,16 +16,16 @@ cc_library( name = "utils", srcs = [ "base64_utils.cc", + "cancelable_alarm.cc", "file_impl.cc", + "pipe.cc", "prng.cc", "reliability_utils.cc", ], hdrs = [ "base64_utils.h", - "cancelable_alarm.cc", "cancelable_alarm.h", "file_impl.h", - "pipe.cc", "pipe.h", "prng.h", "reliability_utils.h", @@ -64,7 +64,6 @@ cc_library( ], deps = [ ":logging", - "//platform/impl/default:lock", "//platform/port:down_cast", "//platform/port:string", ], @@ -78,6 +77,7 @@ cc_library( visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", + "//platform_v2/public:__pkg__", ], deps = [ "//absl/base", @@ -86,75 +86,24 @@ cc_library( ) cc_test( - name = "container_of_test", - srcs = ["container_of_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", + name = "platform_test", + timeout = "short", + srcs = [ + "atomic_reference_test.cc", + "byte_array_test.cc", + "container_of_test.cc", + "file_impl_test.cc", + "pipe_test.cc", + "prng_test.cc", + "ptr_test.cc", + "settable_future_test.cc", ], -) - -cc_test( - name = "ptr_test", - srcs = ["ptr_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "prng_test", - srcs = ["prng_test.cc"], - deps = [ - ":utils", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "file_test", - srcs = ["file_impl_test.cc"], deps = [ ":utils", "//file/util:temp_path", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "exception_test", - srcs = ["exception_test.cc"], - deps = [ - ":types", - "//testing/base/public:gunit_main", - ], -) - -cc_test( - name = "pipe_test", - timeout = "short", - srcs = ["pipe_test.cc"], - deps = [ - ":utils", "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", - "//platform/port:string", - "//testing/base/public:gunit_main", - "//absl/time", - ], -) - -cc_test( - name = "byte_array_test", - timeout = "short", - srcs = ["byte_array_test.cc"], - deps = [ - ":utils", - "//platform:types", - "//platform/impl/default:condition_variable", - "//platform/impl/default:lock", + "//platform/api", + "//platform/impl/g3", "//platform/port:string", "//testing/base/public:gunit_main", "//absl/time", diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt index c346e859..3f33b6ed 100644 --- a/cpp/platform/CMakeLists.txt +++ b/cpp/platform/CMakeLists.txt @@ -13,13 +13,16 @@ # limitations under the License. add_library(platform_utils STATIC - base64_utils.cc - file_impl.cc - prng.cc - reliability_utils.cc ) target_sources(platform_utils + PRIVATE + base64_utils.cc + cancelable_alarm.cc + file_impl.cc + pipe.cc + prng.cc + reliability_utils.cc PUBLIC base64_utils.h cancelable_alarm.h @@ -32,8 +35,11 @@ target_sources(platform_utils target_link_libraries(platform_utils PUBLIC - platform_types platform_api + platform_types + platform_impl_g3 + platform_impl_shared_posix_lock + platform_impl_shared_posix_condition_variable absl::strings ) @@ -76,8 +82,7 @@ target_link_libraries(platform_test gtest gtest_main platform_api - platform_impl_default_cond_var - platform_impl_default_lock + platform_impl_g3 platform_types platform_utils ) @@ -88,7 +93,6 @@ add_test( ) add_subdirectory(api) -add_subdirectory(api2) -add_subdirectory(impl/sample) -add_subdirectory(impl/default) +add_subdirectory(impl/g3) +add_subdirectory(impl/shared) add_subdirectory(port) diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index 80ee2bf1..d97c71df 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -23,6 +23,7 @@ cc_library( hdrs = [ "atomic_boolean.h", "atomic_reference.h", + "atomic_reference_def.h", "ble.h", "ble_v2.h", "bluetooth_adapter.h", @@ -39,12 +40,15 @@ cc_library( "multi_thread_executor.h", "output_file.h", "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", + "settable_future_def.h", "single_thread_executor.h", "socket.h", "submittable_executor.h", + "submittable_executor_def.h", "system_clock.h", "thread_utils.h", "webrtc.h", @@ -55,7 +59,9 @@ cc_library( "//platform:types", "//platform/port:down_cast", "//platform/port:string", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", + "//absl/strings", + "//absl/types:any", + "//webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/atomic_reference.h b/cpp/platform/api/atomic_reference.h index 61ee8c9f..366897bc 100644 --- a/cpp/platform/api/atomic_reference.h +++ b/cpp/platform/api/atomic_reference.h @@ -15,21 +15,48 @@ #ifndef PLATFORM_API_ATOMIC_REFERENCE_H_ #define PLATFORM_API_ATOMIC_REFERENCE_H_ +#include "platform/api/atomic_reference_def.h" +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + namespace location { namespace nearby { -// An object reference that may be updated atomically. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { template -class AtomicReference { +class AtomicReferenceImpl : public AtomicReference { public: - virtual ~AtomicReference() {} + explicit AtomicReferenceImpl(T initial_value) { + atomic_ = platform::ImplementationPlatform::createAtomicReferenceAny( + absl::any(initial_value)); + } - virtual T get() = 0; - virtual void set(T value) = 0; + ~AtomicReferenceImpl() override = default; + + void set(T new_value) override { atomic_->set(absl::any(new_value)); } + + T get() override { return absl::any_cast(atomic_->get()); } + + private: + Ptr> atomic_; }; +} // namespace impl + +template +Ptr> ImplementationPlatform::createAtomicReference( + T initial_value) { + return Ptr>( + new impl::AtomicReferenceImpl{initial_value}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/atomic_reference_def.h b/cpp/platform/api/atomic_reference_def.h new file mode 100644 index 00000000..bab537e8 --- /dev/null +++ b/cpp/platform/api/atomic_reference_def.h @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ +#define PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateAtomicReferenceSizeT() +// Ptr>> CreateAtomicReferencePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class AtomicReference { + public: + virtual ~AtomicReference() = default; + + virtual T get() = 0; + virtual void set(T value) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_ATOMIC_REFERENCE_DEF_H_ diff --git a/cpp/platform/api/ble_v2.h b/cpp/platform/api/ble_v2.h index b620ea32..8645a68b 100644 --- a/cpp/platform/api/ble_v2.h +++ b/cpp/platform/api/ble_v2.h @@ -38,7 +38,7 @@ namespace nearby { struct BLEAdvertisementData { typedef std::int8_t TXPowerLevel; - static const TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = + static constexpr TXPowerLevel UNSPECIFIED_TX_POWER_LEVEL = std::numeric_limits::min(); bool is_connectable; diff --git a/cpp/platform/api/multi_thread_executor.h b/cpp/platform/api/multi_thread_executor.h index 3ffdc46e..ba9597cc 100644 --- a/cpp/platform/api/multi_thread_executor.h +++ b/cpp/platform/api/multi_thread_executor.h @@ -24,11 +24,9 @@ namespace nearby { // unbounded queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { +class MultiThreadExecutor : public SubmittableExecutor { public: - ~MultiThreadExecutor() override {} + ~MultiThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/platform.h b/cpp/platform/api/platform.h new file mode 100644 index 00000000..248a13a8 --- /dev/null +++ b/cpp/platform/api/platform.h @@ -0,0 +1,120 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_API_PLATFORM_H_ +#define PLATFORM_API_PLATFORM_H_ + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +//#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/api/wifi_lan.h" + +// Project-specific basic types, that are not part of API. +// TODO(apolyudov): replace with c++ standard types. +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // Class Templates in platform code. + // + // Platform interface does not support templates directly. + // This is a design decision. The purpose is to have type isolation + // between platform library (or simply platform) and core library. + // Another goal is to make a platform implementation a black box, + // which does not leak implementation details in any form, be that types, + // methods, or variables. + // + // Core library code does provide platform-specific class templates + // on top of (a non-templated) platform support. + // + // For every common library template that needs platform support, + // platform must provide an absl::any specialization of class template: + template + static Ptr> createAtomicReference(T initial_value = T{}); + template + static Ptr> createSettableFuture(); + + // AtomicReference + static Ptr> createAtomicReferenceAny( + absl::any initial_value); + + // SettableFuture + static Ptr> createSettableFutureAny(); + + // Non-template methods: general platform support. + static Ptr createAtomicBoolean(bool initial_value); + static Ptr createCountDownLatch(std::int32_t count); + static Ptr createLock(); + static Ptr createConditionVariable(Ptr lock); + static Ptr createHashUtils(); + static Ptr createThreadUtils(); + static Ptr createSystemClock(); + + // Java-like Executors + // Type aliases used to API 1.0 compatibility. + // They will be retired soon. + // TODO(apolyudov): cleanup. + using SingleThreadExecutorType = SubmittableExecutor; + using MultiThreadExecutorType = SubmittableExecutor; + using ScheduledExecutorType = ScheduledExecutor; + + static Ptr createSingleThreadExecutor(); + static Ptr createMultiThreadExecutor( + std::int32_t max_concurrency); + static Ptr createScheduledExecutor(); + + // Protocol implementations, domain-specific support + static Ptr createBluetoothAdapter(); + static Ptr createWifiMedium(); + static Ptr createBluetoothClassicMedium(); + static Ptr createBLEMedium(); + static Ptr createBLEMediumV2(); + static Ptr createServerSyncMedium(); + static Ptr createWifiLanMedium(); +// static Ptr createWebRtcSignalingMessenger( +// const std::string& self_id); + static std::string getDeviceId(); + static std::string getPayloadPath(int64_t payload_id); +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_PLATFORM_H_ diff --git a/cpp/platform/api/scheduled_executor.h b/cpp/platform/api/scheduled_executor.h index 38410ffd..7b72fdcb 100644 --- a/cpp/platform/api/scheduled_executor.h +++ b/cpp/platform/api/scheduled_executor.h @@ -17,7 +17,7 @@ #include -#include "platform/api/executor.h" +#include "platform/api/submittable_executor_def.h" #include "platform/cancelable.h" #include "platform/ptr.h" #include "platform/runnable.h" @@ -29,9 +29,9 @@ namespace nearby { // execute periodically. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html -class ScheduledExecutor : public Executor { +class ScheduledExecutor : public SubmittableExecutor { public: - virtual ~ScheduledExecutor() {} + ~ScheduledExecutor() override = default; virtual Ptr schedule(Ptr runnable, std::int64_t delay_millis) = 0; diff --git a/cpp/platform/api/server_sync.h b/cpp/platform/api/server_sync.h index 8c20b368..53a906e9 100644 --- a/cpp/platform/api/server_sync.h +++ b/cpp/platform/api/server_sync.h @@ -36,7 +36,7 @@ class ServerSyncDevice { virtual std::string getOwnGuid() = 0; }; -// Container of operations that can be performed over the Chrome Sync medium. +// Container of operations that can be performed over the Server Sync medium. class ServerSyncMedium { public: virtual ~ServerSyncMedium() {} diff --git a/cpp/platform/api/settable_future.h b/cpp/platform/api/settable_future.h index 3aed3d83..b0e64a52 100644 --- a/cpp/platform/api/settable_future.h +++ b/cpp/platform/api/settable_future.h @@ -15,24 +15,65 @@ #ifndef PLATFORM_API_SETTABLE_FUTURE_H_ #define PLATFORM_API_SETTABLE_FUTURE_H_ -#include "platform/api/listenable_future.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future_def.h" +#include "platform/exception.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" namespace location { namespace nearby { -// A SettableFuture is a type of Future whose result can be set. -// -// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +namespace platform { +namespace impl { + template -class SettableFuture : public ListenableFuture { +class SettableFutureImpl : public SettableFuture { public: - ~SettableFuture() override {} + SettableFutureImpl() { + future_ = platform::ImplementationPlatform::createSettableFutureAny(); + } - virtual bool set(T value) = 0; + ~SettableFutureImpl() override = default; - virtual bool setException(Exception exception) = 0; + bool set(T value) override { return future_->set(absl::any(value)); } + + bool setException(Exception exception) override { + return future_->setException(exception); + } + + void addListener(Ptr runnable, Executor* executor) override { + future_->addListener(runnable, executor); + } + + ExceptionOr get() override { return CommonGet(future_->get()); } + ExceptionOr get(std::int64_t timeout_ms) override { + return CommonGet(future_->get(timeout_ms)); + } + + private: + ExceptionOr CommonGet(ExceptionOr ret_val) { + if (ret_val.exception() != Exception::kSuccess) { + return ExceptionOr{ret_val.exception()}; + } + return ExceptionOr{absl::any_cast(ret_val.result())}; + } + + Ptr> future_; }; +} // namespace impl +template +Ptr> ImplementationPlatform::createSettableFuture() { + return Ptr>(new impl::SettableFutureImpl{}); +} + +} // namespace platform } // namespace nearby } // namespace location diff --git a/cpp/platform/api/settable_future_def.h b/cpp/platform/api/settable_future_def.h new file mode 100644 index 00000000..fbefd18f --- /dev/null +++ b/cpp/platform/api/settable_future_def.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_API_SETTABLE_FUTURE_DEF_H_ +#define PLATFORM_API_SETTABLE_FUTURE_DEF_H_ + +#include "platform/api/listenable_future.h" +#include "platform/exception.h" + +namespace location { +namespace nearby { + +// A SettableFuture is a type of Future whose result can be set. +// +// https://google.github.io/guava/releases/20.0/api/docs/com/google/common/util/concurrent/SettableFuture.html +// +// Platform must implentent non-template static member functions +// Ptr> CreateSettableFutureSizeT() +// Ptr>> CreateSettableFuturePtr() +// in the location::nearby::platform::ImplementationPlatform class. +template +class SettableFuture : public ListenableFuture { + public: + ~SettableFuture() override = default; + + virtual bool set(T value) = 0; + + virtual bool setException(Exception exception) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SETTABLE_FUTURE_DEF_H_ diff --git a/cpp/platform/api/single_thread_executor.h b/cpp/platform/api/single_thread_executor.h index ed92e0fa..96f27679 100644 --- a/cpp/platform/api/single_thread_executor.h +++ b/cpp/platform/api/single_thread_executor.h @@ -24,11 +24,9 @@ namespace nearby { // queue. // // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { +class SingleThreadExecutor : public SubmittableExecutor { public: - ~SingleThreadExecutor() override {} + ~SingleThreadExecutor() override = default; }; } // namespace nearby diff --git a/cpp/platform/api/submittable_executor.h b/cpp/platform/api/submittable_executor.h index 3554165b..77d17b96 100644 --- a/cpp/platform/api/submittable_executor.h +++ b/cpp/platform/api/submittable_executor.h @@ -15,37 +15,40 @@ #ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ #define PLATFORM_API_SUBMITTABLE_EXECUTOR_H_ +#include + #include "platform/api/executor.h" #include "platform/api/future.h" -#include "platform/callable.h" -#include "platform/port/down_cast.h" -#include "platform/ptr.h" +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/exception.h" namespace location { namespace nearby { -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class IOSSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// Ptr > submit(Ptr > callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - Ptr> submit(Ptr> callable) { - return DOWN_CAST(this)->submit(callable); +// "Common" part of implementation. +// Placed here for textual compatibility to minimize scope of changes. +// Can be (and should be) moved to a separate file outside "api" folder. +// TODO(apolyudov): for API v2.0 +template +Ptr> SubmittableExecutor::submit(Ptr> callable) { + using Platform = platform::ImplementationPlatform; + Ptr> future{Platform::createSettableFuture()}; + bool submitted = DoSubmit([callable, future]() { + ExceptionOr result = callable->call(); + if (result.ok()) { + future->set(std::move(result.result())); + } else { + future->setException({result.exception()}); + } + }); + if (!submitted) { + // Raise Exception::kExecution if we are shutting down. + future->setException({Exception::kExecution}); } -}; + return future; +} } // namespace nearby } // namespace location diff --git a/cpp/platform/api/submittable_executor_def.h b/cpp/platform/api/submittable_executor_def.h new file mode 100644 index 00000000..e0c5cd20 --- /dev/null +++ b/cpp/platform/api/submittable_executor_def.h @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ +#define PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ + +#include + +#include "platform/api/executor.h" +#include "platform/api/future.h" +#include "platform/callable.h" +#include "platform/ptr.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + template + Ptr> submit(Ptr> callable); + + protected: + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(std::function wrapped_callable) = 0; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_API_SUBMITTABLE_EXECUTOR_DEF_H_ diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index fd73f19d..5a421750 100644 --- a/cpp/platform/api/webrtc.h +++ b/cpp/platform/api/webrtc.h @@ -19,11 +19,11 @@ #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 { - +#if 0 class WebRtcSignalingMessenger { public: virtual ~WebRtcSignalingMessenger() = default; @@ -47,12 +47,13 @@ class WebRtcSignalingMessenger { virtual bool registerSignaling() = 0; virtual bool unregisterSignaling() = 0; - virtual bool sendMessage(const string& peer_id, + virtual bool sendMessage(const std::string& peer_id, ConstPtr message) = 0; virtual bool startReceivingMessages( Ptr listener) = 0; virtual void getIceServers(Ptr ice_servers_listener) = 0; }; +#endif } // namespace nearby } // namespace location diff --git a/cpp/platform/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h index 744813f8..bbea3748 100644 --- a/cpp/platform/api/wifi_lan.h +++ b/cpp/platform/api/wifi_lan.h @@ -21,6 +21,7 @@ #include "platform/exception.h" #include "platform/port/string.h" #include "platform/ptr.h" +#include "absl/strings/string_view.h" namespace location { namespace nearby { @@ -64,9 +65,10 @@ class WifiLanMedium { public: virtual ~WifiLanMedium() = default; - virtual bool StartAdvertising(const std::string& service_id, - const string& wifi_lan_service_info_name) = 0; - virtual void StopAdvertising(const std::string& service_id) = 0; + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; // Callback for WifiLan discover results. class DiscoveredServiceCallback { @@ -78,9 +80,9 @@ class WifiLanMedium { }; virtual bool StartDiscovery( - const std::string& service_id, + absl::string_view service_id, Ptr discovered_service_callback) = 0; - virtual void StopDiscovery(const std::string& service_id) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; class AcceptedConnectionCallback { public: @@ -90,16 +92,16 @@ class WifiLanMedium { // destroyed) by the recipient of the callback methods (i.e. the creator of // the concrete AcceptedConnectionCallback object). virtual void OnConnectionAccepted(Ptr socket, - const string& service_id) = 0; + absl::string_view service_id) = 0; }; virtual bool StartAcceptingConnections( - const std::string& service_id, + absl::string_view service_id, Ptr accepted_connection_callback) = 0; - virtual void StopAcceptingConnections(const std::string& service_id) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; virtual Ptr Connect(Ptr wifi_lan_service, - const std::string& service_id) = 0; + absl::string_view service_id) = 0; }; } // namespace nearby diff --git a/cpp/platform/api2/submittable_executor.h b/cpp/platform/api2/submittable_executor.h deleted file mode 100644 index c55f7a5a..00000000 --- a/cpp/platform/api2/submittable_executor.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ -#define PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ - -#include - -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/callable.h" - -namespace location { -namespace nearby { - -// Each per-platform concrete implementation is expected to extend from -// SubmittableExecutor and provide an override of its submit() method. -// -// e.g. -// class XyzSubmittableExecutor -// : public SubmittableExecutor { -// public: -// template -// std::unique_ptr> submit(std::unique_ptr> callable) { -// ... -// } -// } -template -class SubmittableExecutor : public Executor { - public: - ~SubmittableExecutor() override {} - - template - std::unique_ptr> Submit(std::unique_ptr> callable) { - static_assert( - std::is_base_of_v, - "Class template type is not derived from SubmittableExecutor"); - return static_cast(this)->submit(callable); - } -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_API2_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/atomic_reference_test.cc b/cpp/platform/atomic_reference_test.cc new file mode 100644 index 00000000..25d10ab2 --- /dev/null +++ b/cpp/platform/atomic_reference_test.cc @@ -0,0 +1,94 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform/api/atomic_reference.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(5); + ASSERT_EQ(p->get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createAtomicReference(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createAtomicReference(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createAtomicReference< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = p->get(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + auto ref = + platform::ImplementationPlatform::createAtomicReference(s); + ASSERT_EQ(s, ref->get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/byte_array.h b/cpp/platform/byte_array.h index 79bbd52b..a65767f1 100644 --- a/cpp/platform/byte_array.h +++ b/cpp/platform/byte_array.h @@ -51,7 +51,7 @@ class ByteArray { data_.assign(size, value); } - char* getData() { return data_.data(); } + char* getData() { return &data_[0]; } const char* getData() const { return data_.data(); } size_t size() const { return data_.size(); } diff --git a/cpp/platform/cancelable_alarm.cc b/cpp/platform/cancelable_alarm.cc index faa729fa..85306a11 100644 --- a/cpp/platform/cancelable_alarm.cc +++ b/cpp/platform/cancelable_alarm.cc @@ -14,26 +14,28 @@ #include "platform/cancelable_alarm.h" +#include "platform/api/platform.h" +#include "platform/api/scheduled_executor.h" #include "platform/synchronized.h" namespace location { namespace nearby { -template -CancelableAlarm::CancelableAlarm( - const string &name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor) +namespace { +using Platform = platform::ImplementationPlatform; +} + +CancelableAlarm::CancelableAlarm(const std::string &name, + Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor) : name_(name), lock_(Platform::createLock()), cancelable_(scheduled_executor->schedule(runnable, delay_millis)) {} -template -CancelableAlarm::~CancelableAlarm() { - cancelable_.destroy(); -} +CancelableAlarm::~CancelableAlarm() { cancelable_.destroy(); } -template -bool CancelableAlarm::cancel() { +bool CancelableAlarm::cancel() { Synchronized s(lock_.get()); if (cancelable_.isNull()) { diff --git a/cpp/platform/cancelable_alarm.h b/cpp/platform/cancelable_alarm.h index 3551ecca..87d77654 100644 --- a/cpp/platform/cancelable_alarm.h +++ b/cpp/platform/cancelable_alarm.h @@ -18,6 +18,7 @@ #include #include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" #include "platform/cancelable.h" #include "platform/port/string.h" #include "platform/ptr.h" @@ -31,18 +32,17 @@ namespace nearby { * for posting a Runnable on a ScheduledExecutor and (possibly) later * canceling it. */ -template class CancelableAlarm { public: - CancelableAlarm( - const string& name, Ptr runnable, std::int64_t delay_millis, - Ptr scheduled_executor); + CancelableAlarm(const std::string& name, Ptr runnable, + std::int64_t delay_millis, + Ptr scheduled_executor); ~CancelableAlarm(); bool cancel(); private: - string name_; + std::string name_; ScopedPtr > lock_; Ptr cancelable_; }; @@ -50,6 +50,4 @@ class CancelableAlarm { } // namespace nearby } // namespace location -#include "platform/cancelable_alarm.cc" - #endif // PLATFORM_CANCELABLE_ALARM_H_ diff --git a/cpp/platform/exception.h b/cpp/platform/exception.h index 9976e838..2a97b205 100644 --- a/cpp/platform/exception.h +++ b/cpp/platform/exception.h @@ -29,13 +29,13 @@ struct Exception { EXECUTION, // New code should use the kConstants. // Old CONSTANTS are deprecated, and should not be used. - kFailed = -1, // Initial value of Exception; any unknown error. + kFailed = -1, // Initial value of Exception; any unknown error. kSuccess = NONE, // No exception. - kIo = IO, // IO Error happened. + kIo = IO, // IO Error happened. kInterrupted = INTERRUPTED, // Operation was interrupted. kInvalidProtocolBuffer = INVALID_PROTOCOL_BUFFER, // Couldn't parse. - kExecution = EXECUTION, // Couldn't execute. - kTimeout, // Operarion did not finish within specified time. + kExecution = EXECUTION, // Couldn't execute. + kTimeout, // Operation did not finish within specified time. }; Value value {kFailed}; }; diff --git a/cpp/platform/file_impl.h b/cpp/platform/file_impl.h index 9945afe7..61c80479 100644 --- a/cpp/platform/file_impl.h +++ b/cpp/platform/file_impl.h @@ -28,7 +28,7 @@ namespace nearby { class InputFileImpl final : public InputFile { public: - explicit InputFileImpl(const std::string& path, std::int64_t size); + InputFileImpl(const std::string& path, std::int64_t size); ~InputFileImpl() override {} ExceptionOr> read(std::int64_t size) override; diff --git a/cpp/platform/file_impl_test.cc b/cpp/platform/file_impl_test.cc index b6b03784..b84c6337 100644 --- a/cpp/platform/file_impl_test.cc +++ b/cpp/platform/file_impl_test.cc @@ -56,7 +56,7 @@ class FileImplTest : public ::testing::Test { ASSERT_TRUE(bytes.result().isNull()); } - static const int64_t kMaxSize = 3; + static constexpr int64_t kMaxSize = 3; std::string path_; std::fstream file_; diff --git a/cpp/platform/impl/default/CMakeLists.txt b/cpp/platform/impl/default/CMakeLists.txt deleted file mode 100644 index a542481c..00000000 --- a/cpp/platform/impl/default/CMakeLists.txt +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -add_library(platform_impl_default STATIC) - -target_sources(platform_impl_default - PRIVATE - default_platform.cc - PUBLIC - default_platform.h -) - -target_include_directories(platform_impl_default - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} -) - -target_link_libraries(platform_impl_default - PUBLIC - platform_api - platform_impl_default_cond_var - platform_impl_default_lock - platform_types -) - -add_library(platform_impl_default_lock STATIC) - -target_sources(platform_impl_default_lock - PRIVATE - default_lock.cc - PUBLIC - default_lock.h -) - -target_link_libraries(platform_impl_default_lock - PUBLIC - platform_api -) - -add_library(platform_impl_default_cond_var STATIC) - -target_sources(platform_impl_default_cond_var - PRIVATE - default_condition_variable.cc - PUBLIC - default_condition_variable.h -) - -target_link_libraries(platform_impl_default_cond_var - PUBLIC - platform_api - platform_impl_default_lock - platform_types -) diff --git a/cpp/platform/impl/default/default_platform.h b/cpp/platform/impl/default/default_platform.h deleted file mode 100644 index 54132b19..00000000 --- a/cpp/platform/impl/default/default_platform.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ - -#include "platform/api/condition_variable.h" -#include "platform/api/lock.h" -#include "platform/ptr.h" - -namespace location { -namespace nearby { - -// Provides obvious portable implementations of a subset of the hooks specified -// within //platform/api/. -// -// It's highly recommended that custom Platform implementations delegate to -// these methods unless there's a very good reason not to. -class DefaultPlatform { - public: - static Ptr createLock(); - - static Ptr createConditionVariable(Ptr lock); -}; - -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_PLATFORM_H_ diff --git a/cpp/platform/impl/g3/BUILD b/cpp/platform/impl/g3/BUILD index e69de29b..c0a8c619 100644 --- a/cpp/platform/impl/g3/BUILD +++ b/cpp/platform/impl/g3/BUILD @@ -0,0 +1,40 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "g3", + srcs = [ + "atomic_reference_impl.h", + "platform.cc", + "settable_future_impl.h", + "system_clock_impl.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform/api", + "//platform/impl/shared:atomic_boolean", + "//platform/impl/shared:posix_condition_variable", + "//platform/impl/shared:posix_lock", + "//platform/port:string", + "//absl/base:core_headers", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + ], +) diff --git a/cpp/platform/impl/g3/CMakeLists.txt b/cpp/platform/impl/g3/CMakeLists.txt new file mode 100644 index 00000000..b08f27eb --- /dev/null +++ b/cpp/platform/impl/g3/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(platform_impl_g3 STATIC) + +target_sources(platform_impl_g3 + PRIVATE + "atomic_reference_impl.h" + "platform.cc" + "settable_future_impl.h" + "system_clock_impl.h" +) + +target_include_directories(platform_impl_g3 + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(platform_impl_g3 + PUBLIC + "platform_types" + "platform_api" + "platform_impl_shared_atomic_boolean" + "platform_impl_shared_posix_condition_variable" + "platform_impl_shared_posix_lock" + "platform_port_string" + "absl::base" + "absl::synchronization" + "absl::time" +) diff --git a/cpp/platform/impl/g3/atomic_reference_impl.h b/cpp/platform/impl/g3/atomic_reference_impl.h new file mode 100644 index 00000000..a2afdc0e --- /dev/null +++ b/cpp/platform/impl/g3/atomic_reference_impl.h @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference.h" +#include "platform/ptr.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { + absl::MutexLock lock(&mutex_); + return value_; + } + void set(absl::any value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/g3/platform.cc b/cpp/platform/impl/g3/platform.cc new file mode 100644 index 00000000..98f2b2d4 --- /dev/null +++ b/cpp/platform/impl/g3/platform.cc @@ -0,0 +1,154 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform/api/platform.h" + +#include +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/scheduled_executor.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future.h" +#include "platform/api/submittable_executor.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/webrtc.h" +#include "platform/api/wifi.h" +#include "platform/impl/g3/atomic_reference_impl.h" +#include "platform/impl/g3/settable_future_impl.h" +#include "platform/impl/g3/system_clock_impl.h" +#include "platform/impl/shared/atomic_boolean_impl.h" +#include "platform/impl/shared/posix_condition_variable.h" +#include "platform/impl/shared/posix_lock.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr(/*new SingleThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr(/*new MultiThreadExecutorImpl()*/); +} + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr(/*new ScheduledExecutorImpl()*/); +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr(/*new CountDownLatchImpl(count)*/); +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr(/*new ThreadUtilsImpl()*/); +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr(new SystemClockImpl()); +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr(new AtomicBooleanImpl(initial_value)); +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr(/*new ServerSyncMediumImpl()*/); +} + +Ptr ImplementationPlatform::createWifiLanMedium() { + return Ptr(); +} + +//Ptr +//ImplementationPlatform::createWebRtcSignalingMessenger( +// const std::string& self_id) { +// return Ptr(/*new FCMSignalingMessenger()*/); +//} + +Ptr ImplementationPlatform::createLock() { + return Ptr(new PosixLock()); +} + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr(new PosixConditionVariable(lock)); +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr(/*new HashUtilsImpl()*/); +} + +std::string ImplementationPlatform::getDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/g3/settable_future_impl.h b/cpp/platform/impl/g3/settable_future_impl.h new file mode 100644 index 00000000..3fd5bfa6 --- /dev/null +++ b/cpp/platform/impl/g3/settable_future_impl.h @@ -0,0 +1,108 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ + +#include + +#include "platform/api/platform.h" +#include "platform/api/settable_future.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { + absl::MutexLock lock(&mutex_); + if (!done_) { + value_ = std::move(value); + done_ = true; + exception_ = {Exception::kSuccess}; + completed_.SignalAll(); + } + return true; + } + + bool setException(Exception exception) override { + absl::MutexLock lock(&mutex_); + return SetExceptionLocked(exception); + } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + absl::MutexLock lock(&mutex_); + while (!done_) { + completed_.Wait(&mutex_); + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + absl::MutexLock lock(&mutex_); + absl::Duration timeout = absl::Milliseconds(timeout_ms); + while (!done_) { + absl::Time start_time = absl::Now(); + if (completed_.WaitWithTimeout(&mutex_, timeout)) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + absl::Duration spent = absl::Now() - start_time; + if (spent < timeout) { + timeout -= spent; + } else if (!done_) { + SetExceptionLocked({Exception::kTimeout}); + break; + } + } + return exception_.value != Exception::kSuccess + ? ExceptionOr{exception_.value} + : ExceptionOr{value_}; + } + + private: + bool SetExceptionLocked(Exception exception) { + if (!done_) { + exception_ = exception.value != Exception::kSuccess + ? exception + : Exception{Exception::kFailed}; + done_ = true; + completed_.SignalAll(); + } + return true; + } + + absl::Mutex mutex_; + absl::CondVar completed_; + bool done_{false}; + absl::any value_; + Exception exception_{Exception::kFailed}; +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_G3_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/api2/thread_utils.h b/cpp/platform/impl/g3/system_clock_impl.h similarity index 68% rename from cpp/platform/api2/thread_utils.h rename to cpp/platform/impl/g3/system_clock_impl.h index 013f3a3d..3857ec9a 100644 --- a/cpp/platform/api2/thread_utils.h +++ b/cpp/platform/impl/g3/system_clock_impl.h @@ -12,25 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_THREAD_UTILS_H_ -#define PLATFORM_API2_THREAD_UTILS_H_ +#ifndef PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ +#define PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ #include -#include "platform/exception.h" +#include "platform/api/system_clock.h" +#include "absl/time/clock.h" #include "absl/time/time.h" namespace location { namespace nearby { -class ThreadUtils final { +class SystemClockImpl : public SystemClock { public: - // https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep(long) - // throws Exception::kInterrupted - static Exception Sleep(absl::Duration timeout); + std::int64_t elapsedRealtime() override { + return absl::ToUnixMillis(absl::Now()); + } }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_THREAD_UTILS_H_ +#endif // PLATFORM_IMPL_G3_SYSTEM_CLOCK_IMPL_H_ diff --git a/cpp/platform/impl/sample/BUILD b/cpp/platform/impl/sample/BUILD index e75257c7..3f0f8775 100644 --- a/cpp/platform/impl/sample/BUILD +++ b/cpp/platform/impl/sample/BUILD @@ -13,12 +13,12 @@ # limitations under the License. cc_library( - name = "sample", + name = "sample_platform", srcs = [ - "sample_wifi_medium.cc", - "sample_wifi_medium.h", + "atomic_reference_impl.h", + "sample_platform.cc", + "settable_future_impl.h", ], - hdrs = ["sample_platform.h"], visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//core:__subpackages__", @@ -28,7 +28,9 @@ cc_library( "//platform:types", "//platform:utils", "//platform/api", + "//platform/impl/shared/sample:sample_wifi_medium", "//platform/port:string", "//absl/time", + "//absl/types:any", ], ) diff --git a/cpp/platform/impl/sample/atomic_reference_impl.h b/cpp/platform/impl/sample/atomic_reference_impl.h new file mode 100644 index 00000000..40ea69ec --- /dev/null +++ b/cpp/platform/impl/sample/atomic_reference_impl.h @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ + +#include "platform/api/atomic_reference_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +// Provide implementation for absl::any. +class AtomicReferenceImpl : public AtomicReference { + public: + explicit AtomicReferenceImpl(absl::any initial_value) {} + ~AtomicReferenceImpl() override = default; + + absl::any get() override { return {}; } + void set(absl::any value) override {} +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_ATOMIC_REFERENCE_IMPL_H_ diff --git a/cpp/platform/impl/sample/sample_platform.cc b/cpp/platform/impl/sample/sample_platform.cc new file mode 100644 index 00000000..3e716465 --- /dev/null +++ b/cpp/platform/impl/sample/sample_platform.cc @@ -0,0 +1,139 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "platform/api/atomic_boolean.h" +#include "platform/api/atomic_reference_def.h" +#include "platform/api/ble.h" +#include "platform/api/ble_v2.h" +#include "platform/api/bluetooth_adapter.h" +#include "platform/api/bluetooth_classic.h" +#include "platform/api/condition_variable.h" +#include "platform/api/count_down_latch.h" +#include "platform/api/hash_utils.h" +#include "platform/api/lock.h" +#include "platform/api/platform.h" +#include "platform/api/server_sync.h" +#include "platform/api/settable_future_def.h" +#include "platform/api/submittable_executor_def.h" +#include "platform/api/system_clock.h" +#include "platform/api/thread_utils.h" +#include "platform/api/wifi.h" +#include "platform/cancelable.h" +#include "platform/impl/sample/atomic_reference_impl.h" +#include "platform/impl/sample/settable_future_impl.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" +#include "platform/port/string.h" +#include "platform/ptr.h" +#include "platform/runnable.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +Ptr ImplementationPlatform::createScheduledExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSingleThreadExecutor() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createMultiThreadExecutor( + int max_concurrency) { + return Ptr{}; +} + +Ptr> +ImplementationPlatform::createAtomicReferenceAny(absl::any initial_value) { + return Ptr>( + new AtomicReferenceImpl(initial_value)); +} + +Ptr> +ImplementationPlatform::createSettableFutureAny() { + return Ptr>(new SettableFutureImpl{}); +} + +Ptr ImplementationPlatform::createBluetoothAdapter() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createWifiMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createCountDownLatch( + std::int32_t count) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createThreadUtils() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createSystemClock() { + return Ptr{}; +} + +Ptr ImplementationPlatform::createAtomicBoolean( + bool initial_value) { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createBluetoothClassicMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMedium() { + return Ptr(); +} + +Ptr ImplementationPlatform::createBLEMediumV2() { + return Ptr(); +} + +Ptr ImplementationPlatform::createServerSyncMedium() { + return Ptr{}; +} + +Ptr +ImplementationPlatform::createWebRtcSignalingMessenger( + const std::string& self_id) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createLock() { return Ptr{}; } + +Ptr ImplementationPlatform::createConditionVariable( + Ptr lock) { + return Ptr{}; +} + +Ptr ImplementationPlatform::createHashUtils() { + return Ptr{}; +} + +std::string ImplementationPlatform::getDeviceId() { return "sample"; } + +std::string ImplementationPlatform::getPayloadPath(int64_t payload_id) { + return "/tmp/sample-" + std::to_string(payload_id); +} + +} // namespace platform +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h deleted file mode 100644 index 78b154c5..00000000 --- a/cpp/platform/impl/sample/sample_platform.h +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ - -#include - -#include "platform/api/atomic_boolean.h" -#include "platform/api/atomic_reference.h" -#include "platform/api/ble.h" -#include "platform/api/ble_v2.h" -#include "platform/api/bluetooth_adapter.h" -#include "platform/api/bluetooth_classic.h" -#include "platform/api/condition_variable.h" -#include "platform/api/count_down_latch.h" -#include "platform/api/hash_utils.h" -#include "platform/api/lock.h" -#include "platform/api/multi_thread_executor.h" -#include "platform/api/settable_future.h" -#include "platform/api/single_thread_executor.h" -#include "platform/api/system_clock.h" -#include "platform/api/thread_utils.h" -#include "platform/api/wifi.h" -#include "platform/cancelable.h" -#include "platform/impl/sample/sample_wifi_medium.h" -#include "platform/port/string.h" -#include "platform/ptr.h" -#include "platform/runnable.h" - -namespace location { -namespace nearby { -namespace sample { - -// The SamplePlatform class below shows an example of the factory functions -// and typedefs. -class SamplePlatform { - public: - class SampleSubmittableExecutor - : public SubmittableExecutor { - public: - template - Ptr > submit(Ptr > callable) { - return Ptr >(); - } - }; - - class SampleSingleThreadExecutor - : public SingleThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleMultiThreadExecutor - : public MultiThreadExecutor { - public: - void execute(Ptr runnable) override {} - void shutdown() override {} - }; - - class SampleScheduledExecutor { - public: - Ptr schedule(Ptr runnable, - std::int64_t delay_millis) { - return Ptr(); - } - void shutdown() {} - }; - - typedef SampleSingleThreadExecutor SingleThreadExecutorType; - static Ptr createSingleThreadExecutor() { - return MakePtr(new SingleThreadExecutorType()); - } - - typedef SampleMultiThreadExecutor MultiThreadExecutorType; - static Ptr createMultiThreadExecutor( - std::int32_t max_concurrency) { - return MakePtr(new MultiThreadExecutorType()); - } - - typedef SampleScheduledExecutor ScheduledExecutorType; - static Ptr createScheduledExecutor() { - return MakePtr(new ScheduledExecutorType()); - } - - static Ptr createBluetoothAdapter() { - return Ptr(); - } - - static Ptr createWifiMedium() { - return MakePtr(new SampleWifiMedium()); - } - - static Ptr createCountDownLatch(std::int32_t count) { - return Ptr(); - } - - template - static Ptr > createSettableFuture() { - return Ptr >(); - } - - static Ptr createThreadUtils() { return Ptr(); } - - static Ptr createSystemClock() { return Ptr(); } - - static Ptr createAtomicBoolean(bool initial_value) { - return Ptr(); - } - - template - static Ptr > createAtomicReference(T initial_value) { - return Ptr >(); - } - - static Ptr createBluetoothClassicMedium() { - return Ptr(); - } - - static Ptr createBLEMedium() { return Ptr(); } - - static Ptr createBLEMediumV2() { return Ptr(); } - - static Ptr createLock() { return Ptr(); } - - static Ptr createConditionVariable(Ptr lock) { - return Ptr(); - } - - static Ptr createHashUtils() { return Ptr(); } - - static std::string getDeviceId() { return ""; } - - static std::string getPayloadPath(int64_t payload_id) { - return "/tmp/" + std::to_string(payload_id); - } -}; - -} // namespace sample -} // namespace nearby -} // namespace location - -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_PLATFORM_H_ diff --git a/cpp/platform/impl/sample/settable_future_impl.h b/cpp/platform/impl/sample/settable_future_impl.h new file mode 100644 index 00000000..21cc04a2 --- /dev/null +++ b/cpp/platform/impl/sample/settable_future_impl.h @@ -0,0 +1,49 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ +#define PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ + +#include "platform/api/settable_future_def.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace platform { + +class SettableFutureImpl : public SettableFuture { + public: + explicit SettableFutureImpl() = default; + ~SettableFutureImpl() override = default; + + bool set(absl::any value) override { return true; } + + bool setException(Exception exception) override { return true; } + + void addListener(Ptr runnable, Executor* executor) override {} + + ExceptionOr get() override { + return ExceptionOr{Exception{Exception::kFailed}}; + } + + ExceptionOr get(std::int64_t timeout_ms) override { + return ExceptionOr{Exception{Exception::kFailed}}; + } +}; + +} // namespace platform +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SAMPLE_SETTABLE_FUTURE_IMPL_H_ diff --git a/cpp/platform/impl/default/BUILD b/cpp/platform/impl/shared/BUILD similarity index 60% rename from cpp/platform/impl/default/BUILD rename to cpp/platform/impl/shared/BUILD index 3e5dbe6d..a3b66cca 100644 --- a/cpp/platform/impl/default/BUILD +++ b/cpp/platform/impl/shared/BUILD @@ -13,47 +13,47 @@ # limitations under the License. cc_library( - name = "default", + name = "posix_lock", srcs = [ - "default_platform.cc", + "posix_lock.cc", ], hdrs = [ - "default_condition_variable.h", - "default_lock.h", - "default_platform.h", + "posix_lock.h", ], visibility = [ "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", - "//core:__subpackages__", + "//platform/impl:__subpackages__", ], deps = [ - ":condition_variable", - ":lock", - "//platform:types", "//platform/api", ], ) cc_library( - name = "lock", - srcs = ["default_lock.cc"], - hdrs = ["default_lock.h"], - visibility = [ - "//platform:__subpackages__", + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", ], - deps = ["//platform/api:lock"], -) - -cc_library( - name = "condition_variable", - srcs = ["default_condition_variable.cc"], - hdrs = ["default_condition_variable.h"], visibility = [ - "//platform:__subpackages__", + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", ], deps = [ - ":lock", + ":posix_lock", "//platform:types", "//platform/api:condition_variable", ], ) + +cc_library( + name = "atomic_boolean", + hdrs = ["atomic_boolean_impl.h"], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = ["//platform/api"], +) diff --git a/cpp/platform/impl/shared/CMakeLists.txt b/cpp/platform/impl/shared/CMakeLists.txt new file mode 100644 index 00000000..feade993 --- /dev/null +++ b/cpp/platform/impl/shared/CMakeLists.txt @@ -0,0 +1,81 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(platform_impl_shared_posix_lock STATIC) + +target_sources(platform_impl_shared_posix_lock + PRIVATE + "posix_lock.cc" + PUBLIC + "posix_lock.h" +) + +target_include_directories(platform_impl_shared_posix_lock + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + + +target_link_libraries(platform_impl_shared_posix_lock + PUBLIC + platform_api + platform_types +) + +add_library(platform_impl_shared_posix_condition_variable STATIC) + +target_sources(platform_impl_shared_posix_condition_variable + PRIVATE + "posix_condition_variable.cc" + PUBLIC + "posix_condition_variable.h" +) + +target_include_directories(platform_impl_shared_posix_condition_variable + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(platform_impl_shared_posix_condition_variable + PUBLIC + platform_api + platform_impl_shared_posix_lock + platform_types + absl::raw_logging_internal +) + +add_library(platform_impl_shared_atomic_boolean STATIC) + +target_sources(platform_impl_shared_atomic_boolean + PUBLIC + "atomic_boolean_impl.h" +) + +target_include_directories(platform_impl_shared_atomic_boolean + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(platform_impl_shared_atomic_boolean + PUBLIC + platform_api + platform_types +) + +set_target_properties(platform_impl_shared_atomic_boolean + PROPERTIES + LINKER_LANGUAGE CXX +) + +add_subdirectory(sample) diff --git a/cpp/platform/impl/shared/atomic_boolean_impl.h b/cpp/platform/impl/shared/atomic_boolean_impl.h new file mode 100644 index 00000000..5a784675 --- /dev/null +++ b/cpp/platform/impl/shared/atomic_boolean_impl.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ +#define PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ + +#include + +#include "platform/api/atomic_boolean.h" + +namespace location { +namespace nearby { + +class AtomicBooleanImpl : public AtomicBoolean { + public: + explicit AtomicBooleanImpl(bool initial_value) : value_(initial_value) {} + ~AtomicBooleanImpl() override = default; + + // AtomicBoolean: + bool get() override { + return value_.load(); + } + + // AtomicBoolean: + void set(bool value) override { + value_.store(value); + } + + private: + std::atomic_bool value_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_SHARED_ATOMIC_BOOLEAN_IMPL_H_ diff --git a/cpp/platform/impl/default/default_condition_variable.cc b/cpp/platform/impl/shared/posix_condition_variable.cc similarity index 72% rename from cpp/platform/impl/default/default_condition_variable.cc rename to cpp/platform/impl/shared/posix_condition_variable.cc index a48d77a3..c57ec65a 100644 --- a/cpp/platform/impl/default/default_condition_variable.cc +++ b/cpp/platform/impl/shared/posix_condition_variable.cc @@ -12,30 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "platform/impl/default/default_condition_variable.h" +#include "platform/impl/shared/posix_condition_variable.h" namespace location { namespace nearby { -DefaultConditionVariable::DefaultConditionVariable(Ptr lock) +PosixConditionVariable::PosixConditionVariable(Ptr lock) : lock_(lock), attr_(), cond_() { pthread_condattr_init(&attr_); pthread_cond_init(&cond_, &attr_); } -DefaultConditionVariable::~DefaultConditionVariable() { +PosixConditionVariable::~PosixConditionVariable() { pthread_cond_destroy(&cond_); pthread_condattr_destroy(&attr_); } -void DefaultConditionVariable::notify() { pthread_cond_broadcast(&cond_); } +void PosixConditionVariable::notify() { pthread_cond_broadcast(&cond_); } -Exception::Value DefaultConditionVariable::wait() { +Exception::Value PosixConditionVariable::wait() { pthread_cond_wait(&cond_, &(lock_->mutex_)); - return Exception::NONE; + return Exception::kSuccess; } } // namespace nearby diff --git a/cpp/platform/impl/default/default_condition_variable.h b/cpp/platform/impl/shared/posix_condition_variable.h similarity index 68% rename from cpp/platform/impl/default/default_condition_variable.h rename to cpp/platform/impl/shared/posix_condition_variable.h index 76d7cf8d..31daed57 100644 --- a/cpp/platform/impl/default/default_condition_variable.h +++ b/cpp/platform/impl/shared/posix_condition_variable.h @@ -12,28 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ +#ifndef PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ #include #include "platform/api/condition_variable.h" -#include "platform/impl/default/default_lock.h" +#include "platform/impl/shared/posix_lock.h" #include "platform/ptr.h" namespace location { namespace nearby { -class DefaultConditionVariable : public ConditionVariable { +class PosixConditionVariable : public ConditionVariable { public: - explicit DefaultConditionVariable(Ptr lock); - ~DefaultConditionVariable() override; + explicit PosixConditionVariable(Ptr lock); + ~PosixConditionVariable() override; void notify() override; Exception::Value wait() override; private: - Ptr lock_; + Ptr lock_; pthread_condattr_t attr_; pthread_cond_t cond_; }; @@ -41,4 +41,4 @@ class DefaultConditionVariable : public ConditionVariable { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_CONDITION_VARIABLE_H_ +#endif // PLATFORM_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/impl/default/default_lock.cc b/cpp/platform/impl/shared/posix_lock.cc similarity index 78% rename from cpp/platform/impl/default/default_lock.cc rename to cpp/platform/impl/shared/posix_lock.cc index df67c6c1..7f789bc7 100644 --- a/cpp/platform/impl/default/default_lock.cc +++ b/cpp/platform/impl/shared/posix_lock.cc @@ -12,27 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "platform/impl/default/default_lock.h" +#include "platform/impl/shared/posix_lock.h" namespace location { namespace nearby { -DefaultLock::DefaultLock() : attr_(), mutex_() { +PosixLock::PosixLock() : attr_(), mutex_() { pthread_mutexattr_init(&attr_); pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex_, &attr_); } -DefaultLock::~DefaultLock() { +PosixLock::~PosixLock() { pthread_mutex_destroy(&mutex_); pthread_mutexattr_destroy(&attr_); } -void DefaultLock::lock() { pthread_mutex_lock(&mutex_); } +void PosixLock::lock() { pthread_mutex_lock(&mutex_); } -void DefaultLock::unlock() { pthread_mutex_unlock(&mutex_); } +void PosixLock::unlock() { pthread_mutex_unlock(&mutex_); } } // namespace nearby } // namespace location diff --git a/cpp/platform/impl/default/default_lock.h b/cpp/platform/impl/shared/posix_lock.h similarity index 76% rename from cpp/platform/impl/default/default_lock.h rename to cpp/platform/impl/shared/posix_lock.h index 3e1b2e41..2369cc42 100644 --- a/cpp/platform/impl/default/default_lock.h +++ b/cpp/platform/impl/shared/posix_lock.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ -#define PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#ifndef PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ +#define PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ #include @@ -22,16 +22,16 @@ namespace location { namespace nearby { -class DefaultLock : public Lock { +class PosixLock : public Lock { public: - DefaultLock(); - ~DefaultLock() override; + PosixLock(); + ~PosixLock() override; void lock() override; void unlock() override; private: - friend class DefaultConditionVariable; + friend class PosixConditionVariable; pthread_mutexattr_t attr_; pthread_mutex_t mutex_; @@ -40,4 +40,4 @@ class DefaultLock : public Lock { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_DEFAULT_DEFAULT_LOCK_H_ +#endif // PLATFORM_IMPL_SHARED_POSIX_LOCK_H_ diff --git a/cpp/platform/impl/shared/sample/BUILD b/cpp/platform/impl/shared/sample/BUILD new file mode 100644 index 00000000..7a1c313e --- /dev/null +++ b/cpp/platform/impl/shared/sample/BUILD @@ -0,0 +1,36 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "sample_wifi_medium", + srcs = [ + "sample_wifi_medium.cc", + ], + hdrs = [ + "sample_wifi_medium.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core:__subpackages__", + "//platform/impl:__subpackages__", + "//location/nearby/setup/core:__subpackages__", + ], + deps = [ + "//platform:types", + "//platform:utils", + "//platform/api", + "//platform/port:string", + "//absl/time", + ], +) diff --git a/cpp/platform/impl/shared/sample/CMakeLists.txt b/cpp/platform/impl/shared/sample/CMakeLists.txt new file mode 100644 index 00000000..596487c4 --- /dev/null +++ b/cpp/platform/impl/shared/sample/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_library(platform_impl_shared_sample STATIC) + +target_sources(platform_impl_shared_sample + PRIVATE + sample_wifi_medium.cc + PUBLIC + sample_wifi_medium.h +) + +target_link_libraries(platform_impl_shared_sample + PUBLIC + absl::time + platform_api + platform_port_string + platform_types + platform_utils +) diff --git a/cpp/platform/impl/sample/sample_wifi_medium.cc b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc similarity index 98% rename from cpp/platform/impl/sample/sample_wifi_medium.cc rename to cpp/platform/impl/shared/sample/sample_wifi_medium.cc index 2031cc8e..a63e73b6 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.cc +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "platform/impl/sample/sample_wifi_medium.h" +#include "platform/impl/shared/sample/sample_wifi_medium.h" #include diff --git a/cpp/platform/impl/sample/sample_wifi_medium.h b/cpp/platform/impl/shared/sample/sample_wifi_medium.h similarity index 92% rename from cpp/platform/impl/sample/sample_wifi_medium.h rename to cpp/platform/impl/shared/sample/sample_wifi_medium.h index 46acfc2b..69ffb21d 100644 --- a/cpp/platform/impl/sample/sample_wifi_medium.h +++ b/cpp/platform/impl/shared/sample/sample_wifi_medium.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ -#define PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#ifndef PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#define PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ #include "platform/api/wifi.h" @@ -70,4 +70,4 @@ class SampleWifiMedium : public WifiMedium { } // namespace nearby } // namespace location -#endif // PLATFORM_IMPL_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ +#endif // PLATFORM_IMPL_SHARED_SAMPLE_SAMPLE_WIFI_MEDIUM_H_ diff --git a/cpp/platform/pipe.cc b/cpp/platform/pipe.cc index 52779b95..a82bc083 100644 --- a/cpp/platform/pipe.cc +++ b/cpp/platform/pipe.cc @@ -14,20 +14,22 @@ #include "platform/pipe.h" +#include "platform/api/platform.h" #include "platform/synchronized.h" namespace location { namespace nearby { +namespace { +using Platform = platform::ImplementationPlatform; +} + namespace pipe { -template class PipeInputStream : public InputStream { public: - explicit PipeInputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeInputStream() override { - close(); - } + explicit PipeInputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeInputStream() override { close(); } ExceptionOr> read() override { return read(kChunkSize); } ExceptionOr> read(std::int64_t size) override { @@ -41,18 +43,15 @@ class PipeInputStream : public InputStream { } private: - static const std::int64_t kChunkSize = 64 * 1024; + static constexpr std::int64_t kChunkSize = 64 * 1024; - Ptr> pipe_; + Ptr pipe_; }; -template class PipeOutputStream : public OutputStream { public: - explicit PipeOutputStream(Ptr> pipe) : pipe_(pipe) {} - ~PipeOutputStream() override { - close(); - } + explicit PipeOutputStream(Ptr pipe) : pipe_(pipe) {} + ~PipeOutputStream() override { close(); } Exception::Value write(ConstPtr data) override { // Avoid leaks. @@ -73,13 +72,12 @@ class PipeOutputStream : public OutputStream { } private: - Ptr> pipe_; + Ptr pipe_; }; } // namespace pipe -template -Pipe::Pipe() +Pipe::Pipe() : lock_(Platform::createLock()), cond_(Platform::createConditionVariable(lock_.get())), buffer_(), @@ -87,8 +85,7 @@ Pipe::Pipe() output_stream_closed_(false), read_all_chunks_(false) {} -template -Pipe::~Pipe() { +Pipe::~Pipe() { // Deallocate all the chunks still left in buffer_. for (BufferType::iterator chunk_iter = buffer_.begin(); chunk_iter != buffer_.end(); ++chunk_iter) { @@ -96,20 +93,17 @@ Pipe::~Pipe() { } } -template -Ptr Pipe::createInputStream(Ptr self) { +Ptr Pipe::createInputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeInputStream(self)); + return MakeRefCountedPtr(new pipe::PipeInputStream(self)); } -template -Ptr Pipe::createOutputStream(Ptr self) { +Ptr Pipe::createOutputStream(Ptr self) { assert(self.isRefCounted()); - return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); + return MakeRefCountedPtr(new pipe::PipeOutputStream(self)); } -template -ExceptionOr> Pipe::read(std::int64_t size) { +ExceptionOr> Pipe::read(std::int64_t size) { Synchronized s(lock_.get()); // We're done reading all the chunks that were written before the OutputStream @@ -162,15 +156,13 @@ ExceptionOr> Pipe::read(std::int64_t size) { } } -template -Exception::Value Pipe::write(ConstPtr data) { +Exception::Value Pipe::write(ConstPtr data) { Synchronized s(lock_.get()); return writeLocked(data); } -template -void Pipe::markInputStreamClosed() { +void Pipe::markInputStreamClosed() { Synchronized s(lock_.get()); input_stream_closed_ = true; @@ -179,8 +171,7 @@ void Pipe::markInputStreamClosed() { cond_->notify(); } -template -void Pipe::markOutputStreamClosed() { +void Pipe::markOutputStreamClosed() { Synchronized s(lock_.get()); // Write a sentinel null chunk before marking output_stream_closed as true. @@ -188,8 +179,7 @@ void Pipe::markOutputStreamClosed() { output_stream_closed_ = true; } -template -Exception::Value Pipe::writeLocked(ConstPtr data) { +Exception::Value Pipe::writeLocked(ConstPtr data) { // Avoid leaks. ScopedPtr> scoped_data(data); @@ -204,8 +194,7 @@ Exception::Value Pipe::writeLocked(ConstPtr data) { return Exception::NONE; } -template -bool Pipe::eitherStreamClosed() const { +bool Pipe::eitherStreamClosed() const { return input_stream_closed_ || output_stream_closed_; } diff --git a/cpp/platform/pipe.h b/cpp/platform/pipe.h index c4a8ca72..fe36dd5d 100644 --- a/cpp/platform/pipe.h +++ b/cpp/platform/pipe.h @@ -31,14 +31,11 @@ namespace nearby { namespace pipe { -template class PipeInputStream; -template class PipeOutputStream; } // namespace pipe -template class Pipe { public: Pipe(); @@ -56,9 +53,7 @@ class Pipe { // classes. ////////////////////////////////////////////////////////////////////////////// - template friend class pipe::PipeInputStream; - template friend class pipe::PipeOutputStream; ExceptionOr > read(std::int64_t size); @@ -84,6 +79,4 @@ class Pipe { } // namespace nearby } // namespace location -#include "platform/pipe.cc" - #endif // PLATFORM_PIPE_H_ diff --git a/cpp/platform/pipe_test.cc b/cpp/platform/pipe_test.cc index 7d511c8a..4051c11a 100644 --- a/cpp/platform/pipe_test.cc +++ b/cpp/platform/pipe_test.cc @@ -18,8 +18,7 @@ #include -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" +#include "platform/api/platform.h" #include "platform/port/string.h" #include "platform/prng.h" #include "platform/ptr.h" @@ -31,16 +30,7 @@ namespace location { namespace nearby { namespace { -class SamplePlatform { - public: - static Ptr createLock() { return MakePtr(new DefaultLock()); } - static Ptr createConditionVariable(Ptr lock) { - return MakePtr( - new DefaultConditionVariable(DowncastPtr(lock))); - } -}; - -using SamplePipe = Pipe; +using SamplePipe = Pipe; TEST(PipeTest, SimpleWriteRead) { auto pipe = MakeRefCountedPtr(new SamplePipe()); diff --git a/cpp/platform/ptr.h b/cpp/platform/ptr.h index 45c5e55d..c42e2efe 100644 --- a/cpp/platform/ptr.h +++ b/cpp/platform/ptr.h @@ -60,6 +60,7 @@ class Ptr { Ptr() = default; explicit Ptr(T* pointee) : ptr_(pointee) {} Ptr(const Ptr& that) = default; + Ptr(Ptr&& that) = default; Ptr(std::shared_ptr ptr) : ptr_(ptr) {} // NOLINT @@ -95,18 +96,23 @@ class Ptr { return *(this->ptr_) < *(other.ptr_); } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void destroy(bool = true) {} + void destroy(bool = true) { + // Legacy code expects isNull() to return true after destroy(). + ptr_.reset(); + } - // No-op: refcounted objects will be destroyed correctly ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") - void clear() {} + void clear() { + // Legacy code expects isNull() to return true after clear(). + ptr_.reset(); + } T& operator*() const { return *ptr_; } T* operator->() const { return ptr_.get(); } T* get() { return ptr_.get(); } + T* get() const { return ptr_.get(); } void reset() { return ptr_.reset(); } ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") @@ -194,11 +200,12 @@ class ScopedPtr { // Accessor for the underlying Ptr. PtrType get() const { return this->ptr_; } - // Does nothing; - // this is to avoid unintended destruction of a managed pointer. // TODO(b/149938110): remove this completely. PtrType release() { - return ptr_; + // Legacy code expects isNull() to return true after release(). + PtrType ptr = std::move(ptr_); + ptr_.clear(); + return ptr; } private: @@ -266,14 +273,16 @@ ConstPtr ConstifyPtr(Ptr ptr) { // Ptr my_child_ptr = DowncastPtr(my_base_ptr); template Ptr DowncastPtr(Ptr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return Ptr(std::static_pointer_cast(base_ptr.ptr_)); } // ConstPtr counterpart to DowncastPtr(). template ConstPtr DowncastConstPtr(ConstPtr base_ptr) { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of::value, + "Types do not share base class."); return ConstPtr( std::static_pointer_cast(base_ptr.ptr_)); } diff --git a/cpp/platform/ptr_test.cc b/cpp/platform/ptr_test.cc index 2ce2f9b0..2395fdd9 100644 --- a/cpp/platform/ptr_test.cc +++ b/cpp/platform/ptr_test.cc @@ -130,7 +130,8 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted) { Ptr ref_counted_2 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_2); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); + ASSERT_EQ(1234, *ref_counted_1); ASSERT_EQ(1234, *ref_counted_2); } @@ -141,7 +142,7 @@ TEST(PtrTest, ScopedPtr_Release_RefCounted_Stay_Valid) { Ptr ref_counted_3 = scoped_ref_counted_1.release(); - ASSERT_EQ(*scoped_ref_counted_1, *ref_counted_3); + ASSERT_TRUE(scoped_ref_counted_1.isNull()); ASSERT_EQ(1234, *ref_counted_2); ASSERT_EQ(1234, *ref_counted_3); } diff --git a/cpp/platform/settable_future_test.cc b/cpp/platform/settable_future_test.cc new file mode 100644 index 00000000..5539e5cc --- /dev/null +++ b/cpp/platform/settable_future_test.cc @@ -0,0 +1,98 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform/api/settable_future.h" + +#include "platform/api/platform.h" +#include "platform/ptr.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(SettableFutureTest, SupportIntegralTypes) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(5); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), 5); +} + +TEST(SettableFutureTest, SetExceptionIsPropagated) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->setException({Exception::kIo}); + ASSERT_EQ(p->get().exception(), Exception::kIo); +} + +TEST(SettableFutureTest, SupportEnum) { + auto p = platform::ImplementationPlatform::createSettableFuture(); + p->set(TestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), TestEnum::kValue1); +} + +TEST(SettableFutureTest, SupportScopedEnum) { + auto p = + platform::ImplementationPlatform::createSettableFuture(); + p->set(ScopedTestEnum::kValue1); + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + ASSERT_EQ(p->get().result(), ScopedTestEnum::kValue1); +} + +TEST(SettableFutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + auto p = platform::ImplementationPlatform::createSettableFuture< + BigSizedStruct>(); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + p->set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + ASSERT_EQ(p->get().exception(), Exception::kSuccess); + BigSizedStruct v2 = p->get().result(); + ASSERT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + ASSERT_EQ(v2, v1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/api2/BUILD b/cpp/platform_v2/api/BUILD similarity index 61% rename from cpp/platform/api2/BUILD rename to cpp/platform_v2/api/BUILD index 6051f5c4..30602d57 100644 --- a/cpp/platform/api2/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -12,14 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -package(default_visibility = [ - "//core:__subpackages__", - "//platform:__subpackages__", - "//location/nearby/setup/core:__subpackages__", -]) - cc_library( - name = "api2", + name = "api", hdrs = [ "atomic_boolean.h", "atomic_reference.h", @@ -27,53 +21,37 @@ cc_library( "ble_v2.h", "bluetooth_adapter.h", "bluetooth_classic.h", + "cancelable.h", "condition_variable.h", "count_down_latch.h", + "crypto.h", "executor.h", "future.h", - "hash_utils.h", "input_file.h", - "input_stream.h", "listenable_future.h", - "multi_thread_executor.h", "mutex.h", "output_file.h", - "output_stream.h", + "platform.h", "scheduled_executor.h", "server_sync.h", "settable_future.h", - "single_thread_executor.h", - "socket.h", "submittable_executor.h", "system_clock.h", - "thread_utils.h", "webrtc.h", "wifi.h", + "wifi_lan.h", + ], + visibility = [ + "//platform_v2/base:__pkg__", + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__subpackages__", ], deps = [ - "//platform:types", + "//platform_v2/base", + "//absl/base:core_headers", "//absl/strings", "//absl/time", - "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", - ], -) - -cc_library( - name = "mutex", - hdrs = ["mutex.h"], - visibility = [ - "//platform:__subpackages__", - ], -) - -cc_library( - name = "condition_variable", - hdrs = ["condition_variable.h"], - visibility = [ - "//platform:__subpackages__", - ], - deps = [ - "//platform:types", - "//absl/time", + "//absl/types:any", + "//webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api2/CMakeLists.txt b/cpp/platform_v2/api/CMakeLists.txt similarity index 100% rename from cpp/platform/api2/CMakeLists.txt rename to cpp/platform_v2/api/CMakeLists.txt diff --git a/cpp/platform/api2/atomic_boolean.h b/cpp/platform_v2/api/atomic_boolean.h similarity index 65% rename from cpp/platform/api2/atomic_boolean.h rename to cpp/platform_v2/api/atomic_boolean.h index 52ac5831..21d3cbfc 100644 --- a/cpp/platform/api2/atomic_boolean.h +++ b/cpp/platform_v2/api/atomic_boolean.h @@ -12,24 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_ATOMIC_BOOLEAN_H_ -#define PLATFORM_API2_ATOMIC_BOOLEAN_H_ +#ifndef PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ namespace location { namespace nearby { +namespace api { // A boolean value that may be updated atomically. -// -// https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicBoolean.html class AtomicBoolean { public: - virtual ~AtomicBoolean() {} + virtual ~AtomicBoolean() = default; - virtual bool Get() = 0; - virtual void Set(bool value) = 0; + // Atomically read and return current value. + virtual bool Get() const = 0; + + // Atomically exchange original value with a new one. Return previous value. + virtual bool Set(bool value) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_ATOMIC_BOOLEAN_H_ +#endif // PLATFORM_V2_API_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform/api2/atomic_reference.h b/cpp/platform_v2/api/atomic_reference.h similarity index 75% rename from cpp/platform/api2/atomic_reference.h rename to cpp/platform_v2/api/atomic_reference.h index 7e6b6d96..f9b4f51f 100644 --- a/cpp/platform/api2/atomic_reference.h +++ b/cpp/platform_v2/api/atomic_reference.h @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_ATOMIC_REFERENCE_H_ -#define PLATFORM_API2_ATOMIC_REFERENCE_H_ +#ifndef PLATFORM_V2_API_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_API_ATOMIC_REFERENCE_H_ namespace location { namespace nearby { +namespace api { // An object reference that may be updated atomically. // @@ -24,13 +25,16 @@ namespace nearby { template class AtomicReference { public: - virtual ~AtomicReference() {} + virtual ~AtomicReference() = default; - virtual T Get() = 0; + virtual T Get() const & = 0; + virtual T Get() && = 0; virtual void Set(const T& value) = 0; + virtual void Set(T&& value) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_ATOMIC_REFERENCE_H_ +#endif // PLATFORM_V2_API_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform/api2/ble.h b/cpp/platform_v2/api/ble.h similarity index 91% rename from cpp/platform/api2/ble.h rename to cpp/platform_v2/api/ble.h index a9e79823..a5860c5d 100644 --- a/cpp/platform/api2/ble.h +++ b/cpp/platform_v2/api/ble.h @@ -12,17 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_BLE_H_ -#define PLATFORM_API2_BLE_H_ +#ifndef PLATFORM_V2_API_BLE_H_ +#define PLATFORM_V2_API_BLE_H_ -#include "platform/api2/bluetooth_classic.h" -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Opaque wrapper over a BLE peripheral. Must contain enough data about a // particular BLE device to connect to its GATT server. @@ -30,8 +31,7 @@ class BlePeripheral { public: virtual ~BlePeripheral() {} - // The returned Ptr is not owned by the caller, and can be invalidated once - // the corresponding BLEPeripheral object is destroyed. + // The returned reference lifetime matches BlePeripheral object. virtual BluetoothDevice& GetBluetoothDevice() = 0; }; @@ -119,7 +119,8 @@ class BleMedium { absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_H_ +#endif // PLATFORM_V2_API_BLE_H_ diff --git a/cpp/platform/api2/ble_v2.h b/cpp/platform_v2/api/ble_v2.h similarity index 98% rename from cpp/platform/api2/ble_v2.h rename to cpp/platform_v2/api/ble_v2.h index 58c433fb..a97da8ae 100644 --- a/cpp/platform/api2/ble_v2.h +++ b/cpp/platform_v2/api/ble_v2.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_BLE_V2_H_ -#define PLATFORM_API2_BLE_V2_H_ +#ifndef PLATFORM_V2_API_BLE_V2_H_ +#define PLATFORM_V2_API_BLE_V2_H_ #include #include @@ -23,13 +23,14 @@ #include #include -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { -namespace v2 { +namespace api { +namespace ble_v2 { // https://developer.android.com/reference/android/bluetooth/le/AdvertiseData // @@ -397,8 +398,9 @@ class BleMedium { const BleSocketLifeCycleCallback& callback) = 0; }; -} // namespace v2 +} // namespace ble_v2 +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLE_V2_H_ +#endif // PLATFORM_V2_API_BLE_V2_H_ diff --git a/cpp/platform/api2/bluetooth_adapter.h b/cpp/platform_v2/api/bluetooth_adapter.h similarity index 86% rename from cpp/platform/api2/bluetooth_adapter.h rename to cpp/platform_v2/api/bluetooth_adapter.h index 58bf9dad..ba4946d3 100644 --- a/cpp/platform/api2/bluetooth_adapter.h +++ b/cpp/platform_v2/api/bluetooth_adapter.h @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_BLUETOOTH_ADAPTER_H_ -#define PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ -#include #include #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html class BluetoothAdapter { public: - virtual ~BluetoothAdapter() {} + virtual ~BluetoothAdapter() = default; // Eligible statuses of the BluetoothAdapter. enum class Status { @@ -39,19 +39,21 @@ class BluetoothAdapter { virtual bool SetStatus(Status status) = 0; // Returns true if the BluetoothAdapter's current status is // Status::Value::kEnabled. - virtual bool IsEnabled() = 0; + virtual bool IsEnabled() const = 0; // Scan modes of a BluetoothAdapter, as described at // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode(). enum class ScanMode { kUnknown, + kNone, + kConnectable, kConnectableDiscoverable, }; // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() // // Returns ScanMode::kUnknown on error. - virtual ScanMode GetScanMode() = 0; + virtual ScanMode GetScanMode() const = 0; // Synchronously sets the scan mode of the adapter, and returns true if the // operation was a success. virtual bool SetScanMode(ScanMode scan_mode) = 0; @@ -63,7 +65,8 @@ class BluetoothAdapter { virtual bool SetName(absl::string_view name) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_ADAPTER_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform/api2/bluetooth_classic.h b/cpp/platform_v2/api/bluetooth_classic.h similarity index 92% rename from cpp/platform/api2/bluetooth_classic.h rename to cpp/platform_v2/api/bluetooth_classic.h index b693e671..a2ad0875 100644 --- a/cpp/platform/api2/bluetooth_classic.h +++ b/cpp/platform_v2/api/bluetooth_classic.h @@ -12,20 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_BLUETOOTH_CLASSIC_H_ -#define PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#ifndef PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ #include #include -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. class BluetoothDevice { @@ -33,7 +34,7 @@ class BluetoothDevice { virtual ~BluetoothDevice() {} // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() - virtual std::string GetName() = 0; + virtual std::string GetName() const = 0; }; // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. @@ -132,7 +133,8 @@ class BluetoothClassicMedium { absl::string_view service_name, absl::string_view service_uuid) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_BLUETOOTH_CLASSIC_H_ +#endif // PLATFORM_V2_API_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform/api2/mutex.h b/cpp/platform_v2/api/cancelable.h similarity index 65% rename from cpp/platform/api2/mutex.h rename to cpp/platform_v2/api/cancelable.h index a097da40..51c5161a 100644 --- a/cpp/platform/api2/mutex.h +++ b/cpp/platform_v2/api/cancelable.h @@ -12,25 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_MUTEX_H_ -#define PLATFORM_API2_MUTEX_H_ +#ifndef PLATFORM_V2_API_CANCELABLE_H_ +#define PLATFORM_V2_API_CANCELABLE_H_ namespace location { namespace nearby { +namespace api { -// A lock is a tool for controlling access to a shared resource by multiple -// threads. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html -class Mutex { +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable { public: - virtual ~Mutex() {} + virtual ~Cancelable() = default; - virtual void Lock() = 0; - virtual void Unlock() = 0; + virtual bool Cancel() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_MUTEX_H_ +#endif // PLATFORM_V2_API_CANCELABLE_H_ diff --git a/cpp/platform/api2/condition_variable.h b/cpp/platform_v2/api/condition_variable.h similarity index 85% rename from cpp/platform/api2/condition_variable.h rename to cpp/platform_v2/api/condition_variable.h index f0fd7573..53db711d 100644 --- a/cpp/platform/api2/condition_variable.h +++ b/cpp/platform_v2/api/condition_variable.h @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_CONDITION_VARIABLE_H_ -#define PLATFORM_API2_CONDITION_VARIABLE_H_ +#ifndef PLATFORM_V2_API_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_API_CONDITION_VARIABLE_H_ -#include "platform/exception.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // The ConditionVariable class is a synchronization primitive that can be used // to block a thread, or multiple threads at the same time, until another thread @@ -34,7 +35,8 @@ class ConditionVariable { virtual Exception Wait() = 0; // throws Exception::kInterrupted }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_CONDITION_VARIABLE_H_ +#endif // PLATFORM_V2_API_CONDITION_VARIABLE_H_ diff --git a/cpp/platform/api2/count_down_latch.h b/cpp/platform_v2/api/count_down_latch.h similarity index 82% rename from cpp/platform/api2/count_down_latch.h rename to cpp/platform_v2/api/count_down_latch.h index 8ba4a3c0..ed4eef5f 100644 --- a/cpp/platform/api2/count_down_latch.h +++ b/cpp/platform_v2/api/count_down_latch.h @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_COUNT_DOWN_LATCH_H_ -#define PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#ifndef PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ #include -#include "platform/exception.h" +#include "platform_v2/base/exception.h" #include "absl/time/time.h" namespace location { namespace nearby { +namespace api { // A synchronization aid that allows one or more threads to wait until a set of // operations being performed in other threads completes. @@ -29,7 +30,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html class CountDownLatch { public: - virtual ~CountDownLatch() {} + virtual ~CountDownLatch() = default; virtual Exception Await() = 0; // throws Exception::kInterrupted virtual ExceptionOr Await( @@ -37,7 +38,8 @@ class CountDownLatch { virtual void CountDown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_COUNT_DOWN_LATCH_H_ +#endif // PLATFORM_V2_API_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform/api2/hash_utils.h b/cpp/platform_v2/api/crypto.h similarity index 75% rename from cpp/platform/api2/hash_utils.h rename to cpp/platform_v2/api/crypto.h index fc692ad3..80f20117 100644 --- a/cpp/platform/api2/hash_utils.h +++ b/cpp/platform_v2/api/crypto.h @@ -12,23 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_HASH_UTILS_H_ -#define PLATFORM_API2_HASH_UTILS_H_ +#ifndef PLATFORM_V2_API_CRYPTO_H_ +#define PLATFORM_V2_API_CRYPTO_H_ -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { // A provider of standard hashing algorithms. -class HashUtils { +class Crypto { public: + // Initialize global crypto state. + static void Init(); + // Return MD5 hash of input. static ByteArray Md5(absl::string_view input); + // Return SHA256 hash of input. static ByteArray Sha256(absl::string_view input); }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_HASH_UTILS_H_ +#endif // PLATFORM_V2_API_CRYPTO_H_ diff --git a/cpp/platform/api2/executor.h b/cpp/platform_v2/api/executor.h similarity index 76% rename from cpp/platform/api2/executor.h rename to cpp/platform_v2/api/executor.h index 0ed336c3..64b4e016 100644 --- a/cpp/platform/api2/executor.h +++ b/cpp/platform_v2/api/executor.h @@ -12,29 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_EXECUTOR_H_ -#define PLATFORM_API2_EXECUTOR_H_ +#ifndef PLATFORM_V2_API_EXECUTOR_H_ +#define PLATFORM_V2_API_EXECUTOR_H_ -#include - -#include "platform/runnable.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // This abstract class is the superclass of all classes representing an // Executor. class Executor { public: + // Before returning from destructor, executor must wait for all pending + // jobs to finish. virtual ~Executor() = default; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- - virtual void Execute(std::unique_ptr runnable) = 0; + virtual void Execute(Runnable&& runnable) = 0; // https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown-- virtual void Shutdown() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_EXECUTOR_H_ +#endif // PLATFORM_V2_API_EXECUTOR_H_ diff --git a/cpp/platform/api2/future.h b/cpp/platform_v2/api/future.h similarity index 85% rename from cpp/platform/api2/future.h rename to cpp/platform_v2/api/future.h index 4d566d30..5eb63c66 100644 --- a/cpp/platform/api2/future.h +++ b/cpp/platform_v2/api/future.h @@ -12,14 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_FUTURE_H_ -#define PLATFORM_API2_FUTURE_H_ +#ifndef PLATFORM_V2_API_FUTURE_H_ +#define PLATFORM_V2_API_FUTURE_H_ -#include "platform/exception.h" -#include "absl/time/time.h" +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" namespace location { namespace nearby { +namespace api { // A Future represents the result of an asynchronous computation. // @@ -38,7 +39,8 @@ class Future { virtual ExceptionOr Get(absl::Duration timeout) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_FUTURE_H_ +#endif // PLATFORM_V2_API_FUTURE_H_ diff --git a/cpp/platform/api2/input_file.h b/cpp/platform_v2/api/input_file.h similarity index 73% rename from cpp/platform/api2/input_file.h rename to cpp/platform_v2/api/input_file.h index 29aafb72..bfb1beba 100644 --- a/cpp/platform/api2/input_file.h +++ b/cpp/platform_v2/api/input_file.h @@ -12,27 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_INPUT_FILE_H_ -#define PLATFORM_API2_INPUT_FILE_H_ +#ifndef PLATFORM_V2_API_INPUT_FILE_H_ +#define PLATFORM_V2_API_INPUT_FILE_H_ #include -#include "platform/api2/input_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" namespace location { namespace nearby { +namespace api { // An InputFile represents a readable file on the system. class InputFile : public InputStream { public: ~InputFile() override = default; virtual std::string GetFilePath() const = 0; - virtual size_t GetTotalSize() const = 0; + virtual std::int64_t GetTotalSize() const = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_INPUT_FILE_H_ +#endif // PLATFORM_V2_API_INPUT_FILE_H_ diff --git a/cpp/platform/api2/listenable_future.h b/cpp/platform_v2/api/listenable_future.h similarity index 72% rename from cpp/platform/api2/listenable_future.h rename to cpp/platform_v2/api/listenable_future.h index 0007e98c..a6179028 100644 --- a/cpp/platform/api2/listenable_future.h +++ b/cpp/platform_v2/api/listenable_future.h @@ -12,18 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_LISTENABLE_FUTURE_H_ -#define PLATFORM_API2_LISTENABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#define PLATFORM_V2_API_LISTENABLE_FUTURE_H_ +#include #include -#include "platform/api2/executor.h" -#include "platform/api2/future.h" -#include "platform/exception.h" -#include "platform/runnable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" namespace location { namespace nearby { +namespace api { // A Future that accepts completion listeners. // @@ -33,11 +35,12 @@ class ListenableFuture : public Future { public: ~ListenableFuture() override = default; - virtual void AddListener(std::unique_ptr runnable, + virtual void AddListener(Runnable runnable, Executor* executor) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_LISTENABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_LISTENABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/mutex.h b/cpp/platform_v2/api/mutex.h new file mode 100644 index 00000000..44bad517 --- /dev/null +++ b/cpp/platform_v2/api/mutex.h @@ -0,0 +1,55 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_API_MUTEX_H_ +#define PLATFORM_V2_API_MUTEX_H_ + +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { +namespace api { + +// A lock is a tool for controlling access to a shared resource by multiple +// threads. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +class ABSL_LOCKABLE Mutex { + public: + // Mode to pass to implementation constructor. + // kRegular - produces a regular mutex: disallows multiple locks from + // the same thread; optionally, detects double locks in + // debug mode. + // This is the default option. + // kRecursive - produces recursive mutex: allows multiple locks from the + // same thread. + // kRegularNoCheck - produces a regular mutex: disallows double locks, + // but does not check for deadlocks. + enum class Mode { + kRegular = 0, + kRecursive = 1, + kRegularNoCheck = 2, + }; + + virtual ~Mutex() {} + + virtual void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() = 0; + virtual void Unlock() ABSL_UNLOCK_FUNCTION() = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_MUTEX_H_ diff --git a/cpp/platform/api2/output_file.h b/cpp/platform_v2/api/output_file.h similarity index 74% rename from cpp/platform/api2/output_file.h rename to cpp/platform_v2/api/output_file.h index 1375c65c..366920aa 100644 --- a/cpp/platform/api2/output_file.h +++ b/cpp/platform_v2/api/output_file.h @@ -12,15 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_OUTPUT_FILE_H_ -#define PLATFORM_API2_OUTPUT_FILE_H_ +#ifndef PLATFORM_V2_API_OUTPUT_FILE_H_ +#define PLATFORM_V2_API_OUTPUT_FILE_H_ -#include "platform/api2/output_stream.h" -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/output_stream.h" namespace location { namespace nearby { +namespace api { // An OutputFile represents a writable file on the system. class OutputFile : public OutputStream { @@ -28,7 +29,8 @@ class OutputFile : public OutputStream { ~OutputFile() override = default; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_OUTPUT_FILE_H_ +#endif // PLATFORM_V2_API_OUTPUT_FILE_H_ diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h new file mode 100644 index 00000000..b3280621 --- /dev/null +++ b/cpp/platform_v2/api/platform.h @@ -0,0 +1,92 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_API_PLATFORM_H_ +#define PLATFORM_V2_API_PLATFORM_H_ + +#include +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/crypto.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/system_clock.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/api/wifi_lan.h" +#include "absl/strings/string_view.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace api { + +// API rework notes: +// https://docs.google.com/spreadsheets/d/1erZNkX7pX8s5jWTHdxgjntxTMor3BGiY2H_fC_ldtoQ/edit#gid=381357998 +class ImplementationPlatform { + public: + // General platform support: + // - atomic variables (boolean, and any other copyable type) + // - synchronization primitives: + // - mutex (regular, and recursive) + // - condition variable (must work with regular mutex only) + // - Future : to synchronize on Callable schduled to execute. + // - CountDownLatch : to ensure at least N threads are waiting. + static std::unique_ptr> CreateAtomicReferenceAny( + absl::any initial_value); + static std::unique_ptr> CreateSettableFutureAny(); + static std::unique_ptr CreateAtomicBoolean(bool initial_value); + static std::unique_ptr CreateCountDownLatch( + std::int32_t count); + static std::unique_ptr CreateMutex(Mutex::Mode mode); + static std::unique_ptr CreateConditionVariable( + Mutex* mutex); + + // Java-like Executors + static std::unique_ptr CreateSingleThreadExecutor(); + static std::unique_ptr CreateMultiThreadExecutor( + std::int32_t max_concurrency); + static std::unique_ptr CreateScheduledExecutor(); + + // Protocol implementations, domain-specific support + static std::unique_ptr CreateBluetoothAdapter(); + static std::unique_ptr CreateBluetoothClassicMedium(); + static std::unique_ptr CreateBleMedium(); + static std::unique_ptr CreateBleV2Medium(); + static std::unique_ptr CreateServerSyncMedium(); + static std::unique_ptr CreateWifiMedium(); + static std::unique_ptr CreateWifiLanMedium(); + static std::unique_ptr + CreateWebRtcSignalingMessenger(absl::string_view self_id); + static std::string GetDeviceId(); + static std::string GetPayloadPath(std::int64_t payload_id); +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_PLATFORM_H_ diff --git a/cpp/platform/api2/scheduled_executor.h b/cpp/platform_v2/api/scheduled_executor.h similarity index 58% rename from cpp/platform/api2/scheduled_executor.h rename to cpp/platform_v2/api/scheduled_executor.h index 2bc068a5..ec7dea77 100644 --- a/cpp/platform/api2/scheduled_executor.h +++ b/cpp/platform_v2/api/scheduled_executor.h @@ -12,19 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SCHEDULED_EXECUTOR_H_ -#define PLATFORM_API2_SCHEDULED_EXECUTOR_H_ +#ifndef PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ #include +#include #include -#include "platform/api2/executor.h" -#include "platform/cancelable.h" -#include "platform/runnable.h" +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/executor.h" +#include "platform_v2/base/runnable.h" #include "absl/time/time.h" namespace location { namespace nearby { +namespace api { // An Executor that can schedule commands to run after a given delay, or to // execute periodically. @@ -33,11 +35,16 @@ namespace nearby { class ScheduledExecutor : public Executor { public: ~ScheduledExecutor() override = default; - virtual std::unique_ptr Schedule( - std::unique_ptr runnable, absl::Duration duration) = 0; + // Cancelable is kept both in the executor context, and in the caller context. + // We want Cancelable to live until both caller and executor are done with it. + // Exclusive ownership model does not work for this case; + // using std:shared_ptr<> instead if std::unique_ptr<>. + virtual std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration duration) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SCHEDULED_EXECUTOR_H_ +#endif // PLATFORM_V2_API_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform/api2/server_sync.h b/cpp/platform_v2/api/server_sync.h similarity index 92% rename from cpp/platform/api2/server_sync.h rename to cpp/platform_v2/api/server_sync.h index 46d5c5e2..ca362af6 100644 --- a/cpp/platform/api2/server_sync.h +++ b/cpp/platform_v2/api/server_sync.h @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SERVER_SYNC_H_ -#define PLATFORM_API2_SERVER_SYNC_H_ +#ifndef PLATFORM_V2_API_SERVER_SYNC_H_ +#define PLATFORM_V2_API_SERVER_SYNC_H_ #include -#include "platform/byte_array.h" +#include "platform_v2/base/byte_array.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +namespace api { // Abstraction that represents a Nearby endpoint exchanging data through // ServerSync Medium. @@ -68,7 +69,8 @@ class ServerSyncMedium { virtual void StopDiscovery(absl::string_view service_id) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SERVER_SYNC_H_ +#endif // PLATFORM_V2_API_SERVER_SYNC_H_ diff --git a/cpp/platform/api2/settable_future.h b/cpp/platform_v2/api/settable_future.h similarity index 78% rename from cpp/platform/api2/settable_future.h rename to cpp/platform_v2/api/settable_future.h index 73617ae5..6200056e 100644 --- a/cpp/platform/api2/settable_future.h +++ b/cpp/platform_v2/api/settable_future.h @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SETTABLE_FUTURE_H_ -#define PLATFORM_API2_SETTABLE_FUTURE_H_ +#ifndef PLATFORM_V2_API_SETTABLE_FUTURE_H_ +#define PLATFORM_V2_API_SETTABLE_FUTURE_H_ -#include "platform/api2/listenable_future.h" +#include "platform_v2/api/listenable_future.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { +namespace api { // A SettableFuture is a type of Future whose result can be set. // @@ -29,10 +31,12 @@ class SettableFuture : public ListenableFuture { ~SettableFuture() override = default; virtual bool Set(const T& value) = 0; + virtual bool Set(T&& value) = 0; virtual bool SetException(Exception exception) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SETTABLE_FUTURE_H_ +#endif // PLATFORM_V2_API_SETTABLE_FUTURE_H_ diff --git a/cpp/platform_v2/api/submittable_executor.h b/cpp/platform_v2/api/submittable_executor.h new file mode 100644 index 00000000..5feaeacf --- /dev/null +++ b/cpp/platform_v2/api/submittable_executor.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/future.h" +#include "platform_v2/base/runnable.h" + +namespace location { +namespace nearby { +namespace api { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutorWrapper +// - SingleThreadExecutorWrapper +// Platform must override bool submit(std::function) method. +class SubmittableExecutor : public Executor { + public: + ~SubmittableExecutor() override = default; + + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + virtual bool DoSubmit(Runnable&& wrapped_callable) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform/api2/system_clock.h b/cpp/platform_v2/api/system_clock.h similarity index 63% rename from cpp/platform/api2/system_clock.h rename to cpp/platform_v2/api/system_clock.h index cf1442e9..fd979e43 100644 --- a/cpp/platform/api2/system_clock.h +++ b/cpp/platform_v2/api/system_clock.h @@ -12,25 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SYSTEM_CLOCK_H_ -#define PLATFORM_API2_SYSTEM_CLOCK_H_ +#ifndef PLATFORM_V2_API_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_API_SYSTEM_CLOCK_H_ -#include - -#include "absl/time/time.h" +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" namespace location { namespace nearby { class SystemClock final { public: - // Returns the time (in milliseconds) since the system was booted, and - // includes deep sleep. This clock should be guaranteed to be monotonic, and - // should continue to tick even when the CPU is in power saving modes. + // Initialize global system state. + static void Init(); + // Returns current absolute time. It is guaranteed to be monotonic. static absl::Time ElapsedRealtime(); + // Pauses current thread for the specified duration. + static Exception Sleep(absl::Duration duration); }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SYSTEM_CLOCK_H_ +#endif // PLATFORM_V2_API_SYSTEM_CLOCK_H_ diff --git a/cpp/platform/api2/webrtc.h b/cpp/platform_v2/api/webrtc.h similarity index 87% rename from cpp/platform/api2/webrtc.h rename to cpp/platform_v2/api/webrtc.h index 23ab20ed..9dc8e7cc 100644 --- a/cpp/platform/api2/webrtc.h +++ b/cpp/platform_v2/api/webrtc.h @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_WEBRTC_H_ -#define PLATFORM_API2_WEBRTC_H_ +#ifndef PLATFORM_V2_API_WEBRTC_H_ +#define PLATFORM_V2_API_WEBRTC_H_ #include -#include "platform/byte_array.h" -#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" +#include "platform_v2/base/byte_array.h" +#include "webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { +namespace api { class WebRtcSignalingMessenger { public: @@ -54,7 +55,8 @@ class WebRtcSignalingMessenger { const IceServersListener& ice_servers_listener) = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WEBRTC_H_ +#endif // PLATFORM_V2_API_WEBRTC_H_ diff --git a/cpp/platform/api2/wifi.h b/cpp/platform_v2/api/wifi.h similarity index 93% rename from cpp/platform/api2/wifi.h rename to cpp/platform_v2/api/wifi.h index 93552823..3cc0dc44 100644 --- a/cpp/platform/api2/wifi.h +++ b/cpp/platform_v2/api/wifi.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_WIFI_H_ -#define PLATFORM_API2_WIFI_H_ +#ifndef PLATFORM_V2_API_WIFI_H_ +#define PLATFORM_V2_API_WIFI_H_ #include #include @@ -23,6 +23,7 @@ namespace location { namespace nearby { +namespace api { // Possible authentication types for a WiFi network. enum class WifiAuthType { @@ -48,7 +49,7 @@ enum class WifiConnectionStatus { // Represents a WiFi network found during a call to WifiMedium#scan(). class WifiScanResult { public: - virtual ~WifiScanResult() {} + virtual ~WifiScanResult() = default; // Gets the SSID of this WiFi network. virtual std::string GetSsid() const = 0; @@ -67,7 +68,7 @@ class WifiMedium { class ScanResultCallback { public: - virtual ~ScanResultCallback() {} + virtual ~ScanResultCallback() = default; virtual void OnScanResults( const std::vector& scan_results) = 0; @@ -96,7 +97,8 @@ class WifiMedium { virtual std::string GetIpAddress() = 0; }; +} // namespace api } // namespace nearby } // namespace location -#endif // PLATFORM_API2_WIFI_H_ +#endif // PLATFORM_V2_API_WIFI_H_ diff --git a/cpp/platform_v2/api/wifi_lan.h b/cpp/platform_v2/api/wifi_lan.h new file mode 100644 index 00000000..4802d67c --- /dev/null +++ b/cpp/platform_v2/api/wifi_lan.h @@ -0,0 +1,101 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_API_WIFI_LAN_H_ +#define PLATFORM_V2_API_WIFI_LAN_H_ + +#include + +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { +namespace api { + +// Opaque wrapper over a WifiLan service which contains encoded service name. +class WifiLanService { + public: + virtual ~WifiLanService() = default; + + virtual std::string GetName() = 0; +}; + +class WifiLanSocket { + public: + virtual ~WifiLanSocket() = default; + + // Returns the InputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetInputStream() = 0; + + // Returns the OutputStream of the WifiLanSocket, empty std::unique_ptr<> + // on error. + virtual std::unique_ptr GetOutputStream() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception::Value Close() = 0; + + virtual WifiLanService& GetRemoteWifiLanService() = 0; +}; + +// Container of operations that can be performed over the WifiLan medium. +class WifiLanMedium { + public: + virtual ~WifiLanMedium() = default; + + virtual bool StartAdvertising( + absl::string_view service_id, + absl::string_view wifi_lan_service_info_name) = 0; + virtual void StopAdvertising(absl::string_view service_id) = 0; + + // Callback for WifiLan discover results. + class DiscoveredServiceCallback { + public: + virtual ~DiscoveredServiceCallback() = default; + + virtual void OnServiceDiscovered(WifiLanService* wifi_lan_service) = 0; + virtual void OnServiceLost(WifiLanService* wifi_lan_service) = 0; + }; + + virtual bool StartDiscovery( + absl::string_view service_id, + DiscoveredServiceCallback* discovered_service_callback) = 0; + virtual void StopDiscovery(absl::string_view service_id) = 0; + + class AcceptedConnectionCallback { + public: + virtual ~AcceptedConnectionCallback() = default; + + virtual void OnConnectionAccepted(WifiLanSocket* socket, + absl::string_view service_id) = 0; + }; + + virtual bool StartAcceptingConnections( + absl::string_view service_id, + AcceptedConnectionCallback* accepted_connection_callback) = 0; + virtual void StopAcceptingConnections(absl::string_view service_id) = 0; + + virtual WifiLanSocket* Connect(WifiLanService* wifi_lan_service, + absl::string_view service_id) = 0; +}; + +} // namespace api +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_API_WIFI_LAN_H_ diff --git a/cpp/platform_v2/base/BUILD b/cpp/platform_v2/base/BUILD new file mode 100644 index 00000000..ab8832af --- /dev/null +++ b/cpp/platform_v2/base/BUILD @@ -0,0 +1,87 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//ads/util/non_compile:non_compile.bzl", "cc_with_non_compile_test") + +cc_library( + name = "base", + srcs = [ + "base64_utils.cc", + "prng.cc", + ], + hdrs = [ + "base64_utils.h", + "byte_array.h", + "callable.h", + "exception.h", + "input_stream.h", + "listeners.h", + "output_stream.h", + "prng.h", + "runnable.h", + "socket.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + "//platform_v2/api:__subpackages__", + ], + deps = [ + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "util", + srcs = [ + "base_pipe.cc", + ], + hdrs = [ + "base_mutex_lock.h", + "base_pipe.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + "//platform_v2/public:__pkg__", + ], + deps = [ + ":base", + "//platform_v2/api", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "platform_base_test", + srcs = [ + "byte_array_test.cc", + "prng_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) + +cc_with_non_compile_test( + name = "exception_test", + srcs = [ + "exception_test.cc", + ], + deps = [ + ":base", + "//testing/base/public:gunit_main", + ], +) diff --git a/cpp/platform_v2/base/base64_utils.cc b/cpp/platform_v2/base/base64_utils.cc new file mode 100644 index 00000000..f32dd200 --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.cc @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/base64_utils.h" + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/escaping.h" + +namespace location { +namespace nearby { + +std::string Base64Utils::Encode(const ByteArray& bytes) { + std::string base64_string; + + absl::WebSafeBase64Escape(std::string(bytes), &base64_string); + + return base64_string; +} + +ByteArray Base64Utils::Decode(absl::string_view base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return ByteArray(); + } + + return ByteArray(decoded_string.data(), decoded_string.size()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base64_utils.h b/cpp/platform_v2/base/base64_utils.h new file mode 100644 index 00000000..8b52af35 --- /dev/null +++ b/cpp/platform_v2/base/base64_utils.h @@ -0,0 +1,33 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_BASE64_UTILS_H_ +#define PLATFORM_V2_BASE_BASE64_UTILS_H_ + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class Base64Utils { + public: + static std::string Encode(const ByteArray& bytes); + static ByteArray Decode(absl::string_view base64_string); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE64_UTILS_H_ diff --git a/cpp/platform_v2/base/base_mutex_lock.h b/cpp/platform_v2/base/base_mutex_lock.h new file mode 100644 index 00000000..7ae57dd5 --- /dev/null +++ b/cpp/platform_v2/base/base_mutex_lock.h @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ +#define PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE BaseMutexLock final { + public: + explicit BaseMutexLock(api::Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex) { + mutex_->Lock(); + } + ~BaseMutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/base/base_pipe.cc b/cpp/platform_v2/base/base_pipe.cc new file mode 100644 index 00000000..dfc496e8 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.cc @@ -0,0 +1,110 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/base_pipe.h" + +#include "platform_v2/api/platform.h" +#include "platform_v2/base/base_mutex_lock.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" + +namespace location { +namespace nearby { + +ExceptionOr BasePipe::Read(size_t size) { + BaseMutexLock lock(mutex_.get()); + + // We're done reading all the chunks that were written before the OutputStream + // was closed, so there's nothing to do here other than return an empty chunk + // to serve as an EOF indication to callers. + if (read_all_chunks_) { + return ExceptionOr{ByteArray{}}; + } + + while (buffer_.empty() && !input_stream_closed_) { + Exception wait_exception = cond_->Wait(); + + if (wait_exception.Raised()) { + return ExceptionOr{wait_exception}; + } + } + + if (input_stream_closed_) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray first_chunk{buffer_.front()}; + buffer_.pop_front(); + + // If we received our sentinel chunk, mark the fact that there cannot + // possibly be any more chunks to read here on in, and return an empty chunk + // to serve as an EOF indication to callers. + if (first_chunk.Empty()) { + read_all_chunks_ = true; + return ExceptionOr{ByteArray{}}; + } + + // If first_chunk is small enough to not overshoot the requested 'size', just + // return that. + if (first_chunk.size() <= size) { + return ExceptionOr{first_chunk}; + } else { + // Break first_chunk into 2 parts -- the first one of which (next_chunk) + // will be 'size' bytes long, and will be returned, and the second one of + // which (overflow_chunk) will be re-inserted into buffer_, at the head of + // the queue, to be served up in the next call to read(). + ByteArray next_chunk(first_chunk.data(), size); + buffer_.push_front( + ByteArray(first_chunk.data() + size, first_chunk.size() - size)); + return ExceptionOr{next_chunk}; + } +} + +Exception BasePipe::Write(const ByteArray& data) { + BaseMutexLock lock(mutex_.get()); + + return WriteLocked(data); +} + +void BasePipe::MarkInputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + input_stream_closed_ = true; + // Trigger cond_ to unblock a potentially-blocked call to read(), and to let + // it know to return Exception::IO. + cond_->Notify(); +} + +void BasePipe::MarkOutputStreamClosed() { + BaseMutexLock lock(mutex_.get()); + + // Write a sentinel null chunk before marking output_stream_closed as true. + WriteLocked(ByteArray{}); + output_stream_closed_ = true; +} + +Exception BasePipe::WriteLocked(const ByteArray& data) { + if (input_stream_closed_ || output_stream_closed_) { + return {Exception::kIo}; + } + + buffer_.push_back(data); + // Trigger cond_ to unblock a potentially-blocked call to read(), now that + // there's more data for it to consume. + cond_->Notify(); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/base_pipe.h b/cpp/platform_v2/base/base_pipe.h new file mode 100644 index 00000000..7b08b4e5 --- /dev/null +++ b/cpp/platform_v2/base/base_pipe.h @@ -0,0 +1,142 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_BASE_PIPE_H_ +#define PLATFORM_V2_BASE_BASE_PIPE_H_ + +#include +#include +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// Common Pipe implenentation. +// It does not depend on platform implementation, and this allows it to +// be used in the platform implementation itself. +// Concrete class must be derived from it, as follows: +// +// class DerivedPipe : public BasePipe { +// public: +// DerivedPipe() { +// auto mutex = /* construct platform-dependent mutex */; +// auto cond = /* construct platform-dependent condition variable */; +// Setup(std::move(mutex), std::move(cond)); +// } +// ~DerivedPipe() override = default; +// DerivedPipe(DerivedPipe&&) = default; +// DerivedPipe& operator=(DerivedPipe&&) = default; +// }; +class BasePipe { + public: + static constexpr const size_t kChunkSize = 64 * 1024; + virtual ~BasePipe() = default; + + // Pipe is not copyable or movable, because copy/move will invalidate + // references to input and output streams. + // If move is required, Pipe could be wrapped with std::unique_ptr<>. + BasePipe(BasePipe&&) = delete; + BasePipe& operator=(BasePipe&&) = delete; + + // Get...() methods return references to input and output steam facades. + // It is safe to call Get...() methods multiple times. + InputStream& GetInputStream() { return input_stream_; } + OutputStream& GetOutputStream() { return output_stream_; } + + protected: + BasePipe() = default; + + void Setup(std::unique_ptr mutex, + std::unique_ptr cond) { + mutex_ = std::move(mutex); + cond_ = std::move(cond); + } + + private: + class BasePipeInputStream : public InputStream { + public: + explicit BasePipeInputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeInputStream() override { DoClose(); } + + ExceptionOr Read(std::int64_t size) override { + return pipe_->Read(size); + } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkInputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + class BasePipeOutputStream : public OutputStream { + public: + explicit BasePipeOutputStream(BasePipe* pipe) : pipe_(pipe) {} + ~BasePipeOutputStream() override { DoClose(); } + + Exception Write(const ByteArray& data) override { + return pipe_->Write(data); + } + Exception Flush() override { return {Exception::kSuccess}; } + Exception Close() override { + return DoClose(); + } + + private: + Exception DoClose() { + pipe_->MarkOutputStreamClosed(); + return {Exception::kSuccess}; + } + BasePipe* pipe_; + }; + + ExceptionOr Read(size_t size) ABSL_LOCKS_EXCLUDED(mutex_); + Exception Write(const ByteArray& data) ABSL_LOCKS_EXCLUDED(mutex_); + + void MarkInputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + void MarkOutputStreamClosed() ABSL_LOCKS_EXCLUDED(mutex_); + + Exception WriteLocked(const ByteArray& data) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Order of declaration matters: + // - mutex must be defined before condvar; + // - input & output streams must be after both mutex and condvar. + bool input_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool output_stream_closed_ ABSL_GUARDED_BY(mutex_) = false; + bool read_all_chunks_ ABSL_GUARDED_BY(mutex_) = false; + + std::deque ABSL_GUARDED_BY(mutex_) buffer_; + std::unique_ptr mutex_; + std::unique_ptr cond_; + + BasePipeInputStream input_stream_{this}; + BasePipeOutputStream output_stream_{this}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BASE_PIPE_H_ diff --git a/cpp/platform_v2/base/byte_array.h b/cpp/platform_v2/base/byte_array.h new file mode 100644 index 00000000..12adf3ad --- /dev/null +++ b/cpp/platform_v2/base/byte_array.h @@ -0,0 +1,95 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_BYTE_ARRAY_H_ +#define PLATFORM_V2_BASE_BYTE_ARRAY_H_ + +#include +#include + +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class ByteArray { + public: + // Create an empty ByteArray + ByteArray() = default; + ByteArray(const ByteArray&) = default; + ByteArray& operator=(const ByteArray&) = default; + ByteArray(ByteArray&&) = default; + ByteArray& operator=(ByteArray&&) = default; + + // Create ByteArray from string. + explicit ByteArray(absl::string_view source) { data_ = source; } + + // Create default-initialized ByteArray of a given size. + explicit ByteArray(size_t size) { SetData(size); } + + // Create value-initialized ByteArray of a given size. + ByteArray(const char* data, size_t size) { SetData(data, size); } + + // Assign a new value to this ByteArray, as a copy of data, with a given size. + void SetData(const char* data, size_t size) { + if (data == nullptr) { + size = 0; + } + data_.assign(data, size); + } + + // Assign a new value of a given size to this ByteArray + // (as a repeated char value). + void SetData(size_t size, char value = 0) { data_.assign(size, value); } + + // Returns true, if changes were performed to container, false otherwise. + bool CopyAt(size_t offset, const ByteArray& from, size_t source_offset = 0) { + if (offset >= size()) return false; + if (source_offset >= from.size()) return false; + memcpy(data() + offset, from.data() + source_offset, + std::min(size() - offset, from.size() - source_offset)); + return true; + } + + char* data() { return &data_[0]; } + const char* data() const { return data_.data(); } + size_t size() const { return data_.size(); } + bool Empty() const { return data_.empty(); } + + friend bool operator==(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator!=(const ByteArray& lhs, const ByteArray& rhs); + friend bool operator<(const ByteArray& lhs, const ByteArray& rhs); + + explicit operator std::string() const { return data_; } + + private: + std::string data_; +}; + +inline bool operator==(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ == rhs.data_; +} + +inline bool operator!=(const ByteArray& lhs, const ByteArray& rhs) { + return !(lhs == rhs); +} + +inline bool operator<(const ByteArray& lhs, const ByteArray& rhs) { + return lhs.data_ < rhs.data_; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_BYTE_ARRAY_H_ diff --git a/cpp/platform_v2/base/byte_array_test.cc b/cpp/platform_v2/base/byte_array_test.cc new file mode 100644 index 00000000..5232c426 --- /dev/null +++ b/cpp/platform_v2/base/byte_array_test.cc @@ -0,0 +1,82 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/byte_array.h" + +#include + +#include "gtest/gtest.h" + +namespace { + +using location::nearby::ByteArray; + +TEST(ByteArrayTest, DefaultSizeIsZero) { + ByteArray bytes; + EXPECT_EQ(0, bytes.size()); +} + +TEST(ByteArrayTest, DefaultIsEmpty) { + ByteArray bytes; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, NullArrayIsEmpty) { + ByteArray bytes{nullptr, 5}; + EXPECT_TRUE(bytes.Empty()); +} + +TEST(ByteArrayTest, CopyAtDoesNotExtendArray) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + EXPECT_TRUE(v2.CopyAt(/*offset=*/5, v1)); + EXPECT_TRUE(v2.CopyAt(/*offset=*/1, v1, /*source_offset=*/3)); + EXPECT_EQ(v2, ByteArray("A45DE123")); +} + +TEST(ByteArrayTest, CopyAtOutOfBoundsIsIgnored) { + ByteArray v1("12345"); + ByteArray v2("ABCDEFGH"); + // Try to do an out-of-bounds read. + EXPECT_FALSE(v2.CopyAt(/* offset=*/5, v1, /*source_offset=*/10)); + // Try to do an out-of-bounds write. + EXPECT_FALSE(v2.CopyAt(/* offset=*/9, v1)); + EXPECT_EQ(v2, ByteArray("ABCDEFGH")); +} + +TEST(ByteArrayTest, SetFromString) { + std::string setup("setup_test"); + ByteArray bytes{setup}; // array initialized with a copy of string. + EXPECT_EQ(setup.size(), bytes.size()); + EXPECT_EQ(std::string(bytes), setup); +} + +TEST(ByteArrayTest, SetExplicitSize) { + constexpr size_t kArraySize = 10; + char reference[kArraySize]{}; + ByteArray bytes{kArraySize}; // array of size 10, zero-initialized. + EXPECT_EQ(kArraySize, bytes.size()); + EXPECT_EQ(0, memcmp(bytes.data(), reference, kArraySize)); +} + +TEST(ByteArrayTest, SetExplicitData) { + constexpr static const char message[]{"test_message"}; + constexpr size_t kMessageSize = sizeof(message); + ByteArray bytes{message, kMessageSize}; + EXPECT_EQ(kMessageSize, bytes.size()); + EXPECT_NE(message, bytes.data()); + EXPECT_EQ(0, memcmp(message, bytes.data(), kMessageSize)); +} + +} // namespace diff --git a/cpp/platform/api2/multi_thread_executor.h b/cpp/platform_v2/base/callable.h similarity index 59% rename from cpp/platform/api2/multi_thread_executor.h rename to cpp/platform_v2/base/callable.h index 4f4bb951..d25b408c 100644 --- a/cpp/platform/api2/multi_thread_executor.h +++ b/cpp/platform_v2/base/callable.h @@ -12,26 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ +#ifndef PLATFORM_V2_BASE_CALLABLE_H_ +#define PLATFORM_V2_BASE_CALLABLE_H_ -#include "platform/api2/submittable_executor.h" +#include + +#include "platform_v2/base/exception.h" namespace location { namespace nearby { -// An Executor that reuses a fixed number of threads operating off a shared -// unbounded queue. +// The Callable is and object intended to be executed by a thread, that is able +// to return a value of specified type T. +// It must be invokable without arguments. It must return a value implicitly +// convertible to ExceptionOr. // -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- -template -class MultiThreadExecutor - : public SubmittableExecutor { - public: - ~MultiThreadExecutor() override {} -}; +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html +template +using Callable = std::function()>; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_MULTI_THREAD_EXECUTOR_H_ +#endif // PLATFORM_V2_BASE_CALLABLE_H_ diff --git a/cpp/platform_v2/base/exception.h b/cpp/platform_v2/base/exception.h new file mode 100644 index 00000000..492cc761 --- /dev/null +++ b/cpp/platform_v2/base/exception.h @@ -0,0 +1,111 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_EXCEPTION_H_ +#define PLATFORM_V2_BASE_EXCEPTION_H_ + +#include +#include + +namespace location { +namespace nearby { + +struct Exception { + enum Value : int { + kFailed = -1, // Initial value of Exception; any unknown error. + kSuccess = 0, // No exception. + kIo = 1, // IO Error happened. + kInterrupted = 2, // Operation was interrupted. + kInvalidProtocolBuffer = 3, // Couldn't parse. + kExecution = 4, // Couldn't execute. + kTimeout = 5, // Operarion did not finish within specified time. + }; + bool Ok() const { return value == kSuccess; } + bool Raised() const { return !Ok(); } + bool Raised(Value value) const { return this->value == value; } + Value value{kFailed}; +}; + +constexpr inline bool operator==(const Exception& a, const Exception& b) { + return a.value == b.value; +} + +constexpr inline bool operator!=(const Exception& a, const Exception& b) { + return !(a == b); +} + +// ExceptionOr provides experience similar to StatusOr used in +// Google Cloud API, see: +// https://googleapis.github.io/google-cloud-cpp/0.7.0/common/status__or_8h_source.html +// +// If ok() returns true, result() is a usable return value. Otherwise, +// exception() explains why such a value is not present. +// +// A typical pattern of usage is as follows: +// +// if (!e.ok()) { +// if (Exception::EXCEPTION_TYPE_1 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_1. +// } else if (Exception::EXCEPTION_TYPE_2 == e.exception()) { +// // Handle Exception::EXCEPTION_TYPE_2. +// } +// +// return; +// } +// +// // Use e.result(). +template +class ExceptionOr { + public: + ExceptionOr() = default; + explicit ExceptionOr(T&& result) + : result_{std::move(result)}, + exception_{Exception::kSuccess} {} // NOLINT + explicit ExceptionOr(const T& result) + : result_{result}, exception_{Exception::kSuccess} {} // NOLINT + ExceptionOr(Exception::Value exception) : exception_{exception} {} // NOLINT + ExceptionOr(Exception exception) : exception_{exception} {} // NOLINT + // If there exists explicit conversion from from U to T, + // then allow explicit conversion from ExceptionOr to ExceptionOr. + template ()})>> + explicit ExceptionOr(ExceptionOr value) { + if (!value.ok()) { + exception_ = value.GetException(); + } else { + result_ = T{std::move(value.result())}; + exception_ = Exception{Exception::kSuccess}; + } + } + + bool ok() const { return exception_.value == Exception::kSuccess; } + + T& result() & { return result_; } + const T& result() const& { return result_; } + T&& result() && { return std::move(result_); } + const T&& result() const&& { return std::move(result_); } + + Exception::Value exception() const { return exception_.value; } + + T GetResult() const { return result_; } + Exception GetException() const { return exception_; } + + private: + T result_{}; + Exception exception_{Exception::kFailed}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_EXCEPTION_H_ diff --git a/cpp/platform_v2/base/exception_test.cc b/cpp/platform_v2/base/exception_test.cc new file mode 100644 index 00000000..882d94d9 --- /dev/null +++ b/cpp/platform_v2/base/exception_test.cc @@ -0,0 +1,120 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/exception.h" + +#include + +#include "platform_v2/base/exception_test.nc.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location::nearby { + +TEST(ExceptionOr, Result_Copy_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should not affect the copy. + exception_or_vector.result().clear(); + EXPECT_FALSE(copy.empty()); +} + +TEST(ExceptionOr, Result_Copy_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Expect a copy when not explicitly moving the result. + std::vector copy = exception_or_vector.result(); + EXPECT_FALSE(copy.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Reference_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Modifying |exception_or_vector| should reflect in the reference. + exception_or_vector.result().clear(); + EXPECT_TRUE(reference.empty()); +} + +TEST(ExceptionOr, Result_Reference_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Getting a reference should not modify the source. + const std::vector& reference = exception_or_vector.result(); + EXPECT_FALSE(reference.empty()); + EXPECT_FALSE(exception_or_vector.result().empty()); +} + +TEST(ExceptionOr, Result_Move_NonConst) { + ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving the result should clear the source. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, Result_Move_Const) { + const ExceptionOr> exception_or_vector({1, 2, 3}); + EXPECT_FALSE(exception_or_vector.result().empty()); + + // Moving const rvalue reference will result in a copy. + std::vector moved = std::move(exception_or_vector).result(); + EXPECT_FALSE(moved.empty()); +} + +TEST(ExceptionOr, ExplicitConversionWorks) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + explicit B(A) {} + }; + ExceptionOr a(A{}); + ExceptionOr b(a); + EXPECT_TRUE(a.ok()); + EXPECT_TRUE(b.ok()); +} + +TEST(ExceptionOr, ExplicitConversionFailsToCompile) { + class A { + public: + A() = default; + }; + class B { + public: + B() = default; + }; + ExceptionOr a(A{}); + EXPECT_NON_COMPILE("no matching constructor", { ExceptionOr b(a); }); +} + +} // namespace location::nearby diff --git a/cpp/platform/api2/input_stream.h b/cpp/platform_v2/base/input_stream.h similarity index 68% rename from cpp/platform/api2/input_stream.h rename to cpp/platform_v2/base/input_stream.h index 4caf598f..091ca6b6 100644 --- a/cpp/platform/api2/input_stream.h +++ b/cpp/platform_v2/base/input_stream.h @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_INPUT_STREAM_H_ -#define PLATFORM_API2_INPUT_STREAM_H_ +#ifndef PLATFORM_V2_BASE_INPUT_STREAM_H_ +#define PLATFORM_V2_BASE_INPUT_STREAM_H_ #include -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { @@ -28,14 +28,15 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html class InputStream { public: - virtual ~InputStream() {} + virtual ~InputStream() = default; - virtual ExceptionOr Read( - size_t size) = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo + // throws Exception::kIo + virtual ExceptionOr Read(std::int64_t size) = 0; + // throws Exception::kIo + virtual Exception Close() = 0; }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_INPUT_STREAM_H_ +#endif // PLATFORM_V2_BASE_INPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/listeners.h b/cpp/platform_v2/base/listeners.h new file mode 100644 index 00000000..b2b465d7 --- /dev/null +++ b/cpp/platform_v2/base/listeners.h @@ -0,0 +1,34 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_LISTENERS_H_ +#define PLATFORM_V2_BASE_LISTENERS_H_ + +#include + +namespace location { +namespace nearby { + +// Provides default-initialization with a valid empty method, +// instead of nullptr. This allows partial initialization +// of a set of listeners. +template +constexpr std::function DefaultCallback() { + return std::function{[](Args...) {}}; +} + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_LISTENERS_H_ diff --git a/cpp/platform/api2/output_stream.h b/cpp/platform_v2/base/output_stream.h similarity index 69% rename from cpp/platform/api2/output_stream.h rename to cpp/platform_v2/base/output_stream.h index 95be4cd4..bb092823 100644 --- a/cpp/platform/api2/output_stream.h +++ b/cpp/platform_v2/base/output_stream.h @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_OUTPUT_STREAM_H_ -#define PLATFORM_API2_OUTPUT_STREAM_H_ +#ifndef PLATFORM_V2_BASE_OUTPUT_STREAM_H_ +#define PLATFORM_V2_BASE_OUTPUT_STREAM_H_ -#include "platform/byte_array.h" -#include "platform/exception.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/base/exception.h" namespace location { namespace nearby { @@ -26,14 +26,14 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html class OutputStream { public: - virtual ~OutputStream() {} + virtual ~OutputStream() = default; virtual Exception Write(const ByteArray& data) = 0; // throws Exception::kIo - virtual Exception Flush() = 0; // throws Exception::kIo - virtual Exception Close() = 0; // throws Exception::kIo + virtual Exception Flush() = 0; // throws Exception::kIo + virtual Exception Close() = 0; // throws Exception::kIo }; } // namespace nearby } // namespace location -#endif // PLATFORM_API2_OUTPUT_STREAM_H_ +#endif // PLATFORM_V2_BASE_OUTPUT_STREAM_H_ diff --git a/cpp/platform_v2/base/prng.cc b/cpp/platform_v2/base/prng.cc new file mode 100644 index 00000000..0dafa7b5 --- /dev/null +++ b/cpp/platform_v2/base/prng.cc @@ -0,0 +1,59 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/prng.h" + +#include + +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +#define UNSIGNED_INT_BITMASK (std::numeric_limits::max()) + +Prng::Prng() { + // absl::GetCurrentTimeNanos() returns 64 bits, but srand() wants an unsigned + // int, so we may have to lose some of those 64 bits. + // + // The lower bits of the current-time-in-nanos are likely to have more entropy + // than the upper bits, so choose the former. + srand(static_cast(absl::GetCurrentTimeNanos() & + UNSIGNED_INT_BITMASK)); +} + +Prng::~Prng() { + // Nothing to do. +} + +#define RANDOM_BYTE (rand() & 0x0FF) // NOLINT + +std::int32_t Prng::NextInt32() { + return (static_cast(RANDOM_BYTE) << 24) | + (static_cast(RANDOM_BYTE) << 16) | + (static_cast(RANDOM_BYTE) << 8) | + (static_cast(RANDOM_BYTE)); +} + +std::uint32_t Prng::NextUint32() { + return static_cast(NextInt32()); +} + +std::int64_t Prng::NextInt64() { + return (static_cast(NextInt32()) << 32) | + (static_cast(NextInt32())); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/prng.h b/cpp/platform_v2/base/prng.h new file mode 100644 index 00000000..1125adfb --- /dev/null +++ b/cpp/platform_v2/base/prng.h @@ -0,0 +1,37 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_PRNG_H_ +#define PLATFORM_V2_BASE_PRNG_H_ + +#include + +namespace location { +namespace nearby { + +// A (non-cryptographic) pseudo-random number generator. +class Prng { + public: + Prng(); + ~Prng(); + + std::int32_t NextInt32(); + std::uint32_t NextUint32(); + std::int64_t NextInt64(); +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_PRNG_H_ diff --git a/cpp/platform_v2/base/prng_test.cc b/cpp/platform_v2/base/prng_test.cc new file mode 100644 index 00000000..72687f7d --- /dev/null +++ b/cpp/platform_v2/base/prng_test.cc @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/base/prng.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PrngTest, NextInt32) { + std::int32_t i = Prng().NextInt32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextUInt32) { + std::uint32_t i = Prng().NextUint32(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +TEST(PrngTest, NextInt64) { + std::int64_t i = Prng().NextInt64(); + EXPECT_LE(i, std::numeric_limits::max()); + EXPECT_GE(i, std::numeric_limits::min()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/base/runnable.h b/cpp/platform_v2/base/runnable.h new file mode 100644 index 00000000..480c87a0 --- /dev/null +++ b/cpp/platform_v2/base/runnable.h @@ -0,0 +1,33 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_BASE_RUNNABLE_H_ +#define PLATFORM_V2_BASE_RUNNABLE_H_ + +#include + +namespace location { +namespace nearby { + +// The Runnable is an object intended to be executed by a thread. +// It must be invokable without arguments. It must return void. +// +// https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html + +using Runnable = std::function; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_BASE_RUNNABLE_H_ diff --git a/cpp/platform/api2/socket.h b/cpp/platform_v2/base/socket.h similarity index 81% rename from cpp/platform/api2/socket.h rename to cpp/platform_v2/base/socket.h index 29113ca8..8b95cc63 100644 --- a/cpp/platform/api2/socket.h +++ b/cpp/platform_v2/base/socket.h @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SOCKET_H_ -#define PLATFORM_API2_SOCKET_H_ +#ifndef PLATFORM_V2_BASE_SOCKET_H_ +#define PLATFORM_V2_BASE_SOCKET_H_ -#include "platform/api2/input_stream.h" -#include "platform/api2/output_stream.h" +#include "platform_v2/base/input_stream.h" +#include "platform_v2/base/output_stream.h" namespace location { namespace nearby { @@ -26,7 +26,7 @@ namespace nearby { // https://docs.oracle.com/javase/8/docs/api/java/net/Socket.html class Socket { public: - virtual ~Socket() {} + virtual ~Socket() = default; virtual InputStream& GetInputStream() = 0; virtual OutputStream& GetOutputStream() = 0; @@ -36,4 +36,4 @@ class Socket { } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SOCKET_H_ +#endif // PLATFORM_V2_BASE_SOCKET_H_ diff --git a/cpp/platform_v2/config/BUILD b/cpp/platform_v2/config/BUILD new file mode 100644 index 00000000..3f287f36 --- /dev/null +++ b/cpp/platform_v2/config/BUILD @@ -0,0 +1,35 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "config", + hdrs = [ + "config.h", + ], + visibility = [ + "//visibility:private", + ], +) + +cc_library( + name = "string", + hdrs = [ + "string.h", + ], + visibility = [ + ], + deps = [ + ":config", + ], +) diff --git a/cpp/platform_v2/config/config.h b/cpp/platform_v2/config/config.h new file mode 100644 index 00000000..d2ebfe7e --- /dev/null +++ b/cpp/platform_v2/config/config.h @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_CONFIG_CONFIG_H_ +#define PLATFORM_V2_CONFIG_CONFIG_H_ + +// Clients can modify this file to customize the Nearby C++ codebase as per +// their particular constraints and environments. + +// Note: Every entry in this file should conform to the following format, to +// give precedence to command-line options (-D) that set these symbols: +// +// #ifndef XXX +// #define XXX 0/1 +// #endif + +#ifndef NEARBY_USE_STD_STRING +#define NEARBY_USE_STD_STRING 0 +#endif + +#ifndef NEARBY_USE_RTTI +#define NEARBY_USE_RTTI 1 +#endif + +#endif // PLATFORM_V2_CONFIG_CONFIG_H_ diff --git a/cpp/platform_v2/config/string.h b/cpp/platform_v2/config/string.h new file mode 100644 index 00000000..70c8d6c4 --- /dev/null +++ b/cpp/platform_v2/config/string.h @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_CONFIG_STRING_H_ +#define PLATFORM_V2_CONFIG_STRING_H_ + +#include + +#include "platform_v2/config/config.h" + +#if NEARBY_USE_STD_STRING +using std::string; +#endif + +#endif // PLATFORM_V2_CONFIG_STRING_H_ diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD new file mode 100644 index 00000000..43660b72 --- /dev/null +++ b/cpp/platform_v2/impl/g3/BUILD @@ -0,0 +1,71 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "g3", + srcs = [ + "atomic_boolean.h", + "atomic_reference_any.h", + "bluetooth_adapter.cc", + "bluetooth_adapter.h", + "condition_variable.h", + "count_down_latch.h", + "medium_environment.cc", + "medium_environment.h", + "multi_thread_executor.h", + "mutex.h", + "platform.cc", + "scheduled_executor.cc", + "scheduled_executor.h", + "settable_future_any.h", + "single_thread_executor.h", + "system_clock.cc", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + ":crypto", # build_cleaner: keep + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/impl/shared:posix_mutex", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + "//absl/types:any", + "//thread", + ], +) + +cc_library( + name = "crypto", + srcs = [ + "crypto.cc", + ], + visibility = [ + "//platform_v2/g3:__pkg__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//absl/strings", + "//openssl:crypto", + ], +) diff --git a/cpp/platform_v2/impl/g3/atomic_boolean.h b/cpp/platform_v2/impl/g3/atomic_boolean.h new file mode 100644 index 00000000..1f034f85 --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_boolean.h @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace g3 { + +// See documentation in +// cpp/platform_v2/api/atomic_boolean.h +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/impl/g3/atomic_reference_any.h b/cpp/platform_v2/impl/g3/atomic_reference_any.h new file mode 100644 index 00000000..f072443f --- /dev/null +++ b/cpp/platform_v2/impl/g3/atomic_reference_any.h @@ -0,0 +1,60 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ +#define PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ + +#include "platform_v2/api/atomic_reference.h" +#include "absl/base/integral_types.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { +namespace g3 { + +// Provide implementation for absl::any. +class AtomicReferenceAny : public api::AtomicReference { + public: + explicit AtomicReferenceAny(absl::any initial_value) + : value_(std::move(initial_value)) {} + ~AtomicReferenceAny() override = default; + + absl::any Get() const & override { + absl::MutexLock lock(&mutex_); + return value_; + } + absl::any Get() && override { + absl::MutexLock lock(&mutex_); + return std::move(value_); + } + void Set(const absl::any& value) override { + absl::MutexLock lock(&mutex_); + value_ = value; + } + void Set(absl::any&& value) override { + absl::MutexLock lock(&mutex_); + value_ = std::move(value); + } + + private: + mutable absl::Mutex mutex_; + absl::any value_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_ATOMIC_REFERENCE_ANY_H_ diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc new file mode 100644 index 00000000..f21c634e --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -0,0 +1,79 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/impl/g3/bluetooth_adapter.h" + +#include + +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) + : adapter_(*adapter) {} + +std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } + +bool BluetoothAdapter::SetStatus(Status status) ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + enabled_ = (status == Status::kEnabled); + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +bool BluetoothAdapter::IsEnabled() const { + absl::MutexLock lock(&mutex_); + return enabled_; +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + absl::MutexLock lock(&mutex_); + return mode_; +} + +bool BluetoothAdapter::SetScanMode(BluetoothAdapter::ScanMode mode) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + mode_ = mode; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +std::string BluetoothAdapter::GetName() const { + absl::MutexLock lock(&mutex_); + return name_; +} + +bool BluetoothAdapter::SetName(absl::string_view name) { + absl::MutexLock lock(&mutex_); + if (enabled_) return false; + name_ = name; + RunOnCallbackThread([this]() { + auto& env = MediumEnvironment::Instance(); + env.OnBluetoothAdapterChangedState(*this); + }); + return true; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h new file mode 100644 index 00000000..c2c1aba5 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -0,0 +1,104 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// BluetoothDevice and BluetoothAdapter have a mutual dependency. +class BluetoothAdapter; + +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice : public api::BluetoothDevice { + public: + ~BluetoothDevice() override = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const override; + BluetoothAdapter& GetAdapter(); + + private: + // Only BluetoothAdapter may instantiate BluetoothDevice. + friend class BluetoothAdapter; + + explicit BluetoothDevice(BluetoothAdapter* adapter); + + BluetoothAdapter& adapter_; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() = default; + ~BluetoothAdapter() override = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode mode) override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothDevice& GetDevice() { return device_; } + + private: + void RunOnCallbackThread(std::function runnable) { + serial_executor_.Execute(std::move(runnable)); + } + + mutable absl::Mutex mutex_; + BluetoothDevice device_{this}; + ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; + std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; + bool enabled_ ABSL_GUARDED_BY(mutex_) = false; + SingleThreadExecutor serial_executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/impl/g3/condition_variable.h b/cpp/platform_v2/impl/g3/condition_variable.h new file mode 100644 index 00000000..0a85047c --- /dev/null +++ b/cpp/platform_v2/impl/g3/condition_variable.h @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/impl/g3/mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(g3::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + Exception Wait() override { + cond_var_.Wait(mutex_); + return {Exception::kSuccess}; + } + void Notify() override { cond_var_.SignalAll(); } + + private: + absl::Mutex* mutex_; + absl::CondVar cond_var_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/g3/count_down_latch.h b/cpp/platform_v2/impl/g3/count_down_latch.h new file mode 100644 index 00000000..e0f683e7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/count_down_latch.h @@ -0,0 +1,73 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ + +#include "platform_v2/api/count_down_latch.h" +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + CountDownLatch(CountDownLatch&&) = delete; + CountDownLatch& operator=(CountDownLatch&&) = delete; + ExceptionOr Await(absl::Duration timeout) override { + absl::MutexLock lock(&mutex_); + absl::Time deadline = absl::Now() + timeout; + while (count_ > 0) { + if (cond_.WaitWithDeadline(&mutex_, deadline)) { + return ExceptionOr(false); + } + } + return ExceptionOr(true); + } + Exception Await() override { + absl::MutexLock lock(&mutex_); + while (count_ > 0) { + cond_.Wait(&mutex_); + } + return {Exception::kSuccess}; + } + void CountDown() override { + absl::MutexLock lock(&mutex_); + if (count_ > 0 && --count_ == 0) { + cond_.SignalAll(); + } + } + + private: + absl::Mutex mutex_; // Mutex to be used with cond_.Wait...() method family. + absl::CondVar cond_; // Condition to synchronize up to N waiting threads. + int count_ + ABSL_GUARDED_BY(mutex_); // When zero, latch should release all waiters. +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/impl/g3/crypto.cc b/cpp/platform_v2/impl/g3/crypto.cc new file mode 100644 index 00000000..92bf2078 --- /dev/null +++ b/cpp/platform_v2/impl/g3/crypto.cc @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/api/crypto.h" + +#include +#include + +#include "platform_v2/base/byte_array.h" +#include "absl/strings/string_view.h" +#include "openssl/digest.h" + +namespace location { +namespace nearby { + +// Initialize global crypto state. +void Crypto::Init() {} + +static ByteArray Hash(absl::string_view input, const EVP_MD* algo) { + unsigned int md_out_size = EVP_MAX_MD_SIZE; + uint8_t digest_buffer[EVP_MAX_MD_SIZE]; + if (input.empty()) return {}; + + if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo, + nullptr)) + return {}; + + return ByteArray{reinterpret_cast(digest_buffer), md_out_size}; +} + +// Return MD5 hash of input. +ByteArray Crypto::Md5(absl::string_view input) { + return Hash(input, EVP_md5()); +} + +// Return SHA256 hash of input. +ByteArray Crypto::Sha256(absl::string_view input) { + return Hash(input, EVP_sha256()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.cc b/cpp/platform_v2/impl/g3/medium_environment.cc new file mode 100644 index 00000000..99a8f234 --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.cc @@ -0,0 +1,46 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/impl/g3/medium_environment.h" + +namespace location { +namespace nearby { +namespace g3 { + +MediumEnvironment& MediumEnvironment::Instance() { + static std::aligned_storage_t + storage; + static MediumEnvironment* env = new (&storage) MediumEnvironment(); + return *env; +} + +void MediumEnvironment::Reset() { + absl::MutexLock lock(&mutex_); + bluetooth_adapters_.clear(); +} + +void MediumEnvironment::OnBluetoothAdapterChangedState( + BluetoothAdapter& adapter) { + absl::MutexLock lock(&mutex_); + // We don't care if there is an adapter already since all we store is a + // pointer. + bluetooth_adapters_.emplace(&adapter); + // TODO(apolyudov): Add event propagation code when Medium registration is + // implemented. +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/medium_environment.h b/cpp/platform_v2/impl/g3/medium_environment.h new file mode 100644 index 00000000..48d49ef7 --- /dev/null +++ b/cpp/platform_v2/impl/g3/medium_environment.h @@ -0,0 +1,61 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ +#define PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ + +#include +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// MediumEnvironment is a simulated environment which allowes multiple instances +// of simulated HW devices to "work" together as if they are physical. +// For each medium type it provides necessary methods to implement +// advertising, discovery and establishment of a data link. +class MediumEnvironment { + public: + ~MediumEnvironment() = default; + // Singleton constructor/accessor. + static MediumEnvironment& Instance(); + + // Clear state. No notifications are sent. + void Reset() ABSL_LOCKS_EXCLUDED(mutex_); + + // Add an adapter to internal container. + // Notify BluetoothClassicMediums if any that adapter state has changed. + void OnBluetoothAdapterChangedState(BluetoothAdapter& adapter) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + MediumEnvironment() = default; + absl::Mutex mutex_; + absl::flat_hash_set bluetooth_adapters_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MEDIUM_ENVIRONMENT_H_ diff --git a/cpp/platform_v2/impl/g3/multi_thread_executor.h b/cpp/platform_v2/impl/g3/multi_thread_executor.h new file mode 100644 index 00000000..c5cf331e --- /dev/null +++ b/cpp/platform_v2/impl/g3/multi_thread_executor.h @@ -0,0 +1,68 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ + +#include + +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_parallelism) + : thread_pool_(max_parallelism) { + thread_pool_.StartWorkers(); + } + void Execute(Runnable&& runnable) override { + if (!shutdown_) { + thread_pool_.Schedule(std::move(runnable)); + } + } + bool DoSubmit(Runnable&& runnable) override { + if (shutdown_) return false; + thread_pool_.Schedule(std::move(runnable)); + return true; + } + void Shutdown() override { DoShutdown(); } + ~MultiThreadExecutor() override { DoShutdown(); } + + void ScheduleAfter(absl::Duration delay, Runnable&& runnable) { + if (shutdown_) return; + thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable)); + } + bool InShutdown() const { return shutdown_; } + + private: + void DoShutdown() { + shutdown_ = true; + } + std::atomic_bool shutdown_ = false; + ThreadPool thread_pool_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/mutex.h b/cpp/platform_v2/impl/g3/mutex.h new file mode 100644 index 00000000..c0e15f58 --- /dev/null +++ b/cpp/platform_v2/impl/g3/mutex.h @@ -0,0 +1,61 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_MUTEX_H_ +#define PLATFORM_V2_IMPL_G3_MUTEX_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/impl/shared/posix_mutex.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex(bool check) : check_(check) {} + ~Mutex() override = default; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + if (!check_) mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; + bool check_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex { + public: + ~RecursiveMutex() override = default; + RecursiveMutex() = default; + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_MUTEX_H_ diff --git a/cpp/platform_v2/impl/g3/pipe.h b/cpp/platform_v2/impl/g3/pipe.h new file mode 100644 index 00000000..511436b2 --- /dev/null +++ b/cpp/platform_v2/impl/g3/pipe.h @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_PIPE_H_ +#define PLATFORM_V2_IMPL_G3_PIPE_H_ + +#include + +#include "platform_v2/base/base_pipe.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +class Pipe : public BasePipe { + public: + Pipe() { + auto mutex = std::make_unique(/*check=*/true); + auto cond = std::make_unique(mutex.get()); + Setup(std::move(mutex), std::move(cond)); + } + ~Pipe() override = default; + Pipe(Pipe &&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_PIPE_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc new file mode 100644 index 00000000..7bcc8e4c --- /dev/null +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -0,0 +1,150 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/api/platform.h" + +#include +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/ble.h" +#include "platform_v2/api/ble_v2.h" +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/api/server_sync.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/api/webrtc.h" +#include "platform_v2/api/wifi.h" +#include "platform_v2/impl/g3/atomic_boolean.h" +#include "platform_v2/impl/g3/atomic_reference_any.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/condition_variable.h" +#include "platform_v2/impl/g3/count_down_latch.h" +#include "platform_v2/impl/g3/multi_thread_executor.h" +#include "platform_v2/impl/g3/mutex.h" +#include "platform_v2/impl/g3/scheduled_executor.h" +#include "platform_v2/impl/g3/settable_future_any.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/base/integral_types.h" +#include "absl/memory/memory.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace api { + +std::unique_ptr +ImplementationPlatform::CreateSingleThreadExecutor() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateMultiThreadExecutor(int max_concurrency) { + return absl::make_unique(max_concurrency); +} + +std::unique_ptr +ImplementationPlatform::CreateScheduledExecutor() { + return absl::make_unique(); +} + +std::unique_ptr> +ImplementationPlatform::CreateAtomicReferenceAny(absl::any initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr> +ImplementationPlatform::CreateSettableFutureAny() { + return absl::make_unique(); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothAdapter() { + return absl::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch( + std::int32_t count) { + return absl::make_unique(count); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicBoolean( + bool initial_value) { + return absl::make_unique(initial_value); +} + +std::unique_ptr +ImplementationPlatform::CreateBluetoothClassicMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateServerSyncMedium() { + return std::unique_ptr(/*new ServerSyncMediumImpl()*/); +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { + return std::unique_ptr(); +} + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::unique_ptr(); +} + +std::unique_ptr +ImplementationPlatform::CreateWebRtcSignalingMessenger( + absl::string_view self_id) { + return std::unique_ptr( + /*new FCMSignalingMessenger()*/); +} + +std::unique_ptr ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + if (mode == Mutex::Mode::kRecursive) + return absl::make_unique(); + else + return absl::make_unique(mode == Mutex::Mode::kRegular); +} + +std::unique_ptr +ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::unique_ptr( + new g3::ConditionVariable(static_cast(mutex))); +} + +std::string ImplementationPlatform::GetDeviceId() { + // TODO(alexchau): Get deviceId from base + return "google3"; +} + +std::string ImplementationPlatform::GetPayloadPath(int64_t payload_id) { + return "/tmp/" + std::to_string(payload_id); +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.cc b/cpp/platform_v2/impl/g3/scheduled_executor.cc new file mode 100644 index 00000000..4e609d52 --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.cc @@ -0,0 +1,79 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/impl/g3/scheduled_executor.h" + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { +namespace g3 { + +namespace { + +class ScheduledCancelable : public api::Cancelable { + public: + bool Cancel() override { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kCanceled)) { + return true; + } + } + return false; + } + bool MarkExecuted() { + Status expected = kNotRun; + while (expected == kNotRun) { + if (status_.compare_exchange_strong(expected, kExecuted)) { + return true; + } + } + return false; + } + + private: + enum Status { + kNotRun, + kExecuted, + kCanceled, + }; + std::atomic status_ = kNotRun; +}; + +} // namespace + +std::shared_ptr ScheduledExecutor::Schedule( + Runnable&& runnable, absl::Duration delay) { + auto scheduled_cancelable = std::make_shared(); + if (executor_.InShutdown()) { + return scheduled_cancelable; + } + executor_.ScheduleAfter( + delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() { + if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) { + runnable(); + } + }); + return scheduled_cancelable; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/scheduled_executor.h b/cpp/platform_v2/impl/g3/scheduled_executor.h new file mode 100644 index 00000000..09553eaa --- /dev/null +++ b/cpp/platform_v2/impl/g3/scheduled_executor.h @@ -0,0 +1,56 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/impl/g3/single_thread_executor.h" +#include "absl/time/clock.h" +#include "thread/threadpool.h" + +namespace location { +namespace nearby { +namespace g3 { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +class ScheduledExecutor final : public api::ScheduledExecutor { + public: + ScheduledExecutor() = default; + ~ScheduledExecutor() override { + executor_.Shutdown(); + } + + void Execute(Runnable&& runnable) override { + executor_.Execute(std::move(runnable)); + } + std::shared_ptr Schedule(Runnable&& runnable, + absl::Duration delay) override; + void Shutdown() override { executor_.Shutdown(); } + + private: + SingleThreadExecutor executor_; +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/settable_future_any.h b/cpp/platform_v2/impl/g3/settable_future_any.h new file mode 100644 index 00000000..6b600cb4 --- /dev/null +++ b/cpp/platform_v2/impl/g3/settable_future_any.h @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#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/api2/single_thread_executor.h b/cpp/platform_v2/impl/g3/single_thread_executor.h similarity index 61% rename from cpp/platform/api2/single_thread_executor.h rename to cpp/platform_v2/impl/g3/single_thread_executor.h index 56319d3e..b02cb87c 100644 --- a/cpp/platform/api2/single_thread_executor.h +++ b/cpp/platform_v2/impl/g3/single_thread_executor.h @@ -12,26 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ -#define PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ +#ifndef PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ -#include "platform/api2/submittable_executor.h" +#include "platform_v2/impl/g3/multi_thread_executor.h" namespace location { namespace nearby { +namespace g3 { // An Executor that uses a single worker thread operating off an unbounded // queue. -// -// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- -template -class SingleThreadExecutor - : public SubmittableExecutor { +class SingleThreadExecutor final : public MultiThreadExecutor { public: - ~SingleThreadExecutor() override {} + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; }; +} // namespace g3 } // namespace nearby } // namespace location -#endif // PLATFORM_API2_SINGLE_THREAD_EXECUTOR_H_ +#endif // PLATFORM_V2_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/impl/g3/system_clock.cc b/cpp/platform_v2/impl/g3/system_clock.cc new file mode 100644 index 00000000..89dfa2c1 --- /dev/null +++ b/cpp/platform_v2/impl/g3/system_clock.cc @@ -0,0 +1,30 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/api/system_clock.h" + +#include "platform_v2/base/exception.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +absl::Time SystemClock::ElapsedRealtime() { return absl::Now(); } +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/BUILD b/cpp/platform_v2/impl/shared/BUILD new file mode 100644 index 00000000..d02b0892 --- /dev/null +++ b/cpp/platform_v2/impl/shared/BUILD @@ -0,0 +1,48 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "posix_mutex", + srcs = [ + "posix_mutex.cc", + ], + hdrs = [ + "posix_mutex.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + ], +) + +cc_library( + name = "posix_condition_variable", + srcs = [ + "posix_condition_variable.cc", + ], + hdrs = [ + "posix_condition_variable.h", + ], + visibility = [ + "//platform_v2/impl:__subpackages__", + ], + deps = [ + ":posix_mutex", + "//platform_v2/api", + "//platform_v2/base", + ], +) diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.cc b/cpp/platform_v2/impl/shared/posix_condition_variable.cc new file mode 100644 index 00000000..4d5da456 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.cc @@ -0,0 +1,44 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/impl/shared/posix_condition_variable.h" + +namespace location { +namespace nearby { +namespace posix { + +ConditionVariable::ConditionVariable(Mutex* mutex) + : mutex_(mutex), attr_(), cond_() { + pthread_condattr_init(&attr_); + + pthread_cond_init(&cond_, &attr_); +} + +ConditionVariable::~ConditionVariable() { + pthread_cond_destroy(&cond_); + + pthread_condattr_destroy(&attr_); +} + +void ConditionVariable::Notify() { pthread_cond_broadcast(&cond_); } + +Exception ConditionVariable::Wait() { + pthread_cond_wait(&cond_, &(mutex_->mutex_)); + + return {Exception::kSuccess}; +} + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_condition_variable.h b/cpp/platform_v2/impl/shared/posix_condition_variable.h new file mode 100644 index 00000000..184d876b --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_condition_variable.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ + +#include + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(Mutex* mutex); + ~ConditionVariable() override; + + void Notify() override; + Exception Wait() override; + + private: + Mutex* mutex_; + pthread_condattr_t attr_; + pthread_cond_t cond_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/impl/shared/posix_mutex.cc b/cpp/platform_v2/impl/shared/posix_mutex.cc new file mode 100644 index 00000000..c8ba6329 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.cc @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/impl/shared/posix_mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +Mutex::Mutex() : attr_(), mutex_() { + pthread_mutexattr_init(&attr_); + pthread_mutexattr_settype(&attr_, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&mutex_, &attr_); +} + +Mutex::~Mutex() { + pthread_mutex_destroy(&mutex_); + + pthread_mutexattr_destroy(&attr_); +} + +void Mutex::Lock() { pthread_mutex_lock(&mutex_); } + +void Mutex::Unlock() { pthread_mutex_unlock(&mutex_); } + +} // namespace posix +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/shared/posix_mutex.h b/cpp/platform_v2/impl/shared/posix_mutex.h new file mode 100644 index 00000000..d69f4de5 --- /dev/null +++ b/cpp/platform_v2/impl/shared/posix_mutex.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ +#define PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" + +namespace location { +namespace nearby { +namespace posix { + +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + Mutex(); + ~Mutex() override; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override; + void Unlock() ABSL_UNLOCK_FUNCTION() override; + + private: + friend class ConditionVariable; + + pthread_mutexattr_t attr_; + pthread_mutex_t mutex_; +}; + +} // namespace posix +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_SHARED_POSIX_MUTEX_H_ diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD new file mode 100644 index 00000000..203b6dbb --- /dev/null +++ b/cpp/platform_v2/public/BUILD @@ -0,0 +1,100 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "public", + srcs = [ + "file.cc", + "pipe.cc", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_reference.h", + "bluetooth_adapter.h", + "cancelable.h", + "cancelable_alarm.h", + "condition_variable.h", + "count_down_latch.h", + "crypto.h", + "file.h", + "future.h", + "multi_thread_executor.h", + "mutex.h", + "mutex_lock.h", + "pipe.h", + "scheduled_executor.h", + "single_thread_executor.h", + "submittable_executor.h", + "system_clock.h", + ], + visibility = [ + "//core_v2:__subpackages__", + "//platform_v2/impl:__subpackages__", + ], + deps = [ + "//platform_v2/api", + "//platform_v2/base", + "//platform_v2/base:util", + "//absl/base:core_headers", + "//absl/strings", + "//absl/time", + "//absl/types:any", + ], +) + +cc_library( + name = "logging", + hdrs = [ + "logging.h", + ], + visibility = [ + "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//core_v2:__subpackages__", + "//platform_v2:__subpackages__", + ], + deps = [ + "//platform:logging", + ], +) + +cc_test( + name = "public_test", + srcs = [ + "atomic_boolean_test.cc", + "atomic_reference_test.cc", + "bluetooth_adapter_test.cc", + "count_down_latch_test.cc", + "crypto_test.cc", + "file_test.cc", + "future_test.cc", + "logging_test.cc", + "multi_thread_executor_test.cc", + "mutex_test.cc", + "pipe_test.cc", + "scheduled_executor_test.cc", + "single_thread_executor_test.cc", + ], + shard_count = 16, + deps = [ + ":logging", + ":public", + "//file/util:temp_path", + "//platform_v2/base", + "//platform_v2/impl/g3", + "//testing/base/public:gunit_main", + "//absl/strings", + "//absl/synchronization", + "//absl/time", + ], +) diff --git a/cpp/platform_v2/public/atomic_boolean.h b/cpp/platform_v2/public/atomic_boolean.h new file mode 100644 index 00000000..2b94ac1b --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean.h @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ + +#include + +#include "platform_v2/api/atomic_boolean.h" +#include "platform_v2/api/platform.h" + +namespace location { +namespace nearby { + +// A boolean value that may be updated atomically. +// See documentation in +// cpp/platform_v2/api/atomic_boolean.h +class AtomicBoolean final : public api::AtomicBoolean { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicBoolean(bool value = false) + : impl_(Platform::CreateAtomicBoolean(value)) {} + ~AtomicBoolean() override = default; + AtomicBoolean(AtomicBoolean&&) = default; + AtomicBoolean& operator=(AtomicBoolean&&) = default; + + bool Get() const override { return impl_->Get(); } + bool Set(bool value) override { return impl_->Set(value); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_BOOLEAN_H_ diff --git a/cpp/platform_v2/public/atomic_boolean_test.cc b/cpp/platform_v2/public/atomic_boolean_test.cc new file mode 100644 index 00000000..11e19253 --- /dev/null +++ b/cpp/platform_v2/public/atomic_boolean_test.cc @@ -0,0 +1,38 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/atomic_boolean.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(AtomicBooleanTest, SetReturnsPrevoiusValue) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Set(true)); +} + +TEST(AtomicBooleanTest, GetReturnsWhatWasSet) { + AtomicBoolean value(false); + EXPECT_FALSE(value.Set(true)); + EXPECT_TRUE(value.Get()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/atomic_reference.h b/cpp/platform_v2/public/atomic_reference.h new file mode 100644 index 00000000..14fea551 --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference.h @@ -0,0 +1,54 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ +#define PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ + +#include + +#include "platform_v2/api/atomic_reference.h" +#include "platform_v2/api/platform.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +// An object reference that may be updated atomically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html +template +class AtomicReference final : public api::AtomicReference { + public: + using Platform = api::ImplementationPlatform; + explicit AtomicReference(const T& value) + : impl_(Platform::CreateAtomicReferenceAny(value)) {} + explicit AtomicReference(T&& value) + : impl_(Platform::CreateAtomicReferenceAny(std::move(value))) {} + ~AtomicReference() override = default; + AtomicReference(AtomicReference&&) = default; + AtomicReference& operator=(AtomicReference&&) = default; + + T Get() const& override { return absl::any_cast(impl_->Get()); } + T Get() && override { return absl::any_cast(std::move(impl_->Get())); } + void Set(const T& value) override { impl_->Set(absl::any(value)); } + void Set(T&& value) override { impl_->Set(absl::any(value)); } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_ATOMIC_REFERENCE_H_ diff --git a/cpp/platform_v2/public/atomic_reference_test.cc b/cpp/platform_v2/public/atomic_reference_test.cc new file mode 100644 index 00000000..5cb34605 --- /dev/null +++ b/cpp/platform_v2/public/atomic_reference_test.cc @@ -0,0 +1,89 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/atomic_reference.h" + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +struct BigSizedStruct { + int data[100]{}; +}; + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(AtomicReferenceTest, SupportIntegralTypes) { + AtomicReference atomic_ref({}); + atomic_ref.Set(5); + EXPECT_EQ(atomic_ref.Get(), 5); +} + +TEST(AtomicReferenceTest, SupportEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(TestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), TestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SupportScopedEnum) { + AtomicReference atomic_ref({}); + atomic_ref.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(atomic_ref.Get(), ScopedTestEnum::kValue1); +} + +TEST(AtomicReferenceTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + AtomicReference atomic_ref({}); + v1.data[0] = 5; // Changing value before calling set() will affect stored + v1.data[7] = 3; // value. + atomic_ref.Set(v1); + v1.data[1] = 6; // Changing value after calling set() will not affect stored + v1.data[5] = 4; // value. + BigSizedStruct v2 = atomic_ref.Get(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(AtomicReferenceTest, SupportObjects) { + std::string s{"test"}; + AtomicReference atomic_ref({}); + atomic_ref.Set(s); + EXPECT_EQ(s, atomic_ref.Get()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h new file mode 100644 index 00000000..a0e801eb --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -0,0 +1,77 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ + +#include + +#include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/platform.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html +class BluetoothAdapter : public api::BluetoothAdapter { + public: + using Status = api::BluetoothAdapter::Status; + using ScanMode = api::BluetoothAdapter::ScanMode; + + BluetoothAdapter() + : impl_(api::ImplementationPlatform::CreateBluetoothAdapter()) {} + ~BluetoothAdapter() override = default; + BluetoothAdapter(BluetoothAdapter&&) = default; + BluetoothAdapter& operator=(BluetoothAdapter&&) = default; + + // Synchronously sets the status of the BluetoothAdapter to 'status', and + // returns true if the operation was a success. + bool SetStatus(Status status) override { return impl_->SetStatus(status); } + Status GetStatus() const { + return IsEnabled() ? Status::kEnabled : Status::kDisabled; + } + + // Returns true if the BluetoothAdapter's current status is + // Status::Value::kEnabled. + bool IsEnabled() const override { return impl_->IsEnabled(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() + // + // Returns ScanMode::kUnknown on error. + ScanMode GetScanMode() const override { return impl_->GetScanMode(); } + + // Synchronously sets the scan mode of the adapter, and returns true if the + // operation was a success. + bool SetScanMode(ScanMode scan_mode) override { + return impl_->SetScanMode(scan_mode); + } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() + // Returns an empty string on error + std::string GetName() const override { return impl_->GetName(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) + bool SetName(absl::string_view name) override { return impl_->SetName(name); } + + bool IsValid() const { return impl_ != nullptr; } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLUETOOTH_ADAPTER_H_ diff --git a/cpp/platform_v2/public/bluetooth_adapter_test.cc b/cpp/platform_v2/public/bluetooth_adapter_test.cc new file mode 100644 index 00000000..f84cbafa --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_adapter_test.cc @@ -0,0 +1,58 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/bluetooth_adapter.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(BluetoothAdapterTest, ConstructorDestructorWorks) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.IsValid()); +} + +TEST(BluetoothAdapterTest, CanSetName) { + constexpr char kAdapterName[] = "MyBtAdapter"; + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetName(kAdapterName)); + EXPECT_EQ(adapter.GetName(), std::string(kAdapterName)); +} + +TEST(BluetoothAdapterTest, CanSetStatus) { + BluetoothAdapter adapter; + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kDisabled); + EXPECT_TRUE(adapter.SetStatus(BluetoothAdapter::Status::kEnabled)); + EXPECT_EQ(adapter.GetStatus(), BluetoothAdapter::Status::kEnabled); +} + +TEST(BluetoothAdapterTest, CanSetMode) { + BluetoothAdapter adapter; + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kConnectable)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kConnectable); + EXPECT_TRUE(adapter.SetScanMode( + BluetoothAdapter::ScanMode::kConnectableDiscoverable)); + EXPECT_EQ(adapter.GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(adapter.SetScanMode(BluetoothAdapter::ScanMode::kNone)); + EXPECT_EQ(adapter.GetScanMode(), BluetoothAdapter::ScanMode::kNone); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/cancelable.h b/cpp/platform_v2/public/cancelable.h new file mode 100644 index 00000000..29aa1dac --- /dev/null +++ b/cpp/platform_v2/public/cancelable.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_H_ + +#include +#include + +#include "platform_v2/api/cancelable.h" + +namespace location { +namespace nearby { + +// An interface to provide a cancellation mechanism for objects that represent +// long-running operations. +class Cancelable final { + public: + Cancelable() = default; + Cancelable(const Cancelable&) = default; + Cancelable& operator=(const Cancelable& other) = default; + + ~Cancelable() = default; + + // This constructor is used internally only, + // by other classes in "//platform_v2/public/". + explicit Cancelable(std::shared_ptr impl) + : impl_(std::move(impl)) {} + + bool Cancel() { return impl_->Cancel(); } + + private: + std::shared_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_H_ diff --git a/cpp/platform_v2/public/cancelable_alarm.h b/cpp/platform_v2/public/cancelable_alarm.h new file mode 100644 index 00000000..698714db --- /dev/null +++ b/cpp/platform_v2/public/cancelable_alarm.h @@ -0,0 +1,70 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ +#define PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ + +#include +#include +#include +#include + +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "platform_v2/public/scheduled_executor.h" + +namespace location { +namespace nearby { + +/** + * A cancelable alarm with a name. This is a simple wrapper around the logic + * for posting a Runnable on a ScheduledExecutor and (possibly) later + * canceling it. + */ +class CancelableAlarm { + public: + CancelableAlarm(absl::string_view name, std::function&& runnable, + absl::Duration delay, ScheduledExecutor* scheduled_executor) + : name_(name), + cancelable_(scheduled_executor->Schedule(std::move(runnable), delay)) {} + ~CancelableAlarm() = default; + CancelableAlarm(CancelableAlarm&& other) { + *this = std::move(other); + } + CancelableAlarm& operator=(CancelableAlarm&& other) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + name_ = std::move(other.name_); + cancelable_ = std::move(other.cancelable_); + } + return *this; + } + + bool Cancel() { + MutexLock lock(&mutex_); + return cancelable_.Cancel(); + } + + private: + Mutex mutex_; + std::string name_; + Cancelable cancelable_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CANCELABLE_ALARM_H_ diff --git a/cpp/platform_v2/public/condition_variable.h b/cpp/platform_v2/public/condition_variable.h new file mode 100644 index 00000000..b8859f9c --- /dev/null +++ b/cpp/platform_v2/public/condition_variable.h @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ +#define PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ + +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/public/mutex.h" + +namespace location { +namespace nearby { + +// The ConditionVariable class is a synchronization primitive that can be used +// to block a thread, or multiple threads at the same time, until another thread +// both modifies a shared variable (the condition), and notifies the +// ConditionVariable. +class ConditionVariable final { + public: + using Platform = api::ImplementationPlatform; + explicit ConditionVariable(Mutex* mutex) + : impl_(Platform::CreateConditionVariable(mutex->impl_.get())) {} + ConditionVariable(ConditionVariable&&) = default; + ConditionVariable& operator=(ConditionVariable&&) = default; + + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify-- + void Notify() { impl_->Notify(); } + // https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait-- + Exception Wait() { return impl_->Wait(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_CONDITION_VARIABLE_H_ diff --git a/cpp/platform_v2/public/count_down_latch.h b/cpp/platform_v2/public/count_down_latch.h new file mode 100644 index 00000000..3a8958a0 --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch.h @@ -0,0 +1,54 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ +#define PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ + +#include + +#include "platform_v2/api/count_down_latch.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/base/exception.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// A synchronization aid that allows one or more threads to wait until a set of +// operations being performed in other threads completes. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html +class CountDownLatch final { + public: + using Platform = api::ImplementationPlatform; + explicit CountDownLatch(int count) + : impl_(Platform::CreateCountDownLatch(count)) {} + CountDownLatch(CountDownLatch&&) = default; + CountDownLatch& operator=(CountDownLatch&&) = default; + ~CountDownLatch() = default; + + Exception Await() { return impl_->Await(); } + ExceptionOr Await(absl::Duration timeout) { + return impl_->Await(timeout); + } + void CountDown() { impl_->CountDown(); } + + private: + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_COUNT_DOWN_LATCH_H_ diff --git a/cpp/platform_v2/public/count_down_latch_test.cc b/cpp/platform_v2/public/count_down_latch_test.cc new file mode 100644 index 00000000..ad1cfc56 --- /dev/null +++ b/cpp/platform_v2/public/count_down_latch_test.cc @@ -0,0 +1,62 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/count_down_latch.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace { + +TEST(CountDownLatch, ConstructorDestructorWorks) { CountDownLatch latch(1); } + +TEST(CountDownLatch, LatchAwaitCanWait) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchExtraCountDownIgnored) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + std::atomic_bool done = false; + executor.Execute([&done, &latch]() { + done = true; + latch.CountDown(); + latch.CountDown(); + latch.CountDown(); + }); + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatch, LatchAwaitWithTimeoutCanExpire) { + CountDownLatch latch(1); + SingleThreadExecutor executor; + auto response = latch.Await(absl::Milliseconds(100)); + EXPECT_TRUE(response.ok()); + EXPECT_FALSE(response.result()); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/crypto.h b/cpp/platform_v2/public/crypto.h new file mode 100644 index 00000000..f3efedb4 --- /dev/null +++ b/cpp/platform_v2/public/crypto.h @@ -0,0 +1,20 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_CRYPTO_H_ +#define PLATFORM_V2_PUBLIC_CRYPTO_H_ + +#include "platform_v2/api/crypto.h" + +#endif // PLATFORM_V2_PUBLIC_CRYPTO_H_ diff --git a/cpp/platform_v2/public/crypto_test.cc b/cpp/platform_v2/public/crypto_test.cc new file mode 100644 index 00000000..a245f157 --- /dev/null +++ b/cpp/platform_v2/public/crypto_test.cc @@ -0,0 +1,48 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/crypto.h" + +#include "platform_v2/base/byte_array.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(CryptoTest, Md5GeneratesHash) { + const ByteArray expected_md5( + "\xb4\x5c\xff\xe0\x84\xdd\x3d\x20\xd9\x28\xbe\xe8\x5e\x7b\x0f\x21"); + ByteArray md5_hash = Crypto::Md5("string"); + EXPECT_EQ(md5_hash, expected_md5); +} + +TEST(CryptoTest, Md5ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Md5(""), ByteArray{}); +} + +TEST(CryptoTest, Sha256GeneratesHash) { + const ByteArray expected_sha256( + "\x47\x32\x87\xf8\x29\x8d\xba\x71\x63\xa8\x97\x90\x89\x58\xf7\xc0" + "\xea\xe7\x33\xe2\x5d\x2e\x02\x79\x92\xea\x2e\xdc\x9b\xed\x2f\xa8"); + ByteArray sha256_hash = Crypto::Sha256("string"); + EXPECT_EQ(sha256_hash, expected_sha256); +} + +TEST(CryptoTest, Sha256ReturnsEmptyOnError) { + EXPECT_EQ(Crypto::Sha256(""), ByteArray{}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.cc b/cpp/platform_v2/public/file.cc new file mode 100644 index 00000000..917d42b5 --- /dev/null +++ b/cpp/platform_v2/public/file.cc @@ -0,0 +1,93 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/file.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +// InputFile + +InputFile::InputFile(const std::string& path, std::int64_t size) + : file_(path), path_(path), total_size_(size) {} + +ExceptionOr InputFile::Read(std::int64_t size) { + if (!file_.is_open()) { + return ExceptionOr{Exception::kIo}; + } + + if (file_.peek() == EOF) { + return ExceptionOr{ByteArray{}}; + } + + if (!file_.good()) { + return ExceptionOr{Exception::kIo}; + } + + ByteArray bytes(size); + std::unique_ptr read_bytes{new char[size]}; + file_.read(read_bytes.get(), static_cast(size)); + auto num_bytes_read = file_.gcount(); + if (num_bytes_read == 0) { + return ExceptionOr{Exception::kIo}; + } + + return ExceptionOr(ByteArray(read_bytes.get(), num_bytes_read)); +} + +Exception InputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +// OutputFile + +OutputFile::OutputFile(absl::string_view path) : file_(path) {} + +Exception OutputFile::Write(const ByteArray& data) { + if (!file_.is_open()) { + return {Exception::kIo}; + } + + if (!file_.good()) { + return {Exception::kIo}; + } + + file_.write(data.data(), data.size()); + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Flush() { + file_.flush(); + return {file_.good() ? Exception::kSuccess : Exception::kIo}; +} + +Exception OutputFile::Close() { + if (file_.is_open()) { + file_.close(); + } + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/file.h b/cpp/platform_v2/public/file.h new file mode 100644 index 00000000..5eec975f --- /dev/null +++ b/cpp/platform_v2/public/file.h @@ -0,0 +1,65 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_FILE_H_ +#define PLATFORM_V2_PUBLIC_FILE_H_ + +#include +#include + +#include "platform_v2/api/input_file.h" +#include "platform_v2/api/output_file.h" +#include "platform_v2/base/exception.h" +#include "absl/strings/string_view.h" + +namespace location { +namespace nearby { + +class InputFile final : public api::InputFile { + public: + explicit InputFile(const std::string& path, std::int64_t size); + ~InputFile() override = default; + InputFile(InputFile&&) = default; + InputFile& operator=(InputFile&&) = default; + + ExceptionOr Read(std::int64_t size) override; + std::string GetFilePath() const override { return path_; } + std::int64_t GetTotalSize() const override { return total_size_; } + Exception Close() override; + + private: + std::ifstream file_; + std::string path_; + std::int64_t total_size_; +}; + +class OutputFile final : public api::OutputFile { + public: + explicit OutputFile(absl::string_view path); + ~OutputFile() override = default; + OutputFile(OutputFile&&) = default; + OutputFile& operator=(OutputFile&&) = default; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + std::ofstream file_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FILE_H_ diff --git a/cpp/platform_v2/public/file_test.cc b/cpp/platform_v2/public/file_test.cc new file mode 100644 index 00000000..705b21db --- /dev/null +++ b/cpp/platform_v2/public/file_test.cc @@ -0,0 +1,145 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/file.h" + +#include +#include +#include +#include + +#include "file/util/temp_path.h" +#include "platform_v2/base/byte_array.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +class FileTest : public ::testing::Test { + protected: + void SetUp() override { + temp_path_ = std::make_unique(TempPath::Local); + path_ = temp_path_->path() + "/file.txt"; + std::ofstream output_file(path_); + file_ = std::fstream(path_, std::fstream::in | std::fstream::out); + } + + void WriteToFile(const std::string& text) { + file_ << text; + file_.flush(); + size_ += text.size(); + } + + size_t GetSize() const { return size_; } + + void AssertEquals(const ExceptionOr& bytes, + const std::string& expected) { + EXPECT_TRUE(bytes.ok()); + EXPECT_EQ(std::string(bytes.result()), expected); + } + + void AssertEmpty(const ExceptionOr& bytes) { + EXPECT_TRUE(bytes.ok()); + EXPECT_TRUE(bytes.result().Empty()); + } + + static constexpr int64_t kMaxSize = 3; + + std::unique_ptr temp_path_; + std::string path_; + std::fstream file_; + size_t size_ = 0; +}; + +TEST_F(FileTest, InputFile_NonExistentPath) { + InputFile input_file("/not/a/valid/path.txt", GetSize()); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, InputFile_GetFilePath) { + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetFilePath(), path_); +} + +TEST_F(FileTest, InputFile_EmptyFileEOF) { + InputFile input_file(path_, GetSize()); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWorks) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Read(kMaxSize); + SUCCEED(); +} + +TEST_F(FileTest, InputFile_ReadUntilEOF) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_ReadWithSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(2), "ab"); + AssertEquals(input_file.Read(1), "c"); + AssertEmpty(input_file.Read(kMaxSize)); +} + +TEST_F(FileTest, InputFile_GetTotalSize) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + EXPECT_EQ(input_file.GetTotalSize(), 3); + AssertEquals(input_file.Read(1), "a"); + EXPECT_EQ(input_file.GetTotalSize(), 3); +} + +TEST_F(FileTest, InputFile_Close) { + WriteToFile("abc"); + InputFile input_file(path_, GetSize()); + input_file.Close(); + ExceptionOr read_result = input_file.Read(kMaxSize); + EXPECT_FALSE(read_result.ok()); + EXPECT_TRUE(read_result.GetException().Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_NonExistentPath) { + OutputFile output_file("/not/a/valid/path.txt"); + ByteArray bytes("a", 1); + EXPECT_TRUE(output_file.Write(bytes).Raised(Exception::kIo)); +} + +TEST_F(FileTest, OutputFile_Write) { + OutputFile output_file(path_); + ByteArray bytes1("a"); + ByteArray bytes2("bc"); + EXPECT_EQ(output_file.Write(bytes1), Exception{Exception::kSuccess}); + EXPECT_EQ(output_file.Write(bytes2), Exception{Exception::kSuccess}); + InputFile input_file(path_, GetSize()); + AssertEquals(input_file.Read(kMaxSize), "abc"); +} + +TEST_F(FileTest, OutputFile_Close) { + OutputFile output_file(path_); + output_file.Close(); + ByteArray bytes("a"); + EXPECT_EQ(output_file.Write(bytes), Exception{Exception::kIo}); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/future.h b/cpp/platform_v2/public/future.h new file mode 100644 index 00000000..6c617664 --- /dev/null +++ b/cpp/platform_v2/public/future.h @@ -0,0 +1,77 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_FUTURE_H_ +#define PLATFORM_V2_PUBLIC_FUTURE_H_ + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/platform.h" +#include "platform_v2/api/settable_future.h" +#include "platform_v2/base/exception.h" +#include "platform_v2/base/runnable.h" +#include "absl/time/time.h" +#include "absl/types/any.h" + +namespace location { +namespace nearby { + +template +class Future final : public api::SettableFuture { + public: + using Platform = api::ImplementationPlatform; + ~Future() override = default; + Future() : impl_(Platform::CreateSettableFutureAny().release()) {} + Future(Future&& other) = default; + Future& operator=(Future&& other) = default; + + void AddListener(Runnable runnable, api::Executor* executor) override { + impl_->AddListener(runnable, executor); + } + bool Set(const T& value) override { return impl_->Set(absl::any(value)); } + bool Set(T&& value) override { return impl_->Set(absl::any(value)); } + bool SetException(Exception exception) override { + return impl_->SetException(exception); + } + // throws Exception::kInterrupted, Exception::kExecution + ExceptionOr Get() override { + auto ret_val = impl_->Get(); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + // throws Exception::kInterrupted, Exception::kExecution + // throws Exception::kTimeout if timeout is exceeded while waiting for + // result. + ExceptionOr Get(absl::Duration timeout) override { + auto ret_val = impl_->Get(timeout); + if (ret_val.ok()) { + T result = std::any_cast(ret_val.result()); + return ExceptionOr{result}; + } else { + return ExceptionOr{ret_val.exception()}; + } + } + + private: + std::unique_ptr> impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_FUTURE_H_ diff --git a/cpp/platform_v2/public/future_test.cc b/cpp/platform_v2/public/future_test.cc new file mode 100644 index 00000000..e9cdc9dd --- /dev/null +++ b/cpp/platform_v2/public/future_test.cc @@ -0,0 +1,116 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/future.h" + +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { + +enum TestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +enum class ScopedTestEnum { + kValue1 = 1, + kValue2 = 2, +}; + +struct BigSizedStruct { + int data[100]{}; +}; + +bool operator==(const BigSizedStruct& a, const BigSizedStruct& b) { + return memcmp(a.data, b.data, sizeof(BigSizedStruct::data)) == 0; +} + +bool operator!=(const BigSizedStruct& a, const BigSizedStruct& b) { + return !(a == b); +} + +} // namespace + +TEST(FutureTest, SupportIntegralTypes) { + Future future; + future.Set(5); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), 5); +} + +TEST(FutureTest, SetExceptionIsPropagated) { + Future future; + future.SetException({Exception::kIo}); + EXPECT_EQ(future.Get().exception(), Exception::kIo); +} + +TEST(FutureTest, SupportEnum) { + Future future; + future.Set(TestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), TestEnum::kValue1); +} + +TEST(FutureTest, SupportScopedEnum) { + Future future; + future.Set(ScopedTestEnum::kValue1); + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + EXPECT_EQ(future.Get().result(), ScopedTestEnum::kValue1); +} + +TEST(FutureTest, SetTakesCopyOfValue) { + // Default constructor is zero-initalizing all data in BigSizedStruct. + BigSizedStruct v1; + Future future; + v1.data[0] = 5; // Changing value before calling Set() will affect stored + v1.data[7] = 3; // value. + future.Set(v1); + v1.data[1] = 6; // Changing value after calling Set() will not affect stored + v1.data[5] = 4; // value. + EXPECT_EQ(future.Get().exception(), Exception::kSuccess); + BigSizedStruct v2 = future.Get().result(); + EXPECT_NE(v1, v2); + v1.data[1] = 0; + v1.data[5] = 0; + EXPECT_EQ(v2, v1); +} + +TEST(FutureTest, SetsExceptionOnTimeout) { + Future future; + EXPECT_EQ(future.Get(absl::Milliseconds(100)).exception(), + Exception::kTimeout); +} + +TEST(FutureTest, GetBlocksWhenNotReady) { + Future future; + SingleThreadExecutor executor; + absl::Time start = absl::Now(); + executor.Execute([&future](){ + absl::SleepFor(absl::Milliseconds(500)); + future.Set(10); + }); + auto response = future.Get(); + absl::Duration blocked_duration = absl::Now() - start; + EXPECT_EQ(response.result(), 10); + EXPECT_GE(blocked_duration, absl::Milliseconds(500)); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/logging.h b/cpp/platform_v2/public/logging.h new file mode 100644 index 00000000..7f6dc7bf --- /dev/null +++ b/cpp/platform_v2/public/logging.h @@ -0,0 +1,20 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_LOGGING_H_ +#define PLATFORM_V2_PUBLIC_LOGGING_H_ + +#include "platform/logging.h" + +#endif // PLATFORM_V2_PUBLIC_LOGGING_H_ diff --git a/cpp/platform_v2/public/logging_test.cc b/cpp/platform_v2/public/logging_test.cc new file mode 100644 index 00000000..21e63429 --- /dev/null +++ b/cpp/platform_v2/public/logging_test.cc @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/logging.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +TEST(LoggingTest, CanLog) { + NEARBY_LOG(INFO, "message"); +} + +} diff --git a/cpp/platform_v2/public/multi_thread_executor.h b/cpp/platform_v2/public/multi_thread_executor.h new file mode 100644 index 00000000..3e4696ef --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor.h @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ + +#include "platform_v2/api/platform.h" +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that reuses a fixed number of threads operating off a shared +// unbounded queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool-int- +class MultiThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + explicit MultiThreadExecutor(int max_parallelism) + : SubmittableExecutor( + Platform::CreateMultiThreadExecutor(max_parallelism)) {} + MultiThreadExecutor(MultiThreadExecutor&&) = default; + MultiThreadExecutor& operator=(MultiThreadExecutor&&) = default; + ~MultiThreadExecutor() override = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/multi_thread_executor_test.cc b/cpp/platform_v2/public/multi_thread_executor_test.cc new file mode 100644 index 00000000..43d88f2f --- /dev/null +++ b/cpp/platform_v2/public/multi_thread_executor_test.cc @@ -0,0 +1,108 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/multi_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +namespace { +const int kMaxThreads = 5; +} + +TEST(MultiThreadExecutorTest, ConsructorDestructorWorks) { + MultiThreadExecutor executor(kMaxThreads); +} + +TEST(MultiThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + MultiThreadExecutor executor(kMaxThreads); + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(MultiThreadExecutorTest, JobsExecuteInParallel) { + absl::Mutex mutex; + absl::CondVar thread_cond; + absl::CondVar test_cond; + MultiThreadExecutor executor(kMaxThreads); + int count = 0; + + for (int i = 0; i < kMaxThreads; ++i) { + executor.Execute([&count, &mutex, &test_cond, &thread_cond]() { + absl::MutexLock lock(&mutex); + count++; + test_cond.Signal(); + thread_cond.Wait(&mutex); + count--; + test_cond.Signal(); + }); + } + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count < kMaxThreads) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + + EXPECT_EQ(count, kMaxThreads); + thread_cond.SignalAll(); + + { + absl::Duration duration = absl::Milliseconds(kMaxThreads * 100); + absl::MutexLock lock(&mutex); + while (count > 0) { + absl::Time start = absl::Now(); + if (test_cond.WaitWithTimeout(&mutex, duration)) break; + duration -= absl::Now() - start; + } + } + EXPECT_EQ(count, 0); +} + +TEST(MultiThreadExecutorTest, CanSubmit) { + MultiThreadExecutor executor(kMaxThreads); + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/mutex.h b/cpp/platform_v2/public/mutex.h new file mode 100644 index 00000000..4d02c2bc --- /dev/null +++ b/cpp/platform_v2/public/mutex.h @@ -0,0 +1,78 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_MUTEX_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_H_ + +#include + +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// This is a classic mutex can be acquired at most once. +// Atttempt to acuire mutex from the same thread that is holding it will likely +// cause a deadlock. +class ABSL_LOCKABLE Mutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + explicit Mutex(bool check = true) + : impl_(Platform::CreateMutex(check ? Mode::kRegular + : Mode::kRegularNoCheck)) {} + Mutex(Mutex&&) = default; + Mutex& operator=(Mutex&&) = default; + ~Mutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class ConditionVariable; + friend class MutexLock; + std::unique_ptr impl_; +}; + +// This mutex is compatible with Java definition: +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html +// This mutex may be acuired multiple times by a thread that is already holding +// it without blocking. +// It needs to be released equal number of times before any other thread could +// successfully acquire it. +class ABSL_LOCKABLE RecursiveMutex final { + public: + using Platform = api::ImplementationPlatform; + using Mode = api::Mutex::Mode; + + RecursiveMutex() : impl_(Platform::CreateMutex(Mode::kRecursive)) {} + RecursiveMutex(RecursiveMutex&&) = default; + RecursiveMutex& operator=(RecursiveMutex&&) = default; + ~RecursiveMutex() = default; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { impl_->Lock(); } + void Unlock() ABSL_UNLOCK_FUNCTION() { impl_->Unlock(); } + + private: + friend class MutexLock; + std::unique_ptr impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_H_ diff --git a/cpp/platform_v2/public/mutex_lock.h b/cpp/platform_v2/public/mutex_lock.h new file mode 100644 index 00000000..dc2b7bcf --- /dev/null +++ b/cpp/platform_v2/public/mutex_lock.h @@ -0,0 +1,45 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ +#define PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ + +#include "platform_v2/api/mutex.h" +#include "platform_v2/public/mutex.h" +#include "absl/base/thread_annotations.h" + +namespace location { +namespace nearby { + +// An RAII mechanism to acquire a Lock over a block of code. +class ABSL_SCOPED_LOCKABLE MutexLock final { + public: + explicit MutexLock(Mutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + explicit MutexLock(RecursiveMutex* mutex) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex) + : mutex_(mutex->impl_.get()) { + mutex_->Lock(); + } + ~MutexLock() ABSL_UNLOCK_FUNCTION() { mutex_->Unlock(); } + + private: + api::Mutex* mutex_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_MUTEX_LOCK_H_ diff --git a/cpp/platform_v2/public/mutex_test.cc b/cpp/platform_v2/public/mutex_test.cc new file mode 100644 index 00000000..f12f13d8 --- /dev/null +++ b/cpp/platform_v2/public/mutex_test.cc @@ -0,0 +1,117 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/mutex.h" + +#include "platform_v2/public/condition_variable.h" +#include "platform_v2/public/single_thread_executor.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace { + +class MutexTest : public testing::Test { + public: + void VerifyStepReached(int expected) { + absl::MutexLock lock(&step_mutex_); + absl::Time deadline = absl::Now() + kTimeToWait; + while (step_ != expected) { + if (step_cond_.WaitWithDeadline(&step_mutex_, deadline)) break; + } + EXPECT_EQ(step_, expected); + // Make sure we are not progressing further. + absl::SleepFor(kTimeToWait); + EXPECT_EQ(step_, expected); + } + + protected: + SingleThreadExecutor executor_; + const absl::Duration kTimeToWait = absl::Milliseconds(200); + std::atomic_int step_ = 0; + absl::Mutex step_mutex_; + absl::CondVar step_cond_; +}; + +TEST_F(MutexTest, ConstructorDestructorWorks) { + Mutex test_mutex; + SUCCEED(); +} + +TEST_F(MutexTest, BasicLockingWorks) { + Mutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() { + step_ = 1; + step_cond_.Signal(); + test_mutex.Lock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); + }); + VerifyStepReached(1); + test_mutex.Unlock(); + VerifyStepReached(2); +} + +#ifdef THREAD_SANITIZER +TEST_F(MutexTest, DISABLED_DoubleLockIsDeadlock) +ABSL_NO_THREAD_SAFETY_ANALYSIS { +#else +TEST_F(MutexTest, DoubleLockIsDeadlock) ABSL_NO_THREAD_SAFETY_ANALYSIS { +#endif + Mutex test_mutex{/*check=*/false}; // Disable run-time deadlock detection. + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + step_ = 2; + step_cond_.Signal(); // We acquired the test lock. + test_mutex.Lock(); // Deadlock. (Main thread should save us). + step_ = 3; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor proceed to step 2. + VerifyStepReached(2); + test_mutex.Unlock(); // Bring executor out of deadlock. + VerifyStepReached(3); + test_mutex.Unlock(); // Unlock before shutdown. +} + +TEST_F(MutexTest, DoubleLockIsNotDeadlock) { + RecursiveMutex test_mutex; + test_mutex.Lock(); + executor_.Execute([this, &test_mutex]() ABSL_NO_THREAD_SAFETY_ANALYSIS { + step_ = 1; + step_cond_.Signal(); // We entered executor. + test_mutex.Lock(); + test_mutex.Lock(); + test_mutex.Unlock(); + test_mutex.Unlock(); + step_ = 2; + step_cond_.Signal(); // We are done. + }); + VerifyStepReached(1); + test_mutex.Unlock(); // Let executor continue. + VerifyStepReached(2); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/default/default_platform.cc b/cpp/platform_v2/public/pipe.cc similarity index 62% rename from cpp/platform/impl/default/default_platform.cc rename to cpp/platform_v2/public/pipe.cc index 876ad0f3..63583ad6 100644 --- a/cpp/platform/impl/default/default_platform.cc +++ b/cpp/platform_v2/public/pipe.cc @@ -12,19 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "platform/impl/default/default_platform.h" +#include "platform_v2/public/pipe.h" -#include "platform/impl/default/default_condition_variable.h" -#include "platform/impl/default/default_lock.h" +#include "platform_v2/api/condition_variable.h" +#include "platform_v2/api/mutex.h" +#include "platform_v2/api/platform.h" namespace location { namespace nearby { -Ptr DefaultPlatform::createLock() { return MakePtr(new DefaultLock()); } +namespace { +using Platform = api::ImplementationPlatform; +} -Ptr DefaultPlatform::createConditionVariable( - Ptr lock) { - return MakePtr(new DefaultConditionVariable(DowncastPtr(lock))); +Pipe::Pipe() { + auto mutex = Platform::CreateMutex(api::Mutex::Mode::kRegular); + auto cond = Platform::CreateConditionVariable(mutex.get()); + Setup(std::move(mutex), std::move(cond)); } } // namespace nearby diff --git a/cpp/platform_v2/public/pipe.h b/cpp/platform_v2/public/pipe.h new file mode 100644 index 00000000..f6a02392 --- /dev/null +++ b/cpp/platform_v2/public/pipe.h @@ -0,0 +1,36 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_PIPE_H_ +#define PLATFORM_V2_PUBLIC_PIPE_H_ + +#include "platform_v2/base/base_pipe.h" + +namespace location { +namespace nearby { + +// See for details: +// cpp/platform_v2/base/base_pipe.h +class Pipe final : public BasePipe { + public: + Pipe(); + ~Pipe() override = default; + Pipe(Pipe&&) = delete; + Pipe& operator=(Pipe&&) = delete; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_PIPE_H_ diff --git a/cpp/platform_v2/public/pipe_test.cc b/cpp/platform_v2/public/pipe_test.cc new file mode 100644 index 00000000..893dfcad --- /dev/null +++ b/cpp/platform_v2/public/pipe_test.cc @@ -0,0 +1,346 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/pipe.h" + +#include + +#include +#include +#include + +#include "platform_v2/base/prng.h" +#include "platform_v2/base/runnable.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +TEST(PipeTest, ConstructorDestructorWorks) { + Pipe pipe; + SUCCEED(); +} + +TEST(PipeTest, SimpleWriteRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, WriteEndClosedBeforeRead) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Close the write end before the read end has even begun reading. + EXPECT_TRUE(output_stream.Close().Ok()); + + // We should still be able to read what was written. + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); + + // And after that, we should get our indication that all the data that could + // ever be read, has already been read. + read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(read_data.ok()); + EXPECT_TRUE(read_data.result().Empty()); +} + +TEST(PipeTest, ReadEndClosedBeforeWrite) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // Close the read end before the write end has even begun writing. + EXPECT_TRUE(input_stream.Close().Ok()); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, SizedReadMoreThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // Even though we ask for double of what's there in the first chunk, we should + // get back only what's there in that first chunk, and that's alright. + ExceptionOr read_data = input_stream.Read(data.size() * 2); + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(data, std::string(read_data.result())); +} + +TEST(PipeTest, SizedReadLessThanFirstChunkSize) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + std::string data_first_part("ABCD"); + std::string data_second_part("EFGHIJ"); + std::string data = data_first_part + data_second_part; + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // When we ask for less than what's there in the first chunk, we should get + // back exactly what we asked for, with the remainder still being available + // for the next read. + std::int64_t desired_size = data_first_part.size(); + ExceptionOr first_read_data = input_stream.Read(desired_size); + EXPECT_TRUE(first_read_data.ok()); + EXPECT_EQ(data_first_part, std::string(first_read_data.result())); + + // Now read the remainder, and get everything that ought to have been left. + ExceptionOr second_read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(second_read_data.ok()); + EXPECT_EQ(data_second_part, std::string(second_read_data.result())); +} + +TEST(PipeTest, ReadAfterInputStreamClosed) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + + input_stream.Close(); + + ExceptionOr read_data = input_stream.Read(Pipe::kChunkSize); + EXPECT_TRUE(!read_data.ok()); + EXPECT_TRUE(read_data.GetException().Raised(Exception::kIo)); +} + +TEST(PipeTest, WriteAfterOutputStreamClosed) { + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + output_stream.Close(); + + std::string data("ABCD"); + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Raised(Exception::kIo)); +} + +TEST(PipeTest, RepeatedClose) { + Pipe pipe; + InputStream& input_stream{pipe.GetInputStream()}; + OutputStream& output_stream{pipe.GetOutputStream()}; + + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + EXPECT_TRUE(output_stream.Close().Ok()); + + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); + EXPECT_TRUE(input_stream.Close().Ok()); +} + +class Thread { + public: + Thread() : thread_(), attr_(), runnable_() { + pthread_attr_init(&attr_); + pthread_attr_setdetachstate(&attr_, PTHREAD_CREATE_JOINABLE); + } + ~Thread() { pthread_attr_destroy(&attr_); } + + void Start(Runnable runnable) { + runnable_ = runnable; + + pthread_create(&thread_, &attr_, Thread::Body, this); + } + + void Join() { pthread_join(thread_, nullptr); } + + private: + static void* Body(void* args) { + reinterpret_cast(args)->runnable_(); + return nullptr; + } + + pthread_t thread_; + pthread_attr_t attr_; + Runnable runnable_; +}; + +TEST(PipeTest, ReadBlockedUntilWrite) { + using CrossThreadBool = std::atomic_bool; + + class ReaderRunnable { + public: + ReaderRunnable(InputStream* input_stream, + absl::string_view expected_read_data, + CrossThreadBool* ok_for_read_to_unblock) + : input_stream_(input_stream), + expected_read_data_(expected_read_data), + ok_for_read_to_unblock_(ok_for_read_to_unblock) {} + ~ReaderRunnable() = default; + + // Signature "void()" satisfies Runnable. + void operator()() { + ExceptionOr read_data = input_stream_->Read(Pipe::kChunkSize); + + // Make sure read() doesn't return before it's appropriate. + if (!*ok_for_read_to_unblock_) { + FAIL() << "read() unblocked before it was supposed to."; + } + + // And then run our normal set of checks to make sure the read() was + // successful. + EXPECT_TRUE(read_data.ok()); + EXPECT_EQ(expected_read_data_, std::string(read_data.result())); + } + + private: + InputStream* input_stream_; + const std::string expected_read_data_; + CrossThreadBool* ok_for_read_to_unblock_; + }; + + Pipe pipe; + OutputStream& output_stream{pipe.GetOutputStream()}; + + // State shared between this thread (the writer) and reader_thread. + CrossThreadBool ok_for_read_to_unblock = false; + std::string data("ABCD"); + + // Kick off reader_thread. + Thread reader_thread; + reader_thread.Start( + ReaderRunnable(&pipe.GetInputStream(), data, &ok_for_read_to_unblock)); + + // Introduce a delay before we actually write anything. + absl::SleepFor(absl::Seconds(5)); + // Mark that we're done with the delay, and that the write is about to occur + // (this is slightly earlier than it ought to be, but there's no way to + // atomically set this from within the implementation of write(), and doing it + // after is too late for the purposes of this test). + ok_for_read_to_unblock = true; + + // Perform the actual write. + EXPECT_TRUE(output_stream.Write(ByteArray(data)).Ok()); + + // And wait for reader_thread to finish. + reader_thread.Join(); +} + +TEST(PipeTest, ConcurrentWriteAndRead) { + class BaseRunnable { + protected: + explicit BaseRunnable(const std::vector& chunks) + : chunks_(chunks), prng_() {} + virtual ~BaseRunnable() = default; + + void RandomSleep() { + // Generate a random sleep between 100 and 1000 milliseconds. + absl::SleepFor(absl::Milliseconds(BoundedUint32(100, 1000))); + } + + const std::vector& chunks_; + + private: + // Both ends of the bounds are inclusive. + std::uint32_t BoundedUint32(std::uint32_t lower_bound, + std::uint32_t upper_bound) { + return (prng_.NextUint32() % (upper_bound - lower_bound + 1)) + + lower_bound; + } + + Prng prng_; + }; + + class WriterRunnable : public BaseRunnable { + public: + WriterRunnable(OutputStream* output_stream, + const std::vector& chunks) + : BaseRunnable(chunks), output_stream_(output_stream) {} + ~WriterRunnable() override = default; + + void operator()() { + for (auto& chunk : chunks_) { + RandomSleep(); // Random pauses before each write. + EXPECT_TRUE(output_stream_->Write(ByteArray(chunk)).Ok()); + } + + RandomSleep(); // A random pause before closing the writer end. + EXPECT_TRUE(output_stream_->Close().Ok()); + } + + private: + OutputStream* output_stream_; + }; + + class ReaderRunnable : public BaseRunnable { + public: + ReaderRunnable(InputStream* input_stream, + const std::vector& chunks) + : BaseRunnable(chunks), input_stream_(input_stream) {} + ~ReaderRunnable() override = default; + + void operator()() { + // First, calculate what we expect to receive, in total. + std::string expected_data; + for (auto& chunk : chunks_) { + expected_data += chunk; + } + + // Then, start actually receiving. + std::string actual_data; + while (true) { + RandomSleep(); // Random pauses before each read. + ExceptionOr read_data = + input_stream_->Read(Pipe::kChunkSize); + if (read_data.ok()) { + ByteArray result = read_data.result(); + if (result.Empty()) { + break; // Normal exit from the read loop. + } + actual_data += std::string(result); + } else { + break; // Erroneous exit from the read loop. + } + } + + // And once we're done, check that we got everything we expected. + EXPECT_EQ(expected_data, actual_data); + } + + private: + InputStream* input_stream_; + }; + + Pipe pipe; + + std::vector chunks; + chunks.push_back("ABCD"); + chunks.push_back("EFGH"); + chunks.push_back("IJKL"); + + Thread writer_thread; + Thread reader_thread; + writer_thread.Start(WriterRunnable(&pipe.GetOutputStream(), chunks)); + reader_thread.Start(ReaderRunnable(&pipe.GetInputStream(), chunks)); + writer_thread.Join(); + reader_thread.Join(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/scheduled_executor.h b/cpp/platform_v2/public/scheduled_executor.h new file mode 100644 index 00000000..757cf232 --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor.h @@ -0,0 +1,89 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ + +#include +#include +#include + +#include "platform_v2/api/platform.h" +#include "platform_v2/api/scheduled_executor.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/cancelable.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +// An Executor that can schedule commands to run after a given delay, or to +// execute periodically. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html +class ScheduledExecutor final { + public: + using Platform = api::ImplementationPlatform; + + ScheduledExecutor() : impl_(Platform::CreateScheduledExecutor()) {} + ScheduledExecutor(ScheduledExecutor&& other) { *this = std::move(other); } + ~ScheduledExecutor() { + MutexLock lock(&mutex_); + DoShutdown(); + } + + ScheduledExecutor& operator=(ScheduledExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + DoShutdown(); + } + + Cancelable Schedule(Runnable&& runnable, absl::Duration duration) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return impl_ ? Cancelable(impl_->Schedule(std::move(runnable), duration)) + : Cancelable(); + } + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SCHEDULED_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/scheduled_executor_test.cc b/cpp/platform_v2/public/scheduled_executor_test.cc new file mode 100644 index 00000000..b445d6e0 --- /dev/null +++ b/cpp/platform_v2/public/scheduled_executor_test.cc @@ -0,0 +1,114 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/scheduled_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { + +TEST(ScheduledExecutorTest, ConsructorDestructorWorks) { + ScheduledExecutor executor; +} + +TEST(ScheduledExecutorTest, CanExecute) { + absl::Mutex mutex; + absl::CondVar cond; + std::atomic_bool done = false; + ScheduledExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(ScheduledExecutorTest, CanSchedule) { + ScheduledExecutor executor; + std::atomic_int value = 0; + absl::Mutex mutex; + absl::CondVar cond; + // schedule job due in 100 ms. + executor.Schedule( + [&value, &cond]() { + EXPECT_EQ(value, 1); + value = 5; + cond.Signal(); + }, + absl::Milliseconds(100)); + // schedule job due in 10 ms; must fire before the first one. + executor.Schedule( + [&value]() { + EXPECT_EQ(value, 0); + value = 1; + }, + absl::Milliseconds(10)); + { + // wait for the final job to unblock us. + absl::MutexLock lock(&mutex); + cond.WaitWithTimeout(&mutex, absl::Milliseconds(1000)); + } + EXPECT_EQ(value, 5); +} + +TEST(ScheduledExecutorTest, CanCancel) { + ScheduledExecutor executor; + std::atomic_int value = 0; + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(cancelable.Cancel()); + absl::SleepFor(absl::Milliseconds(500)); + EXPECT_EQ(value, 0); +} + +TEST(ScheduledExecutorTest, FailToCancel) { + absl::Mutex mutex; + absl::CondVar cond; + ScheduledExecutor executor; + std::atomic_int value = 0; + // Schedule job in 10ms, which will we will attempt to cancel later. + Cancelable cancelable = + executor.Schedule([&value]() { value += 1; }, absl::Milliseconds(10)); + // schedule another job to test results of the first one, in 50ms from now. + executor.Schedule( + [&cancelable, &cond]() { + EXPECT_FALSE(cancelable.Cancel()); + // Wake up main thread. + cond.Signal(); + }, + absl::Milliseconds(50)); + { + absl::MutexLock lock(&mutex); + cond.Wait(&mutex); + } + EXPECT_EQ(value, 1); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/single_thread_executor.h b/cpp/platform_v2/public/single_thread_executor.h new file mode 100644 index 00000000..805d6efb --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor.h @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ + +#include "platform_v2/public/submittable_executor.h" + +namespace location { +namespace nearby { + +// An Executor that uses a single worker thread operating off an unbounded +// queue. +// +// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newSingleThreadExecutor-- +class SingleThreadExecutor final : public SubmittableExecutor { + public: + using Platform = api::ImplementationPlatform; + SingleThreadExecutor() + : SubmittableExecutor(Platform::CreateSingleThreadExecutor()) {} + ~SingleThreadExecutor() override = default; + SingleThreadExecutor(SingleThreadExecutor&&) = default; + SingleThreadExecutor& operator=(SingleThreadExecutor&&) = default; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SINGLE_THREAD_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/single_thread_executor_test.cc b/cpp/platform_v2/public/single_thread_executor_test.cc new file mode 100644 index 00000000..800838a4 --- /dev/null +++ b/cpp/platform_v2/public/single_thread_executor_test.cc @@ -0,0 +1,85 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform_v2/public/single_thread_executor.h" + +#include +#include + +#include "platform_v2/base/exception.h" +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +TEST(SingleThreadExecutorTest, ConsructorDestructorWorks) { + SingleThreadExecutor executor; +} + +TEST(SingleThreadExecutorTest, CanExecute) { + absl::CondVar cond; + std::atomic_bool done = false; + SingleThreadExecutor executor; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); +} + +TEST(SingleThreadExecutorTest, JobsExecuteInOrder) { + std::vector results; + SingleThreadExecutor executor; + + for (int i = 0; i < 10; ++i) { + executor.Execute([i, &results]() { results.push_back(i); }); + } + + absl::CondVar cond; + std::atomic_bool done = false; + executor.Execute([&done, &cond]() { + done = true; + cond.SignalAll(); + }); + absl::Mutex mutex; + { + absl::MutexLock lock(&mutex); + if (!done) { + cond.WaitWithTimeout(&mutex, absl::Seconds(1)); + } + } + EXPECT_TRUE(done); + EXPECT_EQ(results, (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); +} + +TEST(SingleThreadExecutorTest, CanSubmit) { + SingleThreadExecutor executor; + Future future; + bool submitted = + executor.Submit([]() { return ExceptionOr{true}; }, &future); + EXPECT_TRUE(submitted); + EXPECT_TRUE(future.Get().result()); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/submittable_executor.h b/cpp/platform_v2/public/submittable_executor.h new file mode 100644 index 00000000..4751ef86 --- /dev/null +++ b/cpp/platform_v2/public/submittable_executor.h @@ -0,0 +1,110 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ +#define PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ + +#include +#include +#include +#include + +#include "platform_v2/api/executor.h" +#include "platform_v2/api/submittable_executor.h" +#include "platform_v2/base/callable.h" +#include "platform_v2/base/runnable.h" +#include "platform_v2/public/future.h" +#include "platform_v2/public/mutex.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +// Main interface to be used by platform as a base class for +// - MultiThreadExecutor +// - SingleThreadExecutor +class SubmittableExecutor : public api::SubmittableExecutor { + public: + ~SubmittableExecutor() override { + MutexLock lock(&mutex_); + DoShutdown(); + } + SubmittableExecutor(SubmittableExecutor&& other) { *this = std::move(other); } + SubmittableExecutor& operator=(SubmittableExecutor&& other) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + { + MutexLock other_lock(&other.mutex_); + impl_ = std::move(other.impl_); + } + return *this; + } + void Execute(Runnable&& runnable) ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + if (impl_) impl_->Execute(std::move(runnable)); + } + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_) override { + MutexLock lock(&mutex_); + DoShutdown(); + } + + // Submits a callable for execution. + // When execution completes, return value is assigned to the passed future. + // Future must outlive the whole execution chain. + template + bool Submit(Callable&& callable, Future* future) + ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + bool submitted = DoSubmit([callable{std::move(callable)}, future]() { + ExceptionOr result = callable(); + if (result.ok()) { + future->Set(result.result()); + } else { + future->SetException({result.exception()}); + } + }); + if (!submitted) { + // complete immediately with kExecution exception value. + future->SetException({Exception::kExecution}); + } + return submitted; + } + + protected: + explicit SubmittableExecutor(std::unique_ptr impl) + : impl_(std::move(impl)) {} + + private: + void DoShutdown() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) { + if (impl_) { + impl_->Shutdown(); + impl_.reset(); + } + } + // Submit a callable (with no delay). + // Returns true, if callable was submitted, false otherwise. + // Callable is not submitted if shutdown is in progress. + bool DoSubmit(Runnable&& wrapped_callable) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_) override { + return impl_ ? impl_->DoSubmit(std::move(wrapped_callable)) : false; + } + Mutex mutex_; + std::unique_ptr ABSL_GUARDED_BY(mutex_) impl_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_SUBMITTABLE_EXECUTOR_H_ diff --git a/cpp/platform_v2/public/system_clock.h b/cpp/platform_v2/public/system_clock.h new file mode 100644 index 00000000..190d86a9 --- /dev/null +++ b/cpp/platform_v2/public/system_clock.h @@ -0,0 +1,20 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ +#define PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ + +#include "platform_v2/api/system_clock.h" + +#endif // PLATFORM_V2_PUBLIC_SYSTEM_CLOCK_H_ diff --git a/proto/BUILD b/proto/BUILD index ae34afb9..124a5ed0 100644 --- a/proto/BUILD +++ b/proto/BUILD @@ -54,6 +54,21 @@ java_proto_library( deps = [":discovery_enums_proto"], ) +proto_library( + name = "error_code_enums_proto", + srcs = ["error_code_enums.proto"], + cc_api_version = 2, + compatible_with = ["//buildenv/target:appengine"], + deps = [ + "//logs/proto/logs_annotations", + ], +) + +java_lite_proto_library( + name = "error_code_enums_java_proto_lite", + deps = [":error_code_enums_proto"], +) + proto_library( name = "connections_enums_proto", srcs = ["connections_enums.proto"], @@ -170,6 +185,11 @@ java_lite_proto_library( deps = [":sharing_enums_proto"], ) +java_proto_library( + name = "sharing_enums_java_proto", + deps = [":sharing_enums_proto"], +) + proto_library( name = "nearby_event_codes_proto", srcs = ["nearby_event_codes.proto"], diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto index 5e2f3360..1f8e7e8e 100644 --- a/proto/connections/offline_wire_formats.proto +++ b/proto/connections/offline_wire_formats.proto @@ -250,4 +250,6 @@ message PairedKeyEncryptionFrame { message MediumMetadata { // True if local device supports 5GHz. optional bool supports_5_ghz = 1; + // WiFi Lan BSSID + optional string bssid = 2; } diff --git a/proto/connections_enums_proto_config.asciipb b/proto/connections_enums_proto_config.asciipb index b5ea0aa5..702328c1 100644 --- a/proto/connections_enums_proto_config.asciipb +++ b/proto/connections_enums_proto_config.asciipb @@ -1,5 +1,7 @@ optimize_mode: LITE_RUNTIME allowed_enum: "location.nearby.proto.connections.Medium" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeResult" +allowed_enum: "location.nearby.proto.connections.BandwidthUpgradeErrorStage" allowed_enum: "location.nearby.proto.connections.DisconnectionReason" allowed_enum: "location.nearby.proto.connections.PayloadStatus" diff --git a/proto/error_code_enums.proto b/proto/error_code_enums.proto new file mode 100644 index 00000000..339ebb42 --- /dev/null +++ b/proto/error_code_enums.proto @@ -0,0 +1,147 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package location.nearby.proto; + +import "logs/proto/logs_annotations/logs_annotations.proto"; + +option (logs_proto.file_not_used_for_logging_except_enums) = true; +option java_api_version = 2; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "ErrorCodeEnums"; +option objc_class_prefix = "GNCP"; + +// The type of the error. +// It help to sort error codes to different types to analyze and also impact the +// logcat print it as Warning or Severe. +enum ErrorType { + UNKNOWN_TYPE = 0; + + // The error should not happen on production, it's like the input is null or + // not invalid or it's a unexpected API call. For example, start advertising + // with empty service ID or start advertising with the same service ID twice. + DEVELOPING = 1; + + // It’s about the device's capabilities, some devices may not support the + // feature Nearby used. E.g. The device not support BLE advertising + DEVICE = 2; + + // The failure return from the system or library API we used to communicate + // with the medium. E.g. get null OS objects or call the API but get a + // negative return value which indicates that the system does not allow to do + // that now. + SYSTEM = 3; + + // The network related failure. E.g. get an EOF exception while reading pipe + // or fail to create connection. + NETWORK = 4; + + // This may not be a failure, it can be the things we are interested in, like + // to count how many BLE advertisements the device received in a specified + // period and how many different advertisements in it, it can help us to know + // the user under a clean or dirty environment. + OTHERS = 5; +} + +// The event which the error occurs on. +enum Event { + UNKNOWN_EVENT = 0; + START_ADVERTISING = 1; + STOP_ADVERTISING = 2; + START_LISTENING_INCOMING_CONNECTION = 3; + STOP_LISTENING_INCOMING_CONNECTION = 4; + START_DISCOVERING = 5; + STOP_DISCOVERING = 6; + CONNECT = 7; + DISCONNECT = 8; + ACCEPT_CONNECTION = 9; + REJECT_CONNECTION = 10; + SEND_PAYLOAD = 11; + CANCEL_PAYLOAD = 12; + RECEIVE_PAYLOAD = 13; +} + +// The error to identify the common failure for all mediums. The range between 0 +// and 30. +enum CommonError { + UNKNOWN_ERROR = 0; + + // The common error for all mediums, the range between 0 and 30. + + // Developing error, the input with invalid format or empty. + INVALID_PARAMETER = 1; + // Device error, the BLE not available on this device. + BLE_NOT_AVAILABLE = 2; + // System error, the medium in the unexpected state, e.g. we have check the + // medium is on, after then it suddently off and cause Nearby + // Connection failed. + UNEXPECTED_MEDIUM_STATE = 3; + + // Reserved 4 to 30 +} + +// The error for event START_ADVERTISING. The range between 31 and 99. +enum StartAdvertisingError { + // Developing error, not allow to advertising fast pair model id and sharing + // fast advertisement at the same time, they are both use fast + // advertisement, and only allow 1 fast advertisement at the same time. + MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED = 31; + // System error, there's already someone advertising fast advertisement, not + // allow to start another one. + FAST_ADVERTISEMENT_ALREADY_ADVERTISED = 32; + // Developing error, this service ID already requested, should not request + // it again without stop advertising. + DUPLICATE_ADVERTISING_REQUESTED = 33; + // System error, failed to start GATT server + START_GATT_SERVER_FAILED = 34; + // System error, all advertising slot ran out, can't available for new + // regular advertisement. + BLE_MAX_GATT_ADVERTISEMENT_SLOT_REACHED = 35; + // System error, failed to start advertising for legacy advertisements + START_LEGACY_ADVERTISING_FAILED = 36; + // System error, start advertising for legacy advertisements but timed out + START_LEGACY_ADVERTISING_TIMEOUT = 37; + // System error, failed to start advertising for extended advertisements + START_EXTENDED_ADVERTISING_FAILED = 38; + // System error, start advertising for extended advertisements but timed out + START_EXTENDED_ADVERTISING_TIMEOUT = 39; + + // Next ID :40 +} + +enum Description { + UNKNOWN = 0; + NULL_SERVICE_ID = 1; + NULL_ADVERTISEMENT_BYTES = 2; + CONNECTIONS_FEATURE_DISABLED = 3; + STALE_SDK_VERSION = 4; + FEATURE_BLUETOOTH_NOT_SUPPORTED = 5; + FEATURE_BLUETOOTH_LE_NOT_SUPPORTED = 6; + NULL_BLUETOOTH_MANAGER = 7; + NULL_BLUETOOTH_ADAPTER = 8; + INVALID_FAST_PAIR_MODEL_ID = 9; + INVALID_FAST_ADVERTISEMENT_DATA = 10; + INVALID_ADVERTISEMENT_HEADER_DATA = 11; + INVALID_REGULAR_ADVERTISEMENT_DATA = 12; + NULL_BLUETOOTH_LE_ADVERTISER_COMPAT = 13; + ADVERTISE_FAILED_ALREADY_STARTED = 14; + ADVERTISE_FAILED_DATA_TOO_LARGE = 15; + ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 16; + ADVERTISE_FAILED_INTERNAL_ERROR = 17; + ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 18; + INTERRUPTED_EXCEPTION = 19; + EXECUTION_EXCEPTION = 20; +} diff --git a/proto/sharing_enums.proto b/proto/sharing_enums.proto index bbb792c5..efe0c976 100644 --- a/proto/sharing_enums.proto +++ b/proto/sharing_enums.proto @@ -118,6 +118,21 @@ enum EventType { // Set data usage preference. SET_DATA_USAGE = 28; + + // Receiver dismisses a fast initialization + DISMISS_FAST_INITIALIZATION = 29; + + // Cancel connection. + CANCEL_CONNECTION = 30; +} + +// Event category to differentiate whether this comes from sender or receiver, +// whether this is for communication flow, or for settings. +enum EventCategory { + UNKNOWN_EVENT_CATEGORY = 0; + SENDING_EVENT = 1; + RECEIVING_EVENT = 2; + SETTINGS_EVENT = 3; } // Status of nearby sharing. @@ -244,6 +259,7 @@ enum ServerResponseState { SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5; SERVER_RESPONSE_STATUS_UNAVAILABLE = 6; SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7; + SERVER_RESPONSE_STATUS_INVALID_ARGUMENT = 9; // For GoogleAuthException. SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8; diff --git a/script/oss.py b/script/oss.py index be08927c..70781b99 100755 --- a/script/oss.py +++ b/script/oss.py @@ -76,7 +76,9 @@ def copy_files_to_oss_project(src_root, dst_root): shutil.rmtree(dst_root + "/proto", ignore_errors=True) shutil.copytree(src_root + "/proto", dst_root + "/proto/") shutil.copytree(src_root + "/cpp/platform/", dst_root + "/cpp/platform/") + shutil.copytree(src_root + "/cpp/platform_v2/", dst_root + "/cpp/platform_v2/") shutil.copytree(src_root + "/connections/core/", dst_root + "/cpp/core/") + shutil.copytree(src_root + "/connections/core_v2/", dst_root + "/cpp/core_v2/") shutil.copytree(src_root + "/connections/proto/", dst_root + "/proto/connections/") def detect_file_copy_header_options(fname, lines): @@ -101,8 +103,12 @@ def post_process_oss_files(path, args): else: top_dirs = ["cpp", "proto"] transforms = ( + ("third_party/webrtc/files/stable/", ""), + ("webrtc/files/stable/", ""), ("third_party/", ""), + ("location/nearby/connections/core_v2", "core_v2"), ("location/nearby/connections/core", "core"), + ("location/nearby/cpp/platform_v2", "platform_v2"), ("location/nearby/cpp/platform", "platform"), ("security/cryptauth/lib/securegcm", "securegcm"), ("testing/base/public/gmock.h", "gmock/gmock.h"), @@ -116,6 +122,7 @@ def post_process_oss_files(path, args): ("_portable_proto.pb.h", ".pb.h"), (".proto.h", ".pb.h"), ) + for root, dirs, files in os.walk(path): if top_level and top_dirs: # we must convert cpp/ and proto/ subtrees. @@ -134,6 +141,8 @@ def post_process_oss_files(path, args): lines=[] google3_ignore = False with open(fname, "r") as f: + add_proto_lite_runtime = args.proto_lite_runtime and fname.endswith(".proto") + for line in f: orig = line @@ -158,6 +167,13 @@ def post_process_oss_files(path, args): if google3_ignore: modified = True continue + + if add_proto_lite_runtime and line.startswith("option "): + lines.append("option optimize_for = LITE_RUNTIME;") + modified = True + # LITE_RUNTIME should be added only once per file. + add_proto_lite_runtime = False + lines.append(line) if args.fix_oss_headers: @@ -167,6 +183,7 @@ def post_process_oss_files(path, args): prefix, offset = options lines = add_copyright(lines, prefix, offset) modified = True + if modified: with open(fname, "w") as f: for line in lines: @@ -187,6 +204,7 @@ def main(): parser.add_argument('--no-copy', action='store_true', default=False) parser.add_argument('--no-subst', action='store_true', default=False) parser.add_argument('--no-recurse', action='store_true', default=False) + parser.add_argument('--proto-lite-runtime', action='store_true', default=False) args = parser.parse_args() if args.google3_filter: print("google3-specific code will be removed") From e2a07376f407812a0f0156e7e756b00e74c0befc Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Thu, 4 Jun 2020 19:24:33 +0100 Subject: [PATCH 22/52] Roll forward to cl/314747126 Change-Id: I858d3227b44ca6d727b39fae95358df58fce9c1b --- cpp/core/internal/mediums/webrtc/BUILD | 8 +- .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 2 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- cpp/core_v2/core.cc | 2 + cpp/core_v2/internal/base_pcp_handler.cc | 3 + cpp/core_v2/internal/endpoint_manager.cc | 5 + cpp/core_v2/internal/mediums/BUILD | 10 +- .../internal/mediums/bluetooth_classic.cc | 377 ++++++++++++++++++ .../internal/mediums/bluetooth_classic.h | 178 +++++++++ .../mediums/bluetooth_classic_test.cc | 194 +++++++++ .../internal/mediums/bluetooth_radio.cc | 2 + cpp/core_v2/internal/mediums/mediums.cc | 17 + cpp/core_v2/internal/mediums/mediums.h | 40 ++ cpp/core_v2/internal/mediums/webrtc/BUILD | 8 +- .../internal/mediums/webrtc/connection_flow.h | 4 +- .../webrtc/local_ice_candidate_listener.h | 2 +- .../webrtc/peer_connection_observer_impl.h | 2 +- .../mediums/webrtc/signaling_frames.h | 2 +- .../internal/mediums/webrtc/webrtc_socket.h | 2 +- .../mediums/webrtc/webrtc_socket_test.cc | 2 +- cpp/core_v2/internal/offline_frames.cc | 2 +- cpp/platform/api/BUILD | 2 +- cpp/platform/api/webrtc.h | 2 +- cpp/platform_v2/api/BUILD | 2 +- cpp/platform_v2/api/platform.h | 8 +- cpp/platform_v2/api/webrtc.h | 2 +- cpp/platform_v2/base/medium_environment.cc | 21 + cpp/platform_v2/base/medium_environment.h | 11 + cpp/platform_v2/impl/g3/BUILD | 12 +- cpp/platform_v2/impl/g3/bluetooth_adapter.cc | 7 +- cpp/platform_v2/impl/g3/bluetooth_adapter.h | 4 + cpp/platform_v2/impl/g3/bluetooth_classic.cc | 240 +++++++++++ cpp/platform_v2/impl/g3/bluetooth_classic.h | 218 ++++++++++ cpp/platform_v2/impl/g3/platform.cc | 12 +- cpp/platform_v2/impl/g3/webrtc.cc | 2 +- cpp/platform_v2/impl/g3/webrtc.h | 2 +- cpp/platform_v2/public/BUILD | 15 +- cpp/platform_v2/public/bluetooth_adapter.h | 42 +- cpp/platform_v2/public/bluetooth_classic.cc | 85 ++++ cpp/platform_v2/public/bluetooth_classic.h | 205 ++++++++++ .../public/bluetooth_classic_test.cc | 197 +++++++++ cpp/platform_v2/public/webrtc.h | 2 +- 43 files changed, 1910 insertions(+), 47 deletions(-) create mode 100644 cpp/core_v2/internal/mediums/bluetooth_classic.cc create mode 100644 cpp/core_v2/internal/mediums/bluetooth_classic.h create mode 100644 cpp/core_v2/internal/mediums/bluetooth_classic_test.cc create mode 100644 cpp/core_v2/internal/mediums/mediums.cc create mode 100644 cpp/core_v2/internal/mediums/mediums.h create mode 100644 cpp/platform_v2/impl/g3/bluetooth_classic.cc create mode 100644 cpp/platform_v2/impl/g3/bluetooth_classic.h create mode 100644 cpp/platform_v2/public/bluetooth_classic.cc create mode 100644 cpp/platform_v2/public/bluetooth_classic.h create mode 100644 cpp/platform_v2/public/bluetooth_classic_test.cc diff --git a/cpp/core/internal/mediums/webrtc/BUILD b/cpp/core/internal/mediums/webrtc/BUILD index 56cf5608..5ab6e446 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/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) @@ -20,7 +20,7 @@ cc_test( "//platform/api", "//platform/impl/g3", # buildcleaner: keep "//testing/base/public:gunit_main", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/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/api:libjingle_peerconnection_api", + "//webrtc/files/stable/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/pc:peerconnection", # buildcleaner: keep + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep ], ) diff --git a/cpp/core/internal/mediums/webrtc/signaling_frames.h b/cpp/core/internal/mediums/webrtc/signaling_frames.h index fb885a58..fec7046c 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket.h b/cpp/core/internal/mediums/webrtc/webrtc_socket.h index d0ec4104..5a55e9d9 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/api/data_channel_interface.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" namespace location { namespace nearby { diff --git a/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc b/cpp/core/internal/mediums/webrtc/webrtc_socket_test.cc index be83d9f1..503b8cd8 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/api/data_channel_interface.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/core.cc b/cpp/core_v2/core.cc index c7848047..412f987c 100644 --- a/cpp/core_v2/core.cc +++ b/cpp/core_v2/core.cc @@ -12,6 +12,8 @@ namespace location { namespace nearby { namespace connections { +constexpr absl::Duration Core::kWaitForDisconnect; + Core::~Core() { CountDownLatch latch(1); router_.ClientDisconnecting( diff --git a/cpp/core_v2/internal/base_pcp_handler.cc b/cpp/core_v2/internal/base_pcp_handler.cc index e57801e6..38459a40 100644 --- a/cpp/core_v2/internal/base_pcp_handler.cc +++ b/cpp/core_v2/internal/base_pcp_handler.cc @@ -21,6 +21,9 @@ namespace connections { using ::location::nearby::proto::connections::Medium; using ::securegcm::UKey2Handshake; +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) {} diff --git a/cpp/core_v2/internal/endpoint_manager.cc b/cpp/core_v2/internal/endpoint_manager.cc index 501df7a3..5d28a6c0 100644 --- a/cpp/core_v2/internal/endpoint_manager.cc +++ b/cpp/core_v2/internal/endpoint_manager.cc @@ -16,6 +16,11 @@ namespace connections { using ::location::nearby::proto::connections::Medium; +constexpr absl::Duration EndpointManager::kKeepAliveWriteInterval; +constexpr absl::Duration EndpointManager::kKeepAliveReadTimeout; +constexpr absl::Duration EndpointManager::kProcessEndpointDisconnectionTimeout; +constexpr absl::Time EndpointManager::kInvalidTimestamp; + // A Runnable that continuously grabs the most recent EndpointChannel available // for an endpoint. // diff --git a/cpp/core_v2/internal/mediums/BUILD b/cpp/core_v2/internal/mediums/BUILD index c15fc9bb..cba5c8d8 100644 --- a/cpp/core_v2/internal/mediums/BUILD +++ b/cpp/core_v2/internal/mediums/BUILD @@ -6,7 +6,9 @@ cc_library( "ble_advertisement_header.cc", "ble_packet.cc", "bloom_filter.cc", + "bluetooth_classic.cc", "bluetooth_radio.cc", + "mediums.cc", "uuid.cc", ], hdrs = [ @@ -16,14 +18,17 @@ cc_library( "ble_packet.h", "ble_peripheral.h", "bloom_filter.h", + "bluetooth_classic.h", "bluetooth_radio.h", "lost_entity_tracker.h", + "mediums.h", "uuid.h", ], visibility = [ - "//core_v2/internal:__pkg__", + "//core_v2/internal:__subpackages__", ], deps = [ + "//core_v2:core_types", "//platform_v2/base", "//platform_v2/public:comm", "//platform_v2/public:logging", @@ -53,6 +58,7 @@ cc_library( cc_test( name = "core_v2_internal_mediums_test", + size = "small", srcs = [ "advertisement_read_result_test.cc", "ble_advertisement_header_test.cc", @@ -60,6 +66,7 @@ cc_test( "ble_packet_test.cc", "ble_peripheral_test.cc", "bloom_filter_test.cc", + "bluetooth_classic_test.cc", "bluetooth_radio_test.cc", "lost_entity_tracker_test.cc", "uuid_test.cc", @@ -68,6 +75,7 @@ cc_test( deps = [ ":mediums", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep "//platform_v2/public:comm", "//platform_v2/public:logging", diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.cc b/cpp/core_v2/internal/mediums/bluetooth_classic.cc new file mode 100644 index 00000000..39ebda64 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.cc @@ -0,0 +1,377 @@ +#include "core_v2/internal/mediums/bluetooth_classic.h" + +#include +#include +#include + +#include "core_v2/internal/mediums/uuid.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : radio_(radio) {} + +BluetoothClassic::~BluetoothClassic() { + // Destructor is not taking locks, but methods it is calling are. + StopDiscovery(); + while (!server_sockets_.empty()) { + StopAcceptingConnections(server_sockets_.begin()->first); + } + TurnOffDiscoverability(); + + // All the AcceptLoopRunnable objects in here should already have gotten an + // opportunity to shut themselves down cleanly in the calls to + // StopAcceptingConnections() above. + accept_loops_runner_.Shutdown(); +} + +bool BluetoothClassic::IsAvailable() const { + MutexLock lock(&mutex_); + + return IsAvailableLocked(); +} + +bool BluetoothClassic::IsAvailableLocked() const { + return medium_.IsValid() && adapter_.IsValid(); +} + +bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) { + MutexLock lock(&mutex_); + + if (device_name.empty()) { + NEARBY_LOG(INFO, + "Refusing to turn on BT discoverability. Empty device name."); + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is off."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is not available."); + return false; + } + + if (IsDiscoverable()) { + NEARBY_LOG(INFO, + "Refusing to turn on BT discoverability; new name='%s'; " + "current name='%s'", + device_name.c_str(), adapter_.GetName().c_str()); + return false; + } + + if (!ModifyDeviceName(device_name)) { + NEARBY_LOG(INFO, + "Failed to turn on BT discoverability; " + "failed to set name to %s", + device_name.c_str()); + return false; + } + + if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) { + NEARBY_LOG(INFO, + "Failed to turn on BT discoverability; " + "failed to set scan_mode to %d", + ScanMode::kConnectableDiscoverable); + + // Don't forget to perform this rollback of the partial state changes we've + // made til now. + RestoreDeviceName(); + return false; + } + + NEARBY_LOG(INFO, "Turned on BT discoverability with device_name=%s", + device_name.c_str()); + return true; +} + +bool BluetoothClassic::TurnOffDiscoverability() { + MutexLock lock(&mutex_); + + if (!IsDiscoverable()) { + NEARBY_LOG(INFO, "Can't turn off BT discoverability; it is already off"); + return false; + } + + RestoreScanMode(); + RestoreDeviceName(); + + NEARBY_LOG(INFO, "Turned Bluetooth discoverability off"); + return true; +} + +bool BluetoothClassic::IsDiscoverable() const { + return (!original_device_name_.empty() && + (adapter_.GetScanMode() == ScanMode::kConnectableDiscoverable)); +} + +bool BluetoothClassic::ModifyDeviceName(const std::string& device_name) { + if (original_device_name_.empty()) { + original_device_name_ = adapter_.GetName(); + } + + return adapter_.SetName(device_name); +} + +bool BluetoothClassic::ModifyScanMode(ScanMode scan_mode) { + if (original_scan_mode_ == ScanMode::kUnknown) { + original_scan_mode_ = adapter_.GetScanMode(); + } + + if (!adapter_.SetScanMode(scan_mode)) { + original_scan_mode_ = ScanMode::kUnknown; + return false; + } + + return true; +} + +bool BluetoothClassic::RestoreScanMode() { + if (original_scan_mode_ == ScanMode::kUnknown || + !adapter_.SetScanMode(original_scan_mode_)) { + NEARBY_LOG(INFO, "Failed to restore original Bluetooth scan mode to %d", + original_scan_mode_); + return false; + } + + // Regardless of whether or not we could actually restore the Bluetooth scan + // mode, reset our relevant state. + original_scan_mode_ = ScanMode::kUnknown; + return true; +} + +bool BluetoothClassic::RestoreDeviceName() { + if (original_device_name_.empty() || + !adapter_.SetName(original_device_name_)) { + NEARBY_LOG(INFO, "Failed to restore original Bluetooth device name to %s", + original_device_name_.c_str()); + return false; + } + original_device_name_.clear(); + return true; +} + +bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) { + MutexLock lock(&mutex_); + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't enabled."); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't available."); + return false; + } + + if (IsDiscovering()) { + NEARBY_LOG(INFO, + "Refusing to start discovery of BT devices because another " + "discovery is already in-progress."); + return false; + } + + if (!medium_.StartDiscovery(callback)) { + NEARBY_LOG(INFO, "Failed to start discovery of BT devices."); + return false; + } + + // Mark the fact that we're currently performing a Bluetooth scan. + scan_info_.valid = true; + + return true; +} + +bool BluetoothClassic::StopDiscovery() { + MutexLock lock(&mutex_); + + if (!IsDiscovering()) { + NEARBY_LOG(INFO, + "Can't stop discovery of BT devices because it never started."); + return false; + } + + if (!medium_.StopDiscovery()) { + NEARBY_LOG(INFO, "Failed to stop discovery of Bluetooth devices."); + return false; + } + + scan_info_.valid = false; + return true; +} + +bool BluetoothClassic::IsDiscovering() const { return scan_info_.valid; } + +bool BluetoothClassic::StartAcceptingConnections( + const std::string& service_name, AcceptedConnectionCallback callback) { + MutexLock lock(&mutex_); + + if (service_name.empty()) { + NEARBY_LOG( + INFO, + "Refusing to start accepting BT connections; service name is empty."); + return false; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, + "Can't create BT server socket [service=%s]; BT is disabled.", + service_name.c_str()); + return false; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG( + INFO, + "Can't start accepting BT connections [service=%s]; BT not available.", + service_name.c_str()); + return false; + } + + if (IsAcceptingConnectionsLocked(service_name)) { + NEARBY_LOG(INFO, + "Refusing to start accepting BT connections [service=%s]; BT " + "server is already in-progress with the same name.", + service_name.c_str()); + return false; + } + + BluetoothServerSocket socket = medium_.ListenForService( + service_name, GenerateUuidFromString(service_name)); + if (!socket.IsValid()) { + NEARBY_LOG(INFO, "Failed to start accepting Bluetooth connections for %s.", + service_name.c_str()); + return false; + } + + // Mark the fact that there's an in-progress Bluetooth server accepting + // connections. + auto owned_socket = + server_sockets_.emplace(service_name, std::move(socket)).first->second; + + // Start the accept loop on a dedicated thread - this stays alive and + // listening for new incoming connections until StopAcceptingConnections() is + // invoked. + accept_loops_runner_.Execute([callback = std::move(callback), + server_socket = std::move(owned_socket), + service_name]() mutable { + while (true) { + BluetoothSocket client_socket = server_socket.Accept(); + if (!client_socket.IsValid()) { + server_socket.Close(); + break; + } + + callback.accepted_cb(std::move(client_socket)); + } + }); + + return true; +} + +bool BluetoothClassic::IsAcceptingConnections(const std::string& service_name) { + MutexLock lock(&mutex_); + + return IsAcceptingConnectionsLocked(service_name); +} + +bool BluetoothClassic::IsAcceptingConnectionsLocked( + const std::string& service_name) { + return server_sockets_.find(service_name) != server_sockets_.end(); +} + +bool BluetoothClassic::StopAcceptingConnections( + const std::string& service_name) { + MutexLock lock(&mutex_); + + if (service_name.empty()) { + NEARBY_LOG(INFO, + "Unable to stop accepting BT connections because the " + "service_name is empty."); + return false; + } + + const auto& it = server_sockets_.find(service_name); + if (it == server_sockets_.end()) { + NEARBY_LOG(INFO, + "Can't stop accepting BT connections for %s because it was " + "never started.", + service_name.c_str()); + return false; + } + + // Closing the BluetoothServerSocket will kick off the suicide of the thread + // in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept(). + // That may take some time to complete, but there's no particular reason to + // wait around for it. + auto item = server_sockets_.extract(it); + + // Store a handle to the BluetoothServerSocket, so we can use it after + // removing the entry from server_sockets_; making it scoped + // is a bonus that takes care of deallocation before we leave this method. + BluetoothServerSocket& listening_socket = item.mapped(); + + // Regardless of whether or not we fail to close the existing + // BluetoothServerSocket, remove it from server_sockets_ so that it + // frees up this service for another round. + + // Finally, close the BluetoothServerSocket. + if (!listening_socket.Close().Ok()) { + NEARBY_LOG(INFO, "Failed to close BT server socket for %s.", + service_name.c_str()); + return false; + } + + return true; +} + +BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device, + const std::string& service_name) { + MutexLock lock(&mutex_); + NEARBY_LOG(INFO, "BluetoothClassic::Connect: device=%p", &bluetooth_device); + // Socket to return. To allow for NRVO to work, it has to be a single object. + BluetoothSocket socket; + + if (service_name.empty()) { + NEARBY_LOG( + INFO, + "Refusing to create client BT socket because service_name is empty."); + return socket; + } + + if (!radio_.IsEnabled()) { + NEARBY_LOG(INFO, + "Can't create client BT socket [service=%s]: BT isn't enabled.", + service_name.c_str()); + return socket; + } + + if (!IsAvailableLocked()) { + NEARBY_LOG( + INFO, "Can't create client BT socket [service=%s]; BT isn't available.", + service_name.c_str()); + return socket; + } + + socket = medium_.ConnectToService(bluetooth_device, + GenerateUuidFromString(service_name)); + if (!socket.IsValid()) { + NEARBY_LOG(INFO, "Failed to Connect via BT [service=%s]", + service_name.c_str()); + } + + return socket; +} + +std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) { + return std::string(Uuid(data)); +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic.h b/cpp/core_v2/internal/mediums/bluetooth_classic.h new file mode 100644 index 00000000..69308309 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic.h @@ -0,0 +1,178 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ +#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "core_v2/listeners.h" +#include "platform_v2/base/byte_array.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/multi_thread_executor.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { +namespace connections { + +class BluetoothClassic { + public: + using DiscoveredDeviceCallback = BluetoothClassicMedium::DiscoveryCallback; + using ScanMode = BluetoothAdapter::ScanMode; + + // Callback that is invoked when a new connection is accepted. + struct AcceptedConnectionCallback { + std::function accepted_cb = + DefaultCallback(); + }; + + explicit BluetoothClassic(BluetoothRadio& bluetooth_radio); + ~BluetoothClassic(); + + // Returns true, if BT communications are supported by a platform. + bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Sets custom device name, and then enables BT discoverable mode. + // Returns true, if name and scan mode are successfully set, and false + // otherwise. + // Called by server. + bool TurnOnDiscoverability(const std::string& device_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables BT discoverability, and restores scan mode and device name to + // what they were before the call to TurnOnDiscoverability(). + // Returns false if no successful call TurnOnDiscoverability() was previously + // made, otherwise returns true. + // Called by server. + bool TurnOffDiscoverability() ABSL_LOCKS_EXCLUDED(mutex_); + + // Enables BT discovery mode. Will report any discoverable devices in range + // through a callback. + // Returns true, if discovery mode was enabled, false otherwise. + // Called by client. + bool StartDiscovery(DiscoveredDeviceCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Disables BT discovery mode. + // Returns true, if discovery mode was previously enabled, false otherwise. + // Called by client. + bool StopDiscovery() ABSL_LOCKS_EXCLUDED(mutex_); + + // Starts a worker thread, creates a BT server socket, associates it with a + // service name; in a worker thread repeatedly calls ServerSocket::Accept(). + // Any connected sockets returned from Accept() are passed to a callback. + // Returns true, if server socket was successfully created, false otherwise. + // Called by server. + bool StartAcceptingConnections(const std::string& service_name, + AcceptedConnectionCallback callback) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true, if object is currently running a Accept() loop. + bool IsAcceptingConnections(const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes server socket corresponding to a service name. This automatically + // terminates Accept() loop, if it were running. + // Called by server. + bool StopAcceptingConnections(const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if this object owns a valid platform implementation. + bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return medium_.IsValid(); + } + + // Returns true if this object has a valid BluetoothAdapter reference. + bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) { + MutexLock lock(&mutex_); + return adapter_.IsValid(); + } + + // Establishes connection to BT service that was might be started on another + // device with StartAcceptingConnections() using the same service_name. + // Blocks until connection is established, or server-side is terminated. + // Returns socket instance. On success, BluetoothSocket.IsValid() return true. + // Called by client. + BluetoothSocket Connect(BluetoothDevice& bluetooth_device, + const std::string& service_name) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + struct ScanInfo { + bool valid = false; + }; + + static constexpr int kMaxConcurrentAcceptLoops = 5; + + // Constructs UUID object from arbitrary string, using MD5 hash, and then + // converts UUID to a readable UUID string and returns it. + static std::string GenerateUuidFromString(const std::string& data); + + // Same as IsAvailable(), but must be called with mutex_ held. + bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Same as IsAcceptingConnections(), but must be called with mutex_ held. + bool IsAcceptingConnectionsLocked(const std::string& service_name) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns true, if discoverability is enabled with TurnOnDiscoverability(). + bool IsDiscoverable() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Assignes a different name to BT adapter. + // Returns true if successful. Stores original device name. + bool ModifyDeviceName(const std::string& device_name) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Changes current scan mode. This is an implementation of + // TurnDiscoveradility() method. Stores original scan mode. + bool ModifyScanMode(ScanMode scan_mode) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Restores original device name (the one before the very first call to + // ModifyDeviceName()). Returns true if successful. + bool RestoreScanMode() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Restores original device scan mode (the one before the very first call to + // ModifyScanMode()). Returns true if successful. + bool RestoreDeviceName() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns true if device is currently in discovery mode. + bool IsDiscovering() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable Mutex mutex_; + BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_); + BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){ + radio_.GetBluetoothAdapter()}; + BluetoothClassicMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_}; + + // A bundle of state required to do a Bluetooth Classic scan. When non-null, + // we are currently performing a Bluetooth scan. + ScanInfo scan_info_ ABSL_GUARDED_BY(mutex_); + + // The original scan mode (that controls visibility to scanners) of the device + // before we modified it. Restored when we stop advertising. + ScanMode original_scan_mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kUnknown; + + // The original Bluetooth device name, before we modified it. If non-empty, we + // are currently Bluetooth discoverable. Restored when we stop advertising. + std::string original_device_name_ ABSL_GUARDED_BY(mutex_); + + // A thread pool dedicated to running all the accept loops from + // StartAcceptingConnections(). + MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops}; + + // A map of service Name -> ServerSocket. If map is non-empty, we + // are currently listening for incoming connections. + // BluetoothServerSocket instances are used from accept_loops_runner_, + // and thus require pointer stability. + absl::flat_hash_map server_sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc new file mode 100644 index 00000000..33fae825 --- /dev/null +++ b/cpp/core_v2/internal/mediums/bluetooth_classic_test.cc @@ -0,0 +1,194 @@ +#include "core_v2/internal/mediums/bluetooth_classic.h" + +#include + +#include "core_v2/internal/mediums/bluetooth_radio.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/bluetooth_classic.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.h" +#include "platform_v2/public/system_clock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/time/time.h" + +namespace location { +namespace nearby { +namespace connections { +namespace { + +constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000); + +class BluetoothClassicTest : public ::testing::Test { + protected: + using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; + + BluetoothClassicTest() { + env_.Reset(); + radio_a_ = std::make_unique(); + radio_b_ = std::make_unique(); + bt_a_ = std::make_unique(*radio_a_); + bt_b_ = std::make_unique(*radio_b_); + radio_a_->GetBluetoothAdapter().SetName("Device-A"); + radio_b_->GetBluetoothAdapter().SetName("Device-B"); + radio_a_->Enable(); + radio_b_->Enable(); + env_.Sync(); + } + + ~BluetoothClassicTest() override { + env_.Sync(false); + radio_a_->Disable(); + radio_b_->Disable(); + bt_a_.reset(); + bt_b_.reset(); + env_.Sync(false); + radio_a_.reset(); + radio_b_.reset(); + env_.Reset(); + } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; + + std::unique_ptr radio_a_; + std::unique_ptr radio_b_; + std::unique_ptr bt_a_; + std::unique_ptr bt_b_; +}; + +TEST_F(BluetoothClassicTest, CanConstructValidObject) { + EXPECT_TRUE(bt_a_->IsMediumValid()); + EXPECT_TRUE(bt_a_->IsAdapterValid()); + EXPECT_TRUE(bt_a_->IsAvailable()); + EXPECT_TRUE(bt_b_->IsMediumValid()); + EXPECT_TRUE(bt_b_->IsAdapterValid()); + EXPECT_TRUE(bt_b_->IsAvailable()); + EXPECT_NE(&radio_a_->GetBluetoothAdapter(), &radio_b_->GetBluetoothAdapter()); +} + +TEST_F(BluetoothClassicTest, CanStartAdvertising) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); +} + +TEST_F(BluetoothClassicTest, CanStopAdvertising) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); + EXPECT_TRUE(bt_a_->TurnOffDiscoverability()); +} + +TEST_F(BluetoothClassicTest, CanStartDiscovery) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName); + CountDownLatch latch(1); + EXPECT_TRUE(bt_b_->StartDiscovery({ + .device_discovered_cb = + [&latch](BluetoothDevice& device) { latch.CountDown(); }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_a_->TurnOffDiscoverability()); +} + +TEST_F(BluetoothClassicTest, CanStopDiscovery) { + CountDownLatch latch(1); + EXPECT_TRUE(bt_a_->StartDiscovery({ + .device_discovered_cb = + [&latch](BluetoothDevice& device) { latch.CountDown(); }, + })); + EXPECT_FALSE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_a_->StopDiscovery()); +} + +TEST_F(BluetoothClassicTest, CanStartAcceptingConnections) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + constexpr absl::string_view kServiceName{"service name"}; + + BluetoothRadio& radio_for_client = *radio_a_; + BluetoothRadio& radio_for_server = *radio_b_; + BluetoothClassic& bt_client = *bt_a_; + BluetoothClassic& bt_server = *bt_b_; + + EXPECT_TRUE(radio_for_client.IsEnabled()); + EXPECT_TRUE(radio_for_server.IsEnabled()); + + EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(), kDeviceName); + CountDownLatch latch(1); + BluetoothDevice discovered_device; + EXPECT_TRUE(bt_client.StartDiscovery({ + .device_discovered_cb = + [&latch, &discovered_device](BluetoothDevice& device) { + discovered_device = device; + NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device, + &device.GetImpl()); + latch.CountDown(); + }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.TurnOffDiscoverability()); + EXPECT_TRUE(discovered_device.IsValid()); + EXPECT_TRUE( + bt_server.StartAcceptingConnections(std::string(kServiceName), {})); + // Allow StartAcceptingConnections do something, before stopping it. + // This is best effort, because no callbacks are invoked in this scenario. + SystemClock::Sleep(kWaitDuration); + EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName))); +} + +TEST_F(BluetoothClassicTest, CanConnect) { + constexpr absl::string_view kDeviceName{"Simulated BT device #1"}; + constexpr absl::string_view kServiceName{"service name"}; + + BluetoothRadio& radio_for_client = *radio_a_; + BluetoothRadio& radio_for_server = *radio_b_; + BluetoothClassic& bt_client = *bt_a_; + BluetoothClassic& bt_server = *bt_b_; + + EXPECT_TRUE(radio_for_client.IsEnabled()); + EXPECT_TRUE(radio_for_server.IsEnabled()); + + EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName))); + EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(), + std::string(kDeviceName)); + CountDownLatch latch(1); + BluetoothDevice discovered_device; + EXPECT_TRUE(bt_client.StartDiscovery({ + .device_discovered_cb = + [&latch, &discovered_device](BluetoothDevice& device) { + discovered_device = device; + NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device, + &device.GetImpl()); + latch.CountDown(); + }, + })); + EXPECT_TRUE(latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.TurnOffDiscoverability()); + ASSERT_TRUE(discovered_device.IsValid()); + BluetoothSocket socket_for_server; + CountDownLatch accept_latch(1); + EXPECT_TRUE(bt_server.StartAcceptingConnections( + std::string(kServiceName), + { + .accepted_cb = + [&socket_for_server, &accept_latch](BluetoothSocket socket) { + socket_for_server = std::move(socket); + accept_latch.CountDown(); + }, + })); + BluetoothSocket socket_for_client = + bt_client.Connect(discovered_device, std::string(kServiceName)); + EXPECT_TRUE(accept_latch.Await(kWaitDuration).result()); + EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName))); + EXPECT_TRUE(socket_for_server.IsValid()); + EXPECT_TRUE(socket_for_client.IsValid()); + EXPECT_TRUE(socket_for_server.GetRemoteDevice().IsValid()); + EXPECT_TRUE(socket_for_client.GetRemoteDevice().IsValid()); +} + +} // namespace +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/bluetooth_radio.cc b/cpp/core_v2/internal/mediums/bluetooth_radio.cc index 77a7ec00..c7a650a4 100644 --- a/cpp/core_v2/internal/mediums/bluetooth_radio.cc +++ b/cpp/core_v2/internal/mediums/bluetooth_radio.cc @@ -8,6 +8,8 @@ namespace location { namespace nearby { namespace connections { +constexpr absl::Duration BluetoothRadio::kPauseBetweenToggle; + BluetoothRadio::BluetoothRadio() { if (!IsAdapterValid()) { NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported"); diff --git a/cpp/core_v2/internal/mediums/mediums.cc b/cpp/core_v2/internal/mediums/mediums.cc new file mode 100644 index 00000000..aa070252 --- /dev/null +++ b/cpp/core_v2/internal/mediums/mediums.cc @@ -0,0 +1,17 @@ +#include "core_v2/internal/mediums/mediums.h" + +namespace location { +namespace nearby { +namespace connections { + +BluetoothRadio& Mediums::GetBluetoothRadio() { + return bluetooth_radio_; +} + +BluetoothClassic& Mediums::GetBluetoothClassic() { + return bluetooth_classic_; +} + +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/cpp/core_v2/internal/mediums/mediums.h b/cpp/core_v2/internal/mediums/mediums.h new file mode 100644 index 00000000..230ba61e --- /dev/null +++ b/cpp/core_v2/internal/mediums/mediums.h @@ -0,0 +1,40 @@ +#ifndef CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ +#define CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ + +#include "core_v2/internal/mediums/bluetooth_classic.h" +#include "core_v2/internal/mediums/bluetooth_radio.h" + +namespace location { +namespace nearby { +namespace connections { + +// Facilitates convenient and reliable usage of various wireless mediums. +class Mediums { + public: + Mediums() = default; + ~Mediums() = default; + + // Returns a handle to the Bluetooth radio. + BluetoothRadio& GetBluetoothRadio(); + + // Returns a handle to the Bluetooth Classic medium. + BluetoothClassic& GetBluetoothClassic(); + + private: + // The order of declaration is critical for both construction and + // destruction. + // + // 1) Construction: The individual mediums have a dependency on the + // corresponding radio, so the radio must be initialized first. + // + // 2) Destruction: The individual mediums should be shut down before the + // corresponding radio. + BluetoothRadio bluetooth_radio_; + BluetoothClassic bluetooth_classic_{bluetooth_radio_}; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_ diff --git a/cpp/core_v2/internal/mediums/webrtc/BUILD b/cpp/core_v2/internal/mediums/webrtc/BUILD index 9805da7e..d354a426 100644 --- a/cpp/core_v2/internal/mediums/webrtc/BUILD +++ b/cpp/core_v2/internal/mediums/webrtc/BUILD @@ -19,7 +19,7 @@ cc_library( "//platform_v2/public:logging", "//platform_v2/public:types", "//absl/memory", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) @@ -35,7 +35,7 @@ cc_test( "//platform_v2/impl/g3", # buildcleaner: keep "//platform_v2/public:comm", "//testing/base/public:gunit_main", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) @@ -61,7 +61,7 @@ cc_test( "//platform_v2/impl/g3", # buildcleaner: keep "//net/proto2/public:proto2", "//testing/base/public:gunit_main", - "//webrtc/pc:peerconnection", # buildcleaner: keep + "//webrtc/files/stable/webrtc/pc:peerconnection", # buildcleaner: keep ], ) @@ -84,6 +84,6 @@ cc_library( ":peer_id", "//platform_v2/base", "//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h index 7f5ca6dc..b2b4d523 100644 --- a/cpp/core_v2/internal/mediums/webrtc/connection_flow.h +++ b/cpp/core_v2/internal/mediums/webrtc/connection_flow.h @@ -10,8 +10,8 @@ #include "platform_v2/public/future.h" #include "platform_v2/public/single_thread_executor.h" #include "platform_v2/public/webrtc.h" -#include "webrtc/api/data_channel_interface.h" -#include "webrtc/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { 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 62adf483..101b6ee0 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { 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 7c30ef7b..fd4491d0 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h b/cpp/core_v2/internal/mediums/webrtc/signaling_frames.h index 78fe328a..63a92718 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h b/cpp/core_v2/internal/mediums/webrtc/webrtc_socket.h index c0268f65..e5d90939 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/api/data_channel_interface.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" namespace location { namespace nearby { namespace connections { 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 423b06ed..89184569 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/api/data_channel_interface.h" +#include "webrtc/files/stable/webrtc/api/data_channel_interface.h" namespace location { namespace nearby { diff --git a/cpp/core_v2/internal/offline_frames.cc b/cpp/core_v2/internal/offline_frames.cc index fc5c5572..792922bb 100644 --- a/cpp/core_v2/internal/offline_frames.cc +++ b/cpp/core_v2/internal/offline_frames.cc @@ -3,7 +3,7 @@ #include #include -#include "google/protobuf/message_lite.h" +#include "core/internal/message_lite.h" #include "platform_v2/base/byte_array.h" namespace location { diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index 62c38942..1b155f0c 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/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform/api/webrtc.h b/cpp/platform/api/webrtc.h index 39e09515..c428c0cb 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/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 a0d9013f..cfe2df3d 100644 --- a/cpp/platform_v2/api/BUILD +++ b/cpp/platform_v2/api/BUILD @@ -52,7 +52,7 @@ cc_library( "//platform_v2/base", "//absl/strings", "//absl/types:optional", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) diff --git a/cpp/platform_v2/api/platform.h b/cpp/platform_v2/api/platform.h index f710897f..05b36280 100644 --- a/cpp/platform_v2/api/platform.h +++ b/cpp/platform_v2/api/platform.h @@ -65,9 +65,11 @@ class ImplementationPlatform { // Protocol implementations, domain-specific support static std::unique_ptr CreateBluetoothAdapter(); - static std::unique_ptr CreateBluetoothClassicMedium(); - static std::unique_ptr CreateBleMedium(); - static std::unique_ptr CreateBleV2Medium(); + static std::unique_ptr CreateBluetoothClassicMedium( + BluetoothAdapter&); + static std::unique_ptr CreateBleMedium(BluetoothAdapter&); + static std::unique_ptr CreateBleV2Medium( + BluetoothAdapter&); static std::unique_ptr CreateServerSyncMedium(); static std::unique_ptr CreateWifiMedium(); static std::unique_ptr CreateWifiLanMedium(); diff --git a/cpp/platform_v2/api/webrtc.h b/cpp/platform_v2/api/webrtc.h index d07bc699..7d89b281 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/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/platform_v2/base/medium_environment.cc b/cpp/platform_v2/base/medium_environment.cc index 6c47cc33..4430a003 100644 --- a/cpp/platform_v2/base/medium_environment.cc +++ b/cpp/platform_v2/base/medium_environment.cc @@ -21,8 +21,23 @@ MediumEnvironment& MediumEnvironment::Instance() { return *env; } +void MediumEnvironment::Start() { + if (!enabled_.exchange(true)) { + NEARBY_LOG(INFO, "MediumEnvironment::Start()"); + Reset(); + } +} + +void MediumEnvironment::Stop() { + if (enabled_.exchange(false)) { + NEARBY_LOG(INFO, "MediumEnvironment::Stop()"); + Sync(false); + } +} + void MediumEnvironment::Reset() { RunOnMediumEnvironmentThread([this]() { + NEARBY_LOG(INFO, "MediumEnvironment::Reset()"); bluetooth_adapters_.clear(); bluetooth_mediums_.clear(); }); @@ -31,6 +46,7 @@ void MediumEnvironment::Reset() { void MediumEnvironment::Sync(bool enable_notifications) { enable_notifications_ = enable_notifications; + NEARBY_LOG(INFO, "MediumEnvironment::sync(%d)", enable_notifications); int count = 0; do { CountDownLatch latch(1); @@ -50,6 +66,7 @@ void MediumEnvironment::Sync(bool enable_notifications) { void MediumEnvironment::OnBluetoothAdapterChangedState( api::BluetoothAdapter& adapter, api::BluetoothDevice& adapter_device, std::string name, bool enabled, api::BluetoothAdapter::ScanMode mode) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &adapter, &adapter_device, name = std::move(name), enabled, mode]() { NEARBY_LOG(INFO, @@ -74,6 +91,7 @@ void MediumEnvironment::OnDeviceStateChanged( 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( @@ -136,6 +154,7 @@ void MediumEnvironment::RunOnMediumEnvironmentThread( void MediumEnvironment::RegisterBluetoothMedium( api::BluetoothClassicMedium& medium, api::BluetoothAdapter& medium_adapter) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium, &medium_adapter]() { auto& context = bluetooth_mediums_ .insert({&medium, @@ -156,6 +175,7 @@ void MediumEnvironment::RegisterBluetoothMedium( void MediumEnvironment::UpdateBluetoothMedium( api::BluetoothClassicMedium& medium, BluetoothDiscoveryCallback callback) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium, callback = std::move(callback)]() { auto item = bluetooth_mediums_.find(&medium); @@ -178,6 +198,7 @@ void MediumEnvironment::UpdateBluetoothMedium( void MediumEnvironment::UnregisterBluetoothMedium( api::BluetoothClassicMedium& medium) { + if (!enabled_) return; RunOnMediumEnvironmentThread([this, &medium]() { auto item = bluetooth_mediums_.extract(&medium); if (item.empty()) return; diff --git a/cpp/platform_v2/base/medium_environment.h b/cpp/platform_v2/base/medium_environment.h index b00eafc2..44e83e1a 100644 --- a/cpp/platform_v2/base/medium_environment.h +++ b/cpp/platform_v2/base/medium_environment.h @@ -27,6 +27,16 @@ class MediumEnvironment { // Creates and returns a reference to the global test environment instance. static MediumEnvironment& Instance(); + // Global ON/OFF switch for medium environment. + // Start & Stop work as On/Off switch for this object. + // Default state (after creation) is ON, to make it compatible with early + // tests that are already using it and relying on it being ON. + + // Enables Medium environment. + void Start(); + // Disables Medium environment. + void Stop(); + // Clears state. No notifications are sent. void Reset(); @@ -95,6 +105,7 @@ class MediumEnvironment { api::BluetoothAdapter::ScanMode mode, bool enabled); void RunOnMediumEnvironmentThread(std::function runnable); + std::atomic_bool enabled_ = true; std::atomic_int job_count_ = 0; std::atomic_bool enable_notifications_ = false; SingleThreadExecutor executor_; diff --git a/cpp/platform_v2/impl/g3/BUILD b/cpp/platform_v2/impl/g3/BUILD index 58b31cb9..69a23663 100644 --- a/cpp/platform_v2/impl/g3/BUILD +++ b/cpp/platform_v2/impl/g3/BUILD @@ -39,10 +39,12 @@ cc_library( testonly = True, srcs = [ "bluetooth_adapter.cc", + "bluetooth_classic.cc", "webrtc.cc", ], hdrs = [ "bluetooth_adapter.h", + "bluetooth_classic.h", "webrtc.h", ], visibility = [ @@ -51,13 +53,17 @@ cc_library( deps = [ ":types", "//platform_v2/api:comm", + "//platform_v2/base", + "//platform_v2/base:logging", "//platform_v2/base:test_util", "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", "//absl/strings", "//absl/synchronization", - "//webrtc/api:create_peerconnection_factory", #buildcleaner: keep - "//webrtc/api:libjingle_peerconnection_api", - "//webrtc/api/task_queue:default_task_queue_factory", + "//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", ], ) diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc index 505ead84..748513b7 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.cc +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.cc @@ -3,6 +3,7 @@ #include #include "platform_v2/base/medium_environment.h" +#include "platform_v2/impl/g3/bluetooth_classic.h" namespace location { namespace nearby { @@ -11,9 +12,13 @@ namespace g3 { BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) : adapter_(*adapter) {} +std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } + BluetoothAdapter::~BluetoothAdapter() { SetStatus(Status::kDisabled); } -std::string BluetoothDevice::GetName() const { return adapter_.GetName(); } +void BluetoothAdapter::SetMedium(api::BluetoothClassicMedium* medium) { + medium_ = medium; +} bool BluetoothAdapter::SetStatus(Status status) { BluetoothAdapter::ScanMode mode; diff --git a/cpp/platform_v2/impl/g3/bluetooth_adapter.h b/cpp/platform_v2/impl/g3/bluetooth_adapter.h index 9747d7e0..8ce2b719 100644 --- a/cpp/platform_v2/impl/g3/bluetooth_adapter.h +++ b/cpp/platform_v2/impl/g3/bluetooth_adapter.h @@ -70,9 +70,13 @@ class BluetoothAdapter : public api::BluetoothAdapter { BluetoothDevice& GetDevice() { return device_; } + void SetMedium(api::BluetoothClassicMedium* medium); + api::BluetoothClassicMedium* GetMedium() { return medium_; } + private: mutable absl::Mutex mutex_; BluetoothDevice device_{this}; + api::BluetoothClassicMedium* medium_ = nullptr; ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone; std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device"; bool enabled_ ABSL_GUARDED_BY(mutex_) = false; diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.cc b/cpp/platform_v2/impl/g3/bluetooth_classic.cc new file mode 100644 index 00000000..12232eb6 --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.cc @@ -0,0 +1,240 @@ +#include "platform_v2/impl/g3/bluetooth_classic.h" + +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#include "platform_v2/base/logging.h" +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +void BluetoothSocket::Connect(BluetoothSocket& other) { + absl::MutexLock lock(&mutex_); + remote_socket_ = &other; +} + +bool BluetoothSocket::IsConnected() const { + absl::MutexLock lock(&mutex_); + return IsConnectedLocked(); +} + +bool BluetoothSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +bool BluetoothSocket::IsConnectedLocked() const { + return remote_socket_ != nullptr; +} + +InputStream& BluetoothSocket::GetInputStream() { + auto* remote_socket = GetRemoteSocket(); + CHECK(remote_socket != nullptr); + return remote_socket->GetLocalInputStream(); +} + +OutputStream& BluetoothSocket::GetOutputStream() { + return GetLocalOutputStream(); +} + +InputStream& BluetoothSocket::GetLocalInputStream() { + absl::MutexLock lock(&mutex_); + return output_.GetInputStream(); +} + +OutputStream& BluetoothSocket::GetLocalOutputStream() { + absl::MutexLock lock(&mutex_); + 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(); + } + return {Exception::kSuccess}; +} + +BluetoothSocket* BluetoothSocket::GetRemoteSocket() { + absl::MutexLock lock(&mutex_); + return remote_socket_; +} + +BluetoothDevice* BluetoothSocket::GetRemoteDevice() { + BluetoothAdapter* remote_adapter = nullptr; + { + absl::MutexLock lock(&mutex_); + if (remote_socket_ == nullptr || remote_socket_->adapter_ == nullptr) { + return nullptr; + } + remote_adapter = remote_socket_->adapter_; + } + return remote_adapter ? &remote_adapter->GetDevice() : nullptr; +} + +std::unique_ptr BluetoothServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + while (pending_sockets_.empty()) { + cond_.Wait(&mutex_); + if (closed_) break; + } + // whether or not we were running in the wait loop, return early if closed. + if (closed_) return {}; + auto* remote_socket = + pending_sockets_.extract(pending_sockets_.begin()).value(); + CHECK(remote_socket); + auto local_socket = std::make_unique(adapter_); + local_socket->Connect(*remote_socket); + remote_socket->Connect(*local_socket); + cond_.SignalAll(); + return local_socket; +} + +bool BluetoothServerSocket::Connect(BluetoothSocket& socket) { + absl::MutexLock lock(&mutex_); + if (closed_) return false; + if (socket.IsConnected()) { + NEARBY_LOG(ERROR, + "Failed to connect to BT server socket: already connected"); + return true; // already connected. + } + // add client socket to the pending list + pending_sockets_.emplace(&socket); + cond_.SignalAll(); + while (!socket.IsConnected()) { + cond_.Wait(&mutex_); + if (closed_) return false; + } + return true; +} + +void BluetoothServerSocket::SetCloseNotifier(std::function notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +BluetoothServerSocket::~BluetoothServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +Exception BluetoothServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception BluetoothServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + +BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter) + // TODO(apolyudov): implement and use downcast<> with static assertions. + : adapter_(static_cast(&adapter)) { + adapter_->SetMedium(this); + auto& env = MediumEnvironment::Instance(); + env.RegisterBluetoothMedium(*this, GetAdapter()); +} + +BluetoothClassicMedium::~BluetoothClassicMedium() { + adapter_->SetMedium(nullptr); + auto& env = MediumEnvironment::Instance(); + env.UnregisterBluetoothMedium(*this); +} + +bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) { + auto& env = MediumEnvironment::Instance(); + env.UpdateBluetoothMedium(*this, std::move(callback)); + return true; +} + +bool BluetoothClassicMedium::StopDiscovery() { + auto& env = MediumEnvironment::Instance(); + env.UpdateBluetoothMedium(*this, {}); + return true; +} + +std::unique_ptr BluetoothClassicMedium::ConnectToService( + api::BluetoothDevice& remote_device, const std::string& service_uuid) { + NEARBY_LOG(INFO, + "G3 ConnectToService [self]: medium=%p, adapter=%p, device=%p", + this, &GetAdapter(), &GetAdapter().GetDevice()); + // First, find an instance of remote medium, that exposed this device. + auto& adapter = static_cast(remote_device).GetAdapter(); + auto* medium = static_cast(adapter.GetMedium()); + + if (!medium) return {}; // Adapter is not bound to medium. Bail out. + + BluetoothServerSocket* server_socket = nullptr; + NEARBY_LOG( + INFO, + "G3 ConnectToService [peer]: medium=%p, adapter=%p, device=%p, uuid=%s", + medium, &adapter, &remote_device, service_uuid.c_str()); + // Then, find our server socket context in this medium. + { + absl::MutexLock medium_lock(&medium->mutex_); + auto item = medium->sockets_.find(service_uuid); + server_socket = item != sockets_.end() ? item->second : nullptr; + if (server_socket == nullptr) { + NEARBY_LOG(ERROR, "Failed to find BT Server socket: uuid=%s", + service_uuid.c_str()); + return {}; + } + } + + auto socket = std::make_unique(&GetAdapter()); + // Finally, Request to connect to this socket. + if (!server_socket->Connect(*socket)) { + NEARBY_LOG(ERROR, "Failed to connect to existing BT Server socket: uuid=%s", + service_uuid.c_str()); + return {}; + } + + NEARBY_LOG(INFO, "G3 ConnectToService: connected: socket=%p", socket.get()); + return socket; +} + +std::unique_ptr +BluetoothClassicMedium::ListenForService(const std::string& service_name, + const std::string& service_uuid) { + auto socket = std::make_unique(GetAdapter()); + socket->SetCloseNotifier([this, uuid = service_uuid]() { + absl::MutexLock lock(&mutex_); + sockets_.erase(uuid); + }); + NEARBY_LOG(INFO, "Adding service: medium=%p, uuid=%s", this, + service_uuid.c_str()); + absl::MutexLock lock(&mutex_); + sockets_.emplace(service_uuid, socket.get()); + return socket; +} + +} // namespace g3 +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/impl/g3/bluetooth_classic.h b/cpp/platform_v2/impl/g3/bluetooth_classic.h new file mode 100644 index 00000000..77dfca5a --- /dev/null +++ b/cpp/platform_v2/impl/g3/bluetooth_classic.h @@ -0,0 +1,218 @@ +#ifndef PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "platform_v2/api/bluetooth_classic.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/listeners.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/pipe.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/synchronization/mutex.h" + +namespace location { +namespace nearby { +namespace g3 { + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket : public api::BluetoothSocket { + public: + BluetoothSocket() = default; + explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {} + ~BluetoothSocket() override = default; + + // Connects to another BluetoothSocket, to form a functional low-level + // channel. From this point on, and until Close is called, connection exists. + void Connect(BluetoothSocket& other); + + // NOTE: + // It is an undefined behavior if GetInputStream() or GetOutputStream() is + // called for a not-connected BluetoothSocket, i.e. any object that is not + // returned by BluetoothClassicMedium::ConnectToService() for client side or + // BluetoothServerSocket::Accept() for server side of connection. + + // Returns the InputStream of this connected BluetoothSocket. + InputStream& GetInputStream() override; + + // Returns the OutputStream of this connected BluetoothSocket. + // This stream is for local side to write. + OutputStream& GetOutputStream() override; + + // Returns address of a remote BluetoothSocket or nullptr. + BluetoothSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns true if socket is closed. + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Closes both input and output streams, marks Socket as closed. + // After this call object should be treated as not connected. + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + // Returns valid BluetoothDevice pointer if there is a connection, and + // nullptr otherwise. + BluetoothDevice* GetRemoteDevice() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + // Returns true if connection exists to the (possibly closed) remote socket. + bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + // Returns InputStream of our side of a connection. + // This is what the remote side is supposed to read from. + // This is a helper for GetInputStream() method. + InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns OutputStream of our side of a connection. + // This is what the local size is supposed to write to. + // This is a helper for GetOutputStream() method. + OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_); + + // 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_; + mutable absl::Mutex mutex_; + BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. + BluetoothSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket : public api::BluetoothServerSocket { + public: + explicit BluetoothServerSocket(BluetoothAdapter& adapter) + : adapter_(&adapter) {} + ~BluetoothServerSocket() override; + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + // + // Called by the server side of a connection. + // Returns BluetoothSocket to the server side. + // If not null, returned socket is connected to its remote (client-side) peer. + std::unique_ptr Accept() override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Blocks until either: + // - connection is available, or + // - server socket is closed, or + // - error happens. + // + // Called by the client side of a connection. + // socket is an initialized BluetoothSocket, associated with a client + // BluetoothAdapter. + // Returns true, if socket is successfully connected. + bool Connect(BluetoothSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_); + + // Called by the server side of a connection before passing ownership of + // BluetoothServerSocker to user, to track validity of a pointer to this + // server socket, + void SetCloseNotifier(std::function notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + // Calls close_notifier if it was previously set, and marks socket as closed. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + absl::Mutex mutex_; + absl::CondVar cond_; + BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only. + absl::flat_hash_set pending_sockets_ + ABSL_GUARDED_BY(mutex_); + std::function close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium : public api::BluetoothClassicMedium { + public: + explicit BluetoothClassicMedium(api::BluetoothAdapter& adapter); + ~BluetoothClassicMedium() override; + + // NOTE(DiscoveryCallback): + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Connects to existing remote BT service. + // + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // On success, returns a new BluetoothSocket. + // On error, returns nullptr. + std::unique_ptr ConnectToService( + api::BluetoothDevice& remote_device, + const std::string& service_uuid) override ABSL_LOCKS_EXCLUDED(mutex_); + + BluetoothAdapter& GetAdapter() { return *adapter_; } + + // Creates BT service, and begins listening for remote attempts to connect. + // + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns nullptr on error. + std::unique_ptr ListenForService( + const std::string& service_name, const std::string& service_uuid) override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + absl::Mutex mutex_; + BluetoothAdapter* adapter_; // Our device adapter; read-only. + absl::flat_hash_map sockets_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace g3 +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_IMPL_G3_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/impl/g3/platform.cc b/cpp/platform_v2/impl/g3/platform.cc index 73673f77..a77f6695 100644 --- a/cpp/platform_v2/impl/g3/platform.cc +++ b/cpp/platform_v2/impl/g3/platform.cc @@ -21,6 +21,7 @@ #include "platform_v2/impl/g3/atomic_boolean.h" #include "platform_v2/impl/g3/atomic_reference_any.h" #include "platform_v2/impl/g3/bluetooth_adapter.h" +#include "platform_v2/impl/g3/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/multi_thread_executor.h" @@ -96,15 +97,18 @@ std::unique_ptr ImplementationPlatform::CreateOutputFile( } std::unique_ptr -ImplementationPlatform::CreateBluetoothClassicMedium() { - return std::unique_ptr(); +ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return absl::make_unique(adapter); } -std::unique_ptr ImplementationPlatform::CreateBleMedium() { +std::unique_ptr ImplementationPlatform::CreateBleMedium( + api::BluetoothAdapter& adapter) { return std::unique_ptr(); } -std::unique_ptr ImplementationPlatform::CreateBleV2Medium() { +std::unique_ptr ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { return std::unique_ptr(); } diff --git a/cpp/platform_v2/impl/g3/webrtc.cc b/cpp/platform_v2/impl/g3/webrtc.cc index 6e70be50..d8f349f4 100644 --- a/cpp/platform_v2/impl/g3/webrtc.cc +++ b/cpp/platform_v2/impl/g3/webrtc.cc @@ -1,6 +1,6 @@ #include "platform_v2/impl/g3/webrtc.h" -#include "webrtc/api/task_queue/default_task_queue_factory.h" +#include "webrtc/files/stable/webrtc/api/task_queue/default_task_queue_factory.h" namespace location { namespace nearby { diff --git a/cpp/platform_v2/impl/g3/webrtc.h b/cpp/platform_v2/impl/g3/webrtc.h index 053a30b8..35a4da10 100644 --- a/cpp/platform_v2/impl/g3/webrtc.h +++ b/cpp/platform_v2/impl/g3/webrtc.h @@ -5,7 +5,7 @@ #include "platform_v2/api/webrtc.h" #include "absl/strings/string_view.h" -#include "webrtc/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { diff --git a/cpp/platform_v2/public/BUILD b/cpp/platform_v2/public/BUILD index 204d713a..0ff90145 100644 --- a/cpp/platform_v2/public/BUILD +++ b/cpp/platform_v2/public/BUILD @@ -28,11 +28,13 @@ cc_library( "//platform_v2/public:__pkg__", ], deps = [ + ":logging", "//platform_v2/api:platform", "//platform_v2/api:types", "//platform_v2/base", "//platform_v2/base:util", "//absl/base:core_headers", + "//absl/container:flat_hash_map", "//absl/time", "//absl/types:any", ], @@ -40,8 +42,12 @@ cc_library( cc_library( name = "comm", + srcs = [ + "bluetooth_classic.cc", + ], hdrs = [ "bluetooth_adapter.h", + "bluetooth_classic.h", "webrtc.h", ], visibility = [ @@ -49,10 +55,14 @@ cc_library( "//platform_v2/public:__pkg__", ], deps = [ + ":logging", + ":types", "//platform_v2/api:comm", "//platform_v2/api:platform", + "//platform_v2/base", + "//absl/container:flat_hash_map", "//absl/strings", - "//webrtc/api:libjingle_peerconnection_api", + "//webrtc/files/stable/webrtc/api:libjingle_peerconnection_api", ], ) @@ -73,10 +83,12 @@ cc_library( cc_test( name = "public_test", + size = "small", srcs = [ "atomic_boolean_test.cc", "atomic_reference_test.cc", "bluetooth_adapter_test.cc", + "bluetooth_classic_test.cc", "count_down_latch_test.cc", "crypto_test.cc", "future_test.cc", @@ -93,6 +105,7 @@ cc_test( ":logging", ":types", "//platform_v2/base", + "//platform_v2/base:test_util", "//platform_v2/impl/g3", # build_cleaner: keep "//testing/base/public:gunit_main", "//absl/synchronization", diff --git a/cpp/platform_v2/public/bluetooth_adapter.h b/cpp/platform_v2/public/bluetooth_adapter.h index f3b9df4e..beaaf4d3 100644 --- a/cpp/platform_v2/public/bluetooth_adapter.h +++ b/cpp/platform_v2/public/bluetooth_adapter.h @@ -4,55 +4,81 @@ #include #include "platform_v2/api/bluetooth_adapter.h" +#include "platform_v2/api/bluetooth_classic.h" #include "platform_v2/api/platform.h" #include "absl/strings/string_view.h" namespace location { namespace nearby { +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice final { + public: + BluetoothDevice() = default; + BluetoothDevice(const BluetoothDevice&) = default; + BluetoothDevice& operator=(const BluetoothDevice&) = default; + explicit BluetoothDevice(api::BluetoothDevice* device) : impl_(device) {} + ~BluetoothDevice() = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const { return impl_->GetName(); } + + api::BluetoothDevice& GetImpl() { return *impl_; } + bool IsValid() const { return impl_ != nullptr; } + + private: + api::BluetoothDevice* impl_; +}; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html -class BluetoothAdapter : public api::BluetoothAdapter { +class BluetoothAdapter final { public: using Status = api::BluetoothAdapter::Status; using ScanMode = api::BluetoothAdapter::ScanMode; BluetoothAdapter() : impl_(api::ImplementationPlatform::CreateBluetoothAdapter()) {} - ~BluetoothAdapter() override = default; + ~BluetoothAdapter() = default; BluetoothAdapter(BluetoothAdapter&&) = default; BluetoothAdapter& operator=(BluetoothAdapter&&) = default; // Synchronously sets the status of the BluetoothAdapter to 'status', and // returns true if the operation was a success. - bool SetStatus(Status status) override { return impl_->SetStatus(status); } + bool SetStatus(Status status) { return impl_->SetStatus(status); } Status GetStatus() const { return IsEnabled() ? Status::kEnabled : Status::kDisabled; } // Returns true if the BluetoothAdapter's current status is // Status::Value::kEnabled. - bool IsEnabled() const override { return impl_->IsEnabled(); } + bool IsEnabled() const { return impl_->IsEnabled(); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode() // // Returns ScanMode::kUnknown on error. - ScanMode GetScanMode() const override { return impl_->GetScanMode(); } + ScanMode GetScanMode() const { return impl_->GetScanMode(); } // Synchronously sets the scan mode of the adapter, and returns true if the // operation was a success. - bool SetScanMode(ScanMode scan_mode) override { + bool SetScanMode(ScanMode scan_mode) { return impl_->SetScanMode(scan_mode); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName() // Returns an empty string on error - std::string GetName() const override { return impl_->GetName(); } + std::string GetName() const { return impl_->GetName(); } // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String) - bool SetName(absl::string_view name) override { return impl_->SetName(name); } + bool SetName(absl::string_view name) { return impl_->SetName(name); } 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 BluetoothAdapter object is + // itself valid. It matches Core() object lifetime. + api::BluetoothAdapter& GetImpl() { return *impl_; } + private: std::unique_ptr impl_; }; diff --git a/cpp/platform_v2/public/bluetooth_classic.cc b/cpp/platform_v2/public/bluetooth_classic.cc new file mode 100644 index 00000000..d3997d30 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic.cc @@ -0,0 +1,85 @@ +#include "platform_v2/public/bluetooth_classic.h" + +#include "platform_v2/public/logging.h" +#include "platform_v2/public/mutex_lock.h" + +namespace location { +namespace nearby { + +BluetoothClassicMedium::~BluetoothClassicMedium() { StopDiscovery(); } + +BluetoothSocket BluetoothClassicMedium::ConnectToService( + BluetoothDevice& remote_device, const std::string& service_uuid) { + NEARBY_LOG(INFO, + "BluetoothClassicMedium::ConnectToService: device=%p [impl=%p]", + &remote_device, &remote_device.GetImpl()); + return BluetoothSocket( + impl_->ConnectToService(remote_device.GetImpl(), service_uuid)); +} + +bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) { + { + MutexLock lock(&mutex_); + if (discovery_enabled_) { + NEARBY_LOG(INFO, "BT Discovery already enabled; impl=%p", &GetImpl()); + return false; + } + discovery_callback_ = std::move(callback); + devices_.clear(); + discovery_enabled_ = true; + NEARBY_LOG(INFO, "BT Discovery enabled; impl=%p", &GetImpl()); + } + return impl_->StartDiscovery({ + .device_discovered_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto pair = devices_.emplace( + &device, absl::make_unique()); + auto& context = *pair.first->second; + if (!pair.second) { + NEARBY_LOG(INFO, "Adding (again) device=%p, impl=%p", + &context.device, &device); + return; + } + context.device = BluetoothDevice(&device); + NEARBY_LOG(INFO, "Adding device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_discovered_cb(context.device); + }, + .device_name_changed_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto& context = *devices_[&device]; + NEARBY_LOG(INFO, "Renaming device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_name_changed_cb(context.device); + }, + .device_lost_cb = + [this](api::BluetoothDevice& device) { + MutexLock lock(&mutex_); + auto item = devices_.extract(&device); + auto& context = *item.mapped(); + NEARBY_LOG(INFO, "Removing device=%p, impl=%p", &context.device, + &device); + if (!discovery_enabled_) return; + discovery_callback_.device_lost_cb(context.device); + }, + }); +} + +bool BluetoothClassicMedium::StopDiscovery() { + { + MutexLock lock(&mutex_); + if (!discovery_enabled_) return true; + discovery_enabled_ = false; + discovery_callback_ = {}; + devices_.clear(); + NEARBY_LOG(INFO, "BT Discovery disabled: impl=%p", &GetImpl()); + } + return impl_->StopDiscovery(); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/bluetooth_classic.h b/cpp/platform_v2/public/bluetooth_classic.h new file mode 100644 index 00000000..459d74b1 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic.h @@ -0,0 +1,205 @@ +#ifndef PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ +#define PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ + +#include +#include + +#include "platform_v2/api/bluetooth_classic.h" +#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/listeners.h" +#include "platform_v2/base/output_stream.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/mutex.h" +#include "absl/container/flat_hash_map.h" + +namespace location { +namespace nearby { + +// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html. +class BluetoothSocket final { + public: + BluetoothSocket() = default; + BluetoothSocket(const BluetoothSocket&) = default; + BluetoothSocket& operator=(const BluetoothSocket&) = default; + explicit BluetoothSocket(std::unique_ptr socket) + : impl_(socket.release()) {} + ~BluetoothSocket() = default; + + // Returns the InputStream of this connected BluetoothSocket. + InputStream& GetInputStream() { return impl_->GetInputStream(); } + + // Returns the OutputStream of this connected BluetoothSocket. + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + + // Closes both input and output streams, marks Socket as closed. + // After this call object should be treated as not connected. + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice() + BluetoothDevice GetRemoteDevice() { + return BluetoothDevice(impl_->GetRemoteDevice()); + } + + // 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 either BluetoothClassicMedium::ConnectTotService or + // BluetoothServerSocket::Accept(). + // 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 BluetoothSocket object is + // itself valid. Typically BluetoothSocket lifetime matches duration of the + // connection, and is controlled by end user, since they hold the instance. + api::BluetoothSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html. +class BluetoothServerSocket final { + public: + BluetoothServerSocket() = default; + BluetoothServerSocket(const BluetoothServerSocket&) = default; + BluetoothServerSocket& operator=(const BluetoothServerSocket&) = default; + ~BluetoothServerSocket() = default; + explicit BluetoothServerSocket( + std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be closed. + BluetoothSocket Accept() { return BluetoothSocket(impl_->Accept()); } + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { return impl_->Close(); } + + bool IsValid() const { return impl_ != nullptr; } + api::BluetoothServerSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; +}; + +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium final { + public: + using Platform = api::ImplementationPlatform; + struct DiscoveryCallback { + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + std::function device_discovered_cb = + DefaultCallback(); + std::function device_name_changed_cb = + DefaultCallback(); + std::function device_lost_cb = + DefaultCallback(); + }; + struct DeviceDiscoveryInfo { + BluetoothDevice device; + }; + + explicit BluetoothClassicMedium(BluetoothAdapter& adapter) + : impl_(Platform::CreateBluetoothClassicMedium(adapter.GetImpl())), + adapter_(adapter) {} + + ~BluetoothClassicMedium(); + + // NOTE(DiscoveryCallback): + // BluetoothDevice is a proxy object created as a result of BT discovery. + // Its lifetime spans between calls to device_discovered_cb and + // device_lost_cb. + // It is safe to use BluetoothDevice in device_discovered_cb() callback + // and at any time afterwards, until device_lost_cb() is called. + // It is not safe to use BluetoothDevice after returning from + // device_lost_cb() callback. + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback callback); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery(); + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns a new BluetoothSocket. On Success, BluetoothSocket::IsValid() + // returns true. + BluetoothSocket ConnectToService(BluetoothDevice& remote_device, + const std::string& service_uuid); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns a new BluetoothServerSocket. + // On Success, BluetoothServerSocket::IsValid() returns true. + BluetoothServerSocket ListenForService(const std::string& service_name, + const std::string& service_uuid) { + return BluetoothServerSocket( + impl_->ListenForService(service_name, service_uuid)); + } + + bool IsValid() const { return impl_ != nullptr; } + + api::BluetoothClassicMedium& GetImpl() { return *impl_; } + BluetoothAdapter& GetAdapter() { return adapter_; } + + private: + Mutex mutex_; + std::unique_ptr impl_; + BluetoothAdapter& adapter_; + absl::flat_hash_map> + devices_ ABSL_GUARDED_BY(mutex_); + DiscoveryCallback discovery_callback_ ABSL_GUARDED_BY(mutex_); + bool discovery_enabled_ ABSL_GUARDED_BY(mutex_) = false; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_V2_PUBLIC_BLUETOOTH_CLASSIC_H_ diff --git a/cpp/platform_v2/public/bluetooth_classic_test.cc b/cpp/platform_v2/public/bluetooth_classic_test.cc new file mode 100644 index 00000000..8bba6c44 --- /dev/null +++ b/cpp/platform_v2/public/bluetooth_classic_test.cc @@ -0,0 +1,197 @@ +#include "platform_v2/public/bluetooth_classic.h" + +#include + +#include "platform_v2/base/medium_environment.h" +#include "platform_v2/public/bluetooth_adapter.h" +#include "platform_v2/public/count_down_latch.h" +#include "platform_v2/public/logging.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 { + +class BluetoothClassicMediumTest : public ::testing::Test { + protected: + using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback; + BluetoothClassicMediumTest() { + env_.Reset(); + adapter_a_ = std::make_unique(); + adapter_b_ = std::make_unique(); + bt_a_ = std::make_unique(*adapter_a_); + bt_b_ = std::make_unique(*adapter_b_); + adapter_a_->SetName("Device-A"); + adapter_b_->SetName("Device-B"); + adapter_a_->SetStatus(BluetoothAdapter::Status::kEnabled); + adapter_b_->SetStatus(BluetoothAdapter::Status::kEnabled); + env_.Sync(); + } + ~BluetoothClassicMediumTest() override { + env_.Sync(false); + adapter_a_->SetStatus(BluetoothAdapter::Status::kDisabled); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + bt_a_.reset(); + bt_b_.reset(); + env_.Sync(false); + adapter_a_.reset(); + adapter_b_.reset(); + env_.Reset(); + } + + MediumEnvironment& env_{MediumEnvironment::Instance()}; + + std::unique_ptr adapter_a_; + std::unique_ptr adapter_b_; + std::unique_ptr bt_a_; + std::unique_ptr bt_b_; +}; + +TEST_F(BluetoothClassicMediumTest, ConstructorDestructorWorks) { + // Make sure we can create functional adapters. + ASSERT_TRUE(adapter_a_->IsValid()); + ASSERT_TRUE(adapter_b_->IsValid()); + + // Make sure we can create 2 distinct adapters. + // NOTE: multiple adapters are supported on a test platform, but not + // necessarily on every available HW platform. + // Often, HW platform supports only one BT adapter. + EXPECT_NE(&adapter_a_->GetImpl(), &adapter_b_->GetImpl()); + + // Make sure we can create functional mediums. + ASSERT_TRUE(bt_a_->IsValid()); + ASSERT_TRUE(bt_b_->IsValid()); + + // Make sure we can create 2 distinct mediums. + EXPECT_NE(&bt_a_->GetImpl(), &bt_b_->GetImpl()); +} + +TEST_F(BluetoothClassicMediumTest, CanStartDiscovery) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + .device_lost_cb = + [this, &lost_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device lost: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + lost_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + EXPECT_FALSE(adapter_b_->IsEnabled()); + EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result()); +} + +TEST_F(BluetoothClassicMediumTest, CanStopDiscovery) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + CountDownLatch lost_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + .device_lost_cb = + [this, &lost_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device lost: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + lost_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + bt_a_->StopDiscovery(); + adapter_b_->SetStatus(BluetoothAdapter::Status::kDisabled); + EXPECT_FALSE(adapter_b_->IsEnabled()); + EXPECT_FALSE(lost_latch.Await(absl::Milliseconds(1000)).result()); +} + +TEST_F(BluetoothClassicMediumTest, CanListenForService) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + found_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + std::string service_name{"service"}; + std::string service_uuid("service-uuid"); + BluetoothServerSocket server_socket = + bt_b_->ListenForService(service_name, service_uuid); + EXPECT_TRUE(server_socket.IsValid()); + server_socket.Close(); +} + +TEST_F(BluetoothClassicMediumTest, CanConnectToService) { + adapter_a_->SetScanMode(BluetoothAdapter::ScanMode::kConnectable); + CountDownLatch found_latch(1); + BluetoothDevice* discovered_device = nullptr; + bt_a_->StartDiscovery(DiscoveryCallback{ + .device_discovered_cb = + [this, &found_latch, &discovered_device](BluetoothDevice& device) { + NEARBY_LOG(INFO, "Device discovered: %s", device.GetName().c_str()); + EXPECT_EQ(device.GetName(), adapter_b_->GetName()); + discovered_device = &device; + found_latch.CountDown(); + }, + }); + adapter_b_->SetScanMode(BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_EQ(adapter_b_->GetScanMode(), + BluetoothAdapter::ScanMode::kConnectableDiscoverable); + EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result()); + std::string service_name{"service"}; + std::string service_uuid("service-uuid"); + BluetoothServerSocket server_socket = + bt_b_->ListenForService(service_name, service_uuid); + EXPECT_TRUE(server_socket.IsValid()); + BluetoothSocket socket_a; + BluetoothSocket socket_b; + EXPECT_FALSE(socket_a.IsValid()); + EXPECT_FALSE(socket_b.IsValid()); + { + SingleThreadExecutor server_executor; + SingleThreadExecutor client_executor; + client_executor.Execute( + [this, &socket_a, discovered_device, &service_uuid, &server_socket]() { + socket_a = bt_a_->ConnectToService(*discovered_device, service_uuid); + if (!socket_a.IsValid()) server_socket.Close(); + }); + server_executor.Execute( + [&socket_b, &server_socket]() { + socket_b = server_socket.Accept(); + if (!socket_b.IsValid()) server_socket.Close(); + }); + } + EXPECT_TRUE(socket_a.IsValid()); + EXPECT_TRUE(socket_b.IsValid()); + server_socket.Close(); +} + +} // namespace +} // namespace nearby +} // namespace location diff --git a/cpp/platform_v2/public/webrtc.h b/cpp/platform_v2/public/webrtc.h index a5bc50de..f0700cef 100644 --- a/cpp/platform_v2/public/webrtc.h +++ b/cpp/platform_v2/public/webrtc.h @@ -5,7 +5,7 @@ #include "platform_v2/api/platform.h" #include "platform_v2/api/webrtc.h" -#include "webrtc/api/peer_connection_interface.h" +#include "webrtc/files/stable/webrtc/api/peer_connection_interface.h" namespace location { namespace nearby { From fdd803842864ea0e189f63de4542c3290cbd1bf9 Mon Sep 17 00:00:00 2001 From: Himanshu Jaju Date: Thu, 4 Jun 2020 20:19:58 +0100 Subject: [PATCH 23/52] Fix oss.py script - Adds newline fix from release branch - Adds transformation for MessageLite from google3 to open source Change-Id: Ie6c3f5263417fbbccc67835eb22fbe568a249125 --- script/oss.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/script/oss.py b/script/oss.py index bcc1673d..e05dcc1b 100755 --- a/script/oss.py +++ b/script/oss.py @@ -88,6 +88,7 @@ def post_process_oss_files(path, args): else: top_dirs = ["cpp", "proto"] transforms = ( + ("::google3_proto_compat::MessageLite", "::google::protobuf::MessageLite"), ("third_party/webrtc/files/stable/", ""), ("webrtc/files/stable/", ""), ("third_party/", ""), @@ -154,8 +155,9 @@ def post_process_oss_files(path, args): continue if add_proto_lite_runtime and line.startswith("option "): - lines.append("option optimize_for = LITE_RUNTIME;") - modified = True + if not line.startswith("option optimize_for = LITE_RUNTIME"): + lines.append("option optimize_for = LITE_RUNTIME;\n") + modified = True # LITE_RUNTIME should be added only once per file. add_proto_lite_runtime = False From 0cabddbff74c6cd5d66e9931e86f85b5aac55f4f Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 4 Jun 2020 13:43:50 -0700 Subject: [PATCH 24/52] Update helper scripts Signed-off-by: Alexey Polyudov Change-Id: Ibaa0221a6cf55775f882d7888272e317ae3e2298 --- script/handle_oss.sh | 2 ++ script/oss.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/script/handle_oss.sh b/script/handle_oss.sh index 68d3e1af..8c5dfdad 100755 --- a/script/handle_oss.sh +++ b/script/handle_oss.sh @@ -18,3 +18,5 @@ ./oss.py --all --no-subst --fix-oss-headers . ./oss.py --all --google3-filter --no-subst --fix-oss-headers --no-recurse .. ./oss.py --google3-filter --fix-oss-headers --proto-lite-runtime .. +cd .. +/google/bin/releases/opensource/thirdparty/cross/cross . diff --git a/script/oss.py b/script/oss.py index e05dcc1b..bcf835bf 100755 --- a/script/oss.py +++ b/script/oss.py @@ -1,5 +1,20 @@ #!/usr/bin/python3 +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import argparse import os import shutil From f307784c97c6916a489ade9980750e2559dfaf0f Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 4 Jun 2020 15:03:55 -0700 Subject: [PATCH 25/52] Add OSS mandatory file Signed-off-by: Alexey Polyudov Change-Id: I2068d59ffaf55c80a66ff9068f678a6b080b5a1a --- CONTRIBUTING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..939e5341 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google.com/conduct/). From f4007e63ceb819430a0ac6abcc206d3de21113ec Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 4 Jun 2020 15:25:44 -0700 Subject: [PATCH 26/52] Improve copyrihgt header detection Signed-off-by: Alexey Polyudov Change-Id: Ib1c0928bc1323dc9b76e0f1fc64d4cb50b520ea6 --- script/oss.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/script/oss.py b/script/oss.py index bcf835bf..cd0199d8 100755 --- a/script/oss.py +++ b/script/oss.py @@ -17,6 +17,7 @@ import argparse import os +import re import shutil import sys @@ -34,6 +35,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""".split("\n") +headline_match=".*Copyright [0-9,\- ]+ Google.*" + MISSING = 0 HEADLINE = 1 PARTIAL = 2 @@ -41,20 +44,24 @@ FULL = 3 def has_copyright(lines, max_lookup=3): pos = 0 + result = MISSING + headline = re.compile(headline_match) for line in lines: pos += 1 # points to the next line - if line.find(copy_header[0]) >= 0: + if headline.match(line) != None: + result = HEADLINE break if pos > max_lookup: - return MISSING - - result = HEADLINE + return result for line in copy_header[1:]: + if pos >= len(lines): + return result if lines[pos].find(line) < 0: return result else: result = PARTIAL + pos += 1 return FULL From c3484b01c830d24e9abe9468ce5c236b9cad5d25 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Thu, 4 Jun 2020 23:16:55 -0700 Subject: [PATCH 27/52] Switch internal URLs to public Signed-off-by: Alexey Polyudov Change-Id: I1560870c3cf61256a6a28bd9aef42f0e3bc33d28 --- .gitmodules | 2 +- third_party/ukey2 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 92e5c3e7..ee1e4011 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "third_party/ukey2"] path = third_party/ukey2 - url = sso://team/nearby-eng/ukey2 + url = https://github.com/apolyudov/ukey2 branch = master [submodule "third_party/protobuf"] path = third_party/protobuf diff --git a/third_party/ukey2 b/third_party/ukey2 index 2fc30c88..5695ef20 160000 --- a/third_party/ukey2 +++ b/third_party/ukey2 @@ -1 +1 @@ -Subproject commit 2fc30c8894da17442c476d9416b5a811bfe88e32 +Subproject commit 5695ef2050b993e8034befcb874097be3867ab97 From 96c2ceaa90c23cbc2cde938ba7e52af9bf05ca3b Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 23 Jun 2020 18:29:24 -0700 Subject: [PATCH 28/52] Switch to google/ukey2 submodule Signed-off-by: Alexey Polyudov Change-Id: Ieeeef4ea2e12f7d87a63a8c89a107218010253a2 --- .gitmodules | 2 +- third_party/ukey2 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index ee1e4011..a0bb8380 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "third_party/ukey2"] path = third_party/ukey2 - url = https://github.com/apolyudov/ukey2 + url = https://github.com/google/ukey2 branch = master [submodule "third_party/protobuf"] path = third_party/protobuf diff --git a/third_party/ukey2 b/third_party/ukey2 index 5695ef20..4550c848 160000 --- a/third_party/ukey2 +++ b/third_party/ukey2 @@ -1 +1 @@ -Subproject commit 5695ef2050b993e8034befcb874097be3867ab97 +Subproject commit 4550c84830286c24f8c189cbd54edbbd986a2dd1 From 32828732ff808319e4c5fba0aa118e251131d603 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 23 Jun 2020 18:45:34 -0700 Subject: [PATCH 29/52] Update oss scripts Signed-off-by: Alexey Polyudov Change-Id: I57a29ead382a00c048ee5f26c1fc242c7dac1e2d --- script/handle_google3.sh | 17 +++++++++++++++ script/oss.py | 46 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100755 script/handle_google3.sh diff --git a/script/handle_google3.sh b/script/handle_google3.sh new file mode 100755 index 00000000..82a35602 --- /dev/null +++ b/script/handle_google3.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +./oss.py --google3-filter --proto-lite-runtime .. diff --git a/script/oss.py b/script/oss.py index c4672f25..cd0199d8 100755 --- a/script/oss.py +++ b/script/oss.py @@ -1,7 +1,23 @@ #!/usr/bin/python3 +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + import argparse import os +import re import shutil import sys @@ -19,6 +35,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.""".split("\n") +headline_match=".*Copyright [0-9,\- ]+ Google.*" + MISSING = 0 HEADLINE = 1 PARTIAL = 2 @@ -26,20 +44,24 @@ FULL = 3 def has_copyright(lines, max_lookup=3): pos = 0 + result = MISSING + headline = re.compile(headline_match) for line in lines: pos += 1 # points to the next line - if line.find(copy_header[0]) >= 0: + if headline.match(line) != None: + result = HEADLINE break if pos > max_lookup: - return MISSING - - result = HEADLINE + return result for line in copy_header[1:]: + if pos >= len(lines): + return result if lines[pos].find(line) < 0: return result else: result = PARTIAL + pos += 1 return FULL @@ -88,6 +110,9 @@ def post_process_oss_files(path, args): else: top_dirs = ["cpp", "proto"] transforms = ( + ("::google3_proto_compat::MessageLite", "::google::protobuf::MessageLite"), + ("third_party/webrtc/files/stable/", ""), + ("webrtc/files/stable/", ""), ("third_party/", ""), ("location/nearby/connections/core_v2", "core_v2"), ("location/nearby/connections/core", "core"), @@ -105,6 +130,7 @@ def post_process_oss_files(path, args): ("_portable_proto.pb.h", ".pb.h"), (".proto.h", ".pb.h"), ) + for root, dirs, files in os.walk(path): if top_level and top_dirs: # we must convert cpp/ and proto/ subtrees. @@ -123,6 +149,8 @@ def post_process_oss_files(path, args): lines=[] google3_ignore = False with open(fname, "r") as f: + add_proto_lite_runtime = args.proto_lite_runtime and fname.endswith(".proto") + for line in f: orig = line @@ -147,6 +175,14 @@ def post_process_oss_files(path, args): if google3_ignore: modified = True continue + + if add_proto_lite_runtime and line.startswith("option "): + if not line.startswith("option optimize_for = LITE_RUNTIME"): + lines.append("option optimize_for = LITE_RUNTIME;\n") + modified = True + # LITE_RUNTIME should be added only once per file. + add_proto_lite_runtime = False + lines.append(line) if args.fix_oss_headers: @@ -156,6 +192,7 @@ def post_process_oss_files(path, args): prefix, offset = options lines = add_copyright(lines, prefix, offset) modified = True + if modified: with open(fname, "w") as f: for line in lines: @@ -176,6 +213,7 @@ def main(): parser.add_argument('--no-copy', action='store_true', default=False) parser.add_argument('--no-subst', action='store_true', default=False) parser.add_argument('--no-recurse', action='store_true', default=False) + parser.add_argument('--proto-lite-runtime', action='store_true', default=False) args = parser.parse_args() if args.google3_filter: print("google3-specific code will be removed") From 6b27a508edbce839ee1b61bda58d7a60ffc62d66 Mon Sep 17 00:00:00 2001 From: Alexey Polyudov Date: Tue, 23 Jun 2020 19:52:26 -0700 Subject: [PATCH 30/52] 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. +// +//