/src/connectedhomeip/src/platform/Linux/bluez/BluezEndpoint.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * |
3 | | * Copyright (c) 2020-2022 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 | | /* |
19 | | * Copyright (c) 2016-2019, The OpenThread Authors. |
20 | | * All rights reserved. |
21 | | * |
22 | | * Redistribution and use in source and binary forms, with or without |
23 | | * modification, are permitted provided that the following conditions are met: |
24 | | * 1. Redistributions of source code must retain the above copyright |
25 | | * notice, this list of conditions and the following disclaimer. |
26 | | * 2. Redistributions in binary form must reproduce the above copyright |
27 | | * notice, this list of conditions and the following disclaimer in the |
28 | | * documentation and/or other materials provided with the distribution. |
29 | | * 3. Neither the name of the copyright holder nor the |
30 | | * names of its contributors may be used to endorse or promote products |
31 | | * derived from this software without specific prior written permission. |
32 | | * |
33 | | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
34 | | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
35 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
36 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
37 | | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
38 | | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
39 | | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
40 | | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
41 | | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
42 | | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
43 | | * POSSIBILITY OF SUCH DAMAGE. |
44 | | */ |
45 | | |
46 | | #include "BluezEndpoint.h" |
47 | | |
48 | | #include <cstring> |
49 | | #include <errno.h> |
50 | | #include <memory> |
51 | | #include <sys/socket.h> |
52 | | #include <unistd.h> |
53 | | #include <utility> |
54 | | |
55 | | #include <gio/gio.h> |
56 | | #include <gio/gunixfdlist.h> |
57 | | #include <glib-object.h> |
58 | | #include <glib.h> |
59 | | |
60 | | #include <ble/Ble.h> |
61 | | #include <lib/support/BitFlags.h> |
62 | | #include <lib/support/CHIPMem.h> |
63 | | #include <lib/support/CodeUtils.h> |
64 | | #include <lib/support/logging/CHIPLogging.h> |
65 | | #include <platform/ConfigurationManager.h> |
66 | | #include <platform/ConnectivityManager.h> |
67 | | #include <platform/DeviceInstanceInfoProvider.h> |
68 | | #include <platform/GLibTypeDeleter.h> |
69 | | #include <platform/Linux/dbus/bluez/DbusBluez.h> |
70 | | #include <platform/PlatformManager.h> |
71 | | #include <platform/internal/BLEManager.h> |
72 | | #include <setup_payload/AdditionalDataPayloadGenerator.h> |
73 | | #include <system/SystemPacketBuffer.h> |
74 | | |
75 | | #include "BluezConnection.h" |
76 | | #include "Types.h" |
77 | | |
78 | | #if !GLIB_CHECK_VERSION(2, 68, 0) |
79 | | #define g_memdup2(mem, size) g_memdup(mem, static_cast<unsigned int>(size)) |
80 | | #endif |
81 | | |
82 | | namespace chip { |
83 | | namespace DeviceLayer { |
84 | | namespace Internal { |
85 | | |
86 | | constexpr uint16_t kMaxConnectRetries = 4; |
87 | | |
88 | | gboolean BluezEndpoint::BluezCharacteristicReadValue(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation, |
89 | | GVariant * aOptions) |
90 | 0 | { |
91 | 0 | ChipLogDetail(DeviceLayer, "Received %s", __func__); |
92 | 0 | GVariant * val = bluez_gatt_characteristic1_get_value(aChar); |
93 | 0 | bluez_gatt_characteristic1_complete_read_value(aChar, aInvocation, val); |
94 | 0 | return TRUE; |
95 | 0 | } |
96 | | |
97 | | gboolean BluezEndpoint::BluezCharacteristicAcquireWrite(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation, |
98 | | GUnixFDList * aFDList, GVariant * aOptions) |
99 | 0 | { |
100 | 0 | int fds[2] = { -1, -1 }; |
101 | 0 | #if CHIP_ERROR_LOGGING |
102 | 0 | char * errStr; |
103 | 0 | #endif // CHIP_ERROR_LOGGING |
104 | 0 | BluezConnection * conn = nullptr; |
105 | 0 | GAutoPtr<GVariant> option_mtu; |
106 | 0 | uint16_t mtu; |
107 | |
|
108 | 0 | conn = GetBluezConnectionViaDevice(); |
109 | 0 | VerifyOrReturnValue( |
110 | 0 | conn != nullptr, FALSE, |
111 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.Failed", "No CHIPoBLE connection")); |
112 | | |
113 | 0 | ChipLogDetail(DeviceLayer, "BluezCharacteristicAcquireWrite is called, conn: %p", conn); |
114 | |
|
115 | 0 | VerifyOrReturnValue( |
116 | 0 | g_variant_lookup(aOptions, "mtu", "q", &mtu), FALSE, ChipLogError(DeviceLayer, "FAIL: No MTU in options in %s", __func__); |
117 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.InvalidArguments", "MTU negotiation failed")); |
118 | 0 | conn->SetMTU(mtu); |
119 | |
|
120 | 0 | if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, fds) < 0) |
121 | 0 | { |
122 | 0 | #if CHIP_ERROR_LOGGING |
123 | 0 | errStr = strerror(errno); |
124 | 0 | #endif // CHIP_ERROR_LOGGING |
125 | 0 | ChipLogError(DeviceLayer, "FAIL: socketpair: %s in %s", StringOrNullMarker(errStr), __func__); |
126 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.Failed", "FD creation failed"); |
127 | 0 | return FALSE; |
128 | 0 | } |
129 | | |
130 | 0 | conn->SetupWriteHandler(fds[0]); |
131 | 0 | bluez_gatt_characteristic1_set_write_acquired(aChar, TRUE); |
132 | |
|
133 | 0 | GUnixFDList * fdList = g_unix_fd_list_new_from_array(&fds[1], 1); |
134 | 0 | bluez_gatt_characteristic1_complete_acquire_write(aChar, aInvocation, fdList, g_variant_new_handle(0), conn->GetMTU()); |
135 | 0 | g_object_unref(fdList); |
136 | |
|
137 | 0 | return TRUE; |
138 | 0 | } |
139 | | |
140 | | static gboolean BluezCharacteristicAcquireWriteError(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation, |
141 | | GUnixFDList * aFDList, GVariant * aOptions) |
142 | 0 | { |
143 | 0 | ChipLogDetail(DeviceLayer, "Received %s", __func__); |
144 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.NotSupported", |
145 | 0 | "AcquireWrite for characteristic is unsupported"); |
146 | 0 | return TRUE; |
147 | 0 | } |
148 | | |
149 | | gboolean BluezEndpoint::BluezCharacteristicAcquireNotify(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation, |
150 | | GUnixFDList * aFDList, GVariant * aOptions) |
151 | 0 | { |
152 | 0 | int fds[2] = { -1, -1 }; |
153 | 0 | #if CHIP_ERROR_LOGGING |
154 | 0 | char * errStr; |
155 | 0 | #endif // CHIP_ERROR_LOGGING |
156 | 0 | BluezConnection * conn = nullptr; |
157 | 0 | bool isAdditionalAdvertising = false; |
158 | 0 | GAutoPtr<GVariant> option_mtu; |
159 | 0 | uint16_t mtu; |
160 | |
|
161 | | #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING |
162 | | isAdditionalAdvertising = (aChar == mC3.get()); |
163 | | #endif |
164 | |
|
165 | 0 | if (bluez_gatt_characteristic1_get_notifying(aChar)) |
166 | 0 | { |
167 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.NotPermitted", "Already notifying"); |
168 | 0 | return FALSE; |
169 | 0 | } |
170 | | |
171 | 0 | conn = GetBluezConnectionViaDevice(); |
172 | 0 | VerifyOrReturnValue( |
173 | 0 | conn != nullptr, FALSE, |
174 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.Failed", "No CHIPoBLE connection")); |
175 | | |
176 | 0 | VerifyOrReturnValue( |
177 | 0 | g_variant_lookup(aOptions, "mtu", "q", &mtu), FALSE, ChipLogError(DeviceLayer, "FAIL: No MTU in options in %s", __func__); |
178 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.InvalidArguments", "MTU negotiation failed");); |
179 | 0 | conn->SetMTU(mtu); |
180 | |
|
181 | 0 | if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, fds) < 0) |
182 | 0 | { |
183 | 0 | #if CHIP_ERROR_LOGGING |
184 | 0 | errStr = strerror(errno); |
185 | 0 | #endif // CHIP_ERROR_LOGGING |
186 | 0 | ChipLogError(DeviceLayer, "FAIL: socketpair: %s in %s", StringOrNullMarker(errStr), __func__); |
187 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.Failed", "FD creation failed"); |
188 | 0 | return FALSE; |
189 | 0 | } |
190 | | |
191 | 0 | conn->SetupNotifyHandler(fds[0], isAdditionalAdvertising); |
192 | 0 | bluez_gatt_characteristic1_set_notify_acquired(aChar, TRUE); |
193 | 0 | conn->SetNotifyAcquired(true); |
194 | |
|
195 | 0 | GUnixFDList * fdList = g_unix_fd_list_new_from_array(&fds[1], 1); |
196 | 0 | bluez_gatt_characteristic1_complete_acquire_notify(aChar, aInvocation, fdList, g_variant_new_handle(0), conn->GetMTU()); |
197 | 0 | g_object_unref(fdList); |
198 | |
|
199 | 0 | BLEManagerImpl::HandleTXCharCCCDWrite(conn); |
200 | |
|
201 | 0 | return TRUE; |
202 | 0 | } |
203 | | |
204 | | static gboolean BluezCharacteristicAcquireNotifyError(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation, |
205 | | GVariant * aOptions) |
206 | 0 | { |
207 | 0 | ChipLogDetail(DeviceLayer, "Received %s", __func__); |
208 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.NotSupported", |
209 | 0 | "AcquireNotify for characteristic is unsupported"); |
210 | 0 | return TRUE; |
211 | 0 | } |
212 | | |
213 | | gboolean BluezEndpoint::BluezCharacteristicConfirm(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation) |
214 | 0 | { |
215 | 0 | BluezConnection * conn = GetBluezConnectionViaDevice(); |
216 | 0 | ChipLogDetail(Ble, "Indication confirmation, %p", conn); |
217 | 0 | bluez_gatt_characteristic1_complete_confirm(aChar, aInvocation); |
218 | 0 | BLEManagerImpl::HandleTXComplete(conn); |
219 | 0 | return TRUE; |
220 | 0 | } |
221 | | |
222 | | static gboolean BluezCharacteristicConfirmError(BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInvocation) |
223 | 0 | { |
224 | 0 | g_dbus_method_invocation_return_dbus_error(aInvocation, "org.bluez.Error.Failed", "Confirm from characteristic is unsupported"); |
225 | 0 | return TRUE; |
226 | 0 | } |
227 | | |
228 | | BluezGattCharacteristic1 * BluezEndpoint::CreateGattCharacteristic(BluezGattService1 * aService, const char * aCharName, |
229 | | const char * aUUID, const char * const * aFlags) |
230 | 0 | { |
231 | 0 | const char * servicePath = g_dbus_object_get_object_path(g_dbus_interface_get_object(G_DBUS_INTERFACE(aService))); |
232 | 0 | GAutoPtr<char> charPath(g_strdup_printf("%s/%s", servicePath, aCharName)); |
233 | 0 | BluezObjectSkeleton * object; |
234 | 0 | BluezGattCharacteristic1 * characteristic; |
235 | |
|
236 | 0 | ChipLogDetail(DeviceLayer, "Create characteristic object at %s", charPath.get()); |
237 | 0 | object = bluez_object_skeleton_new(charPath.get()); |
238 | |
|
239 | 0 | characteristic = bluez_gatt_characteristic1_skeleton_new(); |
240 | 0 | bluez_gatt_characteristic1_set_uuid(characteristic, aUUID); |
241 | 0 | bluez_gatt_characteristic1_set_flags(characteristic, aFlags); |
242 | 0 | bluez_gatt_characteristic1_set_service(characteristic, servicePath); |
243 | | |
244 | | // Initialize value to empty array, so it can be read without prior write from the client side. |
245 | 0 | auto value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, nullptr, 0, sizeof(uint8_t)); |
246 | 0 | bluez_gatt_characteristic1_set_value(characteristic, value); |
247 | |
|
248 | 0 | bluez_object_skeleton_set_gatt_characteristic1(object, characteristic); |
249 | 0 | g_dbus_object_manager_server_export(mRoot.get(), G_DBUS_OBJECT_SKELETON(object)); |
250 | 0 | g_object_unref(object); |
251 | |
|
252 | 0 | return characteristic; |
253 | 0 | } |
254 | | |
255 | | void BluezEndpoint::RegisterGattApplicationDone(GObject * aObject, GAsyncResult * aResult) |
256 | 0 | { |
257 | 0 | GAutoPtr<GError> error; |
258 | 0 | if (!bluez_gatt_manager1_call_register_application_finish(reinterpret_cast<BluezGattManager1 *>(aObject), aResult, |
259 | 0 | &error.GetReceiver())) |
260 | 0 | { |
261 | 0 | ChipLogError(DeviceLayer, "FAIL: RegisterGattApplication: %s", error->message); |
262 | 0 | BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(BluezCallToChipError(error.get())); |
263 | 0 | return; |
264 | 0 | } |
265 | | |
266 | 0 | ChipLogDetail(DeviceLayer, "GATT application registered successfully"); |
267 | 0 | BLEManagerImpl::NotifyBLEPeripheralRegisterAppComplete(CHIP_NO_ERROR); |
268 | 0 | } |
269 | | |
270 | | CHIP_ERROR BluezEndpoint::RegisterGattApplicationImpl() |
271 | 0 | { |
272 | 0 | VerifyOrReturnError(mAdapter, CHIP_ERROR_UNINITIALIZED); |
273 | | |
274 | | // If the adapter configured in the Init() was unplugged, the g_dbus_interface_get_object() |
275 | | // or bluez_object_get_gatt_manager1() might return nullptr (depending on the timing, since |
276 | | // the D-Bus communication is handled on a separate thread). In such case, we should not |
277 | | // report internal error, but adapter unavailable, so the application can handle the situation |
278 | | // properly. |
279 | | |
280 | 0 | GDBusObject * adapterObject = g_dbus_interface_get_object(reinterpret_cast<GDBusInterface *>(mAdapter.get())); |
281 | 0 | VerifyOrReturnError(adapterObject != nullptr, BLE_ERROR_ADAPTER_UNAVAILABLE); |
282 | 0 | GAutoPtr<BluezGattManager1> gattMgr(bluez_object_get_gatt_manager1(reinterpret_cast<BluezObject *>(adapterObject))); |
283 | 0 | VerifyOrReturnError(gattMgr, BLE_ERROR_ADAPTER_UNAVAILABLE); |
284 | | |
285 | 0 | GVariantBuilder optionsBuilder; |
286 | 0 | g_variant_builder_init(&optionsBuilder, G_VARIANT_TYPE("a{sv}")); |
287 | 0 | GVariant * options = g_variant_builder_end(&optionsBuilder); |
288 | |
|
289 | 0 | bluez_gatt_manager1_call_register_application( |
290 | 0 | gattMgr.get(), mpRootPath, options, nullptr, |
291 | 0 | +[](GObject * aObj, GAsyncResult * aResult, void * self) { |
292 | 0 | reinterpret_cast<BluezEndpoint *>(self)->RegisterGattApplicationDone(aObj, aResult); |
293 | 0 | }, |
294 | 0 | this); |
295 | |
|
296 | 0 | return CHIP_NO_ERROR; |
297 | 0 | } |
298 | | |
299 | | /// Update the table of open BLE connections whenever a new device is spotted or its attributes have changed. |
300 | | void BluezEndpoint::UpdateConnectionTable(BluezDevice1 & aDevice) |
301 | 0 | { |
302 | 0 | const char * objectPath = g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(&aDevice)); |
303 | 0 | BluezConnection * conn = GetBluezConnection(objectPath); |
304 | |
|
305 | 0 | if (conn != nullptr && !bluez_device1_get_connected(&aDevice)) |
306 | 0 | { |
307 | 0 | ChipLogDetail(DeviceLayer, "BLE connection closed: conn=%p", conn); |
308 | 0 | BLEManagerImpl::HandleConnectionClosed(conn); |
309 | 0 | mConnMap.erase(objectPath); |
310 | | // TODO: the connection object should be released after BLEManagerImpl finishes cleaning up its resources |
311 | | // after the disconnection. Releasing it here doesn't cause any issues, but it's error-prone. |
312 | 0 | chip::Platform::Delete(conn); |
313 | 0 | return; |
314 | 0 | } |
315 | | |
316 | 0 | if (conn == nullptr) |
317 | 0 | { |
318 | 0 | HandleNewDevice(aDevice); |
319 | 0 | } |
320 | 0 | } |
321 | | |
322 | | void BluezEndpoint::HandleNewDevice(BluezDevice1 & aDevice) |
323 | 0 | { |
324 | 0 | VerifyOrReturn(bluez_device1_get_connected(&aDevice)); |
325 | 0 | VerifyOrReturn(!mIsCentral || bluez_device1_get_services_resolved(&aDevice)); |
326 | 0 | CHIP_ERROR err; |
327 | |
|
328 | 0 | const char * objectPath = g_dbus_proxy_get_object_path(reinterpret_cast<GDBusProxy *>(&aDevice)); |
329 | 0 | BluezConnection * conn = GetBluezConnection(objectPath); |
330 | 0 | VerifyOrReturn(conn == nullptr, |
331 | 0 | ChipLogError(DeviceLayer, "FAIL: Connection already tracked: conn=%p device=%s path=%s", conn, |
332 | 0 | conn->GetPeerAddress(), objectPath)); |
333 | | |
334 | 0 | conn = chip::Platform::New<BluezConnection>(aDevice); |
335 | 0 | VerifyOrExit(conn != nullptr, err = CHIP_ERROR_NO_MEMORY); |
336 | 0 | SuccessOrExit(err = conn->Init(*this)); |
337 | | |
338 | 0 | mpPeerDevicePath = g_strdup(objectPath); |
339 | 0 | mConnMap[mpPeerDevicePath] = conn; |
340 | |
|
341 | 0 | ChipLogDetail(DeviceLayer, "New BLE connection: conn=%p device=%s path=%s", conn, conn->GetPeerAddress(), objectPath); |
342 | |
|
343 | 0 | BLEManagerImpl::HandleNewConnection(conn); |
344 | 0 | return; |
345 | | |
346 | 0 | exit: |
347 | 0 | chip::Platform::Delete(conn); |
348 | 0 | BLEManagerImpl::HandleConnectFailed(err); |
349 | 0 | } |
350 | | |
351 | | void BluezEndpoint::OnDeviceAdded(BluezDevice1 & device) |
352 | 0 | { |
353 | 0 | HandleNewDevice(device); |
354 | 0 | } |
355 | | |
356 | | void BluezEndpoint::OnDevicePropertyChanged(BluezDevice1 & device, GVariant * changedProps, const char * const * invalidatedProps) |
357 | 0 | { |
358 | 0 | UpdateConnectionTable(device); |
359 | 0 | } |
360 | | |
361 | | void BluezEndpoint::OnDeviceRemoved(BluezDevice1 & device) |
362 | 0 | { |
363 | | // Handling device removal is not necessary because disconnection is already handled |
364 | | // in the OnDevicePropertyChanged() - we are checking for the "Connected" property. |
365 | 0 | } |
366 | | |
367 | | BluezGattService1 * BluezEndpoint::CreateGattService(const char * aUUID) |
368 | 0 | { |
369 | 0 | BluezObjectSkeleton * object; |
370 | 0 | BluezGattService1 * service; |
371 | |
|
372 | 0 | mpServicePath = g_strdup_printf("%s/service", mpRootPath); |
373 | 0 | ChipLogDetail(DeviceLayer, "CREATE service object at %s", mpServicePath); |
374 | 0 | object = bluez_object_skeleton_new(mpServicePath); |
375 | |
|
376 | 0 | service = bluez_gatt_service1_skeleton_new(); |
377 | 0 | bluez_gatt_service1_set_uuid(service, aUUID); |
378 | | // device is only valid for remote services |
379 | 0 | bluez_gatt_service1_set_primary(service, TRUE); |
380 | | |
381 | | // includes -- unclear whether required. Might be filled in later |
382 | 0 | bluez_object_skeleton_set_gatt_service1(object, service); |
383 | 0 | g_dbus_object_manager_server_export(mRoot.get(), G_DBUS_OBJECT_SKELETON(object)); |
384 | 0 | g_object_unref(object); |
385 | |
|
386 | 0 | return service; |
387 | 0 | } |
388 | | |
389 | | BluezConnection * BluezEndpoint::GetBluezConnection(const char * aPath) |
390 | 0 | { |
391 | 0 | auto it = mConnMap.find(aPath); |
392 | 0 | return (it != mConnMap.end()) ? it->second : nullptr; |
393 | 0 | } |
394 | | |
395 | | BluezConnection * BluezEndpoint::GetBluezConnectionViaDevice() |
396 | 0 | { |
397 | 0 | return GetBluezConnection(mpPeerDevicePath); |
398 | 0 | } |
399 | | |
400 | | #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING |
401 | | static void UpdateAdditionalDataCharacteristic(BluezGattCharacteristic1 * characteristic) |
402 | | { |
403 | | VerifyOrReturn(characteristic != nullptr); |
404 | | |
405 | | // Construct the TLV for the additional data |
406 | | GVariant * cValue = nullptr; |
407 | | gpointer data; |
408 | | CHIP_ERROR err = CHIP_NO_ERROR; |
409 | | chip::System::PacketBufferHandle bufferHandle; |
410 | | BitFlags<AdditionalDataFields> additionalDataFields; |
411 | | AdditionalDataPayloadGeneratorParams additionalDataPayloadParams; |
412 | | |
413 | | #if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID) |
414 | | uint8_t rotatingDeviceIdUniqueId[ConfigurationManager::kRotatingDeviceIDUniqueIDLength] = {}; |
415 | | MutableByteSpan rotatingDeviceIdUniqueIdSpan(rotatingDeviceIdUniqueId); |
416 | | |
417 | | err = GetDeviceInstanceInfoProvider()->GetRotatingDeviceIdUniqueId(rotatingDeviceIdUniqueIdSpan); |
418 | | SuccessOrExit(err); |
419 | | err = ConfigurationMgr().GetLifetimeCounter(additionalDataPayloadParams.rotatingDeviceIdLifetimeCounter); |
420 | | SuccessOrExit(err); |
421 | | additionalDataPayloadParams.rotatingDeviceIdUniqueId = rotatingDeviceIdUniqueIdSpan; |
422 | | additionalDataFields.Set(AdditionalDataFields::RotatingDeviceId); |
423 | | #endif /* CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID) */ |
424 | | |
425 | | err = AdditionalDataPayloadGenerator().generateAdditionalDataPayload(additionalDataPayloadParams, bufferHandle, |
426 | | additionalDataFields); |
427 | | SuccessOrExit(err); |
428 | | |
429 | | data = g_memdup2(bufferHandle->Start(), bufferHandle->DataLength()); |
430 | | |
431 | | cValue = g_variant_new_from_data(G_VARIANT_TYPE("ay"), data, bufferHandle->DataLength(), TRUE, g_free, data); |
432 | | bluez_gatt_characteristic1_set_value(characteristic, cValue); |
433 | | |
434 | | return; |
435 | | |
436 | | exit: |
437 | | if (err != CHIP_NO_ERROR) |
438 | | { |
439 | | ChipLogError(DeviceLayer, "Failed to generate TLV encoded Additional Data (%s)", __func__); |
440 | | } |
441 | | return; |
442 | | } |
443 | | #endif |
444 | | |
445 | | void BluezEndpoint::SetupGattService() |
446 | 0 | { |
447 | |
|
448 | 0 | static const char * const c1_flags[] = { "write", nullptr }; |
449 | 0 | static const char * const c2_flags[] = { "indicate", nullptr }; |
450 | | #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING |
451 | | static const char * const c3_flags[] = { "read", nullptr }; |
452 | | #endif |
453 | |
|
454 | 0 | mService.reset(CreateGattService(Ble::CHIP_BLE_SERVICE_SHORT_UUID_STR)); |
455 | | |
456 | | // C1 characteristic |
457 | 0 | mC1.reset(CreateGattCharacteristic(mService.get(), "c1", Ble::CHIP_BLE_CHAR_1_UUID_STR, c1_flags)); |
458 | 0 | g_signal_connect(mC1.get(), "handle-read-value", |
459 | 0 | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GVariant * aOpt, |
460 | 0 | BluezEndpoint * self) { return self->BluezCharacteristicReadValue(aChar, aInv, aOpt); }), |
461 | 0 | this); |
462 | 0 | g_signal_connect( |
463 | 0 | mC1.get(), "handle-acquire-write", |
464 | 0 | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GUnixFDList * aFDList, GVariant * aOpt, |
465 | 0 | BluezEndpoint * self) { return self->BluezCharacteristicAcquireWrite(aChar, aInv, aFDList, aOpt); }), |
466 | 0 | this); |
467 | 0 | g_signal_connect(mC1.get(), "handle-acquire-notify", G_CALLBACK(BluezCharacteristicAcquireNotifyError), nullptr); |
468 | 0 | g_signal_connect(mC1.get(), "handle-confirm", G_CALLBACK(BluezCharacteristicConfirmError), nullptr); |
469 | | |
470 | | // C2 characteristic |
471 | 0 | mC2.reset(CreateGattCharacteristic(mService.get(), "c2", Ble::CHIP_BLE_CHAR_2_UUID_STR, c2_flags)); |
472 | 0 | g_signal_connect(mC2.get(), "handle-read-value", |
473 | 0 | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GVariant * aOpt, |
474 | 0 | BluezEndpoint * self) { return self->BluezCharacteristicReadValue(aChar, aInv, aOpt); }), |
475 | 0 | this); |
476 | 0 | g_signal_connect(mC2.get(), "handle-acquire-write", G_CALLBACK(BluezCharacteristicAcquireWriteError), nullptr); |
477 | 0 | g_signal_connect( |
478 | 0 | mC2.get(), "handle-acquire-notify", |
479 | 0 | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GUnixFDList * aFDList, GVariant * aOpt, |
480 | 0 | BluezEndpoint * self) { return self->BluezCharacteristicAcquireNotify(aChar, aInv, aFDList, aOpt); }), |
481 | 0 | this); |
482 | 0 | g_signal_connect(mC2.get(), "handle-confirm", |
483 | 0 | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, BluezEndpoint * self) { |
484 | 0 | return self->BluezCharacteristicConfirm(aChar, aInv); |
485 | 0 | }), |
486 | 0 | this); |
487 | |
|
488 | 0 | ChipLogDetail(DeviceLayer, "CHIP BTP C1 %s", bluez_gatt_characteristic1_get_service(mC1.get())); |
489 | 0 | ChipLogDetail(DeviceLayer, "CHIP BTP C2 %s", bluez_gatt_characteristic1_get_service(mC2.get())); |
490 | |
|
491 | | #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING |
492 | | ChipLogDetail(DeviceLayer, "CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING is TRUE"); |
493 | | // Additional data characteristics |
494 | | mC3.reset(CreateGattCharacteristic(mService.get(), "c3", Ble::CHIP_BLE_CHAR_3_UUID_STR, c3_flags)); |
495 | | g_signal_connect(mC3.get(), "handle-read-value", |
496 | | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GVariant * aOpt, |
497 | | BluezEndpoint * self) { return self->BluezCharacteristicReadValue(aChar, aInv, aOpt); }), |
498 | | this); |
499 | | g_signal_connect(mC3.get(), "handle-acquire-write", G_CALLBACK(BluezCharacteristicAcquireWriteError), nullptr); |
500 | | g_signal_connect( |
501 | | mC3.get(), "handle-acquire-notify", |
502 | | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, GUnixFDList * aFDList, GVariant * aOpt, |
503 | | BluezEndpoint * self) { return self->BluezCharacteristicAcquireNotify(aChar, aInv, aFDList, aOpt); }), |
504 | | this); |
505 | | g_signal_connect(mC3.get(), "handle-confirm", |
506 | | G_CALLBACK(+[](BluezGattCharacteristic1 * aChar, GDBusMethodInvocation * aInv, BluezEndpoint * self) { |
507 | | return self->BluezCharacteristicConfirm(aChar, aInv); |
508 | | }), |
509 | | this); |
510 | | |
511 | | // update the characteristic value |
512 | | UpdateAdditionalDataCharacteristic(mC3.get()); |
513 | | ChipLogDetail(DeviceLayer, "CHIP BTP C3 %s", bluez_gatt_characteristic1_get_service(mC3.get())); |
514 | | #else |
515 | 0 | ChipLogDetail(DeviceLayer, "CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING is FALSE"); |
516 | 0 | #endif |
517 | 0 | } |
518 | | |
519 | | void BluezEndpoint::SetupGattServer(GDBusConnection * aConn) |
520 | 0 | { |
521 | 0 | VerifyOrReturn(!mIsCentral); |
522 | | |
523 | 0 | mpRootPath = g_strdup_printf("/chipoble/%04x", getpid() & 0xffff); |
524 | 0 | mRoot.reset(g_dbus_object_manager_server_new(mpRootPath)); |
525 | |
|
526 | 0 | SetupGattService(); |
527 | | |
528 | | // Set connection after the service is set up in order to reduce the number |
529 | | // of signals sent on the bus. |
530 | 0 | g_dbus_object_manager_server_set_connection(mRoot.get(), aConn); |
531 | 0 | } |
532 | | |
533 | | CHIP_ERROR BluezEndpoint::SetupEndpointBindings() |
534 | 0 | { |
535 | 0 | SetupGattServer(mObjectManager.GetConnection()); |
536 | 0 | return CHIP_NO_ERROR; |
537 | 0 | } |
538 | | |
539 | | CHIP_ERROR BluezEndpoint::RegisterGattApplication() |
540 | 0 | { |
541 | 0 | return PlatformMgrImpl().GLibMatterContextInvokeSync( |
542 | 0 | +[](BluezEndpoint * self) { return self->RegisterGattApplicationImpl(); }, this); |
543 | 0 | } |
544 | | |
545 | | CHIP_ERROR BluezEndpoint::Init(BluezAdapter1 * apAdapter, bool aIsCentral) |
546 | 0 | { |
547 | 0 | VerifyOrReturnError(!mIsInitialized, CHIP_ERROR_INCORRECT_STATE); |
548 | 0 | VerifyOrReturnError(apAdapter != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
549 | | |
550 | 0 | mAdapter.reset(reinterpret_cast<BluezAdapter1 *>(g_object_ref(apAdapter))); |
551 | 0 | mIsCentral = aIsCentral; |
552 | |
|
553 | 0 | CHIP_ERROR err = mObjectManager.SubscribeDeviceNotifications(mAdapter.get(), this); |
554 | 0 | VerifyOrReturnError(err == CHIP_NO_ERROR, err, |
555 | 0 | ChipLogError(DeviceLayer, "Failed to subscribe for notifications: %" CHIP_ERROR_FORMAT, err.Format())); |
556 | | |
557 | 0 | err = PlatformMgrImpl().GLibMatterContextInvokeSync( |
558 | 0 | +[](BluezEndpoint * self) { return self->SetupEndpointBindings(); }, this); |
559 | 0 | VerifyOrReturnError(err == CHIP_NO_ERROR, err, |
560 | 0 | ChipLogError(DeviceLayer, "Failed to schedule endpoint initialization: %" CHIP_ERROR_FORMAT, err.Format())); |
561 | | |
562 | 0 | mIsInitialized = true; |
563 | |
|
564 | 0 | return CHIP_NO_ERROR; |
565 | 0 | } |
566 | | |
567 | | void BluezEndpoint::Shutdown() |
568 | 1 | { |
569 | 1 | VerifyOrReturn(mIsInitialized); |
570 | | |
571 | | // Run endpoint cleanup on the CHIPoBluez thread. This is necessary because the |
572 | | // cleanup function releases the D-Bus manager client object, which handles D-Bus |
573 | | // signals. Otherwise, we will face race condition when the D-Bus signal is in |
574 | | // the middle of being processed when the cleanup function is called. |
575 | 0 | PlatformMgrImpl().GLibMatterContextInvokeSync( |
576 | 0 | +[](BluezEndpoint * self) { |
577 | 0 | self->mAdapter.reset(); |
578 | 0 | self->mRoot.reset(); |
579 | 0 | self->mService.reset(); |
580 | 0 | self->mC1.reset(); |
581 | 0 | self->mC2.reset(); |
582 | | #if CHIP_ENABLE_ADDITIONAL_DATA_ADVERTISING |
583 | | self->mC3.reset(); |
584 | | #endif |
585 | 0 | return CHIP_NO_ERROR; |
586 | 0 | }, |
587 | 0 | this); |
588 | |
|
589 | 0 | g_free(mpRootPath); |
590 | 0 | g_free(mpServicePath); |
591 | 0 | g_free(mpPeerDevicePath); |
592 | |
|
593 | 0 | mIsInitialized = false; |
594 | 0 | } |
595 | | |
596 | | // ConnectDevice callbacks |
597 | | |
598 | | CHIP_ERROR BluezEndpoint::ConnectDeviceImpl(BluezDevice1 & aDevice) |
599 | 0 | { |
600 | | // Due to radio interferences or Wi-Fi coexistence, sometimes the BLE connection may not be |
601 | | // established (e.g. Connection Indication Packet is missed by BLE peripheral). In such case, |
602 | | // BlueZ returns "Software caused connection abort error", and we should make a connection retry. |
603 | | // It's important to make sure that the connection is correctly ceased, by calling `Disconnect()` |
604 | | // D-Bus method, or else `Connect()` returns immediately without any effect. |
605 | 0 | for (uint16_t i = 0; i < kMaxConnectRetries; i++) |
606 | 0 | { |
607 | 0 | GAutoPtr<GError> error; |
608 | 0 | if (bluez_device1_call_connect_sync(&aDevice, mConnectCancellable.get(), &error.GetReceiver())) |
609 | 0 | { |
610 | 0 | ChipLogDetail(DeviceLayer, "ConnectDevice complete"); |
611 | 0 | return CHIP_NO_ERROR; |
612 | 0 | } |
613 | | |
614 | 0 | ChipLogError(DeviceLayer, "FAIL: ConnectDevice: %s (%d)", error->message, error->code); |
615 | 0 | if (!g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_DBUS_ERROR)) |
616 | 0 | { |
617 | 0 | break; |
618 | 0 | } |
619 | | |
620 | 0 | ChipLogProgress(DeviceLayer, "ConnectDevice retry: %u out of %u", i + 1, kMaxConnectRetries); |
621 | 0 | bluez_device1_call_disconnect_sync(&aDevice, nullptr, nullptr); |
622 | 0 | } |
623 | | |
624 | 0 | BLEManagerImpl::HandleConnectFailed(CHIP_ERROR_INTERNAL); |
625 | 0 | return CHIP_ERROR_INTERNAL; |
626 | 0 | } |
627 | | |
628 | | CHIP_ERROR BluezEndpoint::ConnectDevice(BluezDevice1 & aDevice) |
629 | 0 | { |
630 | 0 | auto params = std::make_pair(this, &aDevice); |
631 | 0 | mConnectCancellable.reset(g_cancellable_new()); |
632 | 0 | return PlatformMgrImpl().GLibMatterContextInvokeSync( |
633 | 0 | +[](decltype(params) * aParams) { return aParams->first->ConnectDeviceImpl(*aParams->second); }, ¶ms); |
634 | 0 | } |
635 | | |
636 | | void BluezEndpoint::CancelConnect() |
637 | 0 | { |
638 | 0 | g_cancellable_cancel(mConnectCancellable.get()); |
639 | 0 | mConnectCancellable.reset(); |
640 | 0 | } |
641 | | |
642 | | } // namespace Internal |
643 | | } // namespace DeviceLayer |
644 | | } // namespace chip |