Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-bluez-profile"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <fcntl.h>
9 : :
10 : : #include <gio/gio.h>
11 : : #include <gio/gunixfdlist.h>
12 : :
13 : : #include "valent-bluez-profile.h"
14 : :
15 : :
16 : : struct _ValentBluezProfile
17 : : {
18 : : GDBusInterfaceSkeleton parent_instance;
19 : :
20 : : GDBusInterfaceVTable vtable;
21 : : GDBusNodeInfo *node_info;
22 : : GDBusInterfaceInfo *iface_info;
23 : : unsigned int in_registration : 1;
24 : : unsigned int registered : 1;
25 : : };
26 : :
27 [ + + + - ]: 17 : G_DEFINE_FINAL_TYPE (ValentBluezProfile, valent_bluez_profile, G_TYPE_DBUS_INTERFACE_SKELETON)
28 : :
29 : : enum {
30 : : CONNECTION_OPENED,
31 : : CONNECTION_CLOSED,
32 : : N_SIGNALS
33 : : };
34 : :
35 : : static guint signals[N_SIGNALS] = { 0, };
36 : :
37 : :
38 : : /*
39 : : * org.bluez.Profile1
40 : : */
41 : : static const char interface_xml[] =
42 : : "<node>"
43 : : " <interface name='org.bluez.Profile1'>"
44 : : " <method name='Release'/>"
45 : : " <method name='NewConnection'>"
46 : : " <arg name='device' type='o' direction='in'/>"
47 : : " <arg name='fd' type='h' direction='in'/>"
48 : : " <arg name='fd_properties' type='a{sv}' direction='in'/>"
49 : : " </method>"
50 : : " <method name='RequestDisconnection'>"
51 : : " <arg name='object_path' type='o' direction='in'/>"
52 : : " </method>"
53 : : " </interface>"
54 : : "</node>";
55 : :
56 : :
57 : : /**
58 : : * valent_bluez_profile_new_connection:
59 : : * @profile: a `ValentBluezProfile`
60 : : * @object_path: a DBus object path
61 : : * @fd: a UNIX file descriptor
62 : : * @fd_properties: a `GVariant`
63 : : *
64 : : * This method gets called when a new service level connection has been made and
65 : : * authorized.
66 : : */
67 : : static void
68 : 2 : valent_bluez_profile_new_connection (ValentBluezProfile *profile,
69 : : const char *object_path,
70 : : int fd,
71 : : GVariant *fd_properties)
72 : : {
73 : 2 : g_autoptr (GSocket) socket = NULL;
74 : 2 : g_autoptr (GSocketConnection) connection = NULL;
75 [ + - ]: 2 : g_autoptr (GError) error = NULL;
76 : :
77 [ - + ]: 2 : g_assert (VALENT_IS_BLUEZ_PROFILE (profile));
78 [ + - ]: 2 : g_assert (g_variant_is_object_path (object_path));
79 : :
80 : 2 : socket = g_socket_new_from_fd (fd, &error);
81 [ - + ]: 2 : if (socket == NULL)
82 : : {
83 : 0 : g_warning ("Failed to create socket: %s", error->message);
84 [ # # ]: 0 : return;
85 : : }
86 : :
87 : 2 : connection = g_object_new (G_TYPE_SOCKET_CONNECTION,
88 : : "socket", socket,
89 : : NULL);
90 : :
91 [ - + ]: 2 : g_signal_emit (G_OBJECT (profile),
92 : : signals [CONNECTION_OPENED], 0,
93 : : connection,
94 : : object_path);
95 : : }
96 : :
97 : : /**
98 : : * valent_bluez_profile_request_disconnection:
99 : : * @profile: a `ValentBluezProfile`
100 : : * @object_path: a DBus object path
101 : : *
102 : : * This method gets called when a profile gets disconnected.
103 : : *
104 : : * The file descriptor is no longer owned by the service daemon and the profile
105 : : * implementation needs to take care of cleaning up all connections.
106 : : *
107 : : * If multiple file descriptors are indicated via
108 : : * valent_bluez_profile_new_connection(), it is expected that all of them are
109 : : * disconnected before returning from this method call.
110 : : */
111 : : static void
112 : 0 : valent_bluez_profile_request_disconnection (ValentBluezProfile *profile,
113 : : const char *object_path)
114 : : {
115 [ # # ]: 0 : g_assert (VALENT_IS_BLUEZ_PROFILE (profile));
116 [ # # ]: 0 : g_assert (g_variant_is_object_path (object_path));
117 : :
118 : 0 : g_signal_emit (G_OBJECT (profile),
119 : : signals [CONNECTION_CLOSED], 0,
120 : : object_path);
121 : 0 : }
122 : :
123 : : /**
124 : : * valent_bluez_profile_release:
125 : : * @profile: a `ValentBluezProfile`
126 : : *
127 : : * This method gets called when the service daemon unregisters the profile.
128 : : * A profile can use it to do cleanup tasks. There is no need to unregister
129 : : * the profile, because when this method gets called it has already been
130 : : * unregistered.
131 : : */
132 : : static void
133 : 0 : valent_bluez_profile_release (ValentBluezProfile *profile)
134 : : {
135 : 0 : GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON (profile);
136 : :
137 [ # # ]: 0 : g_assert (VALENT_IS_BLUEZ_PROFILE (profile));
138 : :
139 [ # # ]: 0 : if (g_dbus_interface_skeleton_get_object_path (iface) != NULL)
140 : 0 : g_dbus_interface_skeleton_unexport (iface);
141 : :
142 : 0 : profile->registered = FALSE;
143 : 0 : }
144 : :
145 : :
146 : : /*
147 : : * GDBusInterfaceSkeleton
148 : : */
149 : : static GDBusInterfaceInfo *
150 : 2 : valent_bluez_profile_get_info (GDBusInterfaceSkeleton *skeleton)
151 : : {
152 : 2 : ValentBluezProfile *self = VALENT_BLUEZ_PROFILE (skeleton);
153 : :
154 : 2 : return self->iface_info;
155 : : }
156 : :
157 : : static GDBusInterfaceVTable *
158 : 4 : valent_bluez_profile_get_vtable (GDBusInterfaceSkeleton *skeleton)
159 : : {
160 : 4 : ValentBluezProfile *self = VALENT_BLUEZ_PROFILE (skeleton);
161 : :
162 : 4 : return &(self->vtable);
163 : : }
164 : :
165 : : static void
166 : 2 : valent_bluez_profile_method_call (GDBusConnection *connection,
167 : : const char *sender,
168 : : const char *object_path,
169 : : const char *interface_name,
170 : : const char *method_name,
171 : : GVariant *parameters,
172 : : GDBusMethodInvocation *invocation,
173 : : gpointer user_data)
174 : : {
175 : 2 : ValentBluezProfile *profile = VALENT_BLUEZ_PROFILE (user_data);
176 : :
177 [ - + ]: 2 : g_assert (VALENT_IS_BLUEZ_PROFILE (profile));
178 : :
179 [ + - ]: 2 : if (g_strcmp0 (method_name, "NewConnection") == 0)
180 : : {
181 : 2 : const char *device;
182 : 2 : int32_t fd_idx;
183 : 2 : GDBusMessage *message;
184 : 2 : GUnixFDList *fd_list;
185 : 2 : int fd;
186 : 2 : int fd_flags;
187 : 2 : g_autoptr (GVariant) fd_props = NULL;
188 : :
189 [ + - ]: 2 : g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oha{sv})")));
190 : :
191 : 2 : message = g_dbus_method_invocation_get_message (invocation);
192 : 2 : fd_list = g_dbus_message_get_unix_fd_list (message);
193 : :
194 : 2 : g_variant_get (parameters, "(&oh@a{sv})", &device, &fd_idx, &fd_props);
195 : 2 : fd = g_unix_fd_list_get (fd_list, fd_idx, NULL);
196 : 2 : fd_flags = fcntl (fd, F_GETFD);
197 : 2 : fcntl (fd, F_SETFD, fd_flags &~ FD_CLOEXEC);
198 : :
199 [ + - ]: 2 : valent_bluez_profile_new_connection (profile, device, fd, fd_props);
200 : : }
201 [ # # ]: 0 : else if (g_strcmp0 (method_name, "RequestDisconnection") == 0)
202 : : {
203 : 0 : const char *device;
204 : :
205 [ # # ]: 0 : g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(o)")));
206 : :
207 : 0 : g_variant_get (parameters, "(&o)", &device);
208 : 0 : valent_bluez_profile_request_disconnection (profile, device);
209 : : }
210 [ # # ]: 0 : else if (g_strcmp0 (method_name, "Release") == 0)
211 : : {
212 [ # # ]: 0 : g_assert (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("()")));
213 : :
214 : 0 : valent_bluez_profile_release (profile);
215 : : }
216 : : else
217 : 0 : g_assert_not_reached();
218 : :
219 : 2 : g_dbus_method_invocation_return_value (invocation, NULL);
220 : 2 : }
221 : :
222 : : static GVariant *
223 : 0 : valent_bluez_profile_get_properties (GDBusInterfaceSkeleton *skeleton) {
224 : 0 : return NULL;
225 : : }
226 : :
227 : : static void
228 : 0 : valent_bluez_profile_flush (GDBusInterfaceSkeleton *skeleton) {
229 : 0 : return;
230 : : }
231 : :
232 : : /*
233 : : * GObject
234 : : */
235 : : static void
236 : 1 : valent_bluez_profile_dispose (GObject *object)
237 : : {
238 : 1 : ValentBluezProfile *self = VALENT_BLUEZ_PROFILE (object);
239 : :
240 : 1 : valent_bluez_profile_unregister (self);
241 : :
242 : 1 : G_OBJECT_CLASS (valent_bluez_profile_parent_class)->dispose (object);
243 : 1 : }
244 : :
245 : : static void
246 : 1 : valent_bluez_profile_finalize (GObject *object)
247 : : {
248 : 1 : ValentBluezProfile *self = VALENT_BLUEZ_PROFILE (object);
249 : :
250 [ + - ]: 1 : g_clear_pointer (&self->node_info, g_dbus_node_info_unref);
251 : :
252 : 1 : G_OBJECT_CLASS (valent_bluez_profile_parent_class)->finalize (object);
253 : 1 : }
254 : :
255 : : static void
256 : 1 : valent_bluez_profile_class_init (ValentBluezProfileClass *klass) {
257 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
258 : 1 : GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass);
259 : :
260 : 1 : object_class->dispose = valent_bluez_profile_dispose;
261 : 1 : object_class->finalize = valent_bluez_profile_finalize;
262 : :
263 : 1 : skeleton_class->get_info = valent_bluez_profile_get_info;
264 : 1 : skeleton_class->get_vtable = valent_bluez_profile_get_vtable;
265 : 1 : skeleton_class->get_properties = valent_bluez_profile_get_properties;
266 : 1 : skeleton_class->flush = valent_bluez_profile_flush;
267 : :
268 : : /**
269 : : * ValentBluezProfile::connection-opened:
270 : : * @profile: a `ValentBluezProfile`
271 : : * @connection: a `GSocketConnection`
272 : : * @device: DBus object path of the device
273 : : *
274 : : * The `ValentBluezProfile`::connection-opened signal is emitted when a Bluez
275 : : * socket for @device has been successfully wrapped in a `GSocketConnection`
276 : : * and is ready for protocol negotiation.
277 : : */
278 : 2 : signals [CONNECTION_OPENED] =
279 : 1 : g_signal_new ("connection-opened",
280 : : G_TYPE_FROM_CLASS (klass),
281 : : G_SIGNAL_RUN_LAST,
282 : : 0,
283 : : NULL, NULL, NULL,
284 : : G_TYPE_NONE, 2, G_TYPE_SOCKET_CONNECTION, G_TYPE_STRING);
285 : :
286 : : /**
287 : : * ValentBluezProfile::connection-closed:
288 : : * @profile: a `ValentBluezProfile`
289 : : * @device: DBus object path of the device
290 : : *
291 : : * The `ValentBluezProfile`::connection-closed signal is emitted when a Bluez
292 : : * socket for @device has been closed and should be cleaned up.
293 : : */
294 : 2 : signals [CONNECTION_CLOSED] =
295 : 1 : g_signal_new ("connection-closed",
296 : : G_TYPE_FROM_CLASS (klass),
297 : : G_SIGNAL_RUN_LAST,
298 : : 0,
299 : : NULL, NULL,
300 : : g_cclosure_marshal_VOID__STRING,
301 : : G_TYPE_NONE, 1, G_TYPE_STRING);
302 : 1 : g_signal_set_va_marshaller (signals [CONNECTION_CLOSED],
303 : : G_TYPE_FROM_CLASS (klass),
304 : : g_cclosure_marshal_VOID__STRINGv);
305 : 1 : }
306 : :
307 : : static void
308 : 2 : valent_bluez_profile_init (ValentBluezProfile *self)
309 : : {
310 : 2 : self->node_info = g_dbus_node_info_new_for_xml (interface_xml, NULL);
311 : 2 : self->iface_info = g_dbus_node_info_lookup_interface (self->node_info,
312 : : "org.bluez.Profile1");
313 : :
314 : 2 : self->vtable.method_call = valent_bluez_profile_method_call;
315 : 2 : self->vtable.get_property = NULL;
316 : 2 : self->vtable.set_property = NULL;
317 : 2 : }
318 : :
319 : : /**
320 : : * valent_bluez_profile_new:
321 : : *
322 : : * Create a service profile for client or server connections.
323 : : *
324 : : * Returns: (transfer full): a new `ValentBluezProfile`
325 : : */
326 : : ValentBluezProfile *
327 : 2 : valent_bluez_profile_new (void)
328 : : {
329 : 2 : return g_object_new (VALENT_TYPE_BLUEZ_PROFILE, NULL);
330 : : }
331 : :
332 : : static void
333 : 2 : profile_manager_register_profile_cb (GDBusConnection *connection,
334 : : GAsyncResult *result,
335 : : gpointer user_data)
336 : : {
337 : 2 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
338 : 2 : ValentBluezProfile *self = g_task_get_source_object (task);
339 : 2 : GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON (self);
340 [ + - ]: 2 : g_autoptr (GVariant) reply = NULL;
341 : 2 : GError *error = NULL;
342 : :
343 : 2 : reply = g_dbus_connection_call_finish (connection, result, &error);
344 [ - + ]: 2 : if (reply == NULL)
345 : : {
346 [ # # ]: 0 : if (g_dbus_interface_skeleton_get_object_path (iface) != NULL)
347 : 0 : g_dbus_interface_skeleton_unexport (iface);
348 : :
349 : 0 : g_dbus_error_strip_remote_error (error);
350 : 0 : g_task_return_error (task, g_steal_pointer (&error));
351 : : }
352 : : else
353 : : {
354 : 2 : self->registered = TRUE;
355 : 2 : g_task_return_boolean (task, TRUE);
356 : : }
357 : :
358 [ + - ]: 2 : self->in_registration = FALSE;
359 : 2 : }
360 : :
361 : : /**
362 : : * valent_bluez_profile_register:
363 : : * @profile: a `ValentBluezProfile`
364 : : * @connection: a `GDBusConnection`
365 : : * @cancellable: (nullable): a `GCancellable`
366 : : * @callback: (scope async): a `GAsyncReadyCallback`
367 : : * @user_data: user supplied data
368 : : *
369 : : * Export the bluez profile for Valent on @connection and register it with the
370 : : * profile manager (`org.bluez.ProfileManager1`).
371 : : */
372 : : void
373 : 2 : valent_bluez_profile_register (ValentBluezProfile *profile,
374 : : GDBusConnection *connection,
375 : : GCancellable *cancellable,
376 : : GAsyncReadyCallback callback,
377 : : gpointer user_data)
378 : : {
379 : 2 : GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON (profile);
380 : 2 : g_autoptr (GTask) task = NULL;
381 : 2 : GVariantDict dict;
382 : 2 : GError *error = NULL;
383 : :
384 [ - + ]: 2 : g_return_if_fail (VALENT_IS_BLUEZ_PROFILE (profile));
385 [ + - + - : 2 : g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
- + - - ]
386 [ + - + - : 2 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
387 : :
388 : 2 : task = g_task_new (profile, cancellable, callback, user_data);
389 [ + - ]: 2 : g_task_set_source_tag (task, valent_bluez_profile_register);
390 : :
391 [ + - - + ]: 2 : if (profile->registered || profile->in_registration)
392 : : {
393 : 0 : g_task_return_boolean (task, TRUE);
394 : 0 : return;
395 : : }
396 : :
397 [ - + ]: 2 : if (!g_dbus_interface_skeleton_export (iface,
398 : : connection,
399 : : VALENT_BLUEZ_PROFILE_PATH,
400 : : &error))
401 : : {
402 : 0 : g_task_return_error (task, g_steal_pointer (&error));
403 : 0 : return;
404 : : }
405 : :
406 : 2 : g_variant_dict_init (&dict, NULL);
407 : 2 : g_variant_dict_insert (&dict, "RequireAuthentication", "b", TRUE);
408 : 2 : g_variant_dict_insert (&dict, "RequireAuthorization", "b", FALSE);
409 : 2 : g_variant_dict_insert (&dict, "Service", "s", VALENT_BLUEZ_PROFILE_UUID);
410 : 2 : g_variant_dict_insert (&dict, "Channel", "q", 0x06);
411 : :
412 : 2 : profile->in_registration = TRUE;
413 [ + - ]: 2 : g_dbus_connection_call (connection,
414 : : "org.bluez",
415 : : "/org/bluez",
416 : : "org.bluez.ProfileManager1",
417 : : "RegisterProfile",
418 : : g_variant_new ("(os@a{sv})",
419 : : VALENT_BLUEZ_PROFILE_PATH,
420 : : VALENT_BLUEZ_PROFILE_UUID,
421 : : g_variant_dict_end (&dict)),
422 : : NULL,
423 : : G_DBUS_CALL_FLAGS_NO_AUTO_START,
424 : : -1,
425 : : cancellable,
426 : : (GAsyncReadyCallback)profile_manager_register_profile_cb,
427 : : g_object_ref (task));
428 : : }
429 : :
430 : : /**
431 : : * valent_bluez_profile_register_finish:
432 : : * @profile: a `ValentBluezProfile`
433 : : * @result: a `GAsyncResult`
434 : : * @error: (nullable): a `GError`
435 : : *
436 : : * Finish an operation started with valent_bluez_profile_register().
437 : : *
438 : : * Returns: %TRUE if successful, or %FALSE with @error set
439 : : */
440 : : gboolean
441 : 2 : valent_bluez_profile_register_finish (ValentBluezProfile *profile,
442 : : GAsyncResult *result,
443 : : GError **error)
444 : : {
445 [ - + ]: 2 : g_return_val_if_fail (VALENT_IS_BLUEZ_PROFILE (profile), FALSE);
446 [ + - ]: 2 : g_return_val_if_fail (g_task_is_valid (result, profile), FALSE);
447 [ + - + - ]: 2 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
448 : :
449 : 2 : return g_task_propagate_boolean (G_TASK (result), error);
450 : : }
451 : :
452 : : static void
453 : 1 : profile_manager_unregister_profile_cb (GDBusConnection *connection,
454 : : GAsyncResult *result,
455 : : gpointer user_data)
456 : : {
457 : 1 : g_autoptr (ValentBluezProfile) self = g_steal_pointer (&user_data);
458 : 1 : GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON (self);
459 : 1 : g_autoptr (GVariant) reply = NULL;
460 [ + - ]: 1 : g_autoptr (GError) error = NULL;
461 : :
462 : 1 : reply = g_dbus_connection_call_finish (connection, result, &error);
463 [ - + ]: 1 : if (reply == NULL)
464 : : {
465 : 0 : g_dbus_error_strip_remote_error (error);
466 : 0 : g_debug ("%s(): %s", G_STRFUNC, error->message);
467 : : }
468 : :
469 [ + - ]: 1 : if (g_dbus_interface_skeleton_get_object_path (iface) != NULL)
470 : 1 : g_dbus_interface_skeleton_unexport (iface);
471 : :
472 : 1 : self->registered = FALSE;
473 [ - + ]: 1 : self->in_registration = FALSE;
474 : 1 : }
475 : :
476 : : /**
477 : : * valent_bluez_profile_unregister:
478 : : * @profile: a `ValentBluezProfile`
479 : : *
480 : : * Unexport the bluez profile for Valent from the system bus.
481 : : */
482 : : void
483 : 5 : valent_bluez_profile_unregister (ValentBluezProfile *profile)
484 : : {
485 : 5 : GDBusInterfaceSkeleton *iface = G_DBUS_INTERFACE_SKELETON (profile);
486 : 5 : const char *object_path = NULL;
487 : :
488 [ - + ]: 5 : g_return_if_fail (VALENT_IS_BLUEZ_PROFILE (profile));
489 : :
490 [ + + + + ]: 5 : if (profile->registered && !profile->in_registration)
491 : : {
492 : 2 : object_path = g_dbus_interface_skeleton_get_object_path (iface);
493 [ + - ]: 2 : g_return_if_fail (g_variant_is_object_path (object_path));
494 : :
495 : 2 : profile->in_registration = TRUE;
496 : 2 : g_dbus_connection_call (g_dbus_interface_skeleton_get_connection (iface),
497 : : "org.bluez",
498 : : "/org/bluez",
499 : : "org.bluez.ProfileManager1",
500 : : "UnregisterProfile",
501 : : g_variant_new ("(o)", object_path),
502 : : NULL,
503 : : G_DBUS_CALL_FLAGS_NO_AUTO_START,
504 : : -1,
505 : : NULL,
506 : : (GAsyncReadyCallback)profile_manager_unregister_profile_cb,
507 : : g_object_ref (profile));
508 : : }
509 : : }
510 : :
|