/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 |