diff --git a/cpp/platform/base/BUILD b/cpp/platform/base/BUILD index 6703d20c..42a91fc6 100644 --- a/cpp/platform/base/BUILD +++ b/cpp/platform/base/BUILD @@ -128,6 +128,28 @@ cc_library( ], ) +cc_library( + name = "error_code_recorder", + srcs = [ + "error_code_recorder.cc", + ], + hdrs = [ + "error_code_params.h", + "error_code_recorder.h", + ], + compatible_with = ["//buildenv/target:non_prod"], + visibility = [ + "//third_party/nearby_connections/cpp/analytics:__subpackages__", + "//core/internal:__subpackages__", + "//platform/impl:__subpackages__", + ], + deps = [ + ":logging", + "//proto:connections_enums_portable_proto", + "//third_party/nearby_connections/proto/errorcode:error_code_enums_portable_proto", + ], +) + cc_library( name = "test_util", testonly = True, @@ -194,6 +216,19 @@ cc_test( ], ) +cc_test( + name = "error_code_recorder_test", + srcs = [ + "error_code_recorder_test.cc", + ], + deps = [ + ":error_code_recorder", + ":test_util", + "//testing/base/public:gunit_main", + "//platform/impl/g3", # build_cleaner: keep + ], +) + cc_with_non_compile_test( name = "exception_test", srcs = [ diff --git a/cpp/platform/base/error_code_params.h b/cpp/platform/base/error_code_params.h new file mode 100644 index 00000000..feb47d03 --- /dev/null +++ b/cpp/platform/base/error_code_params.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 PLATFORM_BASE_ERROR_CORE_PARAMS_H_ +#define PLATFORM_BASE_ERROR_CORE_PARAMS_H_ + +#include "proto/connections_enums.proto.h" +#include "proto/errorcode/error_code_enums.proto.h" + +namespace location { +namespace nearby { + +// A struct to consturct error code parameters for the analytics recorder. +struct ErrorCodeParams { + location::nearby::proto::connections::Medium medium = + location::nearby::proto::connections::UNKNOWN_MEDIUM; + location::nearby::errorcode::proto::Event event = + location::nearby::errorcode::proto::UNKNOWN_EVENT; + location::nearby::errorcode::proto::Description description = + location::nearby::errorcode::proto::UNKNOWN; + std::string pii_message = {}; + bool is_common_error = false; + location::nearby::errorcode::proto::CommonError common_error = + location::nearby::errorcode::proto::UNKNOWN_ERROR; + location::nearby::errorcode::proto::ConnectError connect_error = + location::nearby::errorcode::proto::UNKNOWN_CONNECT_ERROR; + location::nearby::errorcode::proto::DisconnectError disconnect_error = + location::nearby::errorcode::proto::UNKNOWN_DISCONNECT_ERROR; + location::nearby::errorcode::proto::StartAdvertisingError + start_advertising_error = + location::nearby::errorcode::proto::UNKNOWN_START_ADVERTISING_ERROR; + location::nearby::errorcode::proto::StopAdvertisingError + stop_advertising_error = + location::nearby::errorcode::proto::UNKNOWN_STOP_ADVERTISING_ERROR; + location::nearby::errorcode::proto::StartDiscoveringError + start_discovering_error = + location::nearby::errorcode::proto::UNKNOWN_START_DISCOVERING_ERROR; + location::nearby::errorcode::proto::StopDiscoveringError + stop_discovering_error = + location::nearby::errorcode::proto::UNKNOWN_STOP_DISCOVERING_ERROR; + location::nearby::errorcode::proto::StartListeningIncomingConnectionError + start_listening_incoming_connection_error = location::nearby::errorcode:: + proto::UNKNOWN_START_LISTENING_INCOMING_CONNECTION_ERROR; + location::nearby::errorcode::proto::StopListeningIncomingConnectionError + stop_listening_incoming_connection_error = location::nearby::errorcode:: + proto::UNKNOWN_STOP_LISTENING_INCOMING_CONNECTION_ERROR; + std::string connection_token = {}; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_BASE_ERROR_CORE_PARAMS_H_ diff --git a/cpp/platform/base/error_code_recorder.cc b/cpp/platform/base/error_code_recorder.cc new file mode 100644 index 00000000..fad626bb --- /dev/null +++ b/cpp/platform/base/error_code_recorder.cc @@ -0,0 +1,116 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "platform/base/error_code_recorder.h" + +#include "platform/base/logging.h" +#include "proto/errorcode/error_code_enums.proto.h" + +namespace location { +namespace nearby { + +using ::location::nearby::errorcode::proto::CommonError; +using ::location::nearby::errorcode::proto::CONNECT; +using ::location::nearby::errorcode::proto::ConnectError; +using ::location::nearby::errorcode::proto::Description; +using ::location::nearby::errorcode::proto::DISCONNECT; +using ::location::nearby::errorcode::proto::DisconnectError; +using ::location::nearby::errorcode::proto::Event; +using ::location::nearby::errorcode::proto::START_ADVERTISING; +using ::location::nearby::errorcode::proto::START_DISCOVERING; +using ::location::nearby::errorcode::proto::START_LISTENING_INCOMING_CONNECTION; +using ::location::nearby::errorcode::proto::StartAdvertisingError; +using ::location::nearby::errorcode::proto::StartDiscoveringError; +using ::location::nearby::errorcode::proto:: + StartListeningIncomingConnectionError; +using ::location::nearby::errorcode::proto::STOP_ADVERTISING; +using ::location::nearby::errorcode::proto::STOP_DISCOVERING; +using ::location::nearby::errorcode::proto::STOP_LISTENING_INCOMING_CONNECTION; +using ::location::nearby::errorcode::proto::StopAdvertisingError; +using ::location::nearby::errorcode::proto::StopDiscoveringError; +using ::location::nearby::errorcode::proto:: + StopListeningIncomingConnectionError; +using ::location::nearby::proto::connections::Medium; + +// Default static no-op listener +ErrorCodeRecorder::ErrorCodeListener ErrorCodeRecorder::listener_ = + [](const ErrorCodeParams&) {}; + +void ErrorCodeRecorder::LogErrorCode(Medium medium, Event event, int error, + Description description, + const std::string& pii_message, + const std::string& connection_token) { + NEARBY_LOGS(INFO) << "ErrorCodeRecorder LogErrorCode"; + ErrorCodeParams params = BuildErrorCodeParams( + medium, event, error, description, pii_message, connection_token); + listener_(params); +} + +ErrorCodeParams ErrorCodeRecorder::BuildErrorCodeParams( + Medium medium, Event event, int error, Description description, + const std::string& pii_message, const std::string& connection_token) { + ErrorCodeParams params = {.medium = medium, + .event = event, + .description = description, + .pii_message = pii_message, + .connection_token = connection_token}; + + if (errorcode::proto::CommonError_IsValid(error)) { + params.common_error = static_cast(error); + params.is_common_error = true; + } else { + params.is_common_error = false; + switch (event) { + case START_ADVERTISING: + params.start_advertising_error = + static_cast(error); + break; + case STOP_ADVERTISING: + params.stop_advertising_error = + static_cast(error); + break; + case START_LISTENING_INCOMING_CONNECTION: + params.start_listening_incoming_connection_error = + static_cast(error); + break; + case STOP_LISTENING_INCOMING_CONNECTION: + params.stop_listening_incoming_connection_error = + static_cast(error); + break; + case START_DISCOVERING: + params.start_discovering_error = + static_cast(error); + break; + case STOP_DISCOVERING: + params.stop_discovering_error = + static_cast(error); + break; + case CONNECT: + params.connect_error = static_cast(error); + break; + case DISCONNECT: + params.disconnect_error = static_cast(error); + break; + // Set the error as unknown if undefined event passed in. + default: + params.common_error = errorcode::proto::UNKNOWN_ERROR; + params.is_common_error = true; + break; + } + } + return params; +} + +} // namespace nearby +} // namespace location diff --git a/cpp/platform/base/error_code_recorder.h b/cpp/platform/base/error_code_recorder.h new file mode 100644 index 00000000..7cf10053 --- /dev/null +++ b/cpp/platform/base/error_code_recorder.h @@ -0,0 +1,86 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_BASE_ERROR_CODE_RECORDER_H_ +#define PLATFORM_BASE_ERROR_CODE_RECORDER_H_ + +#include + +#include "platform/base/error_code_params.h" + +namespace location { +namespace nearby { + +// Deploys the error code of the platform medium to the analytics recorder. +// +// The usage is to call static method ErrorCode::LogErrorCode(...) in each +// medium platform supported whenever error occurred. +class ErrorCodeRecorder { + public: + using ErrorCodeListener = std::function; + + explicit ErrorCodeRecorder(ErrorCodeListener listener) { + listener_ = std::move(listener); + } + ErrorCodeRecorder(const ErrorCodeRecorder&) = delete; + ErrorCodeRecorder& operator=(const ErrorCodeRecorder&) = delete; + + // Logs error code when medium error occurred. + // + // Caller should pass the exact matched event and error enum value except + // the common error. + // e.g. + // - START_ADVERTISING_FAILED(43) in StartAdvertisingError => + // START_ADVERTISING event. + // - DISCONNECT_NETWORK_FAILED(31) in Disconnect_error => DISCONNECT event. + // - INVALID_PARAMETER(1) for Common_error, and the event will be kept as it + // is passed in. + // + // medium - An enum defined in proto::connections::Medium. + // event - An enum defined in errorcode::proto::Event. + // error - An enum integer value in the range from 0..30 defined + // in errorcode::proto::CommonError and the range from 31.. + // nn defined among + // errorcode::proto::StartAdvertisingError to + // errorcode::proto::DisconnectError. + // description - A description defiend in errorcode::proto::description. + // pii_message - A pii info defiend in errorcode::proto::pii_message. An + // empty string won't be recorded. + // connection_token - connection token string. + static void LogErrorCode( + location::nearby::proto::connections::Medium medium, + location::nearby::errorcode::proto::Event event, int error, + location::nearby::errorcode::proto::Description description, + const std::string& pii_message, const std::string& connection_token); + + private: + // An auxiliary funciton for LogError() to assemble the ErrorCodeParams + // struct. + // + // See `LogErrorCode` for reference on the parameters. + static ErrorCodeParams BuildErrorCodeParams( + location::nearby::proto::connections::Medium medium, + location::nearby::errorcode::proto::Event event, int error, + location::nearby::errorcode::proto::Description description, + const std::string& pii_message, const std::string& connection_token); + + // A listener to call back to AnlayticsRecorder.OnErrorCode() by building + // error_code_params. + static ErrorCodeListener listener_; +}; + +} // namespace nearby +} // namespace location + +#endif // PLATFORM_BASE_ERROR_CODE_RECORDER_H_ diff --git a/cpp/platform/base/error_code_recorder_test.cc b/cpp/platform/base/error_code_recorder_test.cc new file mode 100644 index 00000000..f0f84b93 --- /dev/null +++ b/cpp/platform/base/error_code_recorder_test.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 "platform/base/error_code_recorder.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace location { +namespace nearby { + +using ::testing::Field; +using ::testing::MockFunction; +using ::testing::StrictMock; + +TEST(ErrorCodeRecorderTest, TestListenerWork) { + StrictMock> mock_listener; + ErrorCodeRecorder::ErrorCodeListener listener = mock_listener.AsStdFunction(); + EXPECT_CALL(mock_listener, Call).Times(1); + ErrorCodeRecorder error_code_recorder(listener); + + ErrorCodeRecorder::LogErrorCode( + proto::connections::BLE, errorcode::proto::START_ADVERTISING, + errorcode::proto::MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED, + errorcode::proto::FEATURE_BLUETOOTH_NOT_SUPPORTED, "pii_message", + "connection_token"); +} + +TEST(ErrorCodeRecorderTest, TestBuildErrorCodeParamsWork) { + std::string pii_message = "pii_message"; + std::string connection_token = "connection_token"; + + ErrorCodeParams error_code_params; + ErrorCodeRecorder error_recorder( + [&error_code_params](const ErrorCodeParams& params) { + error_code_params = std::move(params); + }); + + ErrorCodeRecorder::LogErrorCode( + proto::connections::BLE, errorcode::proto::START_ADVERTISING, + errorcode::proto::MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED, + errorcode::proto::FEATURE_BLUETOOTH_NOT_SUPPORTED, pii_message, + connection_token); + + EXPECT_THAT( + error_code_params, + AllOf(Field("medium", &ErrorCodeParams::medium, proto::connections::BLE), + Field("event", &ErrorCodeParams::event, + errorcode::proto::START_ADVERTISING), + Field("description", &ErrorCodeParams::description, + errorcode::proto::FEATURE_BLUETOOTH_NOT_SUPPORTED), + Field("pii_message", &ErrorCodeParams::pii_message, pii_message), + Field("is_common_error", &ErrorCodeParams::is_common_error, false), + Field("start_advertising_error", + &ErrorCodeParams::start_advertising_error, + errorcode::proto::MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED), + Field("connection_token", &ErrorCodeParams::connection_token, + connection_token))); +} + +TEST(ErrorCodeRecorderTest, TestBuildErrorCodeParamsWorkForCommonError) { + std::string pii_message = "pii_message"; + std::string connection_token = "connection_token"; + + ErrorCodeParams error_code_params; + ErrorCodeRecorder error_recorder( + [&error_code_params](const ErrorCodeParams& params) { + error_code_params = std::move(params); + }); + + ErrorCodeRecorder::LogErrorCode( + proto::connections::BLE, errorcode::proto::START_ADVERTISING, + errorcode::proto::INVALID_PARAMETER, errorcode::proto::NULL_SERVICE_ID, + pii_message, connection_token); + + EXPECT_THAT( + error_code_params, + AllOf(Field("medium", &ErrorCodeParams::medium, proto::connections::BLE), + Field("event", &ErrorCodeParams::event, + errorcode::proto::START_ADVERTISING), + Field("description", &ErrorCodeParams::description, + errorcode::proto::NULL_SERVICE_ID), + Field("pii_message", &ErrorCodeParams::pii_message, pii_message), + Field("is_common_error", &ErrorCodeParams::is_common_error, true), + Field("common_error", &ErrorCodeParams::common_error, + errorcode::proto::INVALID_PARAMETER), + Field("connection_token", &ErrorCodeParams::connection_token, + connection_token))); +} + +TEST(ErrorCodeRecorderTest, TestBuildErrorCodeParamsWorkForUnknownEvent) { + std::string pii_message = "pii_message"; + std::string connection_token = "connection_token"; + + ErrorCodeParams error_code_params; + ErrorCodeRecorder error_recorder( + [&error_code_params](const ErrorCodeParams& params) { + error_code_params = std::move(params); + }); + + // If event is UNKNOWN_EVENT and the error is not Common_Error then the error + // will be set to UNKNOWN_ERROR. + ErrorCodeRecorder::LogErrorCode( + proto::connections::BLE, errorcode::proto::UNKNOWN_EVENT, + errorcode::proto::MULTIPLE_FAST_ADVERTISEMENT_NOT_ALLOWED, + errorcode::proto::FEATURE_BLUETOOTH_NOT_SUPPORTED, pii_message, + connection_token); + + EXPECT_THAT( + error_code_params, + AllOf(Field("medium", &ErrorCodeParams::medium, proto::connections::BLE), + Field("event", &ErrorCodeParams::event, + errorcode::proto::UNKNOWN_EVENT), + Field("description", &ErrorCodeParams::description, + errorcode::proto::FEATURE_BLUETOOTH_NOT_SUPPORTED), + Field("pii_message", &ErrorCodeParams::pii_message, pii_message), + Field("is_common_error", &ErrorCodeParams::is_common_error, true), + Field("common_error", &ErrorCodeParams::common_error, + errorcode::proto::UNKNOWN_ERROR), + Field("connection_token", &ErrorCodeParams::connection_token, + connection_token))); +} + +} // namespace nearby +} // namespace location diff --git a/proto/errorcode/BUILD b/proto/errorcode/BUILD index 7f3ffe7a..9cd74c39 100644 --- a/proto/errorcode/BUILD +++ b/proto/errorcode/BUILD @@ -14,6 +14,8 @@ # Proto for Nearby products +load("//net/proto2/contrib/portable/cc:portable_proto_build_defs.bzl", "portable_proto_library") + licenses(["notice"]) package(default_visibility = ["//visibility:public"]) @@ -32,3 +34,28 @@ java_lite_proto_library( name = "error_code_enums_java_proto_lite", deps = [":error_code_enums_proto"], ) + +portable_proto_library( + name = "error_code_enums_portable_proto", + compatible_with = [ + "//buildenv/target:non_prod", + ], + config = ":error_code_enums_proto_config", + copts = [ + "-DGOOGLE_PROTOBUF_NO_RTTI=1", + ], + proto_deps = [ + ":error_code_enums_proto", + ], + visibility = [ + "//third_party/nearby_connections:__subpackages__", + ], +) + +filegroup( + name = "error_code_enums_proto_config", + srcs = ["error_code_enums_proto_config.asciipb"], + compatible_with = [ + "//buildenv/target:non_prod", + ], +) diff --git a/proto/errorcode/error_code_enums_proto_config.asciipb b/proto/errorcode/error_code_enums_proto_config.asciipb new file mode 100644 index 00000000..61573480 --- /dev/null +++ b/proto/errorcode/error_code_enums_proto_config.asciipb @@ -0,0 +1,14 @@ +optimize_mode: LITE_RUNTIME + +allowed_enum: "location.nearby.errorcode.proto.ErrorType" +allowed_enum: "location.nearby.errorcode.proto.Event" +allowed_enum: "location.nearby.errorcode.proto.CommonError" +allowed_enum: "location.nearby.errorcode.proto.StartAdvertisingError" +allowed_enum: "location.nearby.errorcode.proto.StopAdvertisingError" +allowed_enum: "location.nearby.errorcode.proto.StartDiscoveringError" +allowed_enum: "location.nearby.errorcode.proto.StopDiscoveringError" +allowed_enum: "location.nearby.errorcode.proto.StartListeningIncomingConnectionError" +allowed_enum: "location.nearby.errorcode.proto.StopListeningIncomingConnectionError" +allowed_enum: "location.nearby.errorcode.proto.ConnectError" +allowed_enum: "location.nearby.errorcode.proto.DisconnectError" +allowed_enum: "location.nearby.errorcode.proto.Description"