blob: 0fed99478cdaf94443f0e4d046370d43cd69ff1d [file] [log] [blame]
/*
*
* Copyright (c) 2020-2023 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 "BluezAdvertisement.h"
#include <memory>
#include <unistd.h>
#include <gio/gio.h>
#include <glib-object.h>
#include <glib.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 "BluezEndpoint.h"
#include "Types.h"
namespace chip {
namespace DeviceLayer {
namespace Internal {
BluezLEAdvertisement1 * BluezAdvertisement::CreateLEAdvertisement()
{
BluezLEAdvertisement1 * adv;
BluezObjectSkeleton * object;
GVariant * serviceUUID;
GVariantBuilder serviceUUIDsBuilder;
ChipLogDetail(DeviceLayer, "Create BLE adv object at %s", mAdvPath);
object = bluez_object_skeleton_new(mAdvPath);
adv = bluez_leadvertisement1_skeleton_new();
g_variant_builder_init(&serviceUUIDsBuilder, G_VARIANT_TYPE("as"));
g_variant_builder_add(&serviceUUIDsBuilder, "s", mAdvUUID);
serviceUUID = g_variant_builder_end(&serviceUUIDsBuilder);
bluez_leadvertisement1_set_type_(adv, "peripheral");
bluez_leadvertisement1_set_service_uuids(adv, serviceUUID);
// empty manufacturer data
// empty solicit UUIDs
// empty data
// 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_leadvertisement1_set_discoverable(adv, TRUE);
// empty discoverable timeout for infinite discoverability
// empty includes
bluez_leadvertisement1_set_local_name(adv, mAdvName);
bluez_leadvertisement1_set_appearance(adv, 0xffff /* no appearance */);
// empty duration
// empty timeout
// empty secondary channel for now
bluez_object_skeleton_set_leadvertisement1(object, adv);
g_signal_connect(adv, "handle-release",
G_CALLBACK(+[](BluezLEAdvertisement1 * aAdv, GDBusMethodInvocation * aInv, BluezAdvertisement * self) {
return self->BluezLEAdvertisement1Release(aAdv, aInv);
}),
this);
g_dbus_object_manager_server_export(mRoot.get(), G_DBUS_OBJECT_SKELETON(object));
g_object_unref(object);
return adv;
}
gboolean BluezAdvertisement::BluezLEAdvertisement1Release(BluezLEAdvertisement1 * aAdv, GDBusMethodInvocation * aInvocation)
{
// This method is called when the advertisement is stopped (released) by BlueZ.
// We can use it to update the state of the advertisement in the CHIP layer.
ChipLogDetail(DeviceLayer, "BLE advertisement stopped by BlueZ");
mIsAdvertising = false;
BLEManagerImpl::NotifyBLEPeripheralAdvReleased();
return TRUE;
}
CHIP_ERROR BluezAdvertisement::InitImpl()
{
// When creating D-Bus proxy object, 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);
mAdv.reset(CreateLEAdvertisement());
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezAdvertisement::Init(const BluezEndpoint & aEndpoint, const char * aAdvUUID, const char * aAdvName)
{
GAutoPtr<char> rootPath;
CHIP_ERROR err;
VerifyOrExit(!mAdv, err = CHIP_ERROR_INCORRECT_STATE;
ChipLogError(DeviceLayer, "FAIL: BLE advertisement already initialized in %s", __func__));
mRoot.reset(reinterpret_cast<GDBusObjectManagerServer *>(g_object_ref(aEndpoint.GetGattApplicationObjectManager())));
mAdapter.reset(reinterpret_cast<BluezAdapter1 *>(g_object_ref(aEndpoint.GetAdapter())));
g_object_get(G_OBJECT(mRoot.get()), "object-path", &rootPath.GetReceiver(), nullptr);
g_snprintf(mAdvPath, sizeof(mAdvPath), "%s/advertising", rootPath.get());
g_strlcpy(mAdvUUID, aAdvUUID, sizeof(mAdvUUID));
if (aAdvName != nullptr)
{
g_strlcpy(mAdvName, aAdvName, sizeof(mAdvName));
}
else
{
// Advertising name corresponding to the PID, for debug purposes.
g_snprintf(mAdvName, sizeof(mAdvName), "%s%04x", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, getpid() & 0xffff);
}
err = PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezAdvertisement * self) { return self->InitImpl(); }, this);
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE,
ChipLogError(Ble, "Failed to schedule BLE advertisement Init() on CHIPoBluez thread"));
mIsInitialized = true;
exit:
return err;
}
CHIP_ERROR BluezAdvertisement::SetIntervals(AdvertisingIntervals aAdvIntervals)
{
VerifyOrReturnError(mAdv, CHIP_ERROR_UNINITIALIZED);
// If the advertisement is already running, BlueZ will update the intervals
// automatically. There is no need to stop and restart the advertisement.
bluez_leadvertisement1_set_min_interval(mAdv.get(), aAdvIntervals.first * 0.625);
bluez_leadvertisement1_set_max_interval(mAdv.get(), aAdvIntervals.second * 0.625);
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezAdvertisement::SetupServiceData(ServiceDataFlags aFlags)
{
VerifyOrReturnError(mAdv, CHIP_ERROR_UNINITIALIZED);
Ble::ChipBLEDeviceIdentificationInfo deviceInfo;
ReturnErrorOnFailure(ConfigurationMgr().GetBLEDeviceIdentificationInfo(deviceInfo));
#if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING
deviceInfo.SetAdditionalDataFlag(true);
#endif
#if CHIP_DEVICE_CONFIG_BLE_EXT_ADVERTISING
if (aFlags & kServiceDataExtendedAnnouncement)
{
deviceInfo.SetExtendedAnnouncementFlag(true);
// In case of extended advertisement, specification requires that
// the vendor ID and product ID are set to 0.
deviceInfo.SetVendorId(0);
deviceInfo.SetProductId(0);
}
#endif
GVariantBuilder serviceDataBuilder;
g_variant_builder_init(&serviceDataBuilder, G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(&serviceDataBuilder, "{sv}", mAdvUUID,
g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &deviceInfo, sizeof(deviceInfo), sizeof(uint8_t)));
GVariant * serviceData = g_variant_builder_end(&serviceDataBuilder);
GAutoPtr<char> debugStr(g_variant_print(serviceData, TRUE));
ChipLogDetail(DeviceLayer, "SET service data to %s", StringOrNullMarker(debugStr.get()));
bluez_leadvertisement1_set_service_data(mAdv.get(), serviceData);
return CHIP_NO_ERROR;
}
void BluezAdvertisement::Shutdown()
{
VerifyOrReturn(mIsInitialized);
// Make sure that the advertisement is stopped before we start cleaning up.
if (mIsAdvertising)
{
CHIP_ERROR err = Stop();
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "Failed to stop BLE advertisement before shutdown: %" CHIP_ERROR_FORMAT, err.Format());
}
}
// Release resources on the glib thread to synchronize with potential signal handlers
// attached to the advertising object that may run on the glib thread.
PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezAdvertisement * self) {
// The object manager server (mRoot) might not be released right away (it may be held
// by other BLE layer objects). We need to unexport the advertisement object in the
// explicit way to make sure that we can export it again in the Init() method.
g_dbus_object_manager_server_unexport(self->mRoot.get(), self->mAdvPath);
self->mRoot.reset();
self->mAdapter.reset();
self->mAdv.reset();
return CHIP_NO_ERROR;
},
this);
mIsInitialized = false;
}
void BluezAdvertisement::StartDone(GObject * aObject, GAsyncResult * aResult)
{
auto * advMgr = reinterpret_cast<BluezLEAdvertisingManager1 *>(aObject);
GAutoPtr<GError> error;
gboolean success = FALSE;
success = bluez_leadvertising_manager1_call_register_advertisement_finish(advMgr, aResult, &error.GetReceiver());
VerifyOrExit(success == TRUE, ChipLogError(DeviceLayer, "FAIL: RegisterAdvertisement : %s", error->message));
mIsAdvertising = true;
ChipLogDetail(DeviceLayer, "RegisterAdvertisement complete");
exit:
BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(success == TRUE);
}
CHIP_ERROR BluezAdvertisement::StartImpl()
{
GDBusObject * adapterObject;
GAutoPtr<BluezLEAdvertisingManager1> advMgr;
GVariantBuilder optionsBuilder;
GVariant * options;
VerifyOrExit(!mIsAdvertising, ChipLogError(DeviceLayer, "FAIL: Advertising has already been enabled in %s", __func__));
VerifyOrExit(mAdapter.get() != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL mAdapter in %s", __func__));
adapterObject = g_dbus_interface_get_object(G_DBUS_INTERFACE(mAdapter.get()));
VerifyOrExit(adapterObject != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL adapterObject in %s", __func__));
advMgr.reset(bluez_object_get_leadvertising_manager1(reinterpret_cast<BluezObject *>(adapterObject)));
VerifyOrExit(advMgr.get() != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL advMgr in %s", __func__));
g_variant_builder_init(&optionsBuilder, G_VARIANT_TYPE("a{sv}"));
options = g_variant_builder_end(&optionsBuilder);
bluez_leadvertising_manager1_call_register_advertisement(
advMgr.get(), mAdvPath, options, nullptr,
[](GObject * aObject, GAsyncResult * aResult, void * aData) {
reinterpret_cast<BluezAdvertisement *>(aData)->StartDone(aObject, aResult);
},
this);
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezAdvertisement::Start()
{
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezAdvertisement * self) { return self->StartImpl(); }, this);
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE,
ChipLogError(Ble, "Failed to schedule BLE advertisement Start() on CHIPoBluez thread"));
return err;
}
void BluezAdvertisement::StopDone(GObject * aObject, GAsyncResult * aResult)
{
auto * advMgr = reinterpret_cast<BluezLEAdvertisingManager1 *>(aObject);
GAutoPtr<GError> error;
gboolean success = FALSE;
success = bluez_leadvertising_manager1_call_unregister_advertisement_finish(advMgr, aResult, &error.GetReceiver());
VerifyOrExit(success == TRUE, ChipLogError(DeviceLayer, "FAIL: UnregisterAdvertisement: %s", error->message));
mIsAdvertising = false;
ChipLogDetail(DeviceLayer, "UnregisterAdvertisement complete");
exit:
BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(success == TRUE);
}
CHIP_ERROR BluezAdvertisement::StopImpl()
{
GDBusObject * adapterObject;
GAutoPtr<BluezLEAdvertisingManager1> advMgr;
VerifyOrExit(mIsAdvertising, ChipLogError(DeviceLayer, "FAIL: Advertising has already been disabled in %s", __func__));
VerifyOrExit(mAdapter.get() != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL mAdapter in %s", __func__));
adapterObject = g_dbus_interface_get_object(G_DBUS_INTERFACE(mAdapter.get()));
VerifyOrExit(adapterObject != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL adapterObject in %s", __func__));
advMgr.reset(bluez_object_get_leadvertising_manager1(reinterpret_cast<BluezObject *>(adapterObject)));
VerifyOrExit(advMgr.get() != nullptr, ChipLogError(DeviceLayer, "FAIL: NULL advMgr in %s", __func__));
bluez_leadvertising_manager1_call_unregister_advertisement(
advMgr.get(), mAdvPath, nullptr,
[](GObject * aObject, GAsyncResult * aResult, void * aData) {
reinterpret_cast<BluezAdvertisement *>(aData)->StopDone(aObject, aResult);
},
this);
exit:
return CHIP_NO_ERROR;
}
CHIP_ERROR BluezAdvertisement::Stop()
{
VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(
+[](BluezAdvertisement * self) { return self->StopImpl(); }, this);
VerifyOrReturnError(err == CHIP_NO_ERROR, CHIP_ERROR_INCORRECT_STATE,
ChipLogError(Ble, "Failed to schedule BLE advertisement Stop() on CHIPoBluez thread"));
return err;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip