blob: 92b0a90e95bf6d01959d2d12e2e13b15fdf8d54b [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bluetooth/newblued/util.h"
#include <algorithm>
#include <cstdint>
#include <regex> // NOLINT(build/c++11)
#include <string>
#include <utility>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <newblue/bt.h>
#include <newblue/uuid.h>
#include "bluetooth/newblued/uuid.h"
namespace {
uint64_t GetNumFromLE(const uint8_t* buf, uint8_t bits) {
uint64_t val = 0;
uint8_t bytes = bits / 8;
CHECK(buf);
buf += bytes;
while (bytes--)
val = (val << 8) | *--buf;
return val;
}
} // namespace
namespace bluetooth {
////////////////////////////////////////////////////////////////////////////////
// Miscellaneous utility functions
////////////////////////////////////////////////////////////////////////////////
uint16_t GetNumFromLE16(const uint8_t* buf) {
return static_cast<uint16_t>(GetNumFromLE(buf, 16));
}
uint32_t GetNumFromLE24(const uint8_t* buf) {
return static_cast<uint32_t>(GetNumFromLE(buf, 24));
}
std::vector<uint8_t> GetBytesFromLE(const uint8_t* buf, size_t buf_len) {
std::vector<uint8_t> ret;
CHECK(buf);
if (!buf_len)
return ret;
ret.assign(buf, buf + buf_len);
std::reverse(ret.begin(), ret.end());
return ret;
}
UniqueId GetNextId() {
static UniqueId next_id = 1;
uint64_t id = next_id++;
if (id)
return id;
next_id -= 1;
LOG(ERROR) << "Run out of unique IDs";
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// Parsing Discovered Device Information
////////////////////////////////////////////////////////////////////////////////
std::string ConvertAppearanceToIcon(uint16_t appearance) {
// These value are defined at https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml.
// The translated strings come from BlueZ.
switch ((appearance & kAppearanceMask) >> 6) {
case 0x00:
return "unknown";
case 0x01:
return "phone";
case 0x02:
return "computer";
case 0x03:
return "watch";
case 0x04:
return "clock";
case 0x05:
return "video-display";
case 0x06:
return "remote-control";
case 0x07:
return "eye-glasses";
case 0x08:
return "tag";
case 0x09:
return "key-ring";
case 0x0a:
return "multimedia-player";
case 0x0b:
return "scanner";
case 0x0c:
return "thermometer";
case 0x0d:
return "heart-rate-sensor";
case 0x0e:
return "blood-pressure";
case 0x0f: // HID Generic
switch (appearance & 0x3f) {
case 0x01:
return "input-keyboard";
case 0x02:
return "input-mouse";
case 0x03:
case 0x04:
return "input-gaming";
case 0x05:
return "input-tablet";
case 0x08:
return "scanner";
}
break;
case 0x10:
return "glucose-meter";
case 0x11:
return "running-walking-sensor";
case 0x12:
return "cycling";
case 0x31:
return "pulse-oximeter";
case 0x32:
return "weight-scale";
case 0x33:
return "personal-mobility-device";
case 0x34:
return "continuous-glucose-monitor";
case 0x35:
return "insulin-pump";
case 0x36:
return "medication-delivery";
case 0x51:
return "outdoor-sports-activity";
default:
break;
}
return std::string();
}
std::string ConvertToAsciiString(std::string name) {
/* Replace all non-ASCII characters with spaces */
for (auto& ch : name) {
if (!isascii(ch))
ch = ' ';
}
return name;
}
std::map<uint16_t, std::vector<uint8_t>> ParseDataIntoManufacturer(
uint16_t manufacturer_id, std::vector<uint8_t> manufacturer_data) {
std::map<uint16_t, std::vector<uint8_t>> manufacturer;
manufacturer.emplace(manufacturer_id, std::move(manufacturer_data));
return manufacturer;
}
static bool IsUuidSizeValid(const uint8_t uuid_size) {
static constexpr uint8_t validUuidSizes[] = {kUuid16Size, kUuid32Size,
kUuid128Size};
for (const auto& validUuidSize : validUuidSizes) {
if (validUuidSize == uuid_size)
return true;
}
return false;
}
void ParseDataIntoUuids(std::set<Uuid>* service_uuids,
const uint8_t uuid_size,
const uint8_t* data,
const uint8_t data_len) {
CHECK(service_uuids && data && IsUuidSizeValid(uuid_size));
if (!data_len || data_len % uuid_size != 0) {
LOG(WARNING) << "Failed to parse EIR service UUIDs";
return;
}
// Service UUIDs are presented in little-endian order.
for (uint8_t i = 0; i < data_len; i += uuid_size) {
Uuid uuid(GetBytesFromLE(data + i, uuid_size));
CHECK(uuid.format() != UuidFormat::UUID_INVALID);
service_uuids->insert(uuid);
}
}
void ParseDataIntoServiceData(
std::map<Uuid, std::vector<uint8_t>>* service_data,
const uint8_t uuid_size,
const uint8_t* data,
const uint8_t data_len) {
CHECK(service_data && data && IsUuidSizeValid(uuid_size));
if (!data_len || data_len <= uuid_size) {
LOG(WARNING) << "Failed to parse EIR service data";
return;
}
// A service UUID and its data are presented in little-endian order where the
// format is {<bytes of service UUID>, <bytes of service data>}. For instance,
// the service data associated with the battery service can be
// {0x0F, 0x18, 0x22, 0x11}
// where {0x18 0x0F} is the UUID and {0x11, 0x22} is the data.
Uuid uuid(GetBytesFromLE(data, uuid_size));
CHECK_NE(UuidFormat::UUID_INVALID, uuid.format());
service_data->emplace(uuid,
GetBytesFromLE(data + uuid_size, data_len - uuid_size));
}
////////////////////////////////////////////////////////////////////////////////
// Translation between D-Bus object path and newblued types.
////////////////////////////////////////////////////////////////////////////////
bool TrimAdapterFromObjectPath(std::string* path) {
std::regex rgx("^/org/bluez/hci[0-9]+$");
std::smatch match;
if (!std::regex_search(*path, match, rgx) || match.size() != 1)
return false;
path->clear();
return true;
}
std::string TrimDeviceFromObjectPath(std::string* device) {
std::regex rgx("/dev_([0-9a-fA-F]{2}_){5}[0-9a-fA-F]{2}$");
std::smatch m;
std::string address;
if (!std::regex_search(*device, m, rgx) || m.empty())
return "";
address = m.str(0).substr(5);
std::replace(address.begin(), address.end(), '_', ':');
*device = device->substr(0, device->size() - m.str(0).size());
return address;
}
int32_t TrimServiceFromObjectPath(std::string* service) {
std::regex rgx("/service[0-9a-fA-F]{4}$");
std::smatch m;
std::string srv;
if (!std::regex_search(*service, m, rgx) || m.empty())
return kInvalidServiceHandle;
srv = m.str(0).substr(8, 4);
*service = service->substr(0, service->size() - m.str(0).size());
return std::stol(srv, nullptr, 16);
}
int32_t TrimCharacteristicFromObjectPath(std::string* characteristic) {
std::regex rgx("/char[0-9a-fA-F]{4}$");
std::smatch m;
std::string charac;
if (!std::regex_search(*characteristic, m, rgx) || m.empty())
return kInvalidCharacteristicHandle;
charac = m.str(0).substr(5, 4);
*characteristic =
characteristic->substr(0, characteristic->size() - m.str(0).size());
return std::stol(charac, nullptr, 16);
}
int32_t TrimDescriptorFromObjectPath(std::string* descriptor) {
std::regex rgx("/desc[0-9a-fA-F]{4}$");
std::smatch m;
std::string desc;
if (!std::regex_search(*descriptor, m, rgx) || m.empty())
return kInvalidDescriptorHandle;
desc = m.str(0).substr(5);
*descriptor = descriptor->substr(0, descriptor->size() - m.str(0).size());
return std::stol(desc, nullptr, 16);
}
std::string ConvertDeviceObjectPathToAddress(const std::string& path) {
std::string p(path);
std::string address = TrimDeviceFromObjectPath(&p);
if (address.empty() || p.empty())
return "";
if (!TrimAdapterFromObjectPath(&p))
return "";
return address;
}
dbus::ObjectPath ConvertDeviceAddressToObjectPath(const std::string& address) {
std::string path;
if (address.empty())
return dbus::ObjectPath("");
path = base::StringPrintf("%s/dev_%s", kAdapterObjectPath, address.c_str());
std::replace(path.begin(), path.end(), ':', '_');
return dbus::ObjectPath(path);
}
bool ConvertServiceObjectPathToHandle(std::string* address,
uint16_t* handle,
const std::string& path) {
std::string p(path);
std::string addr;
int32_t h = TrimServiceFromObjectPath(&p);
if (h == kInvalidServiceHandle || p.empty())
return false;
addr = ConvertDeviceObjectPathToAddress(p);
if (addr.empty())
return false;
*address = addr;
*handle = h;
return true;
}
dbus::ObjectPath ConvertServiceHandleToObjectPath(const std::string& address,
uint16_t handle) {
std::string dev = ConvertDeviceAddressToObjectPath(address).value();
std::string s = base::StringPrintf("/service%04X", handle);
if (dev.empty() || s.empty())
return dbus::ObjectPath("");
return dbus::ObjectPath(dev + s);
}
bool ConvertCharacteristicObjectPathToHandles(std::string* address,
uint16_t* service_handle,
uint16_t* char_handle,
const std::string& path) {
std::string p(path);
std::string addr;
uint16_t sh;
int32_t ch = TrimCharacteristicFromObjectPath(&p);
if (ch == kInvalidCharacteristicHandle || p.empty())
return false;
if (!ConvertServiceObjectPathToHandle(&addr, &sh, p))
return false;
*address = addr;
*service_handle = sh;
*char_handle = ch;
return true;
}
dbus::ObjectPath ConvertCharacteristicHandleToObjectPath(
const std::string& address, uint16_t service_handle, uint16_t char_handle) {
std::string s =
ConvertServiceHandleToObjectPath(address, service_handle).value();
std::string c = base::StringPrintf("/char%04X", char_handle);
if (s.empty() || c.empty())
return dbus::ObjectPath("");
return dbus::ObjectPath(s + c);
}
bool ConvertDescriptorObjectPathToHandles(std::string* address,
uint16_t* service_handle,
uint16_t* char_handle,
uint16_t* desc_handle,
const std::string& path) {
std::string p(path);
std::string addr;
uint16_t sh;
uint16_t ch;
int32_t dh = TrimDescriptorFromObjectPath(&p);
if (dh == kInvalidDescriptorHandle || p.empty())
return false;
if (!ConvertCharacteristicObjectPathToHandles(&addr, &sh, &ch, p))
return false;
*address = addr;
*service_handle = sh;
*char_handle = ch;
*desc_handle = dh;
return true;
}
dbus::ObjectPath ConvertDescriptorHandleToObjectPath(const std::string& address,
uint16_t service_handle,
uint16_t char_handle,
uint16_t desc_handle) {
std::string c = ConvertCharacteristicHandleToObjectPath(
address, service_handle, char_handle)
.value();
std::string d = base::StringPrintf("/desc%04X", desc_handle);
if (c.empty() || d.empty())
return dbus::ObjectPath("");
return dbus::ObjectPath(c + d);
}
////////////////////////////////////////////////////////////////////////////////
// Translation between libnewblue types and newblued types.
////////////////////////////////////////////////////////////////////////////////
bool ConvertToBtAddr(bool is_random_address,
const std::string& address,
struct bt_addr* result) {
CHECK(result);
std::vector<std::string> tokens = base::SplitString(
address, ":", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (tokens.size() != BT_MAC_LEN)
return false;
uint8_t addr[BT_MAC_LEN];
uint8_t* ptr = addr + BT_MAC_LEN;
for (const auto& token : tokens) {
uint32_t value;
if (token.size() != 2 || !base::HexStringToUInt(token, &value))
return false;
*(--ptr) = static_cast<uint8_t>(value);
}
memcpy(result->addr, addr, BT_MAC_LEN);
result->type =
is_random_address ? BT_ADDR_TYPE_LE_RANDOM : BT_ADDR_TYPE_LE_PUBLIC;
return true;
}
std::string ConvertBtAddrToString(const struct bt_addr& addr) {
return base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X", addr.addr[5],
addr.addr[4], addr.addr[3], addr.addr[2],
addr.addr[1], addr.addr[0]);
}
Uuid ConvertToUuid(const struct uuid& uuid) {
std::vector<uint8_t> uuid_value;
uint64_t lo = uuid.lo;
uint64_t hi = uuid.hi;
int i;
for (i = 0; i < sizeof(lo); ++i, lo >>= 8)
uuid_value.emplace(uuid_value.begin(), static_cast<uint8_t>(lo));
for (i = 0; i < sizeof(hi); ++i, hi >>= 8)
uuid_value.emplace(uuid_value.begin(), static_cast<uint8_t>(hi));
return Uuid(uuid_value);
}
struct uuid ConvertToRawUuid(const Uuid& uuid) {
struct uuid u;
uuidZero(&u);
if (uuid.format() == UuidFormat::UUID_INVALID)
return u;
uint64_t tmp;
memcpy(&tmp, &uuid.value()[0], sizeof(uint64_t));
u.hi = be64toh(tmp);
memcpy(&tmp, &uuid.value()[8], sizeof(uint64_t));
u.lo = be64toh(tmp);
return u;
}
std::unique_ptr<GattService> ConvertToGattService(
const struct GattTraversedService& service) {
// struct GattTraversedService is the result of primary service traversal, so
// it is safe to assume that primary is always true in this case.
auto s = std::make_unique<GattService>(service.firstHandle,
service.lastHandle, true /* primary */,
ConvertToUuid(service.uuid));
const auto* included_service = service.inclSvcs;
for (int i = 0; i < service.numInclSvcs; ++i, ++included_service) {
auto is = std::make_unique<GattIncludedService>(
s.get(), included_service->includeDefHandle,
included_service->firstHandle, included_service->lastHandle,
ConvertToUuid(included_service->uuid));
s->AddIncludedService(std::move(is));
}
const auto* characteristic = service.chars;
for (int i = 0; i < service.numChars; ++i, ++characteristic) {
auto c = std::make_unique<GattCharacteristic>(
s.get(), characteristic->valHandle, characteristic->firstHandle,
characteristic->lastHandle, characteristic->charProps,
ConvertToUuid(characteristic->uuid));
const auto* descriptor = characteristic->descrs;
for (int j = 0; j < characteristic->numDescrs; ++j, ++descriptor) {
auto d = std::make_unique<GattDescriptor>(
c.get(), descriptor->handle, ConvertToUuid(descriptor->uuid));
c->AddDescriptor(std::move(d));
}
s->AddCharacteristic(std::move(c));
}
return s;
}
std::vector<uint8_t> GetBytesFromSg(const sg data) {
if (data == nullptr)
return {};
uint32_t data_length = sgLength(data);
if (data_length == 0)
return {};
std::vector<uint8_t> d(data_length);
if (!sgSerializeCutFront(data, d.data(), d.size())) {
LOG(WARNING) << "Failed to extract bytes from sg";
return {};
}
return d;
}
void ParseEir(DeviceInfo* device_info, const std::vector<uint8_t>& eir) {
CHECK(device_info);
uint8_t pos = 0;
std::set<Uuid> service_uuids;
std::map<Uuid, std::vector<uint8_t>> service_data;
while (pos + 1 < eir.size()) {
uint8_t field_len = eir[pos];
// End of EIR
if (field_len == 0)
break;
// Corrupt EIR data
if (pos + field_len >= eir.size())
break;
EirType eir_type = static_cast<EirType>(eir[pos + 1]);
const uint8_t* data = &eir[pos + 2];
// A field consists of 1 byte field type + data:
// | 1-byte field_len | 1-byte type | (field length - 1) bytes data ... |
uint8_t data_len = field_len - 1;
switch (eir_type) {
case EirType::FLAGS:
// No default value should be set for flags according to Supplement to
// the Bluetooth Core Specification. Flags field can be 0 or more octets
// long. If the length is 1, then flags[0] is octet[0]. Store only
// octet[0] for now due to lack of definition of the following octets
// in Supplement to the Bluetooth Core Specification.
if (data_len > 0)
device_info->flags = std::vector<uint8_t>(data, data + 1);
// If |data_len| is 0, we avoid setting zero-length advertising flags as
// this currently causes Chrome to crash.
// TODO(crbug.com/876908): Fix Chrome to not crash with zero-length
// advertising flags.
break;
// If there are more than one instance of either COMPLETE or INCOMPLETE
// type of a UUID size, the later one(s) will be cached as well.
case EirType::UUID16_INCOMPLETE:
case EirType::UUID16_COMPLETE:
ParseDataIntoUuids(&service_uuids, kUuid16Size, data, data_len);
break;
case EirType::UUID32_INCOMPLETE:
case EirType::UUID32_COMPLETE:
ParseDataIntoUuids(&service_uuids, kUuid32Size, data, data_len);
break;
case EirType::UUID128_INCOMPLETE:
case EirType::UUID128_COMPLETE:
ParseDataIntoUuids(&service_uuids, kUuid128Size, data, data_len);
break;
// Name
case EirType::NAME_SHORT:
case EirType::NAME_COMPLETE: {
char c_name[HCI_DEV_NAME_LEN + 1];
// Some device has trailing '\0' at the end of the name data.
// So make sure we only take the characters before '\0' and limited
// to the max length allowed by Bluetooth spec (HCI_DEV_NAME_LEN).
uint8_t name_len =
std::min(data_len, static_cast<uint8_t>(HCI_DEV_NAME_LEN));
strncpy(c_name, reinterpret_cast<const char*>(data), name_len);
c_name[name_len] = '\0';
device_info->name = ConvertToAsciiString(c_name) + kNewblueNameSuffix;
} break;
case EirType::TX_POWER:
if (data_len == 1)
device_info->tx_power = (static_cast<int8_t>(*data));
break;
case EirType::CLASS_OF_DEV:
// 24-bit little endian data
if (data_len == 3)
device_info->eir_class = (GetNumFromLE24(data));
break;
// If the UUID already exists, the service data will be updated.
case EirType::SVC_DATA16:
ParseDataIntoServiceData(&service_data, kUuid16Size, data, data_len);
break;
case EirType::SVC_DATA32:
ParseDataIntoServiceData(&service_data, kUuid32Size, data, data_len);
break;
case EirType::SVC_DATA128:
ParseDataIntoServiceData(&service_data, kUuid128Size, data, data_len);
break;
case EirType::GAP_APPEARANCE:
// 16-bit little endian data
if (data_len == 2) {
uint16_t appearance = GetNumFromLE16(data);
device_info->appearance = (appearance);
device_info->icon = (ConvertAppearanceToIcon(appearance));
}
break;
case EirType::MANUFACTURER_DATA:
if (data_len >= 2) {
// The order of manufacturer data is not specified explicitly in
// Supplement to the Bluetooth Core Specification, so the original
// order used in BlueZ is adopted.
device_info->manufacturer = (ParseDataIntoManufacturer(
GetNumFromLE16(data),
std::vector<uint8_t>(data + 2,
data + std::max<uint8_t>(data_len, 2))));
}
break;
default:
// Do nothing for unhandled EIR type.
break;
}
pos += field_len + 1;
}
// This is different from BlueZ where it memorizes all service UUIDs and
// service data ever received for the same device. If there is no service
// UUIDs/service data being presented, service UUIDs/servicedata will not
// be updated.
if (!service_uuids.empty())
device_info->service_uuids = (std::move(service_uuids));
if (!service_data.empty())
device_info->service_data = (std::move(service_data));
}
} // namespace bluetooth