blob: 8525484d1109373ff4c311ecaa5640fd3f7f99d8 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "advertising_data.h"
#include <endian.h>
#include <zircon/assert.h>
#include <type_traits>
#include "lib/fidl/cpp/type_converter.h"
#include "lib/fidl/cpp/vector.h"
#include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/fxl/strings/utf_codecs.h"
// A partial fidl::TypeConverter template specialization for copying the
// contents of a type that derives from ByteBuffer into a
// fidl::VectorPtr<unsigned char>. If the input array is empty, the output array
// will be empty. Used by Vector<uint8_t>::From() in AsLEAdvertisingData()
template <typename T>
struct fidl::TypeConverter<fidl::VectorPtr<unsigned char>, T> {
static fidl::VectorPtr<unsigned char> Convert(const T& input) {
static_assert(std::is_base_of<bt::ByteBuffer, T>::value, "");
fidl::VectorPtr<unsigned char> result =
fidl::VectorPtr<unsigned char>::New(input.size());
memcpy(result->data(), input.data(), input.size());
return result;
}
};
namespace ble = fuchsia::bluetooth::le;
namespace bt {
namespace gap {
namespace {
using UuidFunction = fit::function<void(const UUID&)>;
bool ParseUuids(const BufferView& data, size_t uuid_size, UuidFunction func) {
ZX_DEBUG_ASSERT(func);
ZX_DEBUG_ASSERT((uuid_size == k16BitUuidElemSize) ||
(uuid_size == k32BitUuidElemSize) ||
(uuid_size == k128BitUuidElemSize));
if (data.size() % uuid_size) {
bt_log(WARN, "gap-le", "malformed service UUIDs list");
return false;
}
size_t uuid_count = data.size() / uuid_size;
for (size_t i = 0; i < uuid_count; i++) {
const BufferView uuid_bytes(data.data() + (i * uuid_size), uuid_size);
UUID uuid;
if (!UUID::FromBytes(uuid_bytes, &uuid))
return false;
func(uuid);
}
return true;
}
size_t SizeForType(DataType type) {
switch (type) {
case DataType::kIncomplete16BitServiceUuids:
case DataType::kComplete16BitServiceUuids:
case DataType::kServiceData16Bit:
return k16BitUuidElemSize;
case DataType::kIncomplete32BitServiceUuids:
case DataType::kComplete32BitServiceUuids:
case DataType::kServiceData32Bit:
return k32BitUuidElemSize;
case DataType::kIncomplete128BitServiceUuids:
case DataType::kComplete128BitServiceUuids:
case DataType::kServiceData128Bit:
return k128BitUuidElemSize;
default:
break;
};
return 0;
}
// clang-format off
// https://www.bluetooth.com/specifications/assigned-numbers/uri-scheme-name-string-mapping
const char* kUriSchemes[] = {"aaa:", "aaas:", "about:", "acap:", "acct:", "cap:", "cid:",
"coap:", "coaps:", "crid:", "data:", "dav:", "dict:", "dns:", "file:", "ftp:", "geo:",
"go:", "gopher:", "h323:", "http:", "https:", "iax:", "icap:", "im:", "imap:", "info:",
"ipp:", "ipps:", "iris:", "iris.beep:", "iris.xpc:", "iris.xpcs:", "iris.lwz:", "jabber:",
"ldap:", "mailto:", "mid:", "msrp:", "msrps:", "mtqp:", "mupdate:", "news:", "nfs:", "ni:",
"nih:", "nntp:", "opaquelocktoken:", "pop:", "pres:", "reload:", "rtsp:", "rtsps:", "rtspu:",
"service:", "session:", "shttp:", "sieve:", "sip:", "sips:", "sms:", "snmp:", "soap.beep:",
"soap.beeps:", "stun:", "stuns:", "tag:", "tel:", "telnet:", "tftp:", "thismessage:",
"tn3270:", "tip:", "turn:", "turns:", "tv:", "urn:", "vemmi:", "ws:", "wss:", "xcon:",
"xcon-userid:", "xmlrpc.beep:", "xmlrpc.beeps:", "xmpp:", "z39.50r:", "z39.50s:", "acr:",
"adiumxtra:", "afp:", "afs:", "aim:", "apt:", "attachment:", "aw:", "barion:", "beshare:",
"bitcoin:", "bolo:", "callto:", "chrome:", "chrome-extension:", "com-eventbrite-attendee:",
"content:", "cvs:", "dlna-playsingle:", "dlna-playcontainer:", "dtn:", "dvb:", "ed2k:",
"facetime:", "feed:", "feedready:", "finger:", "fish:", "gg:", "git:", "gizmoproject:",
"gtalk:", "ham:", "hcp:", "icon:", "ipn:", "irc:", "irc6:", "ircs:", "itms:", "jar:",
"jms:", "keyparc:", "lastfm:", "ldaps:", "magnet:", "maps:", "market:", "message:", "mms:",
"ms-help:", "ms-settings-power:", "msnim:", "mumble:", "mvn:", "notes:", "oid:", "palm:",
"paparazzi:", "pkcs11:", "platform:", "proxy:", "psyc:", "query:", "res:", "resource:",
"rmi:", "rsync:", "rtmfp:", "rtmp:", "secondlife:", "sftp:", "sgn:", "skype:", "smb:",
"smtp:", "soldat:", "spotify:", "ssh:", "steam:", "submit:", "svn:", "teamspeak:",
"teliaeid:", "things:", "udp:", "unreal:", "ut2004:", "ventrilo:", "view-source:",
"webcal:", "wtai:", "wyciwyg:", "xfire:", "xri:", "ymsgr:", "example:",
"ms-settings-cloudstorage:"};
// clang-format on
const size_t kUriSchemesSize = std::extent<decltype(kUriSchemes)>::value;
std::string EncodeUri(const std::string& uri) {
std::string encoded_scheme;
for (size_t i = 0; i < kUriSchemesSize; i++) {
const char* scheme = kUriSchemes[i];
size_t scheme_len = strlen(scheme);
if (std::equal(scheme, scheme + scheme_len, uri.begin())) {
fxl::WriteUnicodeCharacter(i + 2, &encoded_scheme);
return encoded_scheme + uri.substr(scheme_len);
}
}
// First codepoint (U+0001) is for uncompressed schemes.
fxl::WriteUnicodeCharacter(1, &encoded_scheme);
return encoded_scheme + uri;
}
const char kUndefinedScheme = 0x01;
std::string DecodeUri(const std::string& uri) {
if (uri[0] == kUndefinedScheme) {
return uri.substr(1);
}
uint32_t cp = 0;
size_t index = 0;
if (!fxl::ReadUnicodeCharacter(uri.c_str(), uri.size(), &index, &cp)) {
// Malformed UTF-8
return "";
}
ZX_DEBUG_ASSERT(cp >= 2);
return kUriSchemes[cp - 2] + uri.substr(index + 1);
}
template <typename T>
inline size_t BufferWrite(MutableByteBuffer* buffer, size_t pos, const T& var) {
buffer->Write((uint8_t*)&var, sizeof(T), pos);
return sizeof(T);
}
} // namespace
bool AdvertisingData::FromBytes(const ByteBuffer& data,
AdvertisingData* out_ad) {
ZX_DEBUG_ASSERT(out_ad);
AdvertisingDataReader reader(data);
if (!reader.is_valid())
return false;
DataType type;
BufferView field;
while (reader.GetNextField(&type, &field)) {
switch (type) {
case DataType::kTxPowerLevel: {
if (field.size() != kTxPowerLevelSize) {
bt_log(WARN, "gap-le", "received malformed Tx Power Level");
return false;
}
out_ad->SetTxPower(static_cast<int8_t>(field[0]));
break;
}
case DataType::kShortenedLocalName:
// If a name has been previously set (e.g. because the Complete Local
// Name was included in the scan response) then break. Otherwise we fall
// through.
if (out_ad->local_name())
break;
case DataType::kCompleteLocalName: {
out_ad->SetLocalName(field.ToString());
break;
}
case DataType::kIncomplete16BitServiceUuids:
case DataType::kComplete16BitServiceUuids:
case DataType::kIncomplete32BitServiceUuids:
case DataType::kComplete32BitServiceUuids:
case DataType::kIncomplete128BitServiceUuids:
case DataType::kComplete128BitServiceUuids: {
if (!ParseUuids(field, SizeForType(type), [&](const UUID& uuid) {
out_ad->AddServiceUuid(uuid);
}))
return false;
break;
}
case DataType::kManufacturerSpecificData: {
if (field.size() < kManufacturerSpecificDataSizeMin) {
bt_log(WARN, "gap-le", "manufacturer specific data too small");
return false;
}
uint16_t id = le16toh(*reinterpret_cast<const uint16_t*>(field.data()));
const BufferView manuf_data(field.data() + kManufacturerIdSize,
field.size() - kManufacturerIdSize);
out_ad->SetManufacturerData(id, manuf_data);
break;
}
case DataType::kServiceData16Bit:
case DataType::kServiceData32Bit:
case DataType::kServiceData128Bit: {
UUID uuid;
size_t uuid_size = SizeForType(type);
const BufferView uuid_bytes(field.data(), uuid_size);
if (!UUID::FromBytes(uuid_bytes, &uuid))
return false;
const BufferView service_data(field.data() + uuid_size,
field.size() - uuid_size);
out_ad->SetServiceData(uuid, service_data);
break;
}
case DataType::kAppearance: {
// TODO(armansito): Peer should have a function to return the
// device appearance, as it can be obtained either from advertising data
// or via GATT.
if (field.size() != kAppearanceSize) {
bt_log(WARN, "gap-le", "received malformed Appearance");
return false;
}
out_ad->SetAppearance(*reinterpret_cast<const uint16_t*>(field.data()));
break;
}
case DataType::kURI: {
out_ad->AddURI(DecodeUri(field.ToString()));
break;
}
case DataType::kFlags: {
// TODO(jamuraa): is there anything to do here?
break;
}
default:
bt_log(TRACE, "gap-le", "ignored advertising field (type %#.2x)",
static_cast<unsigned int>(type));
break;
}
}
return true;
}
::ble::AdvertisingDataPtr AdvertisingData::AsLEAdvertisingData() const {
auto fidl_data = ::ble::AdvertisingData::New();
ZX_DEBUG_ASSERT(fidl_data);
if (tx_power_) {
fidl_data->tx_power_level = ::fuchsia::bluetooth::Int8::New();
fidl_data->tx_power_level->value = *tx_power_;
}
if (appearance_) {
fidl_data->appearance = ::fuchsia::bluetooth::UInt16::New();
fidl_data->appearance->value = *appearance_;
}
for (const auto& pair : manufacturer_data_) {
::ble::ManufacturerSpecificDataEntry entry;
entry.company_id = pair.first;
entry.data = fidl::To<fidl::VectorPtr<unsigned char>>(pair.second);
fidl_data->manufacturer_specific_data.push_back(std::move(entry));
}
for (const auto& pair : service_data_) {
::ble::ServiceDataEntry entry;
entry.uuid = pair.first.ToString();
entry.data = fidl::To<fidl::VectorPtr<unsigned char>>(pair.second);
fidl_data->service_data.push_back(std::move(entry));
}
for (const auto& uuid : service_uuids_) {
fidl_data->service_uuids.push_back(uuid.ToString());
}
for (const auto& uri : uris_) {
fidl_data->uris.push_back(uri);
}
if (local_name_) {
fidl_data->name = *local_name_;
}
return fidl_data;
}
bool AdvertisingData::FromFidl(const ::ble::AdvertisingData& fidl_ad,
AdvertisingData* out_ad) {
ZX_DEBUG_ASSERT(out_ad);
UUID uuid;
for (const auto& uuid_str : *fidl_ad.service_uuids) {
if (!StringToUuid(uuid_str, &uuid)) {
bt_log(WARN, "gap-le", "FIDL advertising data contains malformd UUID: %s",
uuid_str.c_str());
return false;
}
out_ad->AddServiceUuid(uuid);
}
for (const auto& it : *fidl_ad.manufacturer_specific_data) {
const std::vector<uint8_t>& data = it.data;
BufferView manuf_view(data.data(), data.size());
out_ad->SetManufacturerData(it.company_id, manuf_view);
}
for (const auto& it : *fidl_ad.service_data) {
const std::vector<uint8_t>& data = it.data;
BufferView service_data_bytes(data.data(), data.size());
UUID service_data_uuid;
if (!StringToUuid(it.uuid, &service_data_uuid)) {
bt_log(WARN, "gap-le", "FIDL service data contains malformed UUID: %s",
it.uuid.c_str());
return false;
}
out_ad->SetServiceData(service_data_uuid, service_data_bytes);
}
if (fidl_ad.appearance) {
out_ad->SetAppearance(fidl_ad.appearance->value);
}
if (fidl_ad.tx_power_level) {
out_ad->SetTxPower(fidl_ad.tx_power_level->value);
}
if (fidl_ad.name) {
out_ad->SetLocalName(fidl_ad.name);
}
return true;
}
void AdvertisingData::Copy(AdvertisingData* out) const {
if (local_name_)
out->SetLocalName(*local_name_);
if (tx_power_)
out->SetTxPower(*tx_power_);
if (appearance_)
out->SetAppearance(*appearance_);
for (const auto& it : service_uuids_) {
out->AddServiceUuid(it);
}
for (const auto& it : manufacturer_data_) {
out->SetManufacturerData(it.first, it.second.view());
}
for (const auto& it : service_data_) {
out->SetServiceData(it.first, it.second.view());
}
for (const auto& it : uris_) {
out->AddURI(it);
}
}
void AdvertisingData::AddServiceUuid(const UUID& uuid) {
service_uuids_.insert(uuid);
}
const std::unordered_set<UUID>& AdvertisingData::service_uuids() const {
return service_uuids_;
}
void AdvertisingData::SetServiceData(const UUID& uuid, const ByteBuffer& data) {
DynamicByteBuffer srv_data(data.size());
data.Copy(&srv_data);
service_data_[uuid] = std::move(srv_data);
}
const std::unordered_set<UUID> AdvertisingData::service_data_uuids() const {
std::unordered_set<UUID> uuids;
for (const auto& it : service_data_) {
uuids.emplace(it.first);
}
return uuids;
}
const BufferView AdvertisingData::service_data(const UUID& uuid) const {
auto iter = service_data_.find(uuid);
if (iter == service_data_.end())
return BufferView();
return BufferView(iter->second);
}
void AdvertisingData::SetManufacturerData(const uint16_t company_id,
const BufferView& data) {
DynamicByteBuffer manuf_data(data.size());
data.Copy(&manuf_data);
manufacturer_data_[company_id] = std::move(manuf_data);
}
const std::unordered_set<uint16_t> AdvertisingData::manufacturer_data_ids()
const {
std::unordered_set<uint16_t> manuf_ids;
for (const auto& it : manufacturer_data_) {
manuf_ids.emplace(it.first);
}
return manuf_ids;
}
const BufferView AdvertisingData::manufacturer_data(
const uint16_t company_id) const {
auto iter = manufacturer_data_.find(company_id);
if (iter == manufacturer_data_.end())
return BufferView();
return BufferView(iter->second);
}
void AdvertisingData::SetTxPower(int8_t dbm) { tx_power_ = dbm; }
std::optional<int8_t> AdvertisingData::tx_power() const { return tx_power_; }
void AdvertisingData::SetLocalName(const std::string& name) {
local_name_ = std::string(name);
}
std::optional<std::string> AdvertisingData::local_name() const {
return local_name_;
}
void AdvertisingData::AddURI(const std::string& uri) {
if (!uri.empty())
uris_.insert(uri);
}
const std::unordered_set<std::string>& AdvertisingData::uris() const {
return uris_;
}
void AdvertisingData::SetAppearance(uint16_t appearance) {
appearance_ = appearance;
}
std::optional<uint16_t> AdvertisingData::appearance() const {
return appearance_;
}
size_t AdvertisingData::CalculateBlockSize() const {
size_t len = 0;
if (tx_power_)
len += 3;
if (appearance_)
len += 4;
if (local_name_)
len += 2 + local_name_->size();
for (const auto& manuf_pair : manufacturer_data_) {
len += 2 + 2 + manuf_pair.second.size();
}
for (const auto& service_data_pair : service_data_) {
len += 2 + service_data_pair.first.CompactSize() +
service_data_pair.second.size();
}
for (const auto& uri : uris_) {
len += 2 + EncodeUri(uri).size();
}
size_t small_uuids = 0;
size_t medium_uuids = 0;
size_t big_uuids = 0;
for (const auto& uuid : service_uuids_) {
switch (uuid.CompactSize()) {
case 2: {
if (small_uuids == 0)
len += 2;
small_uuids++;
break;
}
case 4: {
if (medium_uuids == 0)
len += 2;
medium_uuids++;
break;
}
case 16: {
if (big_uuids == 0)
len += 2;
big_uuids++;
break;
}
default: {
bt_log(WARN, "gap-le", "unknown UUID size");
break;
}
}
}
len += (small_uuids * 2) + (medium_uuids * 4) + (big_uuids * 16);
return len;
}
bool AdvertisingData::WriteBlock(MutableByteBuffer* buffer) const {
ZX_DEBUG_ASSERT(buffer);
if (buffer->size() < CalculateBlockSize())
return false;
size_t pos = 0;
if (tx_power_) {
(*buffer)[pos++] = 2;
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kTxPowerLevel);
(*buffer)[pos++] = *reinterpret_cast<uint8_t*>(tx_power_.value());
}
if (appearance_) {
(*buffer)[pos++] = 3;
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kAppearance);
pos += BufferWrite(buffer, pos, appearance_.value());
}
if (local_name_) {
(*buffer)[pos++] = 1 + local_name_->size();
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kCompleteLocalName);
buffer->Write(reinterpret_cast<const uint8_t*>(local_name_->c_str()),
local_name_->length(), pos);
pos += local_name_->size();
}
for (const auto& manuf_pair : manufacturer_data_) {
size_t data_size = manuf_pair.second.size();
(*buffer)[pos++] = 1 + 2 + data_size; // 1 for type, 2 for Manuf. Code
(*buffer)[pos++] =
static_cast<uint8_t>(DataType::kManufacturerSpecificData);
pos += BufferWrite(buffer, pos, manuf_pair.first);
buffer->Write(manuf_pair.second, pos);
pos += data_size;
}
for (const auto& service_data_pair : service_data_) {
size_t uuid_size = service_data_pair.first.CompactSize();
(*buffer)[pos++] = 1 + uuid_size + service_data_pair.second.size();
switch (uuid_size) {
case 2:
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kServiceData16Bit);
break;
case 4:
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kServiceData32Bit);
break;
case 16:
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kServiceData128Bit);
break;
};
auto target = buffer->mutable_view(pos);
pos += service_data_pair.first.ToBytes(&target);
buffer->Write(service_data_pair.second, pos);
pos += service_data_pair.second.size();
}
for (const auto& uri : uris_) {
std::string s = EncodeUri(uri);
(*buffer)[pos++] = 1 + s.size();
(*buffer)[pos++] = static_cast<uint8_t>(DataType::kURI);
buffer->Write(reinterpret_cast<const uint8_t*>(s.c_str()), s.length(), pos);
pos += s.size();
}
std::unordered_map<size_t, std::unordered_set<UUID>> uuid_sets;
for (const auto& uuid : service_uuids_) {
uuid_sets[uuid.CompactSize()].insert(uuid);
}
for (const auto& pair : uuid_sets) {
(*buffer)[pos++] = 1 + pair.first * pair.second.size();
switch (pair.first) {
case 2:
(*buffer)[pos++] =
static_cast<uint8_t>(DataType::kIncomplete16BitServiceUuids);
break;
case 4:
(*buffer)[pos++] =
static_cast<uint8_t>(DataType::kIncomplete32BitServiceUuids);
break;
case 16:
(*buffer)[pos++] =
static_cast<uint8_t>(DataType::kIncomplete128BitServiceUuids);
break;
};
for (const auto& uuid : pair.second) {
auto target = buffer->mutable_view(pos);
pos += uuid.ToBytes(&target);
}
}
return true;
}
bool AdvertisingData::operator==(const AdvertisingData& other) const {
if ((local_name_ != other.local_name_) || (tx_power_ != other.tx_power_) ||
(appearance_ != other.appearance_) ||
(service_uuids_ != other.service_uuids_) || (uris_ != other.uris_)) {
return false;
}
if (manufacturer_data_.size() != other.manufacturer_data_.size()) {
return false;
}
for (const auto& it : manufacturer_data_) {
auto that = other.manufacturer_data_.find(it.first);
if (that == other.manufacturer_data_.end()) {
return false;
}
size_t bytes = it.second.size();
if (bytes != that->second.size()) {
return false;
}
if (std::memcmp(it.second.data(), that->second.data(), bytes) != 0) {
return false;
}
}
if (service_data_.size() != other.service_data_.size()) {
return false;
}
for (const auto& it : service_data_) {
auto that = other.service_data_.find(it.first);
if (that == other.service_data_.end()) {
return false;
}
size_t bytes = it.second.size();
if (bytes != that->second.size()) {
return false;
}
if (std::memcmp(it.second.data(), that->second.data(), bytes) != 0) {
return false;
}
}
return true;
}
bool AdvertisingData::operator!=(const AdvertisingData& other) const {
return !(*this == other);
}
AdvertisingDataReader::AdvertisingDataReader(const ByteBuffer& data)
: is_valid_(true), remaining_(data) {
if (!remaining_.size()) {
is_valid_ = false;
return;
}
// Do a validity check.
BufferView tmp(remaining_);
while (tmp.size()) {
size_t tlv_len = tmp[0];
// A struct can have 0 as its length. In that case its valid to terminate.
if (!tlv_len)
break;
// The full struct includes the length octet itself.
size_t struct_size = tlv_len + 1;
if (struct_size > tmp.size()) {
is_valid_ = false;
break;
}
tmp = tmp.view(struct_size);
}
}
bool AdvertisingDataReader::GetNextField(DataType* out_type,
BufferView* out_data) {
ZX_DEBUG_ASSERT(out_type);
ZX_DEBUG_ASSERT(out_data);
if (!HasMoreData())
return false;
size_t tlv_len = remaining_[0];
size_t cur_struct_size = tlv_len + 1;
ZX_DEBUG_ASSERT(cur_struct_size <= remaining_.size());
*out_type = static_cast<DataType>(remaining_[1]);
*out_data = remaining_.view(2, tlv_len - 1);
// Update |remaining_|.
remaining_ = remaining_.view(cur_struct_size);
return true;
}
bool AdvertisingDataReader::HasMoreData() const {
if (!is_valid_ || !remaining_.size())
return false;
// If the buffer is valid and has remaining bytes but the length of the next
// segment is zero, then we terminate.
return !!remaining_[0];
}
AdvertisingDataWriter::AdvertisingDataWriter(MutableByteBuffer* buffer)
: buffer_(buffer), bytes_written_(0u) {
ZX_DEBUG_ASSERT(buffer_);
}
bool AdvertisingDataWriter::WriteField(DataType type, const ByteBuffer& data) {
size_t next_size = data.size() + 2; // 2 bytes for [length][type].
if (bytes_written_ + next_size > buffer_->size() || next_size > 255)
return false;
(*buffer_)[bytes_written_++] = static_cast<uint8_t>(next_size) - 1;
(*buffer_)[bytes_written_++] = static_cast<uint8_t>(type);
// Get a view into the offset we want to write to.
auto target = buffer_->mutable_view(bytes_written_);
// Copy the data into the view.
size_t written = data.Copy(&target);
ZX_DEBUG_ASSERT(written == data.size());
bytes_written_ += written;
return true;
}
} // namespace gap
} // namespace bt