[Bluetooth][Pre-6.0] Fix BluetoothLEDevice::getServiceAllUuids() 58/244458/1
authorPawel Wasowski <p.wasowski2@samsung.com>
Wed, 5 Feb 2020 13:00:47 +0000 (14:00 +0100)
committerPiotr Kosko/Native/Web API (PLT) /SRPOL/Engineer/Samsung Electronics <p.kosko@samsung.com>
Fri, 18 Sep 2020 11:59:41 +0000 (13:59 +0200)
[Bug] When some remote device advertises non-standard UUID, it was invalidly
translated to 16bit UUID. Which caused "NotFoundError: Service not found" error
in case of reading this service.

Inlcudes:
* fix for BluetoothLEDevice::getServiceAllUuids based on 6.0 implementation
* moving toLowerCase() from messaging to common module (needed for a fix)

[Verification]
Auto TCT 100% pass.

Tested in console:

Precondition:
Remote device need to have a GATT server advertising some non-standard UUID service, e.g.
"12341234-1234-1234-1234-123412341234". Below code should work if your remote device "MyDevice"
has proper service with a characteristic.

Last result will be something like "Value read from 1 characteristic -> 116,101,115,116"

var testDeviceName = "MyDevice";
var nonStandardUUID = "12341234-1234-1234-1234-123412341234";

var device;
function testBle(name) {
    var adapter = tizen.bluetooth.getLEAdapter();

    adapter.startScan(function (device_) {
        device = device_;
        //Item one occurs here:
        if (device.name)
            console.log("Found: " + JSON.stringify(device))
        if (device.name === name) {
            console.log("[Found device] address: " + JSON.stringify(device));
            adapter.stopScan();

            device.connect(function () {
                console.log("[Connected]");
                uuids = device.getServiceAllUuids();
                console.log("all UUIDS\n" + JSON.stringify(uuids))

                if (uuids.includes(nonStandardUUID)) {
                    console.log("Reading: " + nonStandardUUID);
                    var service = device.getService(nonStandardUUID);
                    if (service.characteristics.length > 0) {
                        for (j = 0; j < service.characteristics.length; ++j) {
                            var characteristic = service.characteristics[j];
                            characteristic.readValue(function (val) {
                                console.log("Value read from " + j + " characteristic -> " + val);
                            });
                        }
                    } else {
                        console.log("no characteristics found for service: " + uuid)
                    }
                } else {
                    console.error("UUID " + nonStandardUUID + " not found ");
                }
            }, (e) => console.log(e));
        }
    }, (e) => console.log(e))
}
testBle(testDeviceName)

Change-Id: I38494b6b5037cf06ee7c4bc32d2f8f5e8e6ce97e

src/bluetooth/bluetooth.gyp
src/bluetooth/bluetooth_gatt_service.cc
src/bluetooth/uuid.cc [new file with mode: 0644]
src/bluetooth/uuid.h [new file with mode: 0644]
src/common/tools.cc
src/common/tools.h

index 72eb206..9ccacdc 100644 (file)
@@ -41,6 +41,8 @@
         'bluetooth_util.h',
         'bluetooth_le_device.cc',
         'bluetooth_le_device.h',
+        'uuid.cc',
+        'uuid.h',
       ],
       'includes': [
         '../common/pkg-config.gypi',
index 1200a7b..0be2aa4 100644 (file)
@@ -27,6 +27,7 @@
 #include "bluetooth/bluetooth_instance.h"
 #include "bluetooth/bluetooth_privilege.h"
 #include "bluetooth/bluetooth_util.h"
+#include "bluetooth/uuid.h"
 
 namespace extension {
 namespace bluetooth {
@@ -535,6 +536,7 @@ void BluetoothGATTService::RemoveValueChangeListener(const picojson::value& args
   }
 }
 
+/// copied from 6.0
 common::PlatformResult BluetoothGATTService::GetServiceAllUuids(const std::string& address,
                                                                 picojson::array* array) {
   ScopeLogger();
@@ -549,18 +551,23 @@ common::PlatformResult BluetoothGATTService::GetServiceAllUuids(const std::strin
     ScopeLogger("Entered into asynchronous function, foreach_callback, total: %d, index: %d", total,
                 index);
 
-    char* uuid = nullptr;
-    int ret = bt_gatt_get_uuid(gatt_handle, &uuid);
-
-    if (BT_ERROR_NONE != ret || nullptr == uuid) {
-      LoggerE("Failed to get UUID: %d", ret);
+    auto& uuids = *static_cast<picojson::array*>(user_data);
+    auto uuid = UUID::createFromGatt(gatt_handle);
+    if (uuid) {
+      /*
+       * BACKWARD COMPATIBILITY
+       *
+       * In the past, this function has always trimmed UUIDs retrieved
+       * from native API to 16 bit format. UUIDs that were not created
+       * from 16 bit UUID and BASE_UUID were converted to invalid values.
+       *
+       * We return UUIDs in shortest possible format to comply with past
+       * behaviour when possible. If the UUID is not convertible
+       * to 16 bit UUID, we return a longer form.
+       */
+      uuids.push_back(picojson::value{uuid->ShortestPossibleFormat()});
     } else {
-      std::string u = std::string(uuid);
-      free(uuid);
-      if (u.length() > 4) {  // 128-bit UUID, needs to be converted to 16-bit
-        u = u.substr(4, 4);
-      }
-      static_cast<picojson::array*>(user_data)->push_back(picojson::value(u));
+      LoggerE("Couldn't get UUID from bt_gatt_h");
     }
 
     return true;
diff --git a/src/bluetooth/uuid.cc b/src/bluetooth/uuid.cc
new file mode 100644 (file)
index 0000000..619fb02
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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 <regex>
+#include <string>
+
+#include "bluetooth_util.h"
+#include "common/scope_exit.h"
+#include "common/tools.h"
+#include "uuid.h"
+
+using common::PlatformResult;
+using common::ErrorCode;
+
+namespace {
+
+const std::regex kCanonical128BitFormat{
+    "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"};
+const std::regex k16BitFormat{"[0-9a-fA-F]{4}"};
+const std::regex k32BitFormat{"[0-9a-fA-F]{8}"};
+const std::regex kConvertibleTo16Bit{"0000[0-9a-fA-F]{4}-0000-1000-8000-00805[fF]9[bB]34[fF][bB]"};
+const std::regex kConvertibleTo32Bit{"[0-9a-fA-F]{8}-0000-1000-8000-00805[fF]9[bB]34[fF][bB]"};
+const std::string kBaseUuidLast96Bits = "-0000-1000-8000-00805f9b34fb";
+const optional<extension::bluetooth::UUID> NO_UUID{};
+const optional<std::string> NO_UUID_STR{};
+
+const int k16BitUUIDBegin = 4;
+const int k16BitUUIDLen = 4;
+const int k32BitUUIDBegin = 0;
+const int k32BitUUIDLen = 8;
+
+};  // unnamed namespace
+
+namespace extension {
+
+namespace bluetooth {
+
+UUID::UUID(const std::string& uuid_in_source_format_str, std::string&& uuid_128_bit_str)
+    : uuid_in_source_format{uuid_in_source_format_str}, uuid_128_bit{std::move(uuid_128_bit_str)} {
+  ScopeLogger("UUID in source format: %s; 128 bit UUID: %s", uuid_in_source_format.c_str(),
+              uuid_128_bit.c_str());
+}
+
+optional<UUID> UUID::create(const std::string& uuid) {
+  ScopeLogger("Input: %s", uuid.c_str());
+
+  auto uuid_128_bit = To128Bit(uuid);
+  if (uuid_128_bit.empty()) {
+    return NO_UUID;
+  }
+
+  return UUID{uuid, std::move(uuid_128_bit)};
+}
+
+optional<UUID> UUID::createFromGatt(const bt_gatt_h gatt_handle) {
+  ScopeLogger("Input: <a bt_gatt_h>");
+
+  char* uuid_buffer{nullptr};
+  SCOPE_EXIT {
+    free(uuid_buffer);
+  };
+
+  auto ret = bt_gatt_get_uuid(gatt_handle, &uuid_buffer);
+  if (BT_ERROR_NONE != ret) {
+    LoggerE("Couldn't get UUID. Error: %s", util::GetBluetoothErrorMessage(ret).c_str());
+    return NO_UUID;
+  }
+
+  if (!uuid_buffer) {
+    LoggerE("Couldn't get UUID. Returned uuid is a nullptr");
+    return NO_UUID;
+  }
+
+  LoggerD("uuid_buffer: %s", uuid_buffer);
+
+  return {create(std::string{uuid_buffer})};
+}
+
+optional<std::string> UUID::To16Bit() const {
+  ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str());
+
+  if (std::regex_match(uuid_128_bit, kConvertibleTo16Bit)) {
+    return {uuid_128_bit.substr(k16BitUUIDBegin, k16BitUUIDLen)};
+  }
+
+  return NO_UUID_STR;
+}
+
+optional<std::string> UUID::To32Bit() const {
+  ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str());
+
+  if (std::regex_match(uuid_128_bit, kConvertibleTo32Bit)) {
+    return {uuid_128_bit.substr(k32BitUUIDBegin, k32BitUUIDLen)};
+  }
+
+  return NO_UUID_STR;
+}
+
+std::string UUID::ShortestPossibleFormat() const {
+  ScopeLogger("128 bit UUID: %s", uuid_128_bit.c_str());
+
+  if (std::regex_match(uuid_128_bit, kConvertibleTo16Bit)) {
+    return {uuid_128_bit.substr(k16BitUUIDBegin, k16BitUUIDLen)};
+  }
+
+  if (std::regex_match(uuid_128_bit, kConvertibleTo32Bit)) {
+    return {uuid_128_bit.substr(k32BitUUIDBegin, k32BitUUIDLen)};
+  }
+
+  return uuid_128_bit;
+}
+
+bool UUID::operator==(const UUID& other) const {
+  return uuid_128_bit == other.uuid_128_bit;
+}
+
+UUID::Format UUID::GetFormat(const std::string& uuid) {
+  ScopeLogger("uuid: %s", uuid.c_str());
+
+  if (std::regex_match(uuid, k16BitFormat)) {
+    return Format::UUID_16_BIT;
+  } else if (std::regex_match(uuid, k32BitFormat)) {
+    return Format::UUID_32_BIT;
+  } else if (std::regex_match(uuid, kCanonical128BitFormat)) {
+    return Format::UUID_128_BIT;
+  }
+
+  return Format::UUID_INVALID;
+}
+
+std::string UUID::To128Bit(const std::string& uuid) {
+  ScopeLogger("uuid: %s", uuid.c_str());
+
+  auto uuid_lower_case = common::tools::ConvertToLowerCase(uuid);
+  switch (GetFormat(uuid_lower_case)) {
+    case Format::UUID_16_BIT:
+      return "0000" + uuid_lower_case + kBaseUuidLast96Bits;
+    case Format::UUID_32_BIT:
+      return uuid_lower_case + kBaseUuidLast96Bits;
+    case Format::UUID_128_BIT:
+      return uuid_lower_case;
+    default:
+      LoggerE("Error: uuid format unknown");
+      return "";
+  }
+}
+
+}  // bluetooth
+}  // extension
diff --git a/src/bluetooth/uuid.h b/src/bluetooth/uuid.h
new file mode 100644 (file)
index 0000000..35f30d9
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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 UUID_H_
+#define UUID_H_
+
+#include <string>
+
+#include <bluetooth.h>
+
+#include "common/platform_result.h"
+#if __cplusplus > 201402L
+#include <optional>
+using std::optional;
+#else
+#include "common/optional.h"
+using common::optional;
+#endif
+
+using common::PlatformResult;
+
+namespace extension {
+
+namespace bluetooth {
+
+struct UUID {
+  /*
+   * A UUID object is only created, if the string containing it
+   * is in a proper format or it can be created from the bt_gatt_h.
+   *
+   * create() functions accept the following formats of strings:
+   * 1. 4 hexadecimal digits, e.g.: "018D"
+   * 2. 8 hexadecimal digits, e.g.: "018Dab17"
+   * 3. 128 bit canonical UUID format defined in
+   * https://tools.ietf.org/html/rfc4122, e.g.:
+   * "f95c593e-efd4-43c4-96e4-97186ee52f83"
+   */
+  static optional<UUID> create(const std::string&);
+
+  /*
+   * This function does not validate passed bt_gatt_h.
+   * If the bt_gatt_h is not initialized, the function will likely crash.
+   *
+   * This function was purposedly not made an overload of create().
+   * If it was an overload, (const) char* could be implicitly converted to
+   * const void* (instead of std::string), which is the underlying type of
+   * bt_gatt_h.
+   */
+  static optional<UUID> createFromGatt(const bt_gatt_h);
+
+  UUID() = delete;
+  UUID(const UUID& uuid) = default;
+  UUID(UUID&& uuid) = default;
+
+  /*
+   * Only UUIDs built with kBaseUUID are convertible to 32 bit format
+   * and only a subset of them are convertible to 16 bit format.
+   *
+   * These functions always return hexadecimal lower case letters as digits.
+   */
+  optional<std::string> To16Bit() const;
+  optional<std::string> To32Bit() const;
+
+  /*
+   * This function checks if the UUID is built with kBaseUUID and tries to
+   * return the UUID in different formats in the following order:
+   * 1. 16 bit
+   * 2. 32 bit
+   * 3. 128 bit
+   */
+  std::string ShortestPossibleFormat() const;
+
+  bool operator==(const UUID&) const;
+
+  /*
+   * Some legacy functions, i.e. implementation of BluetoothLEDevice::getService(),
+   * work in the following manner:
+   * 1. the user passes them a UUID, in any acceptable format, e.g.: "aBcD"
+   * 2. the function performs requested operations using the passed UUID
+   * 3. the function creates an object, e.g. BluetoothGATTClientService, with the UUID
+   *    passed by the user.
+   *
+   * As user may expect an identical UUID in the created object, identical to
+   * the one he passed to the function, we need to store the original format.
+   */
+  const std::string uuid_in_source_format;
+  /*
+   * Hexadecimal letter digits are always stored in lower case.
+   */
+  const std::string uuid_128_bit;
+
+ private:
+  /*
+   * These functions don't validate passed arguments.
+   */
+  UUID(const std::string& uuid_in_source_format, std::string&& uuid_128_bit);
+  static std::string To128Bit(const std::string& uuid);
+
+  enum class Format : int { UUID_16_BIT, UUID_32_BIT, UUID_128_BIT, UUID_INVALID };
+
+  static Format GetFormat(const std::string& uuid);
+};
+
+}  // bluetooth
+}  // extension
+
+#endif  // UUID_H_
index 6aa2896..27dcdec 100644 (file)
@@ -530,5 +530,11 @@ PlatformResult CheckFileAvailability(const std::string& path) {
   return CheckFileStatus(path);
 }
 
+std::string ConvertToLowerCase(const std::string& input_string) {
+  std::string output_string = input_string;
+  std::transform(output_string.begin(), output_string.end(), output_string.begin(), ::tolower);
+  return output_string;
+}
+
 }  // namespace tools
 }  // namespace common
index d72a09b..ee58b00 100644 (file)
@@ -91,6 +91,8 @@ PlatformResult CheckFileStatus(const std::string& path);
 
 PlatformResult CheckFileAvailability(const std::string& path);
 
+std::string ConvertToLowerCase(const std::string& input_string);
+
 }  // namespace tools
 }  // namespace common