[Bluetooth] Implement BluetoothGATTServer{Characteristic|Descriptor}::setWriteValueRe... 68/243868/6
authorArkadiusz Pietraszek <a.pietraszek@samsung.com>
Thu, 10 Sep 2020 16:59:10 +0000 (18:59 +0200)
committerPawel Wasowski <p.wasowski2@samsung.com>
Tue, 15 Sep 2020 22:10:48 +0000 (00:10 +0200)
[ACR] https://code.sec.samsung.net/jira/browse/TWDAPI-263

[Verification] Basic use cases were tested in Chrome Dev Tools and run
               properly. More thorough verification should yet be done.

Change-Id: I676dd8f06f8a9b19c2c98ffeaf21cfc9e3fd19d3
Signed-off-by: Arkadiusz Pietraszek <a.pietraszek@samsung.com>
Signed-off-by: Pawel Wasowski <p.wasowski2@samsung.com>
src/bluetooth/bluetooth_api.js
src/bluetooth/bluetooth_gatt_server.cc
src/bluetooth/bluetooth_gatt_server.h
src/bluetooth/bluetooth_gatt_server_service.cc
src/bluetooth/bluetooth_gatt_server_service.h
src/bluetooth/bluetooth_instance.cc
src/bluetooth/bluetooth_instance.h

index 6bfcbec..9a5ed9e 100755 (executable)
@@ -2544,6 +2544,63 @@ function _createReadValueRequestCallback(
     );
 }
 
+function _createWriteValueRequestCallback(
+    _id,
+    sendResponseSuccessCallback,
+    sendResponseErrorCallback
+) {
+    return _singleListenerBuilder(
+        'WriteValueRequestCallback_' + _id,
+        /*
+         * _singleListenerBuilder requires 2 callbacks, the second of which
+         * is an error callback.
+         * Write value request events coming from the native layer
+         * are never errors.
+         * Hence, we don't use the second callback here.
+         */
+        function(event, writeValueRequestCallback, unusedErrorCallback) {
+            var clientAddress = event.clientAddress;
+            var value = event.value;
+            var offset = event.offset;
+            var replyRequired = event.replyRequired;
+
+            if (writeValueRequestCallback) {
+                var requestReply = writeValueRequestCallback(
+                    clientAddress,
+                    BluetoothManager_toByteArray(value),
+                    offset,
+                    replyRequired
+                );
+                var response = {
+                    _id: _id,
+                    requestId: event.requestId,
+                    requestType: event.requestType,
+                    value: null, // Responses to write requests don't contain value
+                    offset: offset,
+                    statusCode: requestReply.statusCode,
+                    data: requestReply.data
+                };
+                var callback = function(result) {
+                    if (native.isFailure(result)) {
+                        native.callIfPossible(
+                            sendResponseErrorCallback,
+                            native.getErrorObject(result)
+                        );
+                    } else {
+                        native.callIfPossible(sendResponseSuccessCallback);
+                    }
+                };
+                var result = native.call(
+                    'BluetoothGATTServerSendResponse',
+                    response,
+                    callback
+                );
+            }
+            return true;
+        }
+    );
+}
+
 var _BluetoothGATTServerReadWriteValueRequestCallbacks = {};
 
 var _setReadValueRequestCallbackCommon = function() {
@@ -2614,7 +2671,76 @@ var _setReadValueRequestCallbackCommon = function() {
     }
 };
 
+var _setWriteValueRequestCallbackCommon = function() {
+    var args = AV.validateArgs(arguments, [
+        {
+            name: 'writeValueRequestCallback',
+            type: AV.Types.FUNCTION
+        },
+        {
+            name: 'successCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'errorCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'sendResponseSuccessCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        },
+        {
+            name: 'sendResponseErrorCallback',
+            type: AV.Types.FUNCTION,
+            optional: true,
+            nullable: true
+        }
+    ]);
+
+    var entityId = this._id;
+
+    var callback = function(result) {
+        if (native.isFailure(result)) {
+            native.callIfPossible(args.errorCallback, native.getErrorObject(result));
+        } else {
+            var writeValueRequestCallback = _createWriteValueRequestCallback(
+                entityId,
+                args.sendResponseSuccessCallback,
+                args.sendResponseErrorCallback
+            );
+            writeValueRequestCallback.addListener(
+                args.writeValueRequestCallback,
+                function unusedErrorCallback() {
+                    /* Intentionally no operation */
+                }
+            );
+            _BluetoothGATTServerReadWriteValueRequestCallbacks[
+                'WriteValueCallback' + entityId
+            ] = writeValueRequestCallback;
+            native.callIfPossible(args.successCallback, native.getErrorObject(result));
+        }
+    };
+
+    var callArgs = { _id: this._id };
+    var result = native.call(
+        'BluetoothGATTServerSetWriteValueRequestCallback',
+        callArgs,
+        callback
+    );
+
+    if (native.isFailure(result)) {
+        throw native.getErrorObject(result);
+    }
+};
+
 BluetoothGATTServerCharacteristic.prototype.setReadValueRequestCallback = _setReadValueRequestCallbackCommon;
+BluetoothGATTServerCharacteristic.prototype.setWriteValueRequestCallback = _setWriteValueRequestCallbackCommon;
 
 /**
  * Creates a manager for specified listener event. Manager handles multiple
@@ -2982,6 +3108,7 @@ Object.defineProperty(BluetoothGATTServerDescriptor.prototype, 'constructor', {
 });
 
 BluetoothGATTServerDescriptor.prototype.setReadValueRequestCallback = _setReadValueRequestCallbackCommon;
+BluetoothGATTServerDescriptor.prototype.setWriteValueRequestCallback = _setWriteValueRequestCallbackCommon;
 
 // class BluetoothAdapter ///////////////////////////
 var BluetoothAdapter = function() {
index b0fd2e4..2c7bb9f 100644 (file)
@@ -205,6 +205,20 @@ void BluetoothGATTServer::SetReadValueRequestCallback(const picojson::value& arg
   ReportSuccess(out);
 }
 
+void BluetoothGATTServer::SetWriteValueRequestCallback(const picojson::value& args,
+                                                       picojson::object& out) {
+  ScopeLogger();
+
+  auto result = service_.SetWriteValueRequestCallback(args);
+
+  if (result.IsError()) {
+    ReportError(result, &out);
+    return;
+  }
+
+  ReportSuccess(out);
+}
+
 void BluetoothGATTServer::SendResponse(const picojson::value& args, picojson::object& out) {
   ScopeLogger();
 
index b3cd150..770d83f 100644 (file)
@@ -41,6 +41,7 @@ class BluetoothGATTServer {
   void RegisterService(const picojson::value& args, picojson::object& out);
   void UnregisterService(const picojson::value& args, picojson::object& out);
   void SetReadValueRequestCallback(const picojson::value& args, picojson::object& out);
+  void SetWriteValueRequestCallback(const picojson::value& args, picojson::object& out);
   void SendResponse(const picojson::value& args, picojson::object& out);
   bool IsRunning();
 
index bf07067..07aeb58 100644 (file)
@@ -60,12 +60,16 @@ const std::string kId = "_id";
 const std::string kIdsToRemoveFromNativeLayer = "idsToRemoveFromNativeLayer";
 const std::string kClientAddress = "clientAddress";
 const std::string kRequestId = "requestId";
+const std::string kValue = "value";
 const std::string kOffset = "offset";
+const std::string kReplyRequired = "replyRequired";
 const std::string kRequestType = "requestType";
 const std::string kReadRequestType = "readRequestType";
 const std::string kWriteRequestType = "writeRequestType";
 const std::string kStatusCode = "statusCode";
 const std::string kData = "data";
+const std::string kReadCallback = "readCallback";
+const std::string kWriteCallback = "writeCallback";
 
 int GetPermissionsInt(const picojson::value& permissions) {
   ScopeLogger("permissions: %s", permissions.serialize().c_str());
@@ -425,7 +429,8 @@ common::PlatformResult BluetoothGATTServerService::UnregisterService(const picoj
 
     auto handle_to_remove = gatt_objects_[id_to_remove];
     LoggerD("Erasing gatt object with _id: %d", id_to_remove);
-    callback_names_.erase(handle_to_remove);
+    callback_names_.erase(std::make_pair(handle_to_remove, kReadCallback));
+    callback_names_.erase(std::make_pair(handle_to_remove, kWriteCallback));
     gatt_objects_.erase(id_to_remove);
   }
 
@@ -440,7 +445,8 @@ PlatformResult BluetoothGATTServerService::SetReadValueRequestCallback(
   LoggerD("Setting read value request callback for a GATT entity with _id: %d", _id);
 
   auto gatt_handle = gatt_objects_[_id];
-  callback_names_[gatt_handle] = "ReadValueRequestCallback_" + std::to_string(_id);
+  callback_names_[std::make_pair(gatt_handle, kReadCallback)] =
+      "ReadValueRequestCallback_" + std::to_string(_id);
 
   auto read_value_request_callback = [](const char* remote_address, int request_id,
                                         bt_gatt_server_h server, bt_gatt_h gatt_handle, int offset,
@@ -463,7 +469,8 @@ PlatformResult BluetoothGATTServerService::SetReadValueRequestCallback(
           read_value_request_obj[kRequestId] = picojson::value{static_cast<double>(request_id)};
           read_value_request_obj[kRequestType] = picojson::value{kReadRequestType};
           read_value_request_obj[kOffset] = picojson::value{static_cast<double>(offset)};
-          const auto callback_name = rw_callback_data->callback_names_map_[gatt_handle];
+          const auto callback_name =
+              rw_callback_data->callback_names_map_[std::make_pair(gatt_handle, kReadCallback)];
           LoggerD("Firing read value request event: %s: %s", callback_name.c_str(),
                   read_value_request.serialize().c_str());
           rw_callback_data->instance_.FireEvent(callback_name, read_value_request);
@@ -482,10 +489,71 @@ PlatformResult BluetoothGATTServerService::SetReadValueRequestCallback(
   return common::PlatformResult{};
 }
 
-PlatformResult BluetoothGATTServerService::SendResponse(const picojson::value& args) {
+PlatformResult BluetoothGATTServerService::SetWriteValueRequestCallback(
+    const picojson::value& args) {
   ScopeLogger();
 
   auto _id = static_cast<int>(args.get(kId).get<double>());
+  LoggerD("Setting write value request callback for a GATT entity with _id: %d", _id);
+
+  auto gatt_handle = gatt_objects_[_id];
+  callback_names_[std::make_pair(gatt_handle, kWriteCallback)] =
+      "WriteValueRequestCallback_" + std::to_string(_id);
+
+  auto write_value_request_callback = [](
+      const char* remote_address, int request_id, bt_gatt_server_h server, bt_gatt_h gatt_handle,
+      bool response_needed, int offset, const char* value, int len, void* user_data) -> void {
+    ScopeLogger(
+        "WriteValueRequestCallback called. remote_address: %s, request_id: %d, response_needed: "
+        "%d, offset: %d, value: %s, len: %d",
+        remote_address, request_id, response_needed, offset, value, len);
+    auto rw_callback_data = static_cast<ReadWriteRequestCallbackData*>(user_data);
+
+    // We create a copy of value and remote_address
+    auto remote_address_copy = std::string{remote_address};
+    auto value_copy = std::string{value};
+    rw_callback_data->instance_.GetWorker().add_job([remote_address_copy, request_id, server,
+                                                     gatt_handle, response_needed, offset, value_copy,
+                                                     rw_callback_data] {
+      ScopeLogger("Async call: SetWriteValueRequestCallback");
+
+      auto write_value_request = picojson::value{picojson::object{}};
+      auto& write_value_request_obj = write_value_request.get<picojson::object>();
+      write_value_request_obj[kClientAddress] = picojson::value{remote_address_copy};
+      write_value_request_obj[kRequestId] = picojson::value{static_cast<double>(request_id)};
+      write_value_request_obj[kRequestType] = picojson::value{kWriteRequestType};
+      write_value_request_obj[kReplyRequired] = picojson::value{response_needed};
+      write_value_request_obj[kOffset] = picojson::value{static_cast<double>(offset)};
+
+      write_value_request_obj[kValue] = picojson::value{picojson::array{}};
+      auto& value_byte_array = write_value_request_obj[kValue].get<picojson::array>();
+      for (auto c: value_copy) {
+        value_byte_array.push_back(picojson::value{static_cast<double>(c)});
+      }
+      const auto callback_name =
+          rw_callback_data->callback_names_map_[std::make_pair(gatt_handle, kWriteCallback)];
+      LoggerD("Firing write value request event: %s: %s", callback_name.c_str(),
+              write_value_request.serialize().c_str());
+      rw_callback_data->instance_.FireEvent(callback_name, write_value_request);
+    });
+  };
+
+  auto ret = bt_gatt_server_set_write_value_requested_cb(gatt_handle, write_value_request_callback,
+                                                         &rw_request_callback_data_);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("bt_gatt_server_set_write_value_requested_cb() failed: %d (%s)", ret,
+            get_error_message(ret));
+    return BluetoothErrorToPlatformResult(ret, "Failed to set write value request callback");
+  }
+
+  LoggerD("bt_gatt_server_set_write_value_requested_cb(): SUCCESS");
+  return common::PlatformResult{};
+}
+
+PlatformResult BluetoothGATTServerService::SendResponse(const picojson::value& args) {
+  ScopeLogger("Response: %s", args.serialize().c_str());
+
+  auto _id = static_cast<int>(args.get(kId).get<double>());
   auto request_id = static_cast<int>(args.get(kRequestId).get<double>());
   auto request_type_str = args.get(kRequestType).get<std::string>();
   auto request_type = (request_type_str == kReadRequestType) ? BT_GATT_REQUEST_TYPE_READ
@@ -508,7 +576,7 @@ PlatformResult BluetoothGATTServerService::SendResponse(const picojson::value& a
 
   LoggerD(
       "Sending response: _id: %d, requestId: %d, requestType: %s, offset: %d, statusCode: %d, "
-      "value: [%s], value size: %d",
+      "value: [%p], value size: %d",
       _id, request_id, request_type_str.c_str(), offset, status_code, value.get(), value_size);
 
   auto ret = bt_gatt_server_send_response(request_id, request_type, offset, status_code,
index e53916b..9321d1c 100644 (file)
@@ -44,17 +44,18 @@ class BluetoothGATTServerService {
                                  const std::vector<std::pair<int, bt_gatt_h>>& new_gatt_objects);
   PlatformResult UnregisterService(const picojson::value& args, bt_gatt_server_h server);
   PlatformResult SetReadValueRequestCallback(const picojson::value& args);
+  PlatformResult SetWriteValueRequestCallback(const picojson::value& args);
   PlatformResult SendResponse(const picojson::value& args);
 
  private:
   std::map<int, bt_gatt_h> gatt_objects_;
-  std::map<bt_gatt_h, std::string> callback_names_;
+  std::map<std::pair<bt_gatt_h, std::string>, std::string> callback_names_;
 
   BluetoothInstance& instance_;
 
   struct ReadWriteRequestCallbackData {
     BluetoothInstance& instance_;
-    std::map<bt_gatt_h, std::string>& callback_names_map_;
+    std::map<std::pair<bt_gatt_h, std::string>, std::string>& callback_names_map_;
   } rw_request_callback_data_;
 
   static bool DestroyService(int total, int index, bt_gatt_h handle, void* user_data);
index 1366972..ad0e2b0 100644 (file)
@@ -113,6 +113,7 @@ BluetoothInstance::BluetoothInstance()
   REGISTER_METHOD(BluetoothGATTServerRegisterService);
   REGISTER_METHOD(BluetoothGATTServerUnregisterService);
   REGISTER_METHOD(BluetoothGATTServerSetReadValueRequestCallback);
+  REGISTER_METHOD(BluetoothGATTServerSetWriteValueRequestCallback);
   REGISTER_METHOD(BluetoothGATTServerSendResponse);
 
 #undef REGISTER_METHOD
@@ -605,6 +606,24 @@ void BluetoothInstance::BluetoothGATTServerSetReadValueRequestCallback(const pic
   ReportSuccess(out);
 }
 
+void BluetoothInstance::BluetoothGATTServerSetWriteValueRequestCallback(const picojson::value& args,
+                                                                        picojson::object& out) {
+  ScopeLogger();
+  CHECK_PRIVILEGE_ACCESS(kPrivilegeBluetooth, &out);
+
+  worker.add_job([this, args] {
+    ScopeLogger("Async call: BluetoothGATTServerSetWriteValueRequestCallback");
+    picojson::value response = picojson::value(picojson::object());
+    picojson::object& async_out = response.get<picojson::object>();
+    double callback_id = args.get("callbackId").get<double>();
+    async_out["callbackId"] = picojson::value(callback_id);
+    this->bluetooth_gatt_server_.SetWriteValueRequestCallback(args, async_out);
+    this->PostMessage(response.serialize().c_str());
+  });
+
+  ReportSuccess(out);
+}
+
 void BluetoothInstance::BluetoothGATTServerSendResponse(const picojson::value& args,
                                                         picojson::object& out) {
   ScopeLogger();
index 9dabeaf..a7043b2 100644 (file)
@@ -126,6 +126,8 @@ class BluetoothInstance : public common::ParsedInstance {
   void BluetoothGATTServerUnregisterService(const picojson::value& args, picojson::object& out);
   void BluetoothGATTServerSetReadValueRequestCallback(const picojson::value& args,
                                                       picojson::object& out);
+  void BluetoothGATTServerSetWriteValueRequestCallback(const picojson::value& args,
+                                                       picojson::object& out);
   void BluetoothGATTServerSendResponse(const picojson::value& args, picojson::object& out);
 
   BluetoothAdapter bluetooth_adapter_;