From: Pawel Wasowski Date: Wed, 5 Feb 2020 13:00:47 +0000 (+0100) Subject: [Bluetooth][Pre-6.0] Fix BluetoothLEDevice::getServiceAllUuids() X-Git-Tag: submit/tizen/20200923.101308~1^2~1^2^2^2~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fchanges%2F58%2F244458%2F1;p=platform%2Fcore%2Fapi%2Fwebapi-plugins.git [Bluetooth][Pre-6.0] Fix BluetoothLEDevice::getServiceAllUuids() [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 --- diff --git a/src/bluetooth/bluetooth.gyp b/src/bluetooth/bluetooth.gyp index 72eb2068..9ccacdc7 100644 --- a/src/bluetooth/bluetooth.gyp +++ b/src/bluetooth/bluetooth.gyp @@ -41,6 +41,8 @@ 'bluetooth_util.h', 'bluetooth_le_device.cc', 'bluetooth_le_device.h', + 'uuid.cc', + 'uuid.h', ], 'includes': [ '../common/pkg-config.gypi', diff --git a/src/bluetooth/bluetooth_gatt_service.cc b/src/bluetooth/bluetooth_gatt_service.cc index 1200a7b1..0be2aa42 100644 --- a/src/bluetooth/bluetooth_gatt_service.cc +++ b/src/bluetooth/bluetooth_gatt_service.cc @@ -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(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(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 index 00000000..619fb020 --- /dev/null +++ b/src/bluetooth/uuid.cc @@ -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 +#include + +#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 NO_UUID{}; +const optional 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::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::createFromGatt(const bt_gatt_h gatt_handle) { + ScopeLogger("Input: "); + + 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 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 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 index 00000000..35f30d9b --- /dev/null +++ b/src/bluetooth/uuid.h @@ -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 + +#include + +#include "common/platform_result.h" +#if __cplusplus > 201402L +#include +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 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 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 To16Bit() const; + optional 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_ diff --git a/src/common/tools.cc b/src/common/tools.cc index 6aa28968..27dcdec7 100644 --- a/src/common/tools.cc +++ b/src/common/tools.cc @@ -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 diff --git a/src/common/tools.h b/src/common/tools.h index d72a09b8..ee58b00b 100644 --- a/src/common/tools.h +++ b/src/common/tools.h @@ -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