You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
11 KiB
308 lines
11 KiB
/******************************************************************************
|
|
*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* 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 "database.h"
|
|
#include "bt_trace.h"
|
|
#include "stack/crypto_toolbox/crypto_toolbox.h"
|
|
#include "stack/include/gattdefs.h"
|
|
|
|
#include <base/logging.h>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <sstream>
|
|
|
|
using bluetooth::Uuid;
|
|
|
|
namespace gatt {
|
|
|
|
namespace {
|
|
const Uuid PRIMARY_SERVICE = Uuid::From16Bit(GATT_UUID_PRI_SERVICE);
|
|
const Uuid SECONDARY_SERVICE = Uuid::From16Bit(GATT_UUID_SEC_SERVICE);
|
|
const Uuid INCLUDE = Uuid::From16Bit(GATT_UUID_INCLUDE_SERVICE);
|
|
const Uuid CHARACTERISTIC = Uuid::From16Bit(GATT_UUID_CHAR_DECLARE);
|
|
const Uuid CHARACTERISTIC_EXTENDED_PROPERTIES =
|
|
Uuid::From16Bit(GATT_UUID_CHAR_EXT_PROP);
|
|
|
|
bool HandleInRange(const Service& svc, uint16_t handle) {
|
|
return handle >= svc.handle && handle <= svc.end_handle;
|
|
}
|
|
} // namespace
|
|
|
|
static size_t UuidSize(const Uuid& uuid) {
|
|
size_t len = uuid.GetShortestRepresentationSize();
|
|
return (len == Uuid::kNumBytes32) ? Uuid::kNumBytes128 : len;
|
|
}
|
|
|
|
Service* FindService(std::list<Service>& services, uint16_t handle) {
|
|
for (Service& service : services) {
|
|
if (handle >= service.handle && handle <= service.end_handle)
|
|
return &service;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::string Database::ToString() const {
|
|
std::stringstream tmp;
|
|
|
|
for (const Service& service : services) {
|
|
tmp << "Service: handle=" << loghex(service.handle)
|
|
<< ", end_handle=" << loghex(service.end_handle)
|
|
<< ", uuid=" << service.uuid << "\n";
|
|
|
|
for (const auto& is : service.included_services) {
|
|
tmp << "\t Included service: handle=" << loghex(is.handle)
|
|
<< ", start_handle=" << loghex(is.start_handle)
|
|
<< ", end_handle=" << loghex(is.end_handle) << ", uuid=" << is.uuid
|
|
<< "\n";
|
|
}
|
|
|
|
for (const Characteristic& c : service.characteristics) {
|
|
tmp << "\t Characteristic: declaration_handle="
|
|
<< loghex(c.declaration_handle)
|
|
<< ", value_handle=" << loghex(c.value_handle) << ", uuid=" << c.uuid
|
|
<< ", prop=" << loghex(c.properties) << "\n";
|
|
|
|
for (const Descriptor& d : c.descriptors) {
|
|
tmp << "\t\t Descriptor: handle=" << loghex(d.handle)
|
|
<< ", uuid=" << d.uuid << "\n";
|
|
}
|
|
}
|
|
}
|
|
return tmp.str();
|
|
}
|
|
|
|
std::vector<StoredAttribute> Database::Serialize() const {
|
|
std::vector<StoredAttribute> nv_attr;
|
|
|
|
if (services.empty()) return std::vector<StoredAttribute>();
|
|
|
|
for (const Service& service : services) {
|
|
// TODO: add constructor to NV_ATTR, use emplace_back
|
|
nv_attr.push_back({service.handle,
|
|
service.is_primary ? PRIMARY_SERVICE : SECONDARY_SERVICE,
|
|
{.service = {.uuid = service.uuid,
|
|
.end_handle = service.end_handle}}});
|
|
}
|
|
|
|
for (const Service& service : services) {
|
|
for (const IncludedService& p_isvc : service.included_services) {
|
|
nv_attr.push_back({p_isvc.handle,
|
|
INCLUDE,
|
|
{.included_service = {.handle = p_isvc.start_handle,
|
|
.end_handle = p_isvc.end_handle,
|
|
.uuid = p_isvc.uuid}}});
|
|
}
|
|
|
|
for (const Characteristic& charac : service.characteristics) {
|
|
nv_attr.push_back(
|
|
{charac.declaration_handle,
|
|
CHARACTERISTIC,
|
|
{.characteristic = {.properties = charac.properties,
|
|
.value_handle = charac.value_handle,
|
|
.uuid = charac.uuid}}});
|
|
|
|
for (const Descriptor& desc : charac.descriptors) {
|
|
if (desc.uuid == CHARACTERISTIC_EXTENDED_PROPERTIES) {
|
|
nv_attr.push_back({desc.handle,
|
|
desc.uuid,
|
|
{.characteristic_extended_properties =
|
|
desc.characteristic_extended_properties}});
|
|
} else {
|
|
nv_attr.push_back({desc.handle, desc.uuid, {}});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nv_attr;
|
|
}
|
|
|
|
Database Database::Deserialize(const std::vector<StoredAttribute>& nv_attr,
|
|
bool* success) {
|
|
// clear reallocating
|
|
Database result;
|
|
auto it = nv_attr.cbegin();
|
|
|
|
for (; it != nv_attr.cend(); ++it) {
|
|
const auto& attr = *it;
|
|
if (attr.type != PRIMARY_SERVICE && attr.type != SECONDARY_SERVICE) break;
|
|
result.services.emplace_back(Service{
|
|
.handle = attr.handle,
|
|
.uuid = attr.value.service.uuid,
|
|
.is_primary = (attr.type == PRIMARY_SERVICE),
|
|
.end_handle = attr.value.service.end_handle,
|
|
});
|
|
}
|
|
|
|
auto current_service_it = result.services.begin();
|
|
for (; it != nv_attr.cend(); it++) {
|
|
const auto& attr = *it;
|
|
|
|
// go to the service this attribute belongs to; attributes are stored in
|
|
// order, so iterating just forward is enough
|
|
while (current_service_it != result.services.end() &&
|
|
current_service_it->end_handle < attr.handle) {
|
|
current_service_it++;
|
|
}
|
|
|
|
if (current_service_it == result.services.end() ||
|
|
!HandleInRange(*current_service_it, attr.handle)) {
|
|
LOG(ERROR) << "Can't find service for attribute with handle: "
|
|
<< loghex(attr.handle);
|
|
*success = false;
|
|
return result;
|
|
}
|
|
|
|
if (attr.type == INCLUDE) {
|
|
Service* included_service =
|
|
FindService(result.services, attr.value.included_service.handle);
|
|
if (!included_service) {
|
|
LOG(ERROR) << __func__ << ": Non-existing included service!";
|
|
*success = false;
|
|
return result;
|
|
}
|
|
current_service_it->included_services.push_back(IncludedService{
|
|
.handle = attr.handle,
|
|
.uuid = attr.value.included_service.uuid,
|
|
.start_handle = attr.value.included_service.handle,
|
|
.end_handle = attr.value.included_service.end_handle,
|
|
});
|
|
} else if (attr.type == CHARACTERISTIC) {
|
|
current_service_it->characteristics.emplace_back(Characteristic{
|
|
.declaration_handle = attr.handle,
|
|
.uuid = attr.value.characteristic.uuid,
|
|
.value_handle = attr.value.characteristic.value_handle,
|
|
.properties = attr.value.characteristic.properties,
|
|
});
|
|
|
|
} else {
|
|
if (attr.type == CHARACTERISTIC_EXTENDED_PROPERTIES) {
|
|
current_service_it->characteristics.back().descriptors.emplace_back(
|
|
Descriptor{.handle = attr.handle,
|
|
.uuid = attr.type,
|
|
.characteristic_extended_properties =
|
|
attr.value.characteristic_extended_properties});
|
|
|
|
} else {
|
|
current_service_it->characteristics.back().descriptors.emplace_back(
|
|
Descriptor{.handle = attr.handle, .uuid = attr.type});
|
|
}
|
|
}
|
|
}
|
|
*success = true;
|
|
return result;
|
|
}
|
|
|
|
Octet16 Database::Hash() const {
|
|
int len = 0;
|
|
// Compute how much space we need to actually hold the data.
|
|
for (const Service& service : services) {
|
|
len += 4 + UuidSize(service.uuid);
|
|
|
|
for (const auto& is : service.included_services) {
|
|
len += 8 + UuidSize(is.uuid);
|
|
}
|
|
|
|
for (const Characteristic& c : service.characteristics) {
|
|
len += 7 + UuidSize(c.uuid);
|
|
|
|
for (const Descriptor& d : c.descriptors) {
|
|
if (UuidSize(d.uuid) != Uuid::kNumBytes16) {
|
|
continue;
|
|
}
|
|
uint16_t value = d.uuid.As16Bit();
|
|
if (value == GATT_UUID_CHAR_DESCRIPTION ||
|
|
value == GATT_UUID_CHAR_CLIENT_CONFIG ||
|
|
value == GATT_UUID_CHAR_SRVR_CONFIG ||
|
|
value == GATT_UUID_CHAR_PRESENT_FORMAT ||
|
|
value == GATT_UUID_CHAR_AGG_FORMAT) {
|
|
len += 2 + UuidSize(d.uuid);
|
|
} else if (value == GATT_UUID_CHAR_EXT_PROP) {
|
|
len += 4 + UuidSize(d.uuid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<uint8_t> serialized(len);
|
|
uint8_t* p = serialized.data();
|
|
for (const Service& service : services) {
|
|
UINT16_TO_STREAM(p, service.handle);
|
|
if (service.is_primary) {
|
|
UINT16_TO_STREAM(p, GATT_UUID_PRI_SERVICE);
|
|
} else {
|
|
UINT16_TO_STREAM(p, GATT_UUID_SEC_SERVICE);
|
|
}
|
|
|
|
if (UuidSize(service.uuid) == Uuid::kNumBytes16) {
|
|
UINT16_TO_STREAM(p, service.uuid.As16Bit());
|
|
} else {
|
|
ARRAY_TO_STREAM(p, service.uuid.To128BitLE(), (int)Uuid::kNumBytes128);
|
|
}
|
|
|
|
for (const auto& is : service.included_services) {
|
|
UINT16_TO_STREAM(p, is.handle);
|
|
UINT16_TO_STREAM(p, GATT_UUID_INCLUDE_SERVICE);
|
|
UINT16_TO_STREAM(p, is.start_handle);
|
|
UINT16_TO_STREAM(p, is.end_handle);
|
|
|
|
if (UuidSize(is.uuid) == Uuid::kNumBytes16) {
|
|
UINT16_TO_STREAM(p, is.uuid.As16Bit());
|
|
} else {
|
|
ARRAY_TO_STREAM(p, is.uuid.To128BitLE(), (int)Uuid::kNumBytes128);
|
|
}
|
|
}
|
|
|
|
for (const Characteristic& c : service.characteristics) {
|
|
UINT16_TO_STREAM(p, c.declaration_handle);
|
|
UINT16_TO_STREAM(p, GATT_UUID_CHAR_DECLARE);
|
|
UINT8_TO_STREAM(p, c.properties);
|
|
UINT16_TO_STREAM(p, c.value_handle);
|
|
|
|
if (UuidSize(c.uuid) == Uuid::kNumBytes16) {
|
|
UINT16_TO_STREAM(p, c.uuid.As16Bit());
|
|
} else {
|
|
ARRAY_TO_STREAM(p, c.uuid.To128BitLE(), (int)Uuid::kNumBytes128);
|
|
}
|
|
|
|
for (const Descriptor& d : c.descriptors) {
|
|
if (UuidSize(d.uuid) != Uuid::kNumBytes16) continue;
|
|
uint16_t value = d.uuid.As16Bit();
|
|
if (value == GATT_UUID_CHAR_DESCRIPTION ||
|
|
value == GATT_UUID_CHAR_CLIENT_CONFIG ||
|
|
value == GATT_UUID_CHAR_SRVR_CONFIG ||
|
|
value == GATT_UUID_CHAR_PRESENT_FORMAT ||
|
|
value == GATT_UUID_CHAR_AGG_FORMAT) {
|
|
UINT16_TO_STREAM(p, d.handle);
|
|
UINT16_TO_STREAM(p, d.uuid.As16Bit());
|
|
} else if (value == GATT_UUID_CHAR_EXT_PROP) {
|
|
UINT16_TO_STREAM(p, d.handle);
|
|
UINT16_TO_STREAM(p, d.uuid.As16Bit());
|
|
UINT16_TO_STREAM(p, d.characteristic_extended_properties);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::reverse(serialized.begin(), serialized.end());
|
|
return crypto_toolbox::aes_cmac(Octet16{0}, serialized.data(),
|
|
serialized.size());
|
|
}
|
|
} // namespace gatt
|