/******************************************************************************
*
* Copyright 2022 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.
*
******************************************************************************/
#define LOG_TAG "bt_bta_gattc"
#include
#include
#include
#include
#include
#include
#include "bta/gatt/bta_gattc_int.h"
#include "gatt/database.h"
#include "os/log.h"
#include "stack/include/gattdefs.h"
#include "types/bluetooth/uuid.h"
using namespace bluetooth;
using gatt::StoredAttribute;
using std::string;
using std::vector;
#ifdef TARGET_FLOSS
#define GATT_CACHE_PREFIX "/var/lib/bluetooth/gatt/gatt_cache_"
#define GATT_CACHE_VERSION 6
#define GATT_HASH_MAX_SIZE 30
#define GATT_HASH_PATH_PREFIX "/var/lib/bluetooth/gatt/gatt_hash_"
#define GATT_HASH_PATH "/var/lib/bluetooth/gatt"
#define GATT_HASH_FILE_PREFIX "gatt_hash_"
#else
#define GATT_CACHE_PREFIX "/data/misc/bluetooth/gatt_cache_"
#define GATT_CACHE_VERSION 6
#define GATT_HASH_MAX_SIZE 30
#define GATT_HASH_PATH_PREFIX "/data/misc/bluetooth/gatt_hash_"
#define GATT_HASH_PATH "/data/misc/bluetooth"
#define GATT_HASH_FILE_PREFIX "gatt_hash_"
#endif
// Default expired time is 7 days
#define GATT_HASH_EXPIRED_TIME 604800
static void bta_gattc_hash_remove_least_recently_used_if_possible();
static void bta_gattc_generate_cache_file_name(char* buffer, size_t buffer_len,
const RawAddress& bda) {
snprintf(buffer, buffer_len, "%s%02x%02x%02x%02x%02x%02x", GATT_CACHE_PREFIX,
bda.address[0], bda.address[1], bda.address[2], bda.address[3],
bda.address[4], bda.address[5]);
}
static void bta_gattc_generate_hash_file_name(char* buffer, size_t buffer_len,
const Octet16& hash) {
snprintf(buffer, buffer_len, "%s%s", GATT_HASH_PATH_PREFIX,
base::HexEncode(hash.data(), 16).c_str());
}
static gatt::Database EMPTY_DB;
/*******************************************************************************
*
* Function bta_gattc_load_db
*
* Description Load GATT database from storage.
*
* Parameter fname: input file name
*
* Returns non-empty GATT database on success, empty GATT database
* otherwise
*
******************************************************************************/
static gatt::Database bta_gattc_load_db(const char* fname) {
FILE* fd = fopen(fname, "rb");
if (!fd) {
log::error("can't open GATT cache file {} for reading, error: {}", fname,
strerror(errno));
return EMPTY_DB;
}
uint16_t cache_ver = 0;
uint16_t num_attr = 0;
if (fread(&cache_ver, sizeof(uint16_t), 1, fd) != 1) {
log::error("can't read GATT cache version from: {}", fname);
goto done;
}
if (cache_ver != GATT_CACHE_VERSION) {
log::error("wrong GATT cache version: {}", fname);
goto done;
}
if (fread(&num_attr, sizeof(uint16_t), 1, fd) != 1) {
log::error("can't read number of GATT attributes: {}", fname);
goto done;
}
{
std::vector attr(num_attr);
if (fread(attr.data(), sizeof(StoredAttribute), num_attr, fd) != num_attr) {
log::error("can't read GATT attributes: {}", fname);
goto done;
}
fclose(fd);
bool success = false;
gatt::Database result = gatt::Database::Deserialize(attr, &success);
return success ? result : EMPTY_DB;
}
done:
fclose(fd);
return EMPTY_DB;
}
/*******************************************************************************
*
* Function bta_gattc_cache_load
*
* Description Load GATT cache from storage for server.
*
* Parameter bd_address: remote device address
*
* Returns non-empty GATT database on success, empty GATT database
* otherwise
*
******************************************************************************/
gatt::Database bta_gattc_cache_load(const RawAddress& server_bda) {
char fname[255] = {0};
bta_gattc_generate_cache_file_name(fname, sizeof(fname), server_bda);
return bta_gattc_load_db(fname);
}
/*******************************************************************************
*
* Function bta_gattc_hash_load
*
* Description Load GATT cache from storage for server.
*
* Parameter hash: 16-byte value
*
* Returns non-empty GATT database on success, empty GATT database
* otherwise
*
******************************************************************************/
gatt::Database bta_gattc_hash_load(const Octet16& hash) {
char fname[255] = {0};
bta_gattc_generate_hash_file_name(fname, sizeof(fname), hash);
return bta_gattc_load_db(fname);
}
void StoredAttribute::SerializeStoredAttribute(const StoredAttribute& attr,
std::vector& bytes) {
size_t original_size = bytes.size();
// handle
bytes.push_back(attr.handle & 0xff);
bytes.push_back(attr.handle >> 8);
auto uuid = attr.type.To128BitBE();
bytes.insert(bytes.cend(), uuid.cbegin(), uuid.cend());
if (attr.type.Is16Bit()) {
switch (attr.type.As16Bit()) {
/* primary or secondary service definition */
case GATT_UUID_PRI_SERVICE:
case GATT_UUID_SEC_SERVICE:
uuid = attr.value.service.uuid.To128BitBE();
bytes.insert(bytes.cend(), uuid.cbegin(), uuid.cend());
bytes.push_back(attr.value.service.end_handle & 0xff);
bytes.push_back(attr.value.service.end_handle >> 8);
break;
case GATT_UUID_INCLUDE_SERVICE:
/* included service definition */
bytes.push_back(attr.value.included_service.handle & 0xff);
bytes.push_back(attr.value.included_service.handle >> 8);
bytes.push_back(attr.value.included_service.end_handle & 0xff);
bytes.push_back(attr.value.included_service.end_handle >> 8);
uuid = attr.value.included_service.uuid.To128BitBE();
bytes.insert(bytes.cend(), uuid.cbegin(), uuid.cend());
break;
case GATT_UUID_CHAR_DECLARE:
/* characteristic definition */
bytes.push_back(attr.value.characteristic.properties);
bytes.push_back(0); // Padding byte
bytes.push_back(attr.value.characteristic.value_handle & 0xff);
bytes.push_back(attr.value.characteristic.value_handle >> 8);
uuid = attr.value.characteristic.uuid.To128BitBE();
bytes.insert(bytes.cend(), uuid.cbegin(), uuid.cend());
break;
case GATT_UUID_CHAR_EXT_PROP:
/* for descriptor we store value only for
* «Characteristic Extended Properties» */
bytes.push_back(attr.value.characteristic_extended_properties & 0xff);
bytes.push_back(attr.value.characteristic_extended_properties >> 8);
break;
default:
// log::verbose("Unhandled type UUID 0x{:04x}", attr.type.As16Bit());
break;
}
}
// padding
for (size_t i = bytes.size() - original_size;
i < StoredAttribute::kSizeOnDisk; i++) {
bytes.push_back(0);
}
}
/*******************************************************************************
*
* Function bta_gattc_store_db
*
* Description Storess GATT db.
*
* Parameter fname: output file name
* attr: attributes to save.
*
* Returns true on success, false otherwise
*
******************************************************************************/
static bool bta_gattc_store_db(const char* fname,
const std::vector& attr) {
FILE* fd = fopen(fname, "wb");
if (!fd) {
log::error("can't open GATT cache file for writing: {}", fname);
return false;
}
uint16_t cache_ver = GATT_CACHE_VERSION;
if (fwrite(&cache_ver, sizeof(uint16_t), 1, fd) != 1) {
log::error("can't write GATT cache version: {}", fname);
fclose(fd);
return false;
}
uint16_t num_attr = attr.size();
if (fwrite(&num_attr, sizeof(uint16_t), 1, fd) != 1) {
log::error("can't write GATT cache attribute count: {}", fname);
fclose(fd);
return false;
}
std::vector db_bytes;
db_bytes.reserve(num_attr * StoredAttribute::kSizeOnDisk);
for (const auto attribute : attr) {
StoredAttribute::SerializeStoredAttribute(attribute, db_bytes);
}
if (fwrite(db_bytes.data(), sizeof(uint8_t), db_bytes.size(), fd) !=
db_bytes.size()) {
log::error("can't write GATT cache attributes: {}", fname);
fclose(fd);
return false;
}
fclose(fd);
return true;
}
/*******************************************************************************
*
* Function bta_gattc_cache_write
*
* Description This callout function is executed by GATT when a server
* cache is available to save. Before calling this API, make
* sure the device is bonded. Otherwise you might get lots of
* address caches for unbonded devices.
*
* Parameter server_bda: server bd address of this cache belongs to
* database: attributes to save.
* Returns
*
******************************************************************************/
void bta_gattc_cache_write(const RawAddress& server_bda,
const gatt::Database& database) {
char addr_file[255] = {0};
char hash_file[255] = {0};
Octet16 hash = database.Hash();
bta_gattc_generate_cache_file_name(addr_file, sizeof(addr_file), server_bda);
bta_gattc_generate_hash_file_name(hash_file, sizeof(hash_file), hash);
bool result = bta_gattc_hash_write(hash, database);
// Only link addr_file to hash file when hash_file is created successfully.
if (result) {
bta_gattc_cache_link(server_bda, hash);
}
}
/*******************************************************************************
*
* Function bta_gattc_cache_link
*
* Description Link address-database file to hash-database file
*
* Parameter server_bda: server bd address of this cache belongs to
* hash: 16-byte value
*
* Returns true on success, false otherwise
*
******************************************************************************/
void bta_gattc_cache_link(const RawAddress& server_bda, const Octet16& hash) {
char addr_file[255] = {0};
char hash_file[255] = {0};
bta_gattc_generate_cache_file_name(addr_file, sizeof(addr_file), server_bda);
bta_gattc_generate_hash_file_name(hash_file, sizeof(hash_file), hash);
unlink(addr_file); // remove addr file first if the file exists
if (link(hash_file, addr_file) == -1) {
log::error("link {} to {}, errno={}", addr_file, hash_file, errno);
}
}
/*******************************************************************************
*
* Function bta_gattc_hash_write
*
* Description This callout function is executed by GATT when a server
* cache is available to save for specific hash.
*
* Parameter hash: 16-byte value
* database: gatt::Database instance.
*
* Returns true on success, false otherwise
*
******************************************************************************/
bool bta_gattc_hash_write(const Octet16& hash, const gatt::Database& database) {
char fname[255] = {0};
bta_gattc_generate_hash_file_name(fname, sizeof(fname), hash);
bta_gattc_hash_remove_least_recently_used_if_possible();
return bta_gattc_store_db(fname, database.Serialize());
}
/*******************************************************************************
*
* Function bta_gattc_cache_reset
*
* Description This callout function is executed by GATTC to reset cache in
* application
*
* Parameter server_bda: server bd address of this cache belongs to
*
* Returns void.
*
******************************************************************************/
void bta_gattc_cache_reset(const RawAddress& server_bda) {
log::verbose("");
char fname[255] = {0};
bta_gattc_generate_cache_file_name(fname, sizeof(fname), server_bda);
unlink(fname);
}
/*******************************************************************************
*
* Function bta_gattc_hash_remove_least_recently_used_if_possible
*
* Description When the max size reaches, find the oldest item and remove
* it if possible
*
* Parameter
*
* Returns void
*
******************************************************************************/
static void bta_gattc_hash_remove_least_recently_used_if_possible() {
std::unique_ptr dirp(opendir(GATT_HASH_PATH),
&closedir);
if (dirp == nullptr) {
log::error("open dir error, dir={}", GATT_HASH_PATH);
return;
}
time_t current_time = time(NULL);
time_t lru_time = current_time;
size_t count = 0;
string candidate_item;
vector expired_items;
log::debug("<-----------Start Local Hash Cache---------->");
dirent* dp;
while ((dp = readdir(dirp.get())) != nullptr) {
if (strncmp(".", dp->d_name, 1) == 0 || strncmp("..", dp->d_name, 2) == 0) {
continue;
}
// pattern match: gatt_hash_
size_t fname_len = strlen(dp->d_name);
size_t pattern_len = strlen(GATT_HASH_FILE_PREFIX);
if (pattern_len > fname_len) {
continue;
}
// check if the file name has gatt_hash_ as prefix
char tmp[255] = {0};
strncpy(tmp, dp->d_name, pattern_len);
if (strncmp(tmp, GATT_HASH_FILE_PREFIX, pattern_len) != 0) {
continue;
}
// increase hash file count
count++;
// generate the full path, in order to get the state of the file
snprintf(tmp, 255, "%s/%s", GATT_HASH_PATH, dp->d_name);
struct stat buf;
int result = lstat(tmp, &buf);
log::debug("name={}, result={}, linknum={}, mtime={}", dp->d_name, result,
(unsigned long)buf.st_nlink, (unsigned long)buf.st_mtime);
// if hard link count of the file is 1, it means no trusted device links to
// the inode. It is safe to be a candidate to be removed
if (buf.st_nlink == 1) {
if (buf.st_mtime < lru_time) {
lru_time = buf.st_mtime;
// Find the LRU candidate during for-loop itreation.
candidate_item.assign(tmp);
}
if (buf.st_mtime + GATT_HASH_EXPIRED_TIME < current_time) {
// Add expired item.
expired_items.emplace_back(tmp);
}
}
}
log::debug("<-----------End Local Hash Cache------------>");
// if the number of hash files exceeds the limit, remove the cadidate item.
if (count > GATT_HASH_MAX_SIZE && !candidate_item.empty()) {
unlink(candidate_item.c_str());
log::debug("delete hash file (size), name={}", candidate_item);
}
// If there is any file expired, also delete it.
for (string expired_item : expired_items) {
unlink(expired_item.c_str());
log::debug("delete hash file (expired), name={}", expired_item);
}
}