diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f65519e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/** diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..92e5c3e7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,24 @@ +[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 +[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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..69831361 --- /dev/null +++ b/CMakeLists.txt @@ -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. + +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/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/cmake/CMakeLists-smhasher.txt b/cmake/CMakeLists-smhasher.txt new file mode 100644 index 00000000..a8fc1fd5 --- /dev/null +++ b/cmake/CMakeLists-smhasher.txt @@ -0,0 +1,28 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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) + +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..3a04d554 --- /dev/null +++ b/cmake/local_build_protobuf.cmake @@ -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. + +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..a7917fe7 --- /dev/null +++ b/cmake/local_build_setup.cmake @@ -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(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..b32a6b2e --- /dev/null +++ b/cmake/local_setup_smhasher.cmake @@ -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. + +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..aae0ce9c --- /dev/null +++ b/cmake/proto_defs.cmake @@ -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. + +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/BUILD b/cpp/core/BUILD new file mode 100644 index 00000000..549ac92a --- /dev/null +++ b/cpp/core/BUILD @@ -0,0 +1,69 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "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/CMakeLists.txt b/cpp/core/CMakeLists.txt new file mode 100644 index 00000000..71077e0e --- /dev/null +++ b/cpp/core/CMakeLists.txt @@ -0,0 +1,72 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + 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/check_compilation.cc b/cpp/core/check_compilation.cc new file mode 100644 index 00000000..e65ba161 --- /dev/null +++ b/cpp/core/check_compilation.cc @@ -0,0 +1,138 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 "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 + +int main() { + location::nearby::connections::check_compilation(); + return 0; +} diff --git a/cpp/core/core.cc b/cpp/core/core.cc new file mode 100644 index 00000000..3727138a --- /dev/null +++ b/cpp/core/core.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/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..6453780e --- /dev/null +++ b/cpp/core/core.h @@ -0,0 +1,97 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..75800414 --- /dev/null +++ b/cpp/core/internal/BUILD @@ -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. + +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", + "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", + "internal_payload_factory.cc", + "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", + "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_service_info.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 = "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"], + 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", + ], +) + +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/CMakeLists.txt b/cpp/core/internal/CMakeLists.txt new file mode 100644 index 00000000..d10c1062 --- /dev/null +++ b/cpp/core/internal/CMakeLists.txt @@ -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. + +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 + wifi_lan_service_info.cc + 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_frames.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 + 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( + NAME core_internal_test + COMMAND core_internal_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 + platform_utils +) + +add_subdirectory(mediums) diff --git a/cpp/core/internal/bandwidth_upgrade_handler.h b/cpp/core/internal/bandwidth_upgrade_handler.h new file mode 100644 index 00000000..901bdc4a --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_handler.h @@ -0,0 +1,64 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..62cf32aa --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_manager.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/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..6715c2a9 --- /dev/null +++ b/cpp/core/internal/bandwidth_upgrade_manager.h @@ -0,0 +1,80 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..85650c0f --- /dev/null +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.cc @@ -0,0 +1,159 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 { +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..867656cd --- /dev/null +++ b/cpp/core/internal/base_bandwidth_upgrade_handler.h @@ -0,0 +1,203 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..7d15c17d --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel.cc @@ -0,0 +1,367 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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.isNull() || 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 + +// TODO(b/150763574): Move implementatiopn to header or .inc file. +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..b7ffec64 --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel.h @@ -0,0 +1,126 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_endpoint_channel_test.cc b/cpp/core/internal/base_endpoint_channel_test.cc new file mode 100644 index 00000000..697d480c --- /dev/null +++ b/cpp/core/internal/base_endpoint_channel_test.cc @@ -0,0 +1,74 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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 new file mode 100644 index 00000000..b83c2409 --- /dev/null +++ b/cpp/core/internal/base_pcp_handler.cc @@ -0,0 +1,1589 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 +#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, + std::static_pointer_cast< + typename EndpointManager::IncomingOfflineFrameProcessor>( + self_)); + + 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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(self_))); + 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..17e9223f --- /dev/null +++ b/cpp/core/internal/base_pcp_handler.h @@ -0,0 +1,522 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_; + std::shared_ptr self_{this, [](void*){}}; +}; + +} // 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..0ee84373 --- /dev/null +++ b/cpp/core/internal/ble_advertisement.cc @@ -0,0 +1,291 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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( + hex_bytes->asString())); + 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..518a2385 --- /dev/null +++ b/cpp/core/internal/ble_advertisement.h @@ -0,0 +1,109 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..959a8716 --- /dev/null +++ b/cpp/core/internal/ble_advertisement_test.cc @@ -0,0 +1,367 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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. + 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_array)); + + // 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. + 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_array)); + + // 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..a10cb38a --- /dev/null +++ b/cpp/core/internal/ble_compat.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_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..73618165 --- /dev/null +++ b/cpp/core/internal/ble_endpoint_channel.cc @@ -0,0 +1,69 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +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..a966f433 --- /dev/null +++ b/cpp/core/internal/ble_endpoint_channel.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_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..19d581ca --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name.cc @@ -0,0 +1,306 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..0da3d5bc --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name.h @@ -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. + +#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..b5640c86 --- /dev/null +++ b/cpp/core/internal/bluetooth_device_name_test.cc @@ -0,0 +1,212 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..59cfd897 --- /dev/null +++ b/cpp/core/internal/bluetooth_endpoint_channel.cc @@ -0,0 +1,69 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +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..74d75bb6 --- /dev/null +++ b/cpp/core/internal/bluetooth_endpoint_channel.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_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..2fa7412e --- /dev/null +++ b/cpp/core/internal/client_proxy.cc @@ -0,0 +1,604 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 +#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..fbad987a --- /dev/null +++ b/cpp/core/internal/client_proxy.h @@ -0,0 +1,255 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..3536d17a --- /dev/null +++ b/cpp/core/internal/encryption_runner.cc @@ -0,0 +1,451 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 +#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..c5d2637c --- /dev/null +++ b/cpp/core/internal/encryption_runner.h @@ -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. + +#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..76e9c426 --- /dev/null +++ b/cpp/core/internal/endpoint_channel.h @@ -0,0 +1,83 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..ebd753de --- /dev/null +++ b/cpp/core/internal/endpoint_channel_manager.cc @@ -0,0 +1,310 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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); + + endpoint_metadata->endpoint_channel = 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..dff4d7b0 --- /dev/null +++ b/cpp/core/internal/endpoint_channel_manager.h @@ -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. + +#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..2586533f --- /dev/null +++ b/cpp/core/internal/endpoint_manager.cc @@ -0,0 +1,763 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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>(self_, frame_type, processor))); +} + +template +void EndpointManager::unregisterIncomingOfflineFrameProcessor( + V1Frame::FrameType frame_type, + Ptr::IncomingOfflineFrameProcessor> + processor) { + runOnEndpointManagerThread(MakePtr( + new endpoint_manager::UnregisterIncomingOfflineFrameProcessorRunnable< + Platform>(self_, 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( + self_, 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( + 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()); +} + +template +void EndpointManager::unregisterEndpoint( + Ptr> client_proxy, const string& endpoint_id) { + ScopedPtr> latch(Platform::createCountDownLatch(1)); + runOnEndpointManagerThread( + MakePtr(new endpoint_manager::UnregisterEndpointRunnable( + self_, 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( + self_, 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..772acf13 --- /dev/null +++ b/cpp/core/internal/endpoint_manager.h @@ -0,0 +1,248 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#include +#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_; + std::shared_ptr> self_{this, [](void*){}}; +}; + +} // 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..fdab80e4 --- /dev/null +++ b/cpp/core/internal/internal_payload.cc @@ -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. + +#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..62f2625d --- /dev/null +++ b/cpp/core/internal/internal_payload.h @@ -0,0 +1,96 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..616b0ba9 --- /dev/null +++ b/cpp/core/internal/internal_payload_factory.cc @@ -0,0 +1,326 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..eb1ab1d5 --- /dev/null +++ b/cpp/core/internal/internal_payload_factory.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 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..9e48338a --- /dev/null +++ b/cpp/core/internal/loop_runner.cc @@ -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. + +#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..8f4091c2 --- /dev/null +++ b/cpp/core/internal/loop_runner.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 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..40c18139 --- /dev/null +++ b/cpp/core/internal/medium_manager.cc @@ -0,0 +1,375 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..002b6e1e --- /dev/null +++ b/cpp/core/internal/medium_manager.h @@ -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. + +#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..4916b4a0 --- /dev/null +++ b/cpp/core/internal/mediums/BUILD @@ -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. + +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/CMakeLists.txt b/cpp/core/internal/mediums/CMakeLists.txt new file mode 100644 index 00000000..e991f711 --- /dev/null +++ b/cpp/core/internal/mediums/CMakeLists.txt @@ -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. + +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 +) + +add_test( + NAME core_internal_mediums_test + COMMAND core_internal_mediums_test +) 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..7629e041 --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result.cc @@ -0,0 +1,200 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..36b80a15 --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result.h @@ -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. + +#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..ecd4923d --- /dev/null +++ b/cpp/core/internal/mediums/advertisement_read_result_test.cc @@ -0,0 +1,170 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace location { +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()); + } +}; + +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 + +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; + 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 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..82c8c9a2 --- /dev/null +++ b/cpp/core/internal/mediums/ble.cc @@ -0,0 +1,295 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..5af2a241 --- /dev/null +++ b/cpp/core/internal/mediums/ble.h @@ -0,0 +1,211 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..1e4c99b0 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement.cc @@ -0,0 +1,304 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..4683301d --- /dev/null +++ b/cpp/core/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_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..b1a48727 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header.cc @@ -0,0 +1,223 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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(cpp/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..db0d1bca --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header.h @@ -0,0 +1,105 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..1ba30371 --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_header_test.cc @@ -0,0 +1,235 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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..b96c881f --- /dev/null +++ b/cpp/core/internal/mediums/ble_advertisement_test.cc @@ -0,0 +1,336 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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(), + 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( + 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..bdea3049 --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet.cc @@ -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. + +#include "core/internal/mediums/ble_packet.h" + +#include +#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..660b374d --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet.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_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_; +}; + +// 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 +} // 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..5a247557 --- /dev/null +++ b/cpp/core/internal/mediums/ble_packet_test.cc @@ -0,0 +1,122 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..ea1c2a92 --- /dev/null +++ b/cpp/core/internal/mediums/ble_peripheral.cc @@ -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. + +#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..7e1a333e --- /dev/null +++ b/cpp/core/internal/mediums/ble_peripheral.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_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_; +}; + + +// 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 +} // 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..7e491814 --- /dev/null +++ b/cpp/core/internal/mediums/ble_v2.cc @@ -0,0 +1,840 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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(self_)); + 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( + self_, 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 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(self_)); + 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(self_)); + 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..8f268959 --- /dev/null +++ b/cpp/core/internal/mediums/ble_v2.h @@ -0,0 +1,327 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_; + std::shared_ptr self_{this, [](void*){}}; +}; + +} // 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..e8107237 --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter.cc @@ -0,0 +1,123 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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..174bba9a --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter.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 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..384e6505 --- /dev/null +++ b/cpp/core/internal/mediums/bloom_filter_test.cc @@ -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. + +#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(scoped_bloom_filter_bytes->asString(), empty_string); +} + +/** + * 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..bf374d26 --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_classic.cc @@ -0,0 +1,482 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..05ec5a5d --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_classic.h @@ -0,0 +1,183 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..0f5b73ba --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_radio.cc @@ -0,0 +1,136 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..00e4cf28 --- /dev/null +++ b/cpp/core/internal/mediums/bluetooth_radio.h @@ -0,0 +1,83 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..b3edbda1 --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_callback.h @@ -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. + +#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..4fee1e4c --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.cc @@ -0,0 +1,758 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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..b1aa8685 --- /dev/null +++ b/cpp/core/internal/mediums/discovered_peripheral_tracker.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_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..1b71bb9a --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker.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/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..b1d30f8e --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker.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_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..7da24e22 --- /dev/null +++ b/cpp/core/internal/mediums/lost_entity_tracker_test.cc @@ -0,0 +1,135 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" +#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..69c1d166 --- /dev/null +++ b/cpp/core/internal/mediums/mediums.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/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..68c6c72d --- /dev/null +++ b/cpp/core/internal/mediums/mediums.h @@ -0,0 +1,66 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..0d76a440 --- /dev/null +++ b/cpp/core/internal/mediums/utils.cc @@ -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. + +#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..264f1fa7 --- /dev/null +++ b/cpp/core/internal/mediums/utils.h @@ -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. + +#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..549c8c2b --- /dev/null +++ b/cpp/core/internal/mediums/uuid.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 "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..bb99460d --- /dev/null +++ b/cpp/core/internal/mediums/uuid.h @@ -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. + +#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..36407111 --- /dev/null +++ b/cpp/core/internal/offline_frames.cc @@ -0,0 +1,268 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 +#include + +#include "platform/byte_array.h" + +namespace location { +namespace nearby { +namespace connections { + +using ExceptionOrOfflineFrame = ExceptionOr>; + +namespace { +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( + static_cast(message.release())); + break; + case V1Frame::CONNECTION_RESPONSE: + v1_frame->set_allocated_connection_response( + static_cast(message.release())); + break; + case V1Frame::PAYLOAD_TRANSFER: + v1_frame->set_allocated_payload_transfer( + static_cast(message.release())); + break; + case V1Frame::BANDWIDTH_UPGRADE_NEGOTIATION: + v1_frame->set_allocated_bandwidth_upgrade_negotiation( + static_cast(message.release())); + break; + case V1Frame::KEEP_ALIVE: + v1_frame->set_allocated_keep_alive( + static_cast(message.release())); + break; + default: + break; + } + + auto offline_frame = std::make_unique(); + offline_frame->set_version(OfflineFrame::V1); + offline_frame->set_allocated_v1(v1_frame); + return offline_frame; +} + +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 + +ExceptionOrOfflineFrame OfflineFrames::fromBytes( + ConstPtr offline_frame_bytes) { + auto offline_frame = std::make_unique(); + + if (!offline_frame->ParseFromString(offline_frame_bytes->asString())) { + return ExceptionOrOfflineFrame(Exception::INVALID_PROTOCOL_BUFFER); + } + + return ExceptionOrOfflineFrame(MakeConstPtr(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) { + 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); + + for (std::vector::const_iterator it = + mediums.begin(); + it != mediums.end(); it++) { + connection_request->add_mediums(mediumToConnectionRequestMedium(*it)); + } + + return toBytes(NewOfflineFrame(V1Frame::CONNECTION_REQUEST, + std::move(connection_request))); +} + +ConstPtr OfflineFrames::forConnectionResponse(std::int32_t status) { + auto connection_response = std::make_unique(); + connection_response->set_status(status); + + return toBytes(NewOfflineFrame(V1Frame::CONNECTION_RESPONSE, + std::move(connection_response))); +} + +ConstPtr OfflineFrames::forDataPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader &header, + const PayloadTransferFrame::PayloadChunk &chunk) { + 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, std::move(payload_transfer))); +} + +ConstPtr OfflineFrames::forControlPayloadTransferFrame( + const PayloadTransferFrame::PayloadHeader &header, + const PayloadTransferFrame::ControlMessage &control) { + 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, std::move(payload_transfer))); +} + +ConstPtr OfflineFrames:: + forWifiHotspotUpgradePathAvailableBandwidthUpgradeNegotiationEvent( + const std::string &ssid, const std::string &password, + std::int32_t port) { + 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); + + 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); + + 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, + std::move(bandwidth_upgrade_negotiation))); +} + +ConstPtr +OfflineFrames::forLastWriteToPriorChannelBandwidthUpgradeNegotiationEvent() { + 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, + std::move(bandwidth_upgrade_negotiation))); +} + +ConstPtr +OfflineFrames::forSafeToClosePriorChannelBandwidthUpgradeNegotiationEvent() { + 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, + std::move(bandwidth_upgrade_negotiation))); +} + +ConstPtr +OfflineFrames::forClientIntroductionBandwidthUpgradeNegotiationEvent( + const std::string &endpoint_id) { + auto *client_introduction = + new BandwidthUpgradeNegotiationFrame::ClientIntroduction(); + client_introduction->set_endpoint_id(endpoint_id); + + 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, + std::move(bandwidth_upgrade_negotiation))); +} + +ConstPtr OfflineFrames::forKeepAlive() { + return toBytes( + NewOfflineFrame(V1Frame::KEEP_ALIVE, std::make_unique())); +} + +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..d82773bb --- /dev/null +++ b/cpp/core/internal/offline_frames.h @@ -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. + +#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 google::protobuf + + +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_frames_test.cc b/cpp/core/internal/offline_frames_test.cc new file mode 100644 index 00000000..1eddd6eb --- /dev/null +++ b/cpp/core/internal/offline_frames_test.cc @@ -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. + +#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 int 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/offline_service_controller.cc b/cpp/core/internal/offline_service_controller.cc new file mode 100644 index 00000000..c7578e26 --- /dev/null +++ b/cpp/core/internal/offline_service_controller.cc @@ -0,0 +1,124 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +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..69d34c0c --- /dev/null +++ b/cpp/core/internal/offline_service_controller.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_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..c98d8041 --- /dev/null +++ b/cpp/core/internal/p2p_cluster_pcp_handler.cc @@ -0,0 +1,802 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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( + 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(self_, 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_, + self_, service_id_, + bluetooth_device))); +} + +template +void P2PClusterPCPHandler::FoundBluetoothAdvertisementProcessor:: + onLostBluetoothDevice(Ptr bluetooth_device) { + pcp_handler_->runOnPCPHandlerThread(MakePtr(new OnLostBluetoothDeviceRunnable( + pcp_handler_, client_proxy_, self_, 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_, self_, 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_, self_, 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( + self_, 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( + 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); + 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..0be713c0 --- /dev/null +++ b/cpp/core/internal/p2p_cluster_pcp_handler.h @@ -0,0 +1,396 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_; + std::shared_ptr self_{this, + [](void*) {}}; + }; + + 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_; + std::shared_ptr self_{this, [](void*) {}}; + }; + + 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_; + std::shared_ptr self_{this, [](void*) {}}; +}; + +} // 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..6dd600d0 --- /dev/null +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.cc @@ -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. + +#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..e73c3140 --- /dev/null +++ b/cpp/core/internal/p2p_point_to_point_pcp_handler.h @@ -0,0 +1,69 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..0e37e616 --- /dev/null +++ b/cpp/core/internal/p2p_star_pcp_handler.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 "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..f7c635ae --- /dev/null +++ b/cpp/core/internal/p2p_star_pcp_handler.h @@ -0,0 +1,74 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..6325244b --- /dev/null +++ b/cpp/core/internal/payload_manager.cc @@ -0,0 +1,1356 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 +#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; + } + + 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; + } + + 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, std::static_pointer_cast< + typename EndpointManager::IncomingOfflineFrameProcessor>( + self_)); +} + +template +PayloadManager::~PayloadManager() { + // TODO(reznor): + // logger.atDebug().log("Initiating shutdown of PayloadManager"); + + // Unregister ourselves from the IncomingOfflineFrameProcessors. + endpoint_manager_->unregisterIncomingOfflineFrameProcessor( + 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(); + 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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( + self_, 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..3f427499 --- /dev/null +++ b/cpp/core/internal/payload_manager.h @@ -0,0 +1,303 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_; + std::shared_ptr self_{this, [](void*){}}; +}; + +} // 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..3a4fe92e --- /dev/null +++ b/cpp/core/internal/pcp.h @@ -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. + +#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..d016c2c3 --- /dev/null +++ b/cpp/core/internal/pcp_handler.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 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..eb618cfe --- /dev/null +++ b/cpp/core/internal/pcp_manager.cc @@ -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. + +#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..b5695796 --- /dev/null +++ b/cpp/core/internal/pcp_manager.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_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..6c7aef31 --- /dev/null +++ b/cpp/core/internal/service_controller.h @@ -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. + +#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..3a7eb23f --- /dev/null +++ b/cpp/core/internal/service_controller_router.cc @@ -0,0 +1,764 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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( + self_, client_proxy, start_advertising_params))); +} + +template +void ServiceControllerRouter::stopAdvertising( + Ptr > client_proxy, + ConstPtr stop_advertising_params) { + routeToServiceController( + MakePtr(new service_controller_router::StopAdvertisingRunnable( + self_, client_proxy, stop_advertising_params))); +} + +template +void ServiceControllerRouter::startDiscovery( + Ptr > client_proxy, + ConstPtr start_discovery_params) { + routeToServiceController( + MakePtr(new service_controller_router::StartDiscoveryRunnable( + self_, client_proxy, start_discovery_params))); +} + +template +void ServiceControllerRouter::stopDiscovery( + Ptr > client_proxy, + ConstPtr stop_discovery_params) { + routeToServiceController( + MakePtr(new service_controller_router::StopDiscoveryRunnable( + self_, client_proxy, stop_discovery_params))); +} + +template +void ServiceControllerRouter::requestConnection( + Ptr > client_proxy, + ConstPtr request_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::SendConnectionRequestRunnable( + self_, client_proxy, request_connection_params))); +} + +template +void ServiceControllerRouter::acceptConnection( + Ptr > client_proxy, + ConstPtr accept_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::AcceptConnectionRequestRunnable( + self_, client_proxy, accept_connection_params))); +} + +template +void ServiceControllerRouter::rejectConnection( + Ptr > client_proxy, + ConstPtr reject_connection_params) { + routeToServiceController(MakePtr( + new service_controller_router::RejectConnectionRequestRunnable( + self_, client_proxy, reject_connection_params))); +} + +template +void ServiceControllerRouter::initiateBandwidthUpgrade( + Ptr > client_proxy, + ConstPtr + initiate_bandwidth_upgrade_params) { + routeToServiceController(MakePtr( + new service_controller_router::InitiateBandwidthUpgradeRunnable( + self_, client_proxy, initiate_bandwidth_upgrade_params))); +} + +template +void ServiceControllerRouter::sendPayload( + Ptr > client_proxy, + ConstPtr send_payload_params) { + routeToServiceController( + MakePtr(new service_controller_router::SendPayloadRunnable( + self_, client_proxy, send_payload_params))); +} + +template +void ServiceControllerRouter::cancelPayload( + Ptr > client_proxy, + ConstPtr cancel_payload_params) { + routeToServiceController( + MakePtr(new service_controller_router::CancelPayloadRunnable( + self_, client_proxy, cancel_payload_params))); +} + +template +void ServiceControllerRouter::disconnectFromEndpoint( + Ptr > client_proxy, + ConstPtr disconnect_from_endpoint_params) { + routeToServiceController(MakePtr( + new service_controller_router::DisconnectFromEndpointRunnable( + self_, 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( + self_, client_proxy, stop_all_endpoint_params))); +} + +template +void ServiceControllerRouter::clientDisconnecting( + Ptr> client_proxy) { + routeToServiceController(MakePtr( + new service_controller_router::ClientDisconnectingRunnable( + self_, 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..11d3ba63 --- /dev/null +++ b/cpp/core/internal/service_controller_router.h @@ -0,0 +1,166 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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_; + std::shared_ptr> self_{this, [](void*){}}; +}; + +} // 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_service_info.cc b/cpp/core/internal/wifi_lan_service_info.cc new file mode 100644 index 00000000..8c22627b --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info.cc @@ -0,0 +1,216 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..97f81b52 --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info.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 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 int kVersionBitmask = 0x0E0; + static constexpr int kPcpBitmask = 0x01F; + static constexpr int 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..79c4b094 --- /dev/null +++ b/cpp/core/internal/wifi_lan_service_info_test.cc @@ -0,0 +1,165 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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/core/internal/wifi_lan_upgrade_handler.cc b/cpp/core/internal/wifi_lan_upgrade_handler.cc new file mode 100644 index 00000000..8ddd1ed5 --- /dev/null +++ b/cpp/core/internal/wifi_lan_upgrade_handler.cc @@ -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. + +#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..54f89804 --- /dev/null +++ b/cpp/core/internal/wifi_lan_upgrade_handler.h @@ -0,0 +1,107 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..3831408a --- /dev/null +++ b/cpp/core/listeners.h @@ -0,0 +1,181 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..05ba1d88 --- /dev/null +++ b/cpp/core/options.h @@ -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. + +#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..a1299dc2 --- /dev/null +++ b/cpp/core/params.h @@ -0,0 +1,162 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..5b878465 --- /dev/null +++ b/cpp/core/payload.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 "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..e07b5894 --- /dev/null +++ b/cpp/core/payload.h @@ -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. + +#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..cce5f41d --- /dev/null +++ b/cpp/core/status.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_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..a5c46a8e --- /dev/null +++ b/cpp/core/strategy.cc @@ -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. + +#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..24c64454 --- /dev/null +++ b/cpp/core/strategy.h @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..fea39046 --- /dev/null +++ b/cpp/platform/BUILD @@ -0,0 +1,162 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "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", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "types", + hdrs = [ + "byte_array.h", + "callable.h", + "cancelable.h", + "container_of.h", + "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 = "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/port:string", + "//testing/base/public:gunit_main", + "//absl/time", + ], +) diff --git a/cpp/platform/CMakeLists.txt b/cpp/platform/CMakeLists.txt new file mode 100644 index 00000000..c346e859 --- /dev/null +++ b/cpp/platform/CMakeLists.txt @@ -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. + +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) + +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 + exception_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 + gmock + 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(api2) +add_subdirectory(impl/sample) +add_subdirectory(impl/default) +add_subdirectory(port) diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD new file mode 100644 index 00000000..80ee2bf1 --- /dev/null +++ b/cpp/platform/api/BUILD @@ -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. + +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", + "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", + ], +) + +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/CMakeLists.txt b/cpp/platform/api/CMakeLists.txt new file mode 100644 index 00000000..935d2d24 --- /dev/null +++ b/cpp/platform/api/CMakeLists.txt @@ -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. + +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 + 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 + PUBLIC + platform_types +) diff --git a/cpp/platform/api/atomic_boolean.h b/cpp/platform/api/atomic_boolean.h new file mode 100644 index 00000000..84949079 --- /dev/null +++ b/cpp/platform/api/atomic_boolean.h @@ -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. + +#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..61ee8c9f --- /dev/null +++ b/cpp/platform/api/atomic_reference.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_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..460daf3a --- /dev/null +++ b/cpp/platform/api/ble.h @@ -0,0 +1,138 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..b620ea32 --- /dev/null +++ b/cpp/platform/api/ble_v2.h @@ -0,0 +1,418 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..170afde6 --- /dev/null +++ b/cpp/platform/api/bluetooth_adapter.h @@ -0,0 +1,72 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..fdf448a3 --- /dev/null +++ b/cpp/platform/api/bluetooth_classic.h @@ -0,0 +1,155 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..d0351065 --- /dev/null +++ b/cpp/platform/api/condition_variable.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_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..ed2cbb45 --- /dev/null +++ b/cpp/platform/api/count_down_latch.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_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..3c1f68cc --- /dev/null +++ b/cpp/platform/api/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_API_EXECUTOR_H_ +#define PLATFORM_API_EXECUTOR_H_ + +#include "platform/ptr.h" +#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() {} + + // 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 +} // 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..7ba3d8db --- /dev/null +++ b/cpp/platform/api/future.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_FUTURE_H_ +#define PLATFORM_API_FUTURE_H_ + +#include + +#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 + + // 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 +} // 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..4bbe1ae7 --- /dev/null +++ b/cpp/platform/api/hash_utils.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_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..8de2ba5c --- /dev/null +++ b/cpp/platform/api/input_file.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_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..49a65921 --- /dev/null +++ b/cpp/platform/api/input_stream.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_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/listenable_future.h b/cpp/platform/api/listenable_future.h new file mode 100644 index 00000000..1f5d47ca --- /dev/null +++ b/cpp/platform/api/listenable_future.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_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/lock.h b/cpp/platform/api/lock.h new file mode 100644 index 00000000..f1256c73 --- /dev/null +++ b/cpp/platform/api/lock.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_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..3ffdc46e --- /dev/null +++ b/cpp/platform/api/multi_thread_executor.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_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..90d31002 --- /dev/null +++ b/cpp/platform/api/output_file.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_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..a94febff --- /dev/null +++ b/cpp/platform/api/output_stream.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..38410ffd --- /dev/null +++ b/cpp/platform/api/scheduled_executor.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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/server_sync.h b/cpp/platform/api/server_sync.h new file mode 100644 index 00000000..8c20b368 --- /dev/null +++ b/cpp/platform/api/server_sync.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_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 new file mode 100644 index 00000000..3aed3d83 --- /dev/null +++ b/cpp/platform/api/settable_future.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_API_SETTABLE_FUTURE_H_ +#define PLATFORM_API_SETTABLE_FUTURE_H_ + +#include "platform/api/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 {} + + virtual bool set(T value) = 0; + + virtual bool setException(Exception exception) = 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..ed92e0fa --- /dev/null +++ b/cpp/platform/api/single_thread_executor.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_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..915a85ba --- /dev/null +++ b/cpp/platform/api/socket.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_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..3554165b --- /dev/null +++ b/cpp/platform/api/submittable_executor.h @@ -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. + +#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" + +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); + } +}; + +} // 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..60e3e2ed --- /dev/null +++ b/cpp/platform/api/system_clock.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_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..50dd3ea2 --- /dev/null +++ b/cpp/platform/api/thread_utils.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_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/webrtc.h b/cpp/platform/api/webrtc.h new file mode 100644 index 00000000..fd73f19d --- /dev/null +++ b/cpp/platform/api/webrtc.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_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 new file mode 100644 index 00000000..3eeb83e5 --- /dev/null +++ b/cpp/platform/api/wifi.h @@ -0,0 +1,105 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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/api/wifi_lan.h b/cpp/platform/api/wifi_lan.h new file mode 100644 index 00000000..744813f8 --- /dev/null +++ b/cpp/platform/api/wifi_lan.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_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..6051f5c4 --- /dev/null +++ b/cpp/platform/api2/BUILD @@ -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. + +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/CMakeLists.txt b/cpp/platform/api2/CMakeLists.txt new file mode 100644 index 00000000..9639502f --- /dev/null +++ b/cpp/platform/api2/CMakeLists.txt @@ -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. + +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/api2/atomic_boolean.h b/cpp/platform/api2/atomic_boolean.h new file mode 100644 index 00000000..52ac5831 --- /dev/null +++ b/cpp/platform/api2/atomic_boolean.h @@ -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. + +#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..7e6b6d96 --- /dev/null +++ b/cpp/platform/api2/atomic_reference.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_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..a9e79823 --- /dev/null +++ b/cpp/platform/api2/ble.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 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..58c433fb --- /dev/null +++ b/cpp/platform/api2/ble_v2.h @@ -0,0 +1,404 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..58bf9dad --- /dev/null +++ b/cpp/platform/api2/bluetooth_adapter.h @@ -0,0 +1,69 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..b693e671 --- /dev/null +++ b/cpp/platform/api2/bluetooth_classic.h @@ -0,0 +1,138 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..f0fd7573 --- /dev/null +++ b/cpp/platform/api2/condition_variable.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_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..8ba4a3c0 --- /dev/null +++ b/cpp/platform/api2/count_down_latch.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..0ed336c3 --- /dev/null +++ b/cpp/platform/api2/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_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..4d566d30 --- /dev/null +++ b/cpp/platform/api2/future.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_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..fc692ad3 --- /dev/null +++ b/cpp/platform/api2/hash_utils.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_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..29aafb72 --- /dev/null +++ b/cpp/platform/api2/input_file.h @@ -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. + +#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..4caf598f --- /dev/null +++ b/cpp/platform/api2/input_stream.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_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..0007e98c --- /dev/null +++ b/cpp/platform/api2/listenable_future.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..4f4bb951 --- /dev/null +++ b/cpp/platform/api2/multi_thread_executor.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_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..a097da40 --- /dev/null +++ b/cpp/platform/api2/mutex.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_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..1375c65c --- /dev/null +++ b/cpp/platform/api2/output_file.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_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..95be4cd4 --- /dev/null +++ b/cpp/platform/api2/output_stream.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_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..2bc068a5 --- /dev/null +++ b/cpp/platform/api2/scheduled_executor.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..46d5c5e2 --- /dev/null +++ b/cpp/platform/api2/server_sync.h @@ -0,0 +1,74 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..73617ae5 --- /dev/null +++ b/cpp/platform/api2/settable_future.h @@ -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. + +#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..56319d3e --- /dev/null +++ b/cpp/platform/api2/single_thread_executor.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_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..29113ca8 --- /dev/null +++ b/cpp/platform/api2/socket.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_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..c55f7a5a --- /dev/null +++ b/cpp/platform/api2/submittable_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_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..cf1442e9 --- /dev/null +++ b/cpp/platform/api2/system_clock.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_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..013f3a3d --- /dev/null +++ b/cpp/platform/api2/thread_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 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..23ab20ed --- /dev/null +++ b/cpp/platform/api2/webrtc.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_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..93552823 --- /dev/null +++ b/cpp/platform/api2/wifi.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 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 new file mode 100644 index 00000000..2210970d --- /dev/null +++ b/cpp/platform/base64_utils.cc @@ -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. + +#include "platform/base64_utils.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(bytes->asString(), &base64_string); + } + + return base64_string; +} + +std::string Base64Utils::encode(const ByteArray& bytes) { + std::string base64_string; + absl::WebSafeBase64Escape(bytes.asString(), &base64_string); + + return base64_string; +} + +std::string Base64Utils::encode(absl::string_view input) { + std::string base64_string; + absl::WebSafeBase64Escape(input, &base64_string); + + return base64_string; +} + +template<> +Ptr Base64Utils::decode(absl::string_view base64_string) { + std::string decoded_string; + if (!absl::WebSafeBase64Unescape(base64_string, &decoded_string)) { + return Ptr(); + } + + return MakePtr(new ByteArray(decoded_string)); +} + +template<> +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); +} + +Ptr Base64Utils::decode(absl::string_view base64_string) { + return decode>(base64_string); +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/base64_utils.h b/cpp/platform/base64_utils.h new file mode 100644 index 00000000..615b4e82 --- /dev/null +++ b/cpp/platform/base64_utils.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_BASE64_UTILS_H_ +#define PLATFORM_BASE64_UTILS_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 { + +class Base64Utils { + public: + 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(absl::string_view base64_string); + static Ptr decode(absl::string_view 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..79bbd52b --- /dev/null +++ b/cpp/platform/byte_array.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 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->data_ == rhs.data_; + } + bool operator!=(const ByteArray& rhs) const { return !(*this == rhs); } + bool operator<(const ByteArray& rhs) const { + return this->data_ < rhs.data_; + } + // 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..3989c4a4 --- /dev/null +++ b/cpp/platform/byte_array_test.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/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..79465ad9 --- /dev/null +++ b/cpp/platform/callable.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_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..2a506896 --- /dev/null +++ b/cpp/platform/cancelable.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_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..faa729fa --- /dev/null +++ b/cpp/platform/cancelable_alarm.cc @@ -0,0 +1,51 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..3551ecca --- /dev/null +++ b/cpp/platform/cancelable_alarm.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_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..e6f61127 --- /dev/null +++ b/cpp/platform/container_of.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 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..46dba31e --- /dev/null +++ b/cpp/platform/container_of_test.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 "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.h b/cpp/platform/exception.h new file mode 100644 index 00000000..9976e838 --- /dev/null +++ b/cpp/platform/exception.h @@ -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. + +#ifndef PLATFORM_EXCEPTION_H_ +#define PLATFORM_EXCEPTION_H_ + +#include + +namespace location { +namespace nearby { + +struct Exception { + 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 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; + 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 { 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; + Exception GetException() const; + + private: + T result_; + Exception exception_ {Exception::kFailed}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_EXCEPTION_H_ diff --git a/cpp/platform/exception_test.cc b/cpp/platform/exception_test.cc new file mode 100644 index 00000000..31058398 --- /dev/null +++ b/cpp/platform/exception_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 "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/file_impl.cc b/cpp/platform/file_impl.cc new file mode 100644 index 00000000..91c596db --- /dev/null +++ b/cpp/platform/file_impl.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/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..9945afe7 --- /dev/null +++ b/cpp/platform/file_impl.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_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..b6b03784 --- /dev/null +++ b/cpp/platform/file_impl_test.cc @@ -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. + +#include "platform/file_impl.h" + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + + +class FileImplTest : public ::testing::Test { + protected: + void SetUp() override { + path_ = std::tmpnam(nullptr);; + 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::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..3e5dbe6d --- /dev/null +++ b/cpp/platform/impl/default/BUILD @@ -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. + +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/CMakeLists.txt b/cpp/platform/impl/default/CMakeLists.txt new file mode 100644 index 00000000..a542481c --- /dev/null +++ b/cpp/platform/impl/default/CMakeLists.txt @@ -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. + +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_condition_variable.cc b/cpp/platform/impl/default/default_condition_variable.cc new file mode 100644 index 00000000..a48d77a3 --- /dev/null +++ b/cpp/platform/impl/default/default_condition_variable.cc @@ -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. + +#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..76d7cf8d --- /dev/null +++ b/cpp/platform/impl/default/default_condition_variable.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_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..df67c6c1 --- /dev/null +++ b/cpp/platform/impl/default/default_lock.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/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..3e1b2e41 --- /dev/null +++ b/cpp/platform/impl/default/default_lock.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..876ad0f3 --- /dev/null +++ b/cpp/platform/impl/default/default_platform.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 "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..54132b19 --- /dev/null +++ b/cpp/platform/impl/default/default_platform.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_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..089b92d6 --- /dev/null +++ b/cpp/platform/impl/ios/BUILD @@ -0,0 +1,23 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 = [ + "//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..e75257c7 --- /dev/null +++ b/cpp/platform/impl/sample/BUILD @@ -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. + +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/CMakeLists.txt b/cpp/platform/impl/sample/CMakeLists.txt new file mode 100644 index 00000000..c5929ffe --- /dev/null +++ b/cpp/platform/impl/sample/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + 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/impl/sample/sample_platform.h b/cpp/platform/impl/sample/sample_platform.h new file mode 100644 index 00000000..78b154c5 --- /dev/null +++ b/cpp/platform/impl/sample/sample_platform.h @@ -0,0 +1,155 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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/sample_wifi_medium.cc b/cpp/platform/impl/sample/sample_wifi_medium.cc new file mode 100644 index 00000000..2031cc8e --- /dev/null +++ b/cpp/platform/impl/sample/sample_wifi_medium.cc @@ -0,0 +1,124 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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..46acfc2b --- /dev/null +++ b/cpp/platform/impl/sample/sample_wifi_medium.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_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..96511073 --- /dev/null +++ b/cpp/platform/logging.h @@ -0,0 +1,43 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..52779b95 --- /dev/null +++ b/cpp/platform/pipe.cc @@ -0,0 +1,213 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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..c4a8ca72 --- /dev/null +++ b/cpp/platform/pipe.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_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..7d511c8a --- /dev/null +++ b/cpp/platform/pipe_test.cc @@ -0,0 +1,421 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 + +#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/clock.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..6b0293d6 --- /dev/null +++ b/cpp/platform/port/BUILD @@ -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. + +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/CMakeLists.txt b/cpp/platform/port/CMakeLists.txt new file mode 100644 index 00000000..ac2b8f16 --- /dev/null +++ b/cpp/platform/port/CMakeLists.txt @@ -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. + +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/cpp/platform/port/config.h b/cpp/platform/port/config.h new file mode 100644 index 00000000..f7da653c --- /dev/null +++ b/cpp/platform/port/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_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 1 +#endif + +#ifndef NEARBY_USE_RTTI +#define NEARBY_USE_RTTI 0 +#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..6cc2fcb7 --- /dev/null +++ b/cpp/platform/port/down_cast.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_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..e0cfda4a --- /dev/null +++ b/cpp/platform/port/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_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..e4512346 --- /dev/null +++ b/cpp/platform/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/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..4ed2198d --- /dev/null +++ b/cpp/platform/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_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..328e3687 --- /dev/null +++ b/cpp/platform/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/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.h b/cpp/platform/ptr.h new file mode 100644 index 00000000..45c5e55d --- /dev/null +++ b/cpp/platform/ptr.h @@ -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. + +#ifndef PLATFORM_PTR_H_ +#define PLATFORM_PTR_H_ + +#include +#include +#include +#include +#include + +#include "platform/logging.h" +#include "platform/port/down_cast.h" + +namespace location { +namespace nearby { + +// 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 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. +// 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() = default; + explicit Ptr(T* pointee) : ptr_(pointee) {} + Ptr(const Ptr& that) = default; + + Ptr(std::shared_ptr ptr) : ptr_(ptr) {} // NOLINT + + 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() { // NOLINT + return Ptr(std::static_pointer_cast(this->ptr_)); + } + operator Ptr() { // NOLINT + return Ptr(*this); + } + + explicit operator std::shared_ptr() { return this->ptr_; } + + ~Ptr() = default; + + bool operator==(const Ptr& other) const { + return *(this->ptr_) == *(other.ptr_); + } + bool operator!=(const Ptr& other) const { return !(*this == other); } + + bool operator<(const Ptr& other) const { + 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) {} + + // No-op: refcounted objects will be destroyed correctly + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + void clear() {} + + T& operator*() const { return *ptr_; } + + T* operator->() const { return ptr_.get(); } + T* get() { return ptr_.get(); } + void reset() { return ptr_.reset(); } + + ABSL_DEPRECATED("Use c++ smart pointers directly instead of Ptr") + bool isNull() const { return !this->ptr_; } + + // 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 + friend ConstPtr ConstifyPtr(Ptr ptr); + template + friend Ptr DowncastPtr(Ptr base_ptr); + template + friend ConstPtr DowncastConstPtr(ConstPtr base_ptr); + + std::shared_ptr ptr_; +}; + +// 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(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 +// 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(const ScopedPtr&) = delete; + ~ScopedPtr() = default; + + ScopedPtr& operator=(const ScopedPtr&) = delete; + + // Shadow methods for the underlying Ptr. + 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 this->ptr_; } + + // Does nothing; + // this is to avoid unintended destruction of a managed pointer. + // TODO(b/149938110): remove this completely. + PtrType release() { + return ptr_; + } + + private: + 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); +} + +// ConstPtr counterpart to MakeRefCountedPtr(). +template +ConstPtr MakeRefCountedConstPtr(T* raw_ptr) { + return ConstPtr(raw_ptr); +} + +// Use this function to convert a Ptr object to a ConstPtr object. +template +ConstPtr ConstifyPtr(Ptr ptr) { + return ConstPtr(ptr); +} + +// 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) { + 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( + std::static_pointer_cast(base_ptr.ptr_)); +} + +} // 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..2ce2f9b0 --- /dev/null +++ b/cpp/platform/ptr_test.cc @@ -0,0 +1,177 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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" + +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_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_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_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); +} + +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_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, DistinctValuesAreNotEqual) { + Ptr value1 = MakePtr(new int(5)); + Ptr value2 = MakePtr(new int(6)); + + ASSERT_NE(value1, value2); +} + +TEST(PtrTest, SameValuesAreEqual) { + Ptr value1 = MakePtr(new int(5)); + Ptr value2 = MakePtr(new int(5)); + + ASSERT_EQ(value1, value2); +} + +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_EQ(*scoped_ref_counted_1, *ref_counted_2); + ASSERT_EQ(1234, *ref_counted_2); +} + +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); + + Ptr ref_counted_3 = scoped_ref_counted_1.release(); + + 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) { + 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_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..917a2fa7 --- /dev/null +++ b/cpp/platform/reliability_utils.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 "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..4c387b60 --- /dev/null +++ b/cpp/platform/reliability_utils.h @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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_ + +#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..76d0d4be --- /dev/null +++ b/cpp/platform/runnable.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_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..c95a1dd5 --- /dev/null +++ b/cpp/platform/synchronized.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_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/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/BUILD b/proto/BUILD new file mode 100644 index 00000000..ae34afb9 --- /dev/null +++ b/proto/BUILD @@ -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. + +# 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"], +) + +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"], +) + +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", + ], +) + +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", + ], +) + +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", + ], +) + +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", + ], +) + +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", + ], +) + +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/CMakeLists.txt b/proto/CMakeLists.txt new file mode 100644 index 00000000..4c6002d9 --- /dev/null +++ b/proto/CMakeLists.txt @@ -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. + +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/bootstrap_enums.proto b/proto/bootstrap_enums.proto new file mode 100644 index 00000000..777ba3b0 --- /dev/null +++ b/proto/bootstrap_enums.proto @@ -0,0 +1,96 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; + +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..03567b3d --- /dev/null +++ b/proto/connections/BUILD @@ -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. + +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__"], +) + +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/CMakeLists.txt b/proto/connections/CMakeLists.txt new file mode 100644 index 00000000..9195a301 --- /dev/null +++ b/proto/connections/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 + INCS ${CMAKE_CURRENT_BINARY_DIR}/../.. +) diff --git a/proto/connections/offline_wire_formats.proto b/proto/connections/offline_wire_formats.proto new file mode 100644 index 00000000..5e2f3360 --- /dev/null +++ b/proto/connections/offline_wire_formats.proto @@ -0,0 +1,253 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; + +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..1961c20b --- /dev/null +++ b/proto/connections_enums.proto @@ -0,0 +1,294 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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.connections; + +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; +} + +// next_id: 16 +// 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(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; + + // 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; + // 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; + + // 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; + // Creating the BLUETOOTH EndpointChannel + BLUETOOTH_SOCKET_CREATION = 30; + + // 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; + // Creating the WIFI Aware EndpointChannel + WIFI_AWARE_SOCKET_CREATION = 31; + + // 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; + // 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/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..9e9adffb --- /dev/null +++ b/proto/discovery_enums.proto @@ -0,0 +1,489 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; + +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "DiscoveryEnums"; + +// NEXT ID: 132 +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 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, + // 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 (131 for low battery). + 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; + + // 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; +} + +// 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..c74dc399 --- /dev/null +++ b/proto/magic_pair_enums.proto @@ -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. + +syntax = "proto2"; + +package location.nearby.proto; + +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..602e338d --- /dev/null +++ b/proto/nearby_client_enums.proto @@ -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. + +syntax = "proto2"; + +package location.nearby.proto; + +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; +} + +// 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..3981e09c --- /dev/null +++ b/proto/nearby_event_codes.proto @@ -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. + +syntax = "proto2"; + +package location.nearby.proto; + +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..a2399207 --- /dev/null +++ b/proto/setup_enums.proto @@ -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. + +syntax = "proto2"; + +package location.nearby.proto.setup; + +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..bbb792c5 --- /dev/null +++ b/proto/sharing_enums.proto @@ -0,0 +1,259 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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; + +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 data usage preference. + SET_DATA_USAGE = 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 DataUsage { + UNKNOWN_DATA_USAGE = 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; +} 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 .. diff --git a/script/oss.py b/script/oss.py new file mode 100755 index 00000000..be08927c --- /dev/null +++ b/script/oss.py @@ -0,0 +1,209 @@ +#!/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 +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) + 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 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 + 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"), + ("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 and top_dirs: + # 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 + 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 + + 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(): + 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()) 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/depot_tools b/third_party/depot_tools new file mode 160000 index 00000000..e521cd14 --- /dev/null +++ b/third_party/depot_tools @@ -0,0 +1 @@ +Subproject commit e521cd14da4a02274de5099543f0b30350e41be3 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