blob: 2d555edc562be86f5688817dbd7271175e81feb3 [file] [log] [blame]
/*
*
* Copyright (c) 2024 Project CHIP Authors
*
* 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 "BluezObjectManager.h"
#include <algorithm>
#include <memory>
#include <string>
#include <string_view>
#include <gio/gio.h>
#include <glib-object.h>
#include <glib.h>
#include <ble/Ble.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/ConfigurationManager.h>
#include <platform/GLibTypeDeleter.h>
#include <platform/Linux/dbus/bluez/DbusBluez.h>
#include <platform/PlatformManager.h>
#include "Types.h"
namespace chip {
namespace DeviceLayer {
namespace Internal {
namespace {
const char * GetAdapterObjectPath(BluezAdapter1 * aAdapter)
{
return g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(aAdapter));
}
} // namespace
CHIP_ERROR BluezObjectManager::Init()
{
return PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezObjectManager * self) {
ReturnErrorOnFailure(self->SetupDBusConnection());
ReturnErrorOnFailure(self->SetupObjectManager());
return CHIP_NO_ERROR;
},
this);
}
void BluezObjectManager::Shutdown()
{
// Run endpoint cleanup on the CHIPoBluez thread. This is necessary because the
// cleanup function releases the D-Bus manager client object, which handles D-Bus
// signals. Otherwise, we will face race condition when the D-Bus signal is in
// the middle of being processed when the cleanup function is called.
PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezObjectManager * self) {
self->mConnection.reset();
self->mObjectManager.reset();
return CHIP_NO_ERROR;
},
this);
}
BluezAdapter1 * BluezObjectManager::GetAdapter(unsigned int aAdapterId)
{
char expectedPath[32];
snprintf(expectedPath, sizeof(expectedPath), BLUEZ_PATH "/hci%u", aAdapterId);
for (BluezObject & object : GetObjects())
{
GAutoPtr<BluezAdapter1> adapter(bluez_object_get_adapter1(&object));
if (adapter && strcmp(g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(adapter.get())), expectedPath) == 0)
{
SetupAdapter(adapter.get());
return adapter.release();
}
}
return nullptr;
}
BluezAdapter1 * BluezObjectManager::GetAdapter(const char * aAdapterAddress)
{
for (BluezObject & object : GetObjects())
{
GAutoPtr<BluezAdapter1> adapter(bluez_object_get_adapter1(&object));
if (adapter && strcmp(bluez_adapter1_get_address(adapter.get()), aAdapterAddress) == 0)
{
SetupAdapter(adapter.get());
return adapter.release();
}
}
return nullptr;
}
CHIP_ERROR BluezObjectManager::SubscribeDeviceNotifications(BluezAdapter1 * aAdapter,
BluezObjectManagerAdapterNotificationsDelegate * aDelegate)
{
std::lock_guard<std::mutex> lock(mSubscriptionsMutex);
mSubscriptions.emplace_back(GetAdapterObjectPath(aAdapter), aDelegate);
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezObjectManager::UnsubscribeDeviceNotifications(BluezAdapter1 * aAdapter,
BluezObjectManagerAdapterNotificationsDelegate * aDelegate)
{
std::lock_guard<std::mutex> lock(mSubscriptionsMutex);
const auto item = std::make_pair(std::string(GetAdapterObjectPath(aAdapter)), aDelegate);
mSubscriptions.erase(std::remove(mSubscriptions.begin(), mSubscriptions.end(), item), mSubscriptions.end());
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezObjectManager::SetupAdapter(BluezAdapter1 * aAdapter)
{
// Make sure the adapter is powered on.
bluez_adapter1_set_powered(aAdapter, TRUE);
// Setting "Discoverable" to False on the adapter and to True on the advertisement convinces
// BlueZ to set "BR/EDR Not Supported" flag. BlueZ doesn't provide API to do that explicitly
// and the flag is necessary to force using LE transport.
bluez_adapter1_set_discoverable(aAdapter, FALSE);
return CHIP_NO_ERROR;
}
void BluezObjectManager::NotifyAdapterAdded(BluezAdapter1 * aAdapter)
{
unsigned int adapterId = 0;
sscanf(GetAdapterObjectPath(aAdapter), BLUEZ_PATH "/hci%u", &adapterId);
// Notify the application that new adapter has been just added
BLEManagerImpl::NotifyBLEAdapterAdded(adapterId, bluez_adapter1_get_address(aAdapter));
}
void BluezObjectManager::NotifyAdapterRemoved(BluezAdapter1 * aAdapter)
{
unsigned int adapterId = 0;
sscanf(GetAdapterObjectPath(aAdapter), BLUEZ_PATH "/hci%u", &adapterId);
// Notify the application that the adapter is no longer available
BLEManagerImpl::NotifyBLEAdapterRemoved(adapterId, bluez_adapter1_get_address(aAdapter));
}
void BluezObjectManager::RemoveAdapterSubscriptions(BluezAdapter1 * aAdapter)
{
std::lock_guard<std::mutex> lock(mSubscriptionsMutex);
const auto adapterPath = GetAdapterObjectPath(aAdapter);
// Remove all device notification subscriptions for the given adapter
mSubscriptions.erase(std::remove_if(mSubscriptions.begin(), mSubscriptions.end(),
[adapterPath](const auto & subscription) { return subscription.first == adapterPath; }),
mSubscriptions.end());
}
CHIP_ERROR BluezObjectManager::SetupDBusConnection()
{
GAutoPtr<GError> err;
mConnection.reset(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &err.GetReceiver()));
VerifyOrReturnError(mConnection != nullptr, CHIP_ERROR_INTERNAL,
ChipLogError(DeviceLayer, "FAIL: Get D-Bus system bus: %s", err->message));
return CHIP_NO_ERROR;
}
BluezObjectManager::NotificationsDelegates BluezObjectManager::GetDeviceNotificationsDelegates(BluezDevice1 * device)
{
const char * deviceAdapterPath = bluez_device1_get_adapter(device);
NotificationsDelegates delegates;
std::lock_guard<std::mutex> lock(mSubscriptionsMutex);
for (auto & [adapterPath, delegate] : mSubscriptions)
{
if (adapterPath == deviceAdapterPath)
{
delegates.push_back(delegate);
}
}
return delegates;
}
void BluezObjectManager::OnObjectAdded(GDBusObjectManager * aMgr, BluezObject * aObj)
{
GAutoPtr<BluezAdapter1> adapter(bluez_object_get_adapter1(aObj));
// Verify that the adapter is properly initialized - the class property must be set.
// BlueZ can export adapter objects on the bus before it is fully initialized. Such
// adapter objects are not usable and must be ignored.
//
// TODO: Find a better way to determine whether the adapter interface exposed by
// BlueZ D-Bus service is fully functional. The current approach is based on
// the assumption that the class property is non-zero, which is true only
// for BR/EDR + LE adapters. LE-only adapters do not have HCI command to read
// the class property and BlueZ sets it to 0 as a default value.
if (adapter && bluez_adapter1_get_class(adapter.get()) != 0)
{
NotifyAdapterAdded(adapter.get());
return;
}
GAutoPtr<BluezDevice1> device(bluez_object_get_device1(aObj));
if (device)
{
for (auto delegate : GetDeviceNotificationsDelegates(device.get()))
{
delegate->OnDeviceAdded(*device.get());
}
}
}
void BluezObjectManager::OnObjectRemoved(GDBusObjectManager * aMgr, BluezObject * aObj)
{
GAutoPtr<BluezAdapter1> adapter(bluez_object_get_adapter1(aObj));
if (adapter)
{
RemoveAdapterSubscriptions(adapter.get());
NotifyAdapterRemoved(adapter.get());
return;
}
GAutoPtr<BluezDevice1> device(bluez_object_get_device1(aObj));
if (device)
{
for (auto delegate : GetDeviceNotificationsDelegates(device.get()))
{
delegate->OnDeviceRemoved(*device.get());
}
}
}
void BluezObjectManager::OnInterfacePropertiesChanged(GDBusObjectManagerClient * aMgr, BluezObject * aObj, GDBusProxy * aIface,
GVariant * aChangedProps, const char * const * aInvalidatedProps)
{
uint32_t classValue = 0;
GAutoPtr<BluezAdapter1> adapter(bluez_object_get_adapter1(aObj));
// When the adapter's readonly class property is set, it means that the adapter has been
// fully initialized and is ready to be used. It's most likely that the adapter has been
// previously ignored in the OnObjectAdded callback, so now we can notify the application
// about the new adapter.
if (adapter && g_variant_lookup(aChangedProps, "Class", "u", &classValue) && classValue != 0)
{
NotifyAdapterAdded(adapter.get());
return;
}
GAutoPtr<BluezDevice1> device(bluez_object_get_device1(aObj));
if (device)
{
for (auto delegate : GetDeviceNotificationsDelegates(device.get()))
{
delegate->OnDevicePropertyChanged(*device.get(), aChangedProps, aInvalidatedProps);
}
}
}
CHIP_ERROR BluezObjectManager::SetupObjectManager()
{
// When connecting to signals, the thread default context must be initialized. Otherwise,
// all D-Bus signals will be delivered to the GLib global default main context.
VerifyOrDie(g_main_context_get_thread_default() != nullptr);
GAutoPtr<GError> err;
mObjectManager.reset(g_dbus_object_manager_client_new_sync(
mConnection.get(), G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, BLUEZ_INTERFACE, "/",
bluez_object_manager_client_get_proxy_type, nullptr /* unused user data in the proxy type func */,
nullptr /* destroy notify */, nullptr /* cancellable */, &err.GetReceiver()));
VerifyOrReturnError(mObjectManager, CHIP_ERROR_INTERNAL,
ChipLogError(DeviceLayer, "FAIL: Get D-Bus object manager client: %s", err->message));
g_signal_connect(mObjectManager.get(), "object-added",
G_CALLBACK(+[](GDBusObjectManager * mgr, GDBusObject * obj, BluezObjectManager * self) {
return self->OnObjectAdded(mgr, reinterpret_cast<BluezObject *>(obj));
}),
this);
g_signal_connect(mObjectManager.get(), "object-removed",
G_CALLBACK(+[](GDBusObjectManager * mgr, GDBusObject * obj, BluezObjectManager * self) {
return self->OnObjectRemoved(mgr, reinterpret_cast<BluezObject *>(obj));
}),
this);
g_signal_connect(mObjectManager.get(), "interface-proxy-properties-changed",
G_CALLBACK(+[](GDBusObjectManagerClient * mgr, GDBusObjectProxy * obj, GDBusProxy * iface, GVariant * changed,
const char * const * invalidated, BluezObjectManager * self) {
return self->OnInterfacePropertiesChanged(mgr, reinterpret_cast<BluezObject *>(obj), iface, changed,
invalidated);
}),
this);
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezCallToChipError(const GError * aError)
{
switch (aError->code)
{
// BlueZ crashed or the D-Bus connection is broken in both cases adapter is not available.
case G_DBUS_ERROR_NO_REPLY:
// BlueZ service is not available on the bus, hence the adapter is not available too.
case G_DBUS_ERROR_SERVICE_UNKNOWN:
// Requested D-Bus object is not available on the given path. This happens when the adapter
// was unplugged and unregistered from the BlueZ object manager.
case G_DBUS_ERROR_UNKNOWN_OBJECT:
return BLE_ERROR_ADAPTER_UNAVAILABLE;
default:
return CHIP_ERROR_INTERNAL;
}
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip