Coverage Report

Created: 2025-06-20 06:55

/src/connectedhomeip/src/platform/Linux/bluez/BluezAdvertisement.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 *
3
 *    Copyright (c) 2020-2023 Project CHIP Authors
4
 *
5
 *    Licensed under the Apache License, Version 2.0 (the "License");
6
 *    you may not use this file except in compliance with the License.
7
 *    You may obtain a copy of the License at
8
 *
9
 *        http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 *    Unless required by applicable law or agreed to in writing, software
12
 *    distributed under the License is distributed on an "AS IS" BASIS,
13
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 *    See the License for the specific language governing permissions and
15
 *    limitations under the License.
16
 */
17
18
#include "BluezAdvertisement.h"
19
20
#include <memory>
21
#include <unistd.h>
22
23
#include <gio/gio.h>
24
#include <glib-object.h>
25
#include <glib.h>
26
27
#include <ble/Ble.h>
28
#include <lib/support/CodeUtils.h>
29
#include <lib/support/logging/CHIPLogging.h>
30
#include <platform/ConfigurationManager.h>
31
#include <platform/GLibTypeDeleter.h>
32
#include <platform/Linux/dbus/bluez/DBusBluez.h>
33
#include <platform/PlatformManager.h>
34
35
#include "BluezEndpoint.h"
36
#include "Types.h"
37
38
namespace chip {
39
namespace DeviceLayer {
40
namespace Internal {
41
42
BluezLEAdvertisement1 * BluezAdvertisement::CreateLEAdvertisement()
43
0
{
44
0
    BluezLEAdvertisement1 * adv;
45
0
    BluezObjectSkeleton * object;
46
0
    GVariant * serviceUUID;
47
0
    GVariantBuilder serviceUUIDsBuilder;
48
49
0
    ChipLogDetail(DeviceLayer, "Create BLE adv object at %s", mAdvPath);
50
0
    object = bluez_object_skeleton_new(mAdvPath);
51
52
0
    adv = bluez_leadvertisement1_skeleton_new();
53
54
0
    g_variant_builder_init(&serviceUUIDsBuilder, G_VARIANT_TYPE("as"));
55
0
    g_variant_builder_add(&serviceUUIDsBuilder, "s", mAdvUUID);
56
57
0
    serviceUUID = g_variant_builder_end(&serviceUUIDsBuilder);
58
59
0
    bluez_leadvertisement1_set_type_(adv, "peripheral");
60
0
    bluez_leadvertisement1_set_service_uuids(adv, serviceUUID);
61
    // empty manufacturer data
62
    // empty solicit UUIDs
63
    // empty data
64
65
    // Setting "Discoverable" to False on the adapter and to True on the advertisement convinces
66
    // Bluez to set "BR/EDR Not Supported" flag. Bluez doesn't provide API to do that explicitly
67
    // and the flag is necessary to force using LE transport.
68
0
    bluez_leadvertisement1_set_discoverable(adv, TRUE);
69
    // empty discoverable timeout for infinite discoverability
70
71
    // empty includes
72
0
    bluez_leadvertisement1_set_local_name(adv, mAdvName);
73
0
    bluez_leadvertisement1_set_appearance(adv, 0xffff /* no appearance */);
74
    // empty duration
75
    // empty timeout
76
    // empty secondary channel for now
77
78
0
    bluez_object_skeleton_set_leadvertisement1(object, adv);
79
0
    g_signal_connect(adv, "handle-release",
80
0
                     G_CALLBACK(+[](BluezLEAdvertisement1 * aAdv, GDBusMethodInvocation * aInv, BluezAdvertisement * self) {
81
0
                         return self->BluezLEAdvertisement1Release(aAdv, aInv);
82
0
                     }),
83
0
                     this);
84
85
0
    g_dbus_object_manager_server_export(mEndpoint.GetGattApplicationObjectManager(), G_DBUS_OBJECT_SKELETON(object));
86
0
    g_object_unref(object);
87
88
0
    return adv;
89
0
}
90
91
gboolean BluezAdvertisement::BluezLEAdvertisement1Release(BluezLEAdvertisement1 * aAdv, GDBusMethodInvocation * aInvocation)
92
0
{
93
    // This method is called when the advertisement is stopped (released) by BlueZ.
94
    // We can use it to update the state of the advertisement in the CHIP layer.
95
0
    ChipLogDetail(DeviceLayer, "BLE advertisement stopped by BlueZ");
96
0
    mIsAdvertising = false;
97
0
    bluez_leadvertisement1_complete_release(aAdv, aInvocation);
98
0
    BLEManagerImpl::NotifyBLEPeripheralAdvReleased();
99
0
    return TRUE;
100
0
}
101
102
CHIP_ERROR BluezAdvertisement::InitImpl()
103
0
{
104
    // When creating D-Bus proxy object, the thread default context must be initialized. Otherwise,
105
    // all D-Bus signals will be delivered to the GLib global default main context.
106
0
    VerifyOrDie(g_main_context_get_thread_default() != nullptr);
107
108
0
    mAdv.reset(CreateLEAdvertisement());
109
0
    return CHIP_NO_ERROR;
110
0
}
111
112
CHIP_ERROR BluezAdvertisement::Init(BluezAdapter1 * apAdapter, const char * aAdvUUID, const char * aAdvName)
113
0
{
114
0
    VerifyOrReturnError(!mAdv, CHIP_ERROR_INCORRECT_STATE,
115
0
                        ChipLogError(DeviceLayer, "FAIL: BLE advertisement already initialized in %s", __func__));
116
117
0
    mAdapter.reset(reinterpret_cast<BluezAdapter1 *>(g_object_ref(apAdapter)));
118
119
0
    GAutoPtr<char> rootPath;
120
0
    g_object_get(G_OBJECT(mEndpoint.GetGattApplicationObjectManager()), "object-path", &rootPath.GetReceiver(), nullptr);
121
0
    g_snprintf(mAdvPath, sizeof(mAdvPath), "%s/advertising", rootPath.get());
122
0
    g_strlcpy(mAdvUUID, aAdvUUID, sizeof(mAdvUUID));
123
124
0
    if (aAdvName != nullptr)
125
0
    {
126
0
        g_strlcpy(mAdvName, aAdvName, sizeof(mAdvName));
127
0
    }
128
0
    else
129
0
    {
130
        // Advertising name corresponding to the PID, for debug purposes.
131
0
        g_snprintf(mAdvName, sizeof(mAdvName), "%s%04x", CHIP_DEVICE_CONFIG_BLE_DEVICE_NAME_PREFIX, getpid() & 0xffff);
132
0
    }
133
134
0
    CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(
135
0
        +[](BluezAdvertisement * self) { return self->InitImpl(); }, this);
136
0
    VerifyOrReturnError(err == CHIP_NO_ERROR, err,
137
0
                        ChipLogError(Ble, "Failed to schedule BLE advertisement Init() on CHIPoBluez thread"));
138
139
0
    mIsInitialized = true;
140
0
    return CHIP_NO_ERROR;
141
0
}
142
143
CHIP_ERROR BluezAdvertisement::SetIntervals(AdvertisingIntervals aAdvIntervals)
144
0
{
145
0
    VerifyOrReturnError(mAdv, CHIP_ERROR_UNINITIALIZED);
146
    // If the advertisement is already running, BlueZ will update the intervals
147
    // automatically. There is no need to stop and restart the advertisement.
148
0
    bluez_leadvertisement1_set_min_interval(mAdv.get(), aAdvIntervals.first * 0.625);
149
0
    bluez_leadvertisement1_set_max_interval(mAdv.get(), aAdvIntervals.second * 0.625);
150
0
    return CHIP_NO_ERROR;
151
0
}
152
153
CHIP_ERROR BluezAdvertisement::SetupServiceData(ServiceDataFlags aFlags)
154
0
{
155
0
    VerifyOrReturnError(mAdv, CHIP_ERROR_UNINITIALIZED);
156
157
0
    Ble::ChipBLEDeviceIdentificationInfo deviceInfo;
158
0
    ReturnErrorOnFailure(ConfigurationMgr().GetBLEDeviceIdentificationInfo(deviceInfo));
159
160
#if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING
161
    deviceInfo.SetAdditionalDataFlag(true);
162
#endif
163
164
#if CHIP_DEVICE_CONFIG_EXT_ADVERTISING
165
    if (aFlags & kServiceDataExtendedAnnouncement)
166
    {
167
        deviceInfo.SetExtendedAnnouncementFlag(true);
168
        // In case of extended advertisement, specification requires that
169
        // the vendor ID and product ID are set to 0.
170
        deviceInfo.SetVendorId(0);
171
        deviceInfo.SetProductId(0);
172
    }
173
#endif
174
175
0
    GVariantBuilder serviceDataBuilder;
176
0
    g_variant_builder_init(&serviceDataBuilder, G_VARIANT_TYPE("a{sv}"));
177
0
    g_variant_builder_add(&serviceDataBuilder, "{sv}", mAdvUUID,
178
0
                          g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &deviceInfo, sizeof(deviceInfo), sizeof(uint8_t)));
179
180
0
    GVariant * serviceData = g_variant_builder_end(&serviceDataBuilder);
181
182
0
    GAutoPtr<char> debugStr(g_variant_print(serviceData, TRUE));
183
0
    ChipLogDetail(DeviceLayer, "SET service data to %s", StringOrNullMarker(debugStr.get()));
184
185
0
    bluez_leadvertisement1_set_service_data(mAdv.get(), serviceData);
186
187
0
    return CHIP_NO_ERROR;
188
0
}
189
190
void BluezAdvertisement::Shutdown()
191
1
{
192
1
    VerifyOrReturn(mIsInitialized);
193
194
    // Make sure that the advertisement is stopped before we start cleaning up.
195
0
    if (mIsAdvertising)
196
0
    {
197
0
        CHIP_ERROR err = Stop();
198
0
        if (err != CHIP_NO_ERROR)
199
0
        {
200
0
            ChipLogError(DeviceLayer, "Failed to stop BLE advertisement before shutdown: %" CHIP_ERROR_FORMAT, err.Format());
201
0
        }
202
0
    }
203
204
    // Release resources on the glib thread to synchronize with potential signal handlers
205
    // attached to the advertising object that may run on the glib thread.
206
0
    PlatformMgrImpl().GLibMatterContextInvokeSync(
207
0
        +[](BluezAdvertisement * self) {
208
            // The application object manager might not be released right away (it may be held
209
            // by other BLE layer objects). We need to unexport the advertisement object in the
210
            // explicit way to make sure that we can export it again in the Init() method.
211
0
            g_dbus_object_manager_server_unexport(self->mEndpoint.GetGattApplicationObjectManager(), self->mAdvPath);
212
0
            self->mAdapter.reset();
213
0
            self->mAdv.reset();
214
0
            return CHIP_NO_ERROR;
215
0
        },
216
0
        this);
217
218
0
    mIsInitialized = false;
219
0
}
220
221
void BluezAdvertisement::StartDone(GObject * aObject, GAsyncResult * aResult)
222
0
{
223
0
    GAutoPtr<GError> error;
224
0
    if (!bluez_leadvertising_manager1_call_register_advertisement_finish(reinterpret_cast<BluezLEAdvertisingManager1 *>(aObject),
225
0
                                                                         aResult, &error.GetReceiver()))
226
0
    {
227
0
        ChipLogError(DeviceLayer, "FAIL: RegisterAdvertisement: %s", error->message);
228
0
        BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(BluezCallToChipError(error.get()));
229
0
        return;
230
0
    }
231
232
0
    mIsAdvertising = true;
233
234
0
    ChipLogDetail(DeviceLayer, "BLE advertisement started successfully");
235
0
    BLEManagerImpl::NotifyBLEPeripheralAdvStartComplete(CHIP_NO_ERROR);
236
0
}
237
238
CHIP_ERROR BluezAdvertisement::StartImpl()
239
0
{
240
0
    VerifyOrReturnError(mAdapter, CHIP_ERROR_UNINITIALIZED);
241
242
    // If the adapter configured in the Init() was unplugged, the g_dbus_interface_get_object()
243
    // or bluez_object_get_leadvertising_manager1() might return nullptr (depending on the timing,
244
    // since the D-Bus communication is handled on a separate thread). In such case, we should not
245
    // report internal error, but adapter unavailable, so the application can handle the situation
246
    // properly.
247
248
0
    GDBusObject * adapterObject = g_dbus_interface_get_object(reinterpret_cast<GDBusInterface *>(mAdapter.get()));
249
0
    VerifyOrReturnError(adapterObject != nullptr, BLE_ERROR_ADAPTER_UNAVAILABLE);
250
0
    GAutoPtr<BluezLEAdvertisingManager1> advMgr(
251
0
        bluez_object_get_leadvertising_manager1(reinterpret_cast<BluezObject *>(adapterObject)));
252
0
    VerifyOrReturnError(advMgr, BLE_ERROR_ADAPTER_UNAVAILABLE);
253
254
0
    GVariantBuilder optionsBuilder;
255
0
    g_variant_builder_init(&optionsBuilder, G_VARIANT_TYPE("a{sv}"));
256
0
    GVariant * options = g_variant_builder_end(&optionsBuilder);
257
258
0
    bluez_leadvertising_manager1_call_register_advertisement(
259
0
        advMgr.get(), mAdvPath, options, nullptr,
260
0
        [](GObject * aObject, GAsyncResult * aResult, void * aData) {
261
0
            reinterpret_cast<BluezAdvertisement *>(aData)->StartDone(aObject, aResult);
262
0
        },
263
0
        this);
264
265
0
    return CHIP_NO_ERROR;
266
0
}
267
268
CHIP_ERROR BluezAdvertisement::Start()
269
0
{
270
0
    VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
271
0
    VerifyOrReturnValue(!mIsAdvertising, CHIP_NO_ERROR, ChipLogDetail(DeviceLayer, "BLE advertising already started"));
272
0
    return PlatformMgrImpl().GLibMatterContextInvokeSync(
273
0
        +[](BluezAdvertisement * self) { return self->StartImpl(); }, this);
274
0
}
275
276
void BluezAdvertisement::StopDone(GObject * aObject, GAsyncResult * aResult)
277
0
{
278
0
    GAutoPtr<GError> error;
279
0
    if (!bluez_leadvertising_manager1_call_unregister_advertisement_finish(reinterpret_cast<BluezLEAdvertisingManager1 *>(aObject),
280
0
                                                                           aResult, &error.GetReceiver()))
281
0
    {
282
0
        ChipLogError(DeviceLayer, "FAIL: UnregisterAdvertisement: %s", error->message);
283
0
        BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(BluezCallToChipError(error.get()));
284
0
        return;
285
0
    }
286
287
0
    mIsAdvertising = false;
288
289
0
    ChipLogDetail(DeviceLayer, "BLE advertisement stopped successfully");
290
0
    BLEManagerImpl::NotifyBLEPeripheralAdvStopComplete(CHIP_NO_ERROR);
291
0
}
292
293
CHIP_ERROR BluezAdvertisement::StopImpl()
294
0
{
295
0
    VerifyOrReturnError(mAdapter, CHIP_ERROR_UNINITIALIZED);
296
297
    // If the adapter configured in the Init() was unplugged, the g_dbus_interface_get_object()
298
    // or bluez_object_get_leadvertising_manager1() might return nullptr (depending on the timing,
299
    // since the D-Bus communication is handled on a separate thread). In such case, we should not
300
    // report internal error, but adapter unavailable, so the application can handle the situation
301
    // properly.
302
303
0
    GDBusObject * adapterObject = g_dbus_interface_get_object(reinterpret_cast<GDBusInterface *>(mAdapter.get()));
304
0
    VerifyOrReturnError(adapterObject != nullptr, BLE_ERROR_ADAPTER_UNAVAILABLE);
305
0
    GAutoPtr<BluezLEAdvertisingManager1> advMgr(
306
0
        bluez_object_get_leadvertising_manager1(reinterpret_cast<BluezObject *>(adapterObject)));
307
0
    VerifyOrReturnError(advMgr, BLE_ERROR_ADAPTER_UNAVAILABLE);
308
309
0
    bluez_leadvertising_manager1_call_unregister_advertisement(
310
0
        advMgr.get(), mAdvPath, nullptr,
311
0
        [](GObject * aObject, GAsyncResult * aResult, void * aData) {
312
0
            reinterpret_cast<BluezAdvertisement *>(aData)->StopDone(aObject, aResult);
313
0
        },
314
0
        this);
315
316
0
    return CHIP_NO_ERROR;
317
0
}
318
319
CHIP_ERROR BluezAdvertisement::Stop()
320
0
{
321
0
    VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
322
0
    VerifyOrReturnValue(mIsAdvertising, CHIP_NO_ERROR, ChipLogDetail(DeviceLayer, "BLE advertising already stopped"));
323
0
    return PlatformMgrImpl().GLibMatterContextInvokeSync(
324
0
        +[](BluezAdvertisement * self) { return self->StopImpl(); }, this);
325
0
}
326
327
} // namespace Internal
328
} // namespace DeviceLayer
329
} // namespace chip