blob: a2644998a0408623e209c8ac2c9ce1ef06c376c4 [file] [log] [blame]
/*
* Copyright 2021 HIMSA II K/S - www.himsa.com.
* Represented by EHIMA - www.ehima.com
*
* 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 <base/logging.h>
#include <bluetooth/log.h>
#include <algorithm>
#include <limits>
#include <map>
#include <mutex>
#include <unordered_set>
#include "bta_groups.h"
#include "btif/include/btif_profile_storage.h"
#include "os/logging/log_adapter.h"
#include "stack/include/bt_types.h"
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"
using bluetooth::Uuid;
namespace bluetooth {
namespace groups {
class DeviceGroupsImpl;
DeviceGroupsImpl* instance;
std::mutex instance_mutex;
static constexpr int kMaxGroupId = 0xEF;
class DeviceGroup {
public:
DeviceGroup(int group_id, Uuid uuid)
: group_id_(group_id), group_uuid_(uuid) {}
void Add(const RawAddress& addr) { devices_.insert(addr); }
void Remove(const RawAddress& addr) { devices_.erase(addr); }
bool Contains(const RawAddress& addr) const {
return (devices_.count(addr) != 0);
}
void ForEachDevice(std::function<void(const RawAddress&)> cb) const {
for (auto const& addr : devices_) {
cb(addr);
}
}
int Size(void) const { return devices_.size(); }
int GetGroupId(void) const { return group_id_; }
const Uuid& GetUuid(void) const { return group_uuid_; }
private:
friend std::ostream& operator<<(std::ostream& out,
const bluetooth::groups::DeviceGroup& value);
int group_id_;
Uuid group_uuid_;
std::unordered_set<RawAddress> devices_;
};
class DeviceGroupsImpl : public DeviceGroups {
static constexpr uint8_t GROUP_STORAGE_CURRENT_LAYOUT_MAGIC = 0x10;
static constexpr size_t GROUP_STORAGE_HEADER_SZ =
sizeof(GROUP_STORAGE_CURRENT_LAYOUT_MAGIC) +
sizeof(uint8_t); /* num_of_groups */
static constexpr size_t GROUP_STORAGE_ENTRY_SZ =
sizeof(uint8_t) /* group_id */ + Uuid::kNumBytes128;
public:
DeviceGroupsImpl(DeviceGroupsCallbacks* callbacks) {
AddCallbacks(callbacks);
btif_storage_load_bonded_groups();
}
int GetGroupId(const RawAddress& addr, Uuid uuid) const override {
for (const auto& [id, g] : groups_) {
if ((g.Contains(addr)) && (uuid == g.GetUuid())) return id;
}
return kGroupUnknown;
}
void add_to_group(const RawAddress& addr, DeviceGroup* group) {
group->Add(addr);
bool first_device_in_group = (group->Size() == 1);
for (auto c : callbacks_) {
if (first_device_in_group) {
c->OnGroupAdded(addr, group->GetUuid(), group->GetGroupId());
} else {
c->OnGroupMemberAdded(addr, group->GetGroupId());
}
}
}
int AddDevice(const RawAddress& addr, Uuid uuid, int group_id) override {
DeviceGroup* group = nullptr;
if (group_id == kGroupUnknown) {
auto gid = GetGroupId(addr, uuid);
if (gid != kGroupUnknown) return gid;
group = create_group(uuid);
} else {
group = get_or_create_group_with_id(group_id, uuid);
if (!group) {
return kGroupUnknown;
}
}
LOG_ASSERT(group);
if (group->Contains(addr)) {
log::error("device {} already in the group: {}",
ADDRESS_TO_LOGGABLE_STR(addr), group_id);
return group->GetGroupId();
}
add_to_group(addr, group);
btif_storage_add_groups(addr);
return group->GetGroupId();
}
void RemoveDevice(const RawAddress& addr, int group_id) override {
int num_of_groups_dev_belongs = 0;
/* Remove from all the groups. Usually happens on unbond */
for (auto it = groups_.begin(); it != groups_.end();) {
auto& [id, g] = *it;
if (!g.Contains(addr)) {
++it;
continue;
}
num_of_groups_dev_belongs++;
if ((group_id != bluetooth::groups::kGroupUnknown) && (group_id != id)) {
++it;
continue;
}
num_of_groups_dev_belongs--;
g.Remove(addr);
for (auto c : callbacks_) {
c->OnGroupMemberRemoved(addr, id);
}
if (g.Size() == 0) {
for (auto c : callbacks_) {
c->OnGroupRemoved(g.GetUuid(), g.GetGroupId());
}
it = groups_.erase(it);
} else {
++it;
}
}
btif_storage_remove_groups(addr);
if (num_of_groups_dev_belongs > 0) {
btif_storage_add_groups(addr);
}
}
bool SerializeGroups(const RawAddress& addr,
std::vector<uint8_t>& out) const {
auto num_groups = std::count_if(
groups_.begin(), groups_.end(), [&addr](auto& id_group_pair) {
return id_group_pair.second.Contains(addr);
});
if ((num_groups == 0) || (num_groups > std::numeric_limits<uint8_t>::max()))
return false;
out.resize(GROUP_STORAGE_HEADER_SZ + (num_groups * GROUP_STORAGE_ENTRY_SZ));
auto* ptr = out.data();
/* header */
UINT8_TO_STREAM(ptr, GROUP_STORAGE_CURRENT_LAYOUT_MAGIC);
UINT8_TO_STREAM(ptr, num_groups);
/* group entries */
for (const auto& [id, g] : groups_) {
if (g.Contains(addr)) {
UINT8_TO_STREAM(ptr, id);
Uuid::UUID128Bit uuid128 = g.GetUuid().To128BitLE();
memcpy(ptr, uuid128.data(), Uuid::kNumBytes128);
ptr += Uuid::kNumBytes128;
}
}
return true;
}
void DeserializeGroups(const RawAddress& addr,
const std::vector<uint8_t>& in) {
if (in.size() < GROUP_STORAGE_HEADER_SZ + GROUP_STORAGE_ENTRY_SZ) return;
auto* ptr = in.data();
uint8_t magic;
STREAM_TO_UINT8(magic, ptr);
if (magic == GROUP_STORAGE_CURRENT_LAYOUT_MAGIC) {
uint8_t num_groups;
STREAM_TO_UINT8(num_groups, ptr);
if (in.size() <
GROUP_STORAGE_HEADER_SZ + (num_groups * GROUP_STORAGE_ENTRY_SZ)) {
log::error("Invalid persistent storage data");
return;
}
/* group entries */
while (num_groups--) {
uint8_t id;
STREAM_TO_UINT8(id, ptr);
Uuid::UUID128Bit uuid128;
STREAM_TO_ARRAY(uuid128.data(), ptr, (int)Uuid::kNumBytes128);
auto* group =
get_or_create_group_with_id(id, Uuid::From128BitLE(uuid128));
if (group) add_to_group(addr, group);
for (auto c : callbacks_) {
c->OnGroupAddFromStorage(addr, Uuid::From128BitLE(uuid128), id);
}
}
}
}
void AddCallbacks(DeviceGroupsCallbacks* callbacks) {
callbacks_.push_back(std::move(callbacks));
/* Notify new user about known groups */
for (const auto& [id, g] : groups_) {
auto group_uuid = g.GetUuid();
auto group_id = g.GetGroupId();
g.ForEachDevice([&](auto& dev) {
callbacks->OnGroupAdded(dev, group_uuid, group_id);
});
}
}
bool Clear(DeviceGroupsCallbacks* callbacks) {
auto it = find_if(callbacks_.begin(), callbacks_.end(),
[callbacks](auto c) { return c == callbacks; });
if (it != callbacks_.end()) callbacks_.erase(it);
if (callbacks_.size() != 0) {
return false;
}
/* When all clients were unregistered */
groups_.clear();
return true;
}
void Dump(int fd) {
std::stringstream stream;
stream << " Num. registered clients: " << callbacks_.size() << std::endl;
stream << " Groups:\n";
for (const auto& kv_pair : groups_) {
stream << kv_pair.second << std::endl;
}
dprintf(fd, "%s", stream.str().c_str());
}
private:
DeviceGroup* find_device_group(int group_id) {
return groups_.count(group_id) ? &groups_.at(group_id) : nullptr;
}
DeviceGroup* get_or_create_group_with_id(int group_id, Uuid uuid) {
auto group = find_device_group(group_id);
if (group) {
if (group->GetUuid() != uuid) {
log::error(
"group {} exists but for different uuid: {}, user request uuid: {}",
group_id, group->GetUuid(), uuid);
return nullptr;
}
log::info("group already exists: {}", group_id);
return group;
}
DeviceGroup new_group(group_id, uuid);
groups_.insert({group_id, std::move(new_group)});
return &groups_.at(group_id);
}
DeviceGroup* create_group(Uuid& uuid) {
/* Generate new group id and return empty group */
/* Find first free id */
int group_id = -1;
for (int i = 1; i < kMaxGroupId; i++) {
if (groups_.count(i) == 0) {
group_id = i;
break;
}
}
if (group_id < 0) {
log::error("too many groups");
return nullptr;
}
DeviceGroup group(group_id, uuid);
groups_.insert({group_id, std::move(group)});
return &groups_.at(group_id);
}
std::map<int, DeviceGroup> groups_;
std::list<DeviceGroupsCallbacks*> callbacks_;
};
void DeviceGroups::Initialize(DeviceGroupsCallbacks* callbacks) {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (instance == nullptr) {
instance = new DeviceGroupsImpl(callbacks);
return;
}
instance->AddCallbacks(callbacks);
}
void DeviceGroups::AddFromStorage(const RawAddress& addr,
const std::vector<uint8_t>& in) {
if (!instance) {
log::error("Not initialized yet");
return;
}
instance->DeserializeGroups(addr, in);
}
bool DeviceGroups::GetForStorage(const RawAddress& addr,
std::vector<uint8_t>& out) {
if (!instance) {
log::error("Not initialized yet");
return false;
}
return instance->SerializeGroups(addr, out);
}
void DeviceGroups::CleanUp(DeviceGroupsCallbacks* callbacks) {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (!instance) return;
if (instance->Clear(callbacks)) {
delete (instance);
instance = nullptr;
}
}
std::ostream& operator<<(std::ostream& out,
bluetooth::groups::DeviceGroup const& group) {
out << " == Group id: " << group.group_id_ << " == \n"
<< " Uuid: " << group.group_uuid_ << std::endl;
out << " Devices:\n";
for (auto const& addr : group.devices_) {
out << " " << ADDRESS_TO_LOGGABLE_STR(addr) << std::endl;
}
return out;
}
void DeviceGroups::DebugDump(int fd) {
std::scoped_lock<std::mutex> lock(instance_mutex);
dprintf(fd, "Device Groups Manager:\n");
if (instance)
instance->Dump(fd);
else
dprintf(fd, " Not initialized \n");
}
DeviceGroups* DeviceGroups::Get() { return instance; }
} // namespace groups
} // namespace bluetooth