[Bluetooth] Add BluetoothGATTServer. Implement start, stop and getServer 89/235889/13
authorPawel Wasowski <p.wasowski2@samsung.com>
Mon, 6 Jul 2020 18:36:21 +0000 (20:36 +0200)
committerPawel Wasowski <p.wasowski2@samsung.com>
Mon, 17 Aug 2020 16:49:25 +0000 (18:49 +0200)
ACR: TWDAPI-263

This commit adds BluetoothGATTServer class in C++ and implements JS
APIs:
- tizen.bluetooth.getGATTServer()
- BluetoothGATTServer.start()
- BluetoothGATTServer.stop()

Verification: The code tested in the Chrome DevTools console works fine

Change-Id: I688df55298be1b5ec997a5ae8ff42877f1bd6a24
Signed-off-by: Pawel Wasowski <p.wasowski2@samsung.com>
src/bluetooth/bluetooth.gyp
src/bluetooth/bluetooth_api.js
src/bluetooth/bluetooth_gatt_server.cc [new file with mode: 0644]
src/bluetooth/bluetooth_gatt_server.h [new file with mode: 0644]
src/bluetooth/bluetooth_instance.cc
src/bluetooth/bluetooth_instance.h
src/bluetooth/bluetooth_util.cc
src/bluetooth/bluetooth_util.h

index 07c310e..73df5d3 100644 (file)
@@ -42,7 +42,9 @@
         'bluetooth_le_device.cc',
         'bluetooth_le_device.h',
         'uuid.cc',
-        'uuid.h'
+        'uuid.h',
+        'bluetooth_gatt_server.cc',
+        'bluetooth_gatt_server.h'
       ],
       'includes': [
         '../common/pkg-config.gypi',
index 6e55fad..37799e0 100755 (executable)
@@ -2715,6 +2715,95 @@ BluetoothAdapter.prototype.getBluetoothProfileHandler = function() {
     }
 };
 
+// class BluetoothGATTServer ////////////////////////
+var BluetoothGATTServer = function() {
+  var services_ = [];
+  Object.defineProperties(this, {
+    services: {
+      get: function() {
+        return services_;
+      },
+      set: function() {}
+    }
+  });
+}
+
+var AbortError = new WebAPIException('AbortError', 'An unknown error occurred');
+
+var BluetoothGATTServer_valid_start_errors = ['InvalidStateError', 'NotSupportedError', 'AbortError'];
+var BluetoothGATTServer_valid_start_exceptions = ['InvalidStateError', 'TypeMismatchError', 'SecurityError'];
+
+BluetoothGATTServer.prototype.start = function() {
+    privUtils_.log('Entered BluetoothGATTServer.start()');
+    var args = AV.validateArgs(arguments, [
+        {
+            name: 'successCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'errorCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        }
+    ]);
+
+    var callback = function(result) {
+        if (native.isFailure(result)) {
+            native.callIfPossible(args.errorCallback,
+                                  native.getErrorObjectAndValidate(result,
+                                    BluetoothGATTServer_valid_start_errors, AbortError));
+        } else {
+            native.callIfPossible(args.successCallback);
+        }
+    };
+
+    var result = native.call('BluetoothGATTServerStart', {}, callback);
+    if (native.isFailure(result)) {
+        throw native.getErrorObjectAndValidate(result, BluetoothGATTServer_valid_start_exceptions, AbortError);
+    }
+}
+
+var BluetoothGATTServer_valid_stop_errors = ['InvalidStateError', 'NotSupportedError', 'AbortError'];
+var BluetoothGATTServer_valid_stop_exceptions = ['InvalidStateError', 'TypeMismatchError', 'SecurityError'];
+
+BluetoothGATTServer.prototype.stop = function() {
+    privUtils_.log('Entered BluetoothGATTServer.stop()');
+    var args = AV.validateArgs(arguments, [
+        {
+            name: 'successCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'errorCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        }
+    ]);
+
+    var callback = function(result) {
+        if (native.isFailure(result)) {
+            native.callIfPossible(args.errorCallback,
+                                  native.getErrorObjectAndValidate(result,
+                                    BluetoothGATTServer_valid_stop_errors, AbortError));
+        } else {
+            native.callIfPossible(args.successCallback);
+        }
+    };
+
+    var result = native.call('BluetoothGATTServerStop', {}, callback);
+    if (native.isFailure(result)) {
+        throw native.getErrorObjectAndValidate(result, BluetoothGATTServer_valid_stop_exceptions, AbortError);
+    }
+}
+
+var GATTServer = new BluetoothGATTServer();
+
 // class BluetoothManager ///////////////////////////
 var BluetoothManager = function() {
     Object.defineProperties(this, {
@@ -2993,5 +3082,14 @@ BluetoothManager.prototype.uuidsEqual = function(uuid1, uuid2) {
     ]);
     return BluetoothManager_UUIDsEqual(args.uuid1, args.uuid2);
 };
+BluetoothManager.prototype.getGATTServer = function() {
+    privUtils_.log('Entered BluetoothManager.getGATTServer()');
+    return BluetoothManager_getGATTServer();
+};
+
+var BluetoothManager_getGATTServer = function() {
+    return GATTServer;
+};
+
 // exports //////////////////////////////////////////
 exports = new BluetoothManager();
diff --git a/src/bluetooth/bluetooth_gatt_server.cc b/src/bluetooth/bluetooth_gatt_server.cc
new file mode 100644 (file)
index 0000000..255433e
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    Licensed under the Apache License, Version 2.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.
+ */
+
+#include "bluetooth/bluetooth_gatt_server.h"
+#include "bluetooth/bluetooth_util.h"
+
+#include "common/logger.h"
+#include "common/tools.h"
+
+using namespace extension::bluetooth::util;
+using namespace common;
+using namespace common::tools;
+
+namespace extension {
+namespace bluetooth {
+
+BluetoothGATTServer::BluetoothGATTServer(const BluetoothInstance& instance)
+    : instance_{instance}, initialized_{false}, running_{false}, handle_{nullptr} {
+  ScopeLogger();
+
+  /*
+   * TODO: register a callback, that deinitializes a server, unregisters its
+   * services, destroys handle_ and sets running_ to false
+   * when Bluetooth is turned off.
+   */
+}
+
+BluetoothGATTServer::~BluetoothGATTServer() {
+  ScopeLogger();
+
+  UnregisterAllServicesImpl();
+  DestroyAllGATTObjects();
+  Deinitialize();
+}
+
+void BluetoothGATTServer::Start(picojson::object& out) {
+  ScopeLogger();
+
+  if (running_) {
+    LoggerD("Server is running");
+    ReportSuccess(out);
+    return;
+  }
+
+  if (!initialized_) {
+    /*
+     * Server is initialized when its services are registered.
+     * However, the case when server is uninitialized until start is
+     * also valid. It happens, when user doesn't register any services
+     * and starts the server with the default ones.
+     */
+    auto result = InitializeAndCreateHandle();
+    if (!result) {
+      result.SetMessage("Couldn't start GATT server");
+      ReportError(result, &out);
+      return;
+    }
+  }
+
+  auto ret = bt_gatt_server_start();
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_start(): %d (%s)", ret, get_error_message(ret));
+    ReportError(BluetoothErrorToPlatformResult(ret, "Couldn't start GATT server"), &out);
+    return;
+  }
+  LoggerD("bt_gatt_server_start(): success");
+
+  running_ = true;
+  ReportSuccess(out);
+}
+
+void BluetoothGATTServer::Stop(picojson::object& out) {
+  ScopeLogger();
+
+  if (!running_) {
+    LoggerD("Server is not running");
+    ReportSuccess(out);
+    return;
+  }
+
+  // Unregistering all services should not fail.
+  // Even if it does we just let UnregisterAllSerivcesImpl() log the error
+  // and proceed with server destruction.
+  UnregisterAllServicesImpl();
+
+  // DestroyAllGATTObjects() should not fail
+  // If it does, it logs the error message.
+  DestroyAllGATTObjects();
+
+  // Deinitialize() is the function that actually stops the server,
+  // thus we return the result, it returns.
+  auto result = Deinitialize();
+  if (!result) {
+    result.SetMessage("Couldn't stop GATT server");
+    ReportError(result, &out);
+  } else {
+    ReportSuccess(out);
+    running_ = false;
+  }
+}
+
+PlatformResult BluetoothGATTServer::Initialize() {
+  ScopeLogger();
+
+  auto ret = bt_gatt_server_initialize();
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_initialize(): %d (%s)", ret, get_error_message(ret));
+    return BluetoothErrorToPlatformResult(ret);
+  }
+  LoggerD("bt_gatt_server_initialize(): success");
+  initialized_ = true;
+
+  return PlatformResult{};
+}
+
+PlatformResult BluetoothGATTServer::CreateHandle() {
+  ScopeLogger();
+
+  if (handle_) {
+    LoggerE("Server handle is already created");
+    return PlatformResult{ErrorCode::INVALID_STATE_ERR};
+  }
+
+  auto ret = bt_gatt_server_create(&handle_);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_create(): %d (%s)", ret, get_error_message(ret));
+  } else {
+    LoggerD("bt_gatt_server_create(): success");
+  }
+
+  return BluetoothErrorToPlatformResult(ret);
+}
+
+PlatformResult BluetoothGATTServer::Deinitialize() {
+  ScopeLogger();
+
+  if (!initialized_) {
+    LoggerE("Server is not initialized");
+    return PlatformResult{ErrorCode::INVALID_STATE_ERR};
+  }
+
+  auto ret = bt_gatt_server_deinitialize();
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_deinitialize(): %d (%s)", ret, get_error_message(ret));
+    return BluetoothErrorToPlatformResult(ret);
+  }
+
+  LoggerD("bt_gatt_server_deinitialize(): success");
+  initialized_ = false;
+
+  /*
+   * Undocumented behavior of native API: bt_gatt_server_deinitialize() calls
+   * bt_gatt_server_destroy() - after Deinitialize() bt_gatt_server_destroy()
+   * should not be called (a call results in a BT_INVALID_STATE_ERROR).
+   * handle_ is to be nullified, as it is no longer valid.
+   */
+  handle_ = nullptr;
+
+  return PlatformResult{};
+}
+
+PlatformResult BluetoothGATTServer::InitializeAndCreateHandle() {
+  ScopeLogger();
+
+  auto result = Initialize();
+  if (!result) {
+    return result;
+  }
+
+  result = CreateHandle();
+  if (!result) {
+    Deinitialize();
+  }
+  return result;
+}
+
+PlatformResult BluetoothGATTServer::UnregisterAllServicesImpl() {
+  ScopeLogger();
+
+  if (!handle_) {
+    LoggerE("Server handle is a nullptr");
+    return PlatformResult{ErrorCode::INVALID_STATE_ERR};
+  }
+
+  auto ret = bt_gatt_server_unregister_all_services(handle_);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_unregister_all_services(): %d (%s)", ret, get_error_message(ret));
+    return BluetoothErrorToPlatformResult(ret);
+  } else {
+    LoggerD("bt_gatt_server_unregister_all_services(): success");
+    return PlatformResult{};
+  }
+}
+
+PlatformResult BluetoothGATTServer::DestroyAllGATTObjects() {
+  ScopeLogger();
+
+  if (!handle_) {
+    LoggerE("Server handle is a nullptr");
+    return PlatformResult{ErrorCode::INVALID_STATE_ERR};
+  }
+
+  auto ret = bt_gatt_server_foreach_services(handle_, DestroyService, nullptr);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_unregister_all_services(): %d (%s)", ret, get_error_message(ret));
+    return BluetoothErrorToPlatformResult(ret);
+  } else {
+    LoggerD("bt_gatt_server_unregister_all_services(): success");
+    return PlatformResult{};
+  }
+
+  /*
+   * TODO: remove handles from service id -> service handle map
+   */
+}
+
+bool BluetoothGATTServer::DestroyService(int total, int index, bt_gatt_h handle, void* user_data) {
+  ScopeLogger("total: %d, index: %d, handle: %p", total, index, handle);
+
+  auto ret = bt_gatt_service_foreach_included_services(handle, DestroyService, nullptr);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_service_foreach_included_servics(): %d (%s)", ret, get_error_message(ret));
+  }
+  LoggerD("bt_gatt_service_foreach_included_servics(): success");
+
+  ret = bt_gatt_service_foreach_characteristics(handle, DestroyCharacteristic, nullptr);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_service_foreach_characteristics(): %d (%s)", ret, get_error_message(ret));
+  }
+  LoggerD("bt_gatt_service_foreach_characteristics(): success");
+
+  ret = bt_gatt_service_destroy(handle);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_service_destroy(): %d (%s)", ret, get_error_message(ret));
+  }
+
+  return true;
+}
+
+bool BluetoothGATTServer::DestroyCharacteristic(int total, int index, bt_gatt_h handle,
+                                                void* user_data) {
+  ScopeLogger("total: %d, index: %d, handle: %p", total, index, handle);
+
+  auto ret = bt_gatt_characteristic_foreach_descriptors(handle, DestroyDescriptor, nullptr);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_characteristic_foreach_descriptors(): %d (%s)", ret, get_error_message(ret));
+  }
+  LoggerD("bt_gatt_characteristic_foreach_descriptors(): success");
+
+  ret = bt_gatt_characteristic_destroy(handle);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_characteristic_destroy(): %d (%s)", ret, get_error_message(ret));
+  }
+
+  return true;
+}
+
+bool BluetoothGATTServer::DestroyDescriptor(int total, int index, bt_gatt_h handle,
+                                            void* user_data) {
+  ScopeLogger("total: %d, index: %d, handle: %p", total, index, handle);
+
+  auto ret = bt_gatt_descriptor_destroy(handle);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_descriptor_destroy(): %d (%s)", ret, get_error_message(ret));
+  }
+  return true;
+}
+
+}  // namespace bluetooth
+}  // namespace extension
diff --git a/src/bluetooth/bluetooth_gatt_server.h b/src/bluetooth/bluetooth_gatt_server.h
new file mode 100644 (file)
index 0000000..6c5d83d
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ *    Licensed under the Apache License, Version 2.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.
+ */
+
+#ifndef BLUETOOTH_BLUETOOTH_GATT_SERVER_H_
+#define BLUETOOTH_BLUETOOTH_GATT_SERVER_H_
+
+#include <bluetooth.h>
+
+#include "common/picojson.h"
+#include "common/platform_result.h"
+
+using common::PlatformResult;
+
+namespace extension {
+namespace bluetooth {
+
+class BluetoothInstance;
+
+class BluetoothGATTServer {
+ public:
+  BluetoothGATTServer(const BluetoothInstance&);
+  ~BluetoothGATTServer();
+
+  void Start(picojson::object& out);
+  void Stop(picojson::object& out);
+
+  static bool DestroyService(int total, int index, bt_gatt_h handle, void* user_data);
+  static bool DestroyCharacteristic(int total, int index, bt_gatt_h handle, void* user_data);
+  static bool DestroyDescriptor(int total, int index, bt_gatt_h handle, void* user_data);
+
+ private:
+  const BluetoothInstance& instance_;
+  bool initialized_;
+  bool running_;
+  bt_gatt_server_h handle_;
+
+  PlatformResult UnregisterAllServicesImpl();
+  PlatformResult DestroyAllGATTObjects();
+  PlatformResult Initialize();
+  PlatformResult CreateHandle();
+  PlatformResult Deinitialize();
+  PlatformResult InitializeAndCreateHandle();
+};
+
+}  // namespace bluetooth
+}  // namespace extension
+
+#endif  // BLUETOOTH_BLUETOOTH_GATT_SERVER_H_
index 6b68e5f..2ff079e 100644 (file)
@@ -36,7 +36,9 @@ BluetoothInstance::BluetoothInstance()
       bluetooth_socket_(bluetooth_adapter_),
       bluetooth_le_adapter_(*this),
       bluetooth_gatt_service_(*this),
-      bluetooth_le_device_(*this, bluetooth_gatt_service_) {
+      bluetooth_le_device_(*this, bluetooth_gatt_service_),
+      worker(),
+      bluetooth_gatt_server_(*this) {
   ScopeLogger();
   using std::placeholders::_1;
   using std::placeholders::_2;
@@ -98,16 +100,21 @@ BluetoothInstance::BluetoothInstance()
   REGISTER_METHOD(BluetoothGATTServiceAddValueChangeListener);
   REGISTER_METHOD(BluetoothGATTServiceRemoveValueChangeListener);
 
+  REGISTER_METHOD(BluetoothGATTServerStart);
+  REGISTER_METHOD(BluetoothGATTServerStop);
+
 #undef REGISTER_METHOD
 }
 
 BluetoothInstance::~BluetoothInstance() {
   ScopeLogger();
+  worker.stop();
 }
 
 namespace {
 const char* JSON_CALLBACK_ID = "callbackId";
 const char* JSON_LISTENER_ID = "listenerId";
+const std::string kPrivilegeBluetooth = "http://tizen.org/privilege/bluetooth";
 }  // namespace
 
 void BluetoothInstance::AsyncResponse(double callback_handle,
@@ -435,5 +442,43 @@ void BluetoothInstance::BluetoothGATTServiceRemoveValueChangeListener(const pico
   bluetooth_gatt_service_.RemoveValueChangeListener(args, out);
 }
 
+void BluetoothInstance::BluetoothGATTServerStart(const picojson::value& args,
+                                                 picojson::object& out) {
+  ScopeLogger();
+
+  CHECK_PRIVILEGE_ACCESS(kPrivilegeBluetooth, &out);
+
+  double callback_id = args.get(JSON_CALLBACK_ID).get<double>();
+  worker.add_job([this, callback_id] {
+    ScopeLogger("Async call: BluetoothGATTServerStart");
+    picojson::value response = picojson::value(picojson::object());
+    picojson::object& async_out = response.get<picojson::object>();
+    async_out[JSON_CALLBACK_ID] = picojson::value(callback_id);
+    this->bluetooth_gatt_server_.Start(async_out);
+    this->PostMessage(response.serialize().c_str());
+  });
+
+  ReportSuccess(out);
+}
+
+void BluetoothInstance::BluetoothGATTServerStop(const picojson::value& args,
+                                                picojson::object& out) {
+  ScopeLogger();
+
+  CHECK_PRIVILEGE_ACCESS(kPrivilegeBluetooth, &out);
+
+  double callback_id = args.get(JSON_CALLBACK_ID).get<double>();
+  worker.add_job([this, callback_id] {
+    ScopeLogger("Async call: BluetoothGATTServerStop");
+    picojson::value response = picojson::value(picojson::object());
+    picojson::object& async_out = response.get<picojson::object>();
+    async_out[JSON_CALLBACK_ID] = picojson::value(callback_id);
+    this->bluetooth_gatt_server_.Stop(async_out);
+    this->PostMessage(response.serialize().c_str());
+  });
+
+  ReportSuccess(out);
+}
+
 }  // namespace bluetooth
 }  // namespace extension
index 0ed4909..0ee235d 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "bluetooth/bluetooth_adapter.h"
 #include "bluetooth/bluetooth_device.h"
+#include "bluetooth/bluetooth_gatt_server.h"
 #include "bluetooth/bluetooth_gatt_service.h"
 #include "bluetooth/bluetooth_health_application.h"
 #include "bluetooth/bluetooth_health_channel.h"
@@ -30,6 +31,7 @@
 #include "bluetooth/bluetooth_service_handler.h"
 #include "bluetooth/bluetooth_socket.h"
 #include "bluetooth/bluetooth_util.h"
+#include "common/worker.h"
 
 namespace extension {
 namespace bluetooth {
@@ -106,6 +108,8 @@ class BluetoothInstance : public common::ParsedInstance {
                                                   picojson::object& out);
   void BluetoothGATTServiceRemoveValueChangeListener(const picojson::value& args,
                                                      picojson::object& out);
+  void BluetoothGATTServerStart(const picojson::value& args, picojson::object& out);
+  void BluetoothGATTServerStop(const picojson::value& args, picojson::object& out);
 
   BluetoothAdapter bluetooth_adapter_;
   BluetoothDevice bluetooth_device_;
@@ -117,6 +121,14 @@ class BluetoothInstance : public common::ParsedInstance {
   BluetoothLEAdapter bluetooth_le_adapter_;
   BluetoothGATTService bluetooth_gatt_service_;
   BluetoothLEDevice bluetooth_le_device_;
+  common::Worker worker;
+  // If all operations on bluetooth_gatt_server_ will be done by the worker,
+  // no mutex to prevent concurrent access of this object is needed
+  // - all operations will be done in a single worker thread.
+  // If any operation on the bluetooth_gatt_server_ object will be done
+  // synchronously, add a mutex and lock it before each call of server's
+  // method.
+  BluetoothGATTServer bluetooth_gatt_server_;
 };
 
 }  // namespace bluetooth
index 1b378a1..64a3c83 100644 (file)
@@ -36,10 +36,13 @@ const picojson::object& GetArguments(const picojson::value& data) {
   return data.get<picojson::object>();
 }
 
-PlatformResult GetBluetoothError(int error_code, const std::string& hint) {
+PlatformResult BluetoothErrorToPlatformResult(int error_code, const std::string& message) {
   common::ErrorCode error = ErrorCode::UNKNOWN_ERR;
 
   switch (error_code) {
+    case BT_ERROR_NONE:
+      error = ErrorCode::NO_ERROR;
+      break;
     case BT_ERROR_RESOURCE_BUSY:
     case BT_ERROR_NOW_IN_PROGRESS:
     case BT_ERROR_NOT_ENABLED:
@@ -58,19 +61,31 @@ PlatformResult GetBluetoothError(int error_code, const std::string& hint) {
       error = ErrorCode::QUOTA_EXCEEDED_ERR;
       break;
 
+    case BT_ERROR_NOT_SUPPORTED:
+      error = ErrorCode::NOT_SUPPORTED_ERR;
+      break;
+
     default:
       error = ErrorCode::UNKNOWN_ERR;
       break;
   }
 
-  std::string message = hint + " : " + GetBluetoothErrorMessage(error_code);
+  return PlatformResult{error, message};
+}
+
+PlatformResult GetBluetoothError(int error_code, const std::string& hint) {
+  auto message = hint + ": " + GetBluetoothErrorMessage(error_code);
+  auto result = BluetoothErrorToPlatformResult(error_code, message);
+
+  LoggerE("%s %d (%s)", message.c_str(), error_code, get_error_message(error_code));
 
-  return LogAndCreateResult(error, message.c_str(), ("%s %d (%s)", message.c_str(), error_code,
-                                                     get_error_message(error_code)));
+  return result;
 }
 
 std::string GetBluetoothErrorMessage(int error_code) {
   switch (error_code) {
+    case BT_ERROR_NONE:
+      return "Success";
     case BT_ERROR_CANCELLED:
       return "Operation cancelled";
     case BT_ERROR_INVALID_PARAMETER:
index 69f5d46..e182b13 100644 (file)
@@ -31,6 +31,8 @@ double GetAsyncCallbackHandle(const picojson::value& data);
 
 const picojson::object& GetArguments(const picojson::value& data);
 
+common::PlatformResult BluetoothErrorToPlatformResult(int error_code,
+                                                      const std::string& message = "");
 common::PlatformResult GetBluetoothError(int error_code, const std::string& hint);
 std::string GetBluetoothErrorMessage(int error_code);