[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
'bluetooth_util.h',
'bluetooth_le_device.cc',
'bluetooth_le_device.h',
+ 'uuid.cc',
+ 'uuid.h',
],
'includes': [
'../common/pkg-config.gypi',
#include "bluetooth/bluetooth_instance.h"
#include "bluetooth/bluetooth_privilege.h"
#include "bluetooth/bluetooth_util.h"
+#include "bluetooth/uuid.h"
namespace extension {
namespace bluetooth {
}
}
+/// copied from 6.0
common::PlatformResult BluetoothGATTService::GetServiceAllUuids(const std::string& address,
picojson::array* array) {
ScopeLogger();
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;
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_
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
PlatformResult CheckFileAvailability(const std::string& path);
+std::string ConvertToLowerCase(const std::string& input_string);
+
} // namespace tools
} // namespace common