LCOV - code coverage report
Current view: top level - src/libvalent/device - valent-device.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 90.4 % 740 669
Test Date: 2025-12-04 03:51:48 Functions: 98.3 % 58 57
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 62.8 % 495 311

             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-device"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <inttypes.h>
       9                 :             : #include <math.h>
      10                 :             : 
      11                 :             : #include <glib/gi18n-lib.h>
      12                 :             : #include <gio/gio.h>
      13                 :             : #include <libvalent-core.h>
      14                 :             : 
      15                 :             : #include "../core/valent-component-private.h"
      16                 :             : #include "valent-certificate.h"
      17                 :             : #include "valent-channel.h"
      18                 :             : #include "valent-device-common.h"
      19                 :             : #include "valent-device-enums.h"
      20                 :             : #include "valent-device-plugin.h"
      21                 :             : #include "valent-packet.h"
      22                 :             : 
      23                 :             : #include "valent-device.h"
      24                 :             : #include "valent-device-private.h"
      25                 :             : 
      26                 :             : #define DEVICE_TYPE_DESKTOP  "desktop"
      27                 :             : #define DEVICE_TYPE_LAPTOP   "laptop"
      28                 :             : #define DEVICE_TYPE_PHONE    "phone"
      29                 :             : #define DEVICE_TYPE_TABLET   "tablet"
      30                 :             : #define DEVICE_TYPE_TV       "tv"
      31                 :             : 
      32                 :             : #define PAIR_REQUEST_ID           "pair-request"
      33                 :             : #define PAIR_REQUEST_TIMEOUT      (30)
      34                 :             : #define PAIR_CLOCK_SKEW_THRESHOLD (1800)
      35                 :             : 
      36                 :             : 
      37                 :             : /**
      38                 :             :  * ValentDevice:
      39                 :             :  *
      40                 :             :  * A class representing a remote device, such as a smartphone or desktop.
      41                 :             :  *
      42                 :             :  * Device functionality is limited to pairing and sending packets, while other
      43                 :             :  * functionality is delegated to [class@Valent.DevicePlugin] extensions.
      44                 :             :  *
      45                 :             :  * `ValentDevice` implements the [iface@Gio.ActionGroup] interface, acting as an
      46                 :             :  * aggregate action group for plugins. Plugin actions are automatically included
      47                 :             :  * in the device action group with the plugin module name as a prefix
      48                 :             :  * (eg. `share.files`).
      49                 :             :  *
      50                 :             :  * Since: 1.0
      51                 :             :  */
      52                 :             : 
      53                 :             : struct _ValentDevice
      54                 :             : {
      55                 :             :   ValentObject     parent_instance;
      56                 :             : 
      57                 :             :   ValentContext   *context;
      58                 :             : 
      59                 :             :   /* Properties */
      60                 :             :   char            *icon_name;
      61                 :             :   char            *id;
      62                 :             :   char            *name;
      63                 :             :   char            *type;
      64                 :             :   char           **incoming_capabilities;
      65                 :             :   char           **outgoing_capabilities;
      66                 :             :   int64_t          protocol_version;
      67                 :             : 
      68                 :             :   /* State */
      69                 :             :   GListModel      *channels;
      70                 :             :   ValentChannel   *channel;
      71                 :             :   gboolean         paired;
      72                 :             :   unsigned int     incoming_pair;
      73                 :             :   unsigned int     outgoing_pair;
      74                 :             :   int64_t          pair_timestamp;
      75                 :             : 
      76                 :             :   /* Plugins */
      77                 :             :   PeasEngine      *engine;
      78                 :             :   GHashTable      *plugins;
      79                 :             :   GHashTable      *handlers;
      80                 :             :   GHashTable      *actions;
      81                 :             :   GMenu           *menu;
      82                 :             : };
      83                 :             : 
      84                 :             : static void   g_action_group_iface_init (GActionGroupInterface *iface);
      85                 :             : 
      86   [ +  +  +  - ]:        4177 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentDevice, valent_device, VALENT_TYPE_OBJECT,
      87                 :             :                                G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_group_iface_init))
      88                 :             : 
      89                 :             : typedef enum {
      90                 :             :   PROP_CONTEXT = 1,
      91                 :             :   PROP_ICON_NAME,
      92                 :             :   PROP_ID,
      93                 :             :   PROP_NAME,
      94                 :             :   PROP_STATE,
      95                 :             : } ValentDeviceProperty;
      96                 :             : 
      97                 :             : static GParamSpec *properties[PROP_STATE + 1] = { NULL, };
      98                 :             : 
      99                 :             : 
     100                 :             : /*
     101                 :             :  * GActionGroup
     102                 :             :  */
     103                 :             : static void
     104                 :          43 : valent_device_activate_action (GActionGroup *action_group,
     105                 :             :                                const char   *action_name,
     106                 :             :                                GVariant     *parameter)
     107                 :             : {
     108                 :          43 :   ValentDevice *self = VALENT_DEVICE (action_group);
     109                 :          43 :   GAction *action;
     110                 :             : 
     111         [ +  - ]:          43 :   if ((action = g_hash_table_lookup (self->actions, action_name)) != NULL)
     112                 :          43 :     g_action_activate (action, parameter);
     113                 :          43 : }
     114                 :             : 
     115                 :             : static void
     116                 :           3 : valent_device_change_action_state (GActionGroup *action_group,
     117                 :             :                                    const char   *action_name,
     118                 :             :                                    GVariant     *value)
     119                 :             : {
     120                 :           3 :   ValentDevice *self = VALENT_DEVICE (action_group);
     121                 :           3 :   GAction *action;
     122                 :             : 
     123         [ +  - ]:           3 :   if ((action = g_hash_table_lookup (self->actions, action_name)) != NULL)
     124                 :           3 :     g_action_change_state (action, value);
     125                 :           3 : }
     126                 :             : 
     127                 :             : static char **
     128                 :           9 : valent_device_list_actions (GActionGroup *action_group)
     129                 :             : {
     130                 :           9 :   ValentDevice *self = VALENT_DEVICE (action_group);
     131                 :          18 :   g_auto (GStrv) actions = NULL;
     132                 :           9 :   GHashTableIter iter;
     133                 :           9 :   gpointer key;
     134                 :           9 :   unsigned int i = 0;
     135                 :             : 
     136         [ -  + ]:           9 :   actions = g_new0 (char *, g_hash_table_size (self->actions) + 1);
     137                 :             : 
     138                 :           9 :   g_hash_table_iter_init (&iter, self->actions);
     139                 :             : 
     140         [ +  + ]:          39 :   while (g_hash_table_iter_next (&iter, &key, NULL))
     141         [ -  + ]:          60 :     actions[i++] = g_strdup (key);
     142                 :             : 
     143                 :           9 :   return g_steal_pointer (&actions);
     144                 :             : }
     145                 :             : 
     146                 :             : static gboolean
     147                 :         186 : valent_device_query_action (GActionGroup        *action_group,
     148                 :             :                             const char          *action_name,
     149                 :             :                             gboolean            *enabled,
     150                 :             :                             const GVariantType **parameter_type,
     151                 :             :                             const GVariantType **state_type,
     152                 :             :                             GVariant           **state_hint,
     153                 :             :                             GVariant           **state)
     154                 :             : {
     155                 :         186 :   ValentDevice *self = VALENT_DEVICE (action_group);
     156                 :         186 :   GAction *action;
     157                 :             : 
     158         [ +  + ]:         186 :   if ((action = g_hash_table_lookup (self->actions, action_name)) == NULL)
     159                 :             :     return FALSE;
     160                 :             : 
     161         [ +  + ]:         166 :   if (enabled)
     162                 :          90 :     *enabled = g_action_get_enabled (action);
     163                 :             : 
     164         [ +  + ]:         166 :   if (parameter_type)
     165                 :          29 :     *parameter_type = g_action_get_parameter_type (action);
     166                 :             : 
     167         [ +  + ]:         166 :   if (state_type)
     168                 :           1 :     *state_type = g_action_get_state_type (action);
     169                 :             : 
     170         [ +  + ]:         166 :   if (state_hint)
     171                 :           1 :     *state_hint = g_action_get_state_hint (action);
     172                 :             : 
     173         [ +  + ]:         166 :   if (state)
     174                 :          48 :     *state = g_action_get_state (action);
     175                 :             : 
     176                 :             :   return TRUE;
     177                 :             : }
     178                 :             : 
     179                 :             : static void
     180                 :          32 : g_action_group_iface_init (GActionGroupInterface *iface)
     181                 :             : {
     182                 :          32 :   iface->activate_action = valent_device_activate_action;
     183                 :          32 :   iface->change_action_state = valent_device_change_action_state;
     184                 :          32 :   iface->list_actions = valent_device_list_actions;
     185                 :          32 :   iface->query_action = valent_device_query_action;
     186                 :          32 : }
     187                 :             : 
     188                 :             : 
     189                 :             : /*
     190                 :             :  * Private plugin methods
     191                 :             :  */
     192                 :             : static void
     193                 :         281 : on_plugin_action_added (GActionGroup *action_group,
     194                 :             :                         const char   *action_name,
     195                 :             :                         ValentPlugin *plugin)
     196                 :             : {
     197                 :         281 :   ValentDevice *self = VALENT_DEVICE (plugin->parent);
     198                 :         562 :   g_autofree char *full_name = NULL;
     199                 :         281 :   GAction *action;
     200                 :             : 
     201                 :         281 :   full_name = g_strdup_printf ("%s.%s",
     202                 :         281 :                                peas_plugin_info_get_module_name (plugin->info),
     203                 :             :                                action_name);
     204                 :         281 :   action = g_action_map_lookup_action (G_ACTION_MAP (action_group),
     205                 :             :                                        action_name);
     206                 :             : 
     207                 :         281 :   g_hash_table_replace (self->actions,
     208         [ -  + ]:         562 :                         g_strdup (full_name),
     209                 :             :                         g_object_ref (action));
     210                 :         281 :   g_action_group_action_added (G_ACTION_GROUP (plugin->parent), full_name);
     211                 :         281 : }
     212                 :             : 
     213                 :             : static void
     214                 :         470 : on_plugin_action_enabled_changed (GActionGroup *action_group,
     215                 :             :                                   const char   *action_name,
     216                 :             :                                   gboolean      enabled,
     217                 :             :                                   ValentPlugin *plugin)
     218                 :             : {
     219                 :         940 :   g_autofree char *full_name = NULL;
     220                 :             : 
     221                 :         470 :   full_name = g_strdup_printf ("%s.%s",
     222                 :         470 :                                peas_plugin_info_get_module_name (plugin->info),
     223                 :             :                                action_name);
     224                 :         470 :   g_action_group_action_enabled_changed (G_ACTION_GROUP (plugin->parent),
     225                 :             :                                          full_name,
     226                 :             :                                          enabled);
     227                 :         470 : }
     228                 :             : 
     229                 :             : static void
     230                 :         281 : on_plugin_action_removed (GActionGroup *action_group,
     231                 :             :                           const char   *action_name,
     232                 :             :                           ValentPlugin *plugin)
     233                 :             : {
     234                 :         281 :   ValentDevice *self = VALENT_DEVICE (plugin->parent);
     235                 :         562 :   g_autofree char *full_name = NULL;
     236                 :             : 
     237                 :         281 :   full_name = g_strdup_printf ("%s.%s",
     238                 :         281 :                                peas_plugin_info_get_module_name (plugin->info),
     239                 :             :                                action_name);
     240                 :             : 
     241                 :         281 :   g_action_group_action_removed (G_ACTION_GROUP (plugin->parent), full_name);
     242                 :         281 :   g_hash_table_remove (self->actions, full_name);
     243                 :         281 : }
     244                 :             : 
     245                 :             : static void
     246                 :          19 : on_plugin_action_state_changed (GActionGroup *action_group,
     247                 :             :                                 const char   *action_name,
     248                 :             :                                 GVariant     *value,
     249                 :             :                                 ValentPlugin *plugin)
     250                 :             : {
     251                 :          38 :   g_autofree char *full_name = NULL;
     252                 :             : 
     253                 :          19 :   full_name = g_strdup_printf ("%s.%s",
     254                 :          19 :                                peas_plugin_info_get_module_name (plugin->info),
     255                 :             :                                action_name);
     256                 :          19 :   g_action_group_action_state_changed (G_ACTION_GROUP (plugin->parent),
     257                 :             :                                        full_name,
     258                 :             :                                        value);
     259                 :          19 : }
     260                 :             : 
     261                 :             : static void
     262                 :         115 : valent_device_enable_plugin (ValentDevice *device,
     263                 :             :                              ValentPlugin *plugin)
     264                 :             : {
     265                 :         230 :   g_auto (GStrv) actions = NULL;
     266         [ +  - ]:         115 :   g_autofree char *urn = NULL;
     267                 :         115 :   const char *incoming = NULL;
     268                 :             : 
     269         [ -  + ]:         115 :   g_assert (VALENT_IS_DEVICE (device));
     270         [ +  - ]:         115 :   g_assert (plugin != NULL);
     271                 :             : 
     272                 :         115 :   plugin->extension = peas_engine_create_extension (device->engine,
     273                 :             :                                                     plugin->info,
     274                 :             :                                                     VALENT_TYPE_DEVICE_PLUGIN,
     275                 :             :                                                     "context",     plugin->context,
     276                 :             :                                                     "parent",      plugin->parent,
     277                 :             :                                                     NULL);
     278         [ +  - ]:         115 :   g_return_if_fail (G_IS_OBJECT (plugin->extension));
     279                 :             : 
     280                 :             :   /* Register packet handlers
     281                 :             :    */
     282                 :         115 :   incoming = peas_plugin_info_get_external_data (plugin->info,
     283                 :             :                                                  "DevicePluginIncoming");
     284         [ +  - ]:         115 :   if (incoming != NULL)
     285                 :             :     {
     286                 :         230 :       g_auto (GStrv) capabilities = NULL;
     287                 :             : 
     288                 :         115 :       capabilities = g_strsplit (incoming, ";", -1);
     289                 :             : 
     290         [ +  + ]:         333 :       for (unsigned int i = 0; capabilities[i] != NULL; i++)
     291                 :             :         {
     292                 :         218 :           GPtrArray *handlers = NULL;
     293                 :         218 :           const char *type = capabilities[i];
     294                 :             : 
     295         [ +  + ]:         218 :           if ((handlers = g_hash_table_lookup (device->handlers, type)) == NULL)
     296                 :             :             {
     297                 :         208 :               handlers = g_ptr_array_new ();
     298         [ -  + ]:         416 :               g_hash_table_insert (device->handlers, g_strdup (type), handlers);
     299                 :             :             }
     300                 :             : 
     301                 :         218 :           g_ptr_array_add (handlers, plugin->extension);
     302                 :             :         }
     303                 :             :     }
     304                 :             : 
     305                 :             :   /* Register plugin actions
     306                 :             :    */
     307                 :         115 :   actions = g_action_group_list_actions (G_ACTION_GROUP (plugin->extension));
     308         [ +  + ]:         396 :   for (unsigned int i = 0; actions[i] != NULL; i++)
     309                 :             :     {
     310                 :         281 :       on_plugin_action_added (G_ACTION_GROUP (plugin->extension),
     311                 :             :                               actions[i],
     312                 :             :                               plugin);
     313                 :             :     }
     314                 :             : 
     315                 :         115 :   g_signal_connect (plugin->extension,
     316                 :             :                     "action-added",
     317                 :             :                     G_CALLBACK (on_plugin_action_added),
     318                 :             :                     plugin);
     319                 :         115 :   g_signal_connect (plugin->extension,
     320                 :             :                     "action-enabled-changed",
     321                 :             :                     G_CALLBACK (on_plugin_action_enabled_changed),
     322                 :             :                     plugin);
     323                 :         115 :   g_signal_connect (plugin->extension,
     324                 :             :                     "action-removed",
     325                 :             :                     G_CALLBACK (on_plugin_action_removed),
     326                 :             :                     plugin);
     327                 :         115 :   g_signal_connect (plugin->extension,
     328                 :             :                     "action-state-changed",
     329                 :             :                     G_CALLBACK (on_plugin_action_state_changed),
     330                 :             :                     plugin);
     331                 :             : 
     332                 :             :   /* Bootstrap the newly instantiated plugin
     333                 :             :    */
     334                 :         115 :   valent_device_plugin_update_state (VALENT_DEVICE_PLUGIN (plugin->extension),
     335                 :             :                                      valent_device_get_state (device));
     336                 :             : }
     337                 :             : 
     338                 :             : static void
     339                 :           1 : valent_device_disable_plugin (ValentDevice *device,
     340                 :             :                               ValentPlugin *plugin)
     341                 :             : {
     342                 :           2 :   g_auto (GStrv) actions = NULL;
     343                 :           1 :   const char *incoming = NULL;
     344                 :             : 
     345         [ -  + ]:           1 :   g_assert (VALENT_IS_DEVICE (device));
     346         [ +  - ]:           1 :   g_assert (plugin != NULL);
     347         [ +  - ]:           1 :   g_return_if_fail (G_IS_OBJECT (plugin->extension));
     348                 :             : 
     349                 :             :   /* Unregister actions
     350                 :             :    */
     351                 :           1 :   g_signal_handlers_disconnect_by_data (plugin->extension, plugin);
     352                 :           1 :   actions = g_action_group_list_actions (G_ACTION_GROUP (plugin->extension));
     353         [ +  + ]:           3 :   for (size_t i = 0; actions[i]; i++)
     354                 :             :     {
     355                 :           2 :       on_plugin_action_removed (G_ACTION_GROUP (plugin->extension),
     356                 :             :                                 actions[i],
     357                 :             :                                 plugin);
     358                 :             :     }
     359                 :             : 
     360                 :             :   /* Unregister packet handlers
     361                 :             :    */
     362                 :           1 :   incoming = peas_plugin_info_get_external_data (plugin->info,
     363                 :             :                                                  "DevicePluginIncoming");
     364         [ +  - ]:           1 :   if (incoming != NULL)
     365                 :             :     {
     366                 :           2 :       g_auto (GStrv) capabilities = NULL;
     367                 :             : 
     368                 :           1 :       capabilities = g_strsplit (incoming, ";", -1);
     369         [ +  + ]:           3 :       for (size_t i = 0; capabilities[i] != NULL; i++)
     370                 :             :         {
     371                 :           2 :           GPtrArray *handlers = NULL;
     372                 :             : 
     373                 :           2 :           handlers = g_hash_table_lookup (device->handlers, capabilities[i]);
     374         [ -  + ]:           2 :           if (handlers == NULL)
     375                 :           0 :             continue;
     376                 :             : 
     377                 :           2 :           g_ptr_array_remove (handlers, plugin->extension);
     378         [ -  + ]:           2 :           if (handlers->len == 0)
     379                 :           0 :             g_hash_table_remove (device->handlers, capabilities[i]);
     380                 :             :         }
     381                 :             :     }
     382                 :             : 
     383                 :           1 :   valent_object_destroy (VALENT_OBJECT (plugin->extension));
     384   [ +  -  +  - ]:           1 :   g_clear_object (&plugin->extension);
     385                 :             : }
     386                 :             : 
     387                 :             : static void
     388                 :           2 : on_plugin_enabled_changed (ValentPlugin *plugin)
     389                 :             : {
     390         [ -  + ]:           2 :   g_assert (plugin != NULL);
     391         [ +  - ]:           2 :   g_assert (VALENT_IS_DEVICE (plugin->parent));
     392                 :             : 
     393         [ +  + ]:           2 :   if (valent_plugin_get_enabled (plugin))
     394                 :           1 :     valent_device_enable_plugin (plugin->parent, plugin);
     395                 :             :   else
     396                 :           1 :     valent_device_disable_plugin (plugin->parent, plugin);
     397                 :           2 : }
     398                 :             : 
     399                 :             : static gboolean
     400                 :         924 : valent_device_supports_plugin (ValentDevice   *device,
     401                 :             :                                PeasPluginInfo *info)
     402                 :             : {
     403                 :         924 :   const char **device_incoming;
     404                 :         924 :   const char **device_outgoing;
     405                 :         924 :   const char *in_str, *out_str;
     406                 :             : 
     407         [ -  + ]:         924 :   g_assert (VALENT_IS_DEVICE (device));
     408         [ +  - ]:         924 :   g_assert (info != NULL);
     409                 :             : 
     410         [ +  + ]:         924 :   if (!peas_engine_provides_extension (device->engine,
     411                 :             :                                        info,
     412                 :             :                                        VALENT_TYPE_DEVICE_PLUGIN))
     413                 :             :     return FALSE;
     414                 :             : 
     415                 :             :   /* Plugins that don't handle packets aren't dependent on capabilities
     416                 :             :    */
     417                 :         634 :   in_str = peas_plugin_info_get_external_data (info, "DevicePluginIncoming");
     418                 :         634 :   out_str = peas_plugin_info_get_external_data (info, "DevicePluginOutgoing");
     419         [ +  - ]:         634 :   if (in_str == NULL && out_str == NULL)
     420                 :             :     return TRUE;
     421                 :             : 
     422                 :             :   /* If capabilities are ready, check if the plugin outgoing matches the
     423                 :             :    * incoming device or vice-versa.
     424                 :             :    */
     425                 :         634 :   device_incoming = (const char **)device->incoming_capabilities;
     426                 :         634 :   device_outgoing = (const char **)device->outgoing_capabilities;
     427         [ +  + ]:         634 :   if (device_incoming == NULL || device_outgoing == NULL)
     428                 :             :     return FALSE;
     429                 :             : 
     430         [ +  - ]:         402 :   if (out_str != NULL)
     431                 :             :     {
     432                 :        1526 :       g_auto (GStrv) plugin_outgoing = NULL;
     433                 :             : 
     434                 :         402 :       plugin_outgoing = g_strsplit (out_str, ";", -1);
     435         [ +  + ]:         799 :       for (size_t i = 0; plugin_outgoing[i] != NULL; i++)
     436                 :             :         {
     437         [ +  + ]:         599 :           if (g_strv_contains (device_incoming, plugin_outgoing[i]))
     438         [ +  - ]:         202 :             return TRUE;
     439                 :             :         }
     440                 :             :     }
     441                 :             : 
     442         [ -  + ]:         200 :   if (in_str != NULL)
     443                 :             :     {
     444                 :        1124 :       g_auto (GStrv) plugin_incoming = NULL;
     445                 :             : 
     446                 :         200 :       plugin_incoming = g_strsplit (in_str, ";", -1);
     447         [ +  + ]:         575 :       for (size_t i = 0; plugin_incoming[i] != NULL; i++)
     448                 :             :         {
     449         [ +  + ]:         383 :           if (g_strv_contains (device_outgoing, plugin_incoming[i]))
     450         [ +  - ]:           8 :             return TRUE;
     451                 :             :         }
     452                 :             :     }
     453                 :             : 
     454                 :             :   return FALSE;
     455                 :             : }
     456                 :             : 
     457                 :             : static void
     458                 :         828 : on_load_plugin (PeasEngine     *engine,
     459                 :             :                 PeasPluginInfo *plugin_info,
     460                 :             :                 ValentDevice   *self)
     461                 :             : {
     462                 :         828 :   ValentPlugin *plugin;
     463                 :             : 
     464         [ -  + ]:         828 :   g_assert (PEAS_IS_ENGINE (engine));
     465         [ +  - ]:         828 :   g_assert (plugin_info != NULL);
     466         [ +  - ]:         828 :   g_assert (VALENT_IS_DEVICE (self));
     467                 :             : 
     468         [ +  + ]:         828 :   if (!valent_device_supports_plugin (self, plugin_info))
     469                 :             :     return;
     470                 :             : 
     471                 :         114 :   VALENT_NOTE ("%s: %s",
     472                 :             :                self->name,
     473                 :             :                peas_plugin_info_get_module_name (plugin_info));
     474                 :             : 
     475                 :         114 :   plugin = valent_plugin_new (self, self->context, plugin_info,
     476                 :             :                               G_CALLBACK (on_plugin_enabled_changed));
     477                 :         114 :   g_hash_table_insert (self->plugins, plugin_info, plugin);
     478                 :             : 
     479         [ +  - ]:         114 :   if (valent_plugin_get_enabled (plugin))
     480                 :         114 :     valent_device_enable_plugin (self, plugin);
     481                 :             : }
     482                 :             : 
     483                 :             : static void
     484                 :           5 : on_unload_plugin (PeasEngine     *engine,
     485                 :             :                   PeasPluginInfo *plugin_info,
     486                 :             :                   ValentDevice   *device)
     487                 :             : {
     488         [ -  + ]:           5 :   g_assert (PEAS_IS_ENGINE (engine));
     489         [ +  - ]:           5 :   g_assert (plugin_info != NULL);
     490         [ +  - ]:           5 :   g_assert (VALENT_IS_DEVICE (device));
     491                 :             : 
     492                 :           5 :   if (g_hash_table_remove (device->plugins, plugin_info))
     493                 :             :     {
     494                 :           5 :       VALENT_NOTE ("%s: %s",
     495                 :             :                    device->name,
     496                 :             :                    peas_plugin_info_get_module_name (plugin_info));
     497                 :             :     }
     498                 :           5 : }
     499                 :             : 
     500                 :             : static void
     501                 :         176 : valent_device_reload_plugins (ValentDevice *self)
     502                 :             : {
     503                 :         176 :   unsigned int n_plugins = 0;
     504                 :             : 
     505         [ -  + ]:         176 :   g_assert (VALENT_IS_DEVICE (self));
     506                 :             : 
     507                 :         176 :   n_plugins = g_list_model_get_n_items (G_LIST_MODEL (self->engine));
     508         [ +  + ]:         760 :   for (unsigned int i = 0; i < n_plugins; i++)
     509                 :             :     {
     510                 :         584 :       g_autoptr (PeasPluginInfo) plugin_info = NULL;
     511                 :             : 
     512                 :         584 :       plugin_info = g_list_model_get_item (G_LIST_MODEL (self->engine), i);
     513         [ +  + ]:         584 :       if (!g_hash_table_contains (self->plugins, plugin_info))
     514                 :         488 :         on_load_plugin (self->engine, plugin_info, self);
     515         [ -  + ]:          96 :       else if (!valent_device_supports_plugin (self, plugin_info))
     516                 :           0 :         on_unload_plugin (self->engine, plugin_info, self);
     517                 :             :     }
     518                 :         176 : }
     519                 :             : 
     520                 :             : static void
     521                 :          91 : valent_device_update_plugins (ValentDevice *self)
     522                 :             : {
     523                 :          91 :   ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
     524                 :          91 :   GHashTableIter iter;
     525                 :          91 :   ValentPlugin *plugin;
     526                 :             : 
     527         [ -  + ]:          91 :   g_assert (VALENT_IS_DEVICE (self));
     528                 :             : 
     529                 :          91 :   state = valent_device_get_state (self);
     530                 :             : 
     531                 :          91 :   g_hash_table_iter_init (&iter, self->plugins);
     532         [ +  + ]:         198 :   while (g_hash_table_iter_next (&iter, NULL, (void **)&plugin))
     533                 :             :     {
     534         [ -  + ]:         107 :       if (plugin->extension == NULL)
     535                 :           0 :         continue;
     536                 :             : 
     537                 :         107 :       valent_device_plugin_update_state (VALENT_DEVICE_PLUGIN (plugin->extension),
     538                 :             :                                          state);
     539                 :             :     }
     540                 :          91 : }
     541                 :             : 
     542                 :             : /*
     543                 :             :  * Private pairing methods
     544                 :             :  */
     545                 :             : static void
     546                 :         115 : valent_device_reset_pair (gpointer object)
     547                 :             : {
     548                 :         115 :   ValentDevice *device = VALENT_DEVICE (object);
     549                 :         115 :   GApplication *application = g_application_get_default ();
     550                 :             : 
     551         [ -  + ]:         115 :   g_assert (VALENT_IS_DEVICE (device));
     552                 :             : 
     553         [ -  + ]:         115 :   if (application != NULL)
     554                 :             :     {
     555                 :           0 :       g_autofree char *notification_id = NULL;
     556                 :             : 
     557                 :           0 :       notification_id = g_strdup_printf ("%s::%s", device->id, PAIR_REQUEST_ID);
     558                 :           0 :       g_application_withdraw_notification (application, notification_id);
     559                 :             :     }
     560                 :             : 
     561         [ +  + ]:         115 :   g_clear_handle_id (&device->incoming_pair, g_source_remove);
     562         [ +  + ]:         115 :   g_clear_handle_id (&device->outgoing_pair, g_source_remove);
     563                 :         115 :   device->pair_timestamp = 0;
     564                 :         115 : }
     565                 :             : 
     566                 :             : static gboolean
     567                 :           2 : valent_device_cancel_pair (gpointer object)
     568                 :             : {
     569                 :           2 :   ValentDevice *self = VALENT_DEVICE (object);
     570                 :             : 
     571         [ -  + ]:           2 :   g_assert (VALENT_IS_DEVICE (self));
     572                 :             : 
     573   [ +  +  +  - ]:           2 :   if (self->incoming_pair != 0 || self->outgoing_pair != 0)
     574                 :             :     {
     575                 :           2 :       valent_device_reset_pair (self);
     576                 :           2 :       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STATE]);
     577                 :             :     }
     578                 :             : 
     579                 :           2 :   return G_SOURCE_REMOVE;
     580                 :             : }
     581                 :             : 
     582                 :             : static void
     583                 :          13 : send_pair_cb (ValentChannel *channel,
     584                 :             :               GAsyncResult  *result,
     585                 :             :               ValentDevice  *device)
     586                 :             : {
     587                 :          26 :   g_autoptr (GError) error = NULL;
     588                 :             : 
     589         [ -  + ]:          13 :   g_assert (VALENT_IS_CHANNEL (channel));
     590         [ +  - ]:          13 :   g_assert (VALENT_IS_DEVICE (device));
     591                 :             : 
     592         [ -  + ]:          13 :   if (!valent_channel_write_packet_finish (channel, result, &error))
     593                 :             :     {
     594                 :           0 :       VALENT_NOTE ("%s: %s", device->name, error->message);
     595                 :             : 
     596                 :           0 :       valent_device_reset_pair (device);
     597                 :           0 :       valent_object_destroy (VALENT_OBJECT (channel));
     598                 :             :     }
     599                 :             : 
     600         [ -  + ]:          13 :   g_object_unref (device);
     601                 :          13 : }
     602                 :             : 
     603                 :             : static void
     604                 :          15 : valent_device_send_pair (ValentDevice *device,
     605                 :             :                          gboolean      pair)
     606                 :             : {
     607                 :          15 :   g_autoptr (JsonBuilder) builder = NULL;
     608   [ -  +  -  + ]:          15 :   g_autoptr (JsonNode) packet = NULL;
     609   [ -  +  +  - ]:          15 :   g_autoptr (GCancellable) cancellable = NULL;
     610                 :             : 
     611         [ -  + ]:          15 :   g_assert (VALENT_IS_DEVICE (device));
     612                 :             : 
     613         [ +  + ]:          15 :   if (device->channel == NULL)
     614         [ -  + ]:           2 :     return;
     615                 :             : 
     616                 :          13 :   valent_packet_init (&builder, "kdeconnect.pair");
     617                 :          13 :   json_builder_set_member_name (builder, "pair");
     618                 :          13 :   json_builder_add_boolean_value (builder, pair);
     619         [ +  - ]:          13 :   if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8 &&
     620   [ +  +  +  + ]:          13 :       device->pair_timestamp == 0 && pair)
     621                 :             :     {
     622                 :           6 :       device->pair_timestamp = (int64_t)floor (valent_timestamp_ms () / 1000);
     623                 :           6 :       json_builder_set_member_name (builder, "timestamp");
     624                 :           6 :       json_builder_add_int_value (builder, device->pair_timestamp);
     625                 :             :     }
     626                 :          13 :   packet = valent_packet_end (&builder);
     627                 :             : 
     628                 :          13 :   cancellable = valent_object_ref_cancellable (VALENT_OBJECT (device));
     629         [ +  - ]:          13 :   valent_channel_write_packet (device->channel,
     630                 :             :                                packet,
     631                 :             :                                cancellable,
     632                 :             :                                (GAsyncReadyCallback)send_pair_cb,
     633                 :             :                                g_object_ref (device));
     634                 :             : }
     635                 :             : 
     636                 :             : static void
     637                 :          13 : valent_device_set_paired (ValentDevice *self,
     638                 :             :                           gboolean      paired)
     639                 :             : {
     640                 :          13 :   g_autoptr (GFile) certificate_file = NULL;
     641         [ +  - ]:          13 :   g_autoptr (GError) error = NULL;
     642                 :             : 
     643         [ -  + ]:          13 :   g_assert (VALENT_IS_DEVICE (self));
     644                 :             : 
     645         [ +  + ]:          13 :   if (self->paired == paired)
     646                 :             :     {
     647                 :           2 :       valent_device_cancel_pair (self);
     648         [ -  + ]:           2 :       return;
     649                 :             :     }
     650                 :             : 
     651                 :             :   /* Store the certificate in the configuration directory if paired,
     652                 :             :    * otherwise delete the certificate and clear the data context.
     653                 :             :    *
     654                 :             :    * TODO: KDE Connect uses certificate pinning, but there is no shared resource
     655                 :             :    *       mediating between a ValentDevice and a ValentChannelService.
     656                 :             :    */
     657                 :          11 :   certificate_file = valent_context_get_config_file (self->context,
     658                 :             :                                                      "certificate.pem");
     659         [ +  + ]:          11 :   if (paired)
     660                 :             :     {
     661                 :           6 :       g_autoptr (GTlsCertificate) certificate = NULL;
     662         [ +  - ]:           6 :       g_autofree char *certificate_pem = NULL;
     663                 :             : 
     664         [ +  - ]:           6 :       if (self->channel != NULL)
     665                 :             :         {
     666                 :           6 :           g_object_get (self->channel,
     667                 :             :                         "peer-certificate", &certificate,
     668                 :             :                         NULL);
     669                 :           6 :           g_object_get (certificate,
     670                 :             :                         "certificate-pem", &certificate_pem,
     671                 :             :                         NULL);
     672                 :           6 :           g_file_replace_contents (certificate_file,
     673                 :             :                                    certificate_pem,
     674                 :           6 :                                    strlen (certificate_pem),
     675                 :             :                                    NULL,  /* etag */
     676                 :             :                                    FALSE, /* make_backup */
     677                 :             :                                    (G_FILE_CREATE_PRIVATE |
     678                 :             :                                     G_FILE_CREATE_REPLACE_DESTINATION),
     679                 :             :                                    NULL,  /* etag (out) */
     680                 :             :                                    NULL,
     681                 :             :                                    &error);
     682         [ -  + ]:           6 :           if (error != NULL)
     683                 :             :             {
     684                 :           0 :               g_warning ("%s(): failed to write \"%s\": %s",
     685                 :             :                          G_STRFUNC,
     686                 :             :                          g_file_peek_path (certificate_file),
     687                 :             :                          error->message);
     688                 :             :             }
     689                 :             :         }
     690                 :             :     }
     691                 :             :   else
     692                 :             :     {
     693   [ -  +  -  - ]:           5 :       if (!g_file_delete (certificate_file, NULL, &error) &&
     694                 :           0 :           !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
     695                 :             :         {
     696                 :           0 :           g_warning ("%s(): failed to delete \"%s\": %s",
     697                 :             :                      G_STRFUNC,
     698                 :             :                      g_file_peek_path (certificate_file),
     699                 :             :                      error->message);
     700                 :             :         }
     701                 :             : 
     702                 :           5 :       valent_context_clear (self->context);
     703                 :             :     }
     704                 :             : 
     705                 :          11 :   self->paired = paired;
     706                 :             : 
     707                 :          11 :   valent_device_reset_pair (self);
     708                 :          11 :   valent_device_update_plugins (self);
     709         [ -  + ]:          11 :   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STATE]);
     710                 :             : }
     711                 :             : 
     712                 :             : static void
     713                 :           2 : valent_device_notify_pair_requested (ValentDevice *device)
     714                 :             : {
     715                 :           2 :   GApplication *application = NULL;
     716                 :           2 :   g_autofree char *notification_id = NULL;
     717                 :           2 :   g_autoptr (GNotification) notification = NULL;
     718         [ -  - ]:           2 :   g_autoptr (GIcon) icon = NULL;
     719   [ -  -  -  + ]:           2 :   g_autofree char *title = NULL;
     720                 :           2 :   g_autofree char *verification_key = NULL;
     721                 :             : 
     722         [ -  + ]:           2 :   g_assert (VALENT_IS_DEVICE (device));
     723                 :             : 
     724                 :           2 :   application = g_application_get_default ();
     725         [ -  + ]:           2 :   if (application == NULL)
     726                 :             :     return;
     727                 :             : 
     728                 :           0 :   title = g_strdup_printf (_("Pairing request from “%s”"), device->name);
     729                 :           0 :   verification_key = valent_device_get_verification_key (device);
     730                 :           0 :   icon = g_themed_icon_new (APPLICATION_ID);
     731                 :             : 
     732         [ #  # ]:           0 :   g_return_if_fail (verification_key != NULL);
     733                 :             : 
     734                 :           0 :   notification = g_notification_new (title);
     735                 :           0 :   g_notification_set_body (notification, verification_key);
     736                 :           0 :   g_notification_set_icon (notification, icon);
     737                 :           0 :   g_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_URGENT);
     738                 :           0 :   g_notification_add_button_with_target (notification, _("Reject"), "app.device",
     739                 :             :                                          "(ssav)",
     740                 :             :                                          device->id,
     741                 :             :                                          "unpair",
     742                 :             :                                          NULL);
     743                 :           0 :   g_notification_add_button_with_target (notification, _("Accept"), "app.device",
     744                 :             :                                          "(ssav)",
     745                 :             :                                          device->id,
     746                 :             :                                          "pair",
     747                 :             :                                          NULL);
     748                 :             : 
     749                 :             :   /* Show the pairing notification and set a timeout for 30s
     750                 :             :    */
     751                 :           0 :   notification_id = g_strdup_printf ("%s::%s", device->id, PAIR_REQUEST_ID);
     752                 :           0 :   g_application_send_notification (application,
     753                 :             :                                    notification_id,
     754                 :             :                                    notification);
     755                 :             : }
     756                 :             : 
     757                 :             : static void
     758                 :           8 : valent_device_handle_pair (ValentDevice *device,
     759                 :             :                            JsonNode     *packet)
     760                 :             : {
     761                 :           8 :   gboolean pair;
     762                 :             : 
     763                 :           8 :   VALENT_ENTRY;
     764                 :             : 
     765         [ -  + ]:           8 :   g_assert (VALENT_IS_DEVICE (device));
     766         [ +  - ]:           8 :   g_assert (VALENT_IS_PACKET (packet));
     767                 :             : 
     768         [ -  + ]:           8 :   if (!valent_packet_get_boolean (packet, "pair", &pair))
     769                 :             :     {
     770                 :           0 :       g_warning ("%s(): expected \"pair\" field holding a boolean from \"%s\"",
     771                 :             :                  G_STRFUNC,
     772                 :             :                  device->name);
     773                 :           0 :       VALENT_EXIT;
     774                 :             :     }
     775                 :             : 
     776         [ +  + ]:           8 :   if (pair)
     777                 :             :     {
     778         [ +  + ]:           7 :       if (device->outgoing_pair > 0)
     779                 :             :         {
     780                 :           5 :           g_debug ("Pairing accepted by \"%s\"", device->name);
     781                 :           5 :           valent_device_set_paired (device, TRUE);
     782                 :             :         }
     783         [ -  + ]:           2 :       else if (device->incoming_pair > 0)
     784                 :             :         {
     785                 :           0 :           g_debug ("Ignoring concurrent pairing request from \"%s\"",
     786                 :             :                    device->name);
     787                 :             :         }
     788                 :             :       else
     789                 :             :         {
     790                 :           2 :           g_debug ("Pairing requested by \"%s\"", device->name);
     791                 :             : 
     792         [ +  - ]:           2 :           if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8)
     793                 :             :             {
     794                 :           2 :               int64_t timestamp = 0;
     795                 :           2 :               int64_t localtime = 0;
     796                 :             : 
     797         [ -  + ]:           2 :               if (!valent_packet_get_int (packet, "timestamp", &timestamp))
     798                 :             :                 {
     799                 :           0 :                   g_warning ("%s(): expected \"timestamp\" field holding an integer",
     800                 :             :                              G_STRFUNC);
     801                 :           0 :                   VALENT_EXIT;
     802                 :             :                 }
     803                 :             : 
     804                 :           2 :               localtime = (int64_t)floor (valent_timestamp_ms () / 1000);
     805         [ -  + ]:           2 :               if (ABS (localtime - timestamp) > PAIR_CLOCK_SKEW_THRESHOLD)
     806                 :             :                 {
     807                 :           0 :                   g_warning ("%s(): device clocks are out of sync", G_STRFUNC);
     808                 :           0 :                   VALENT_EXIT;
     809                 :             :                 }
     810                 :             : 
     811                 :           2 :               device->pair_timestamp = timestamp;
     812                 :             :             }
     813                 :             : 
     814         [ -  + ]:           2 :           if (device->paired)
     815                 :             :             {
     816                 :             :               /* In protocol v8, the timestamp field is mandatory for pair
     817                 :             :                * requests; unsolicited pair accept packets have been handled
     818                 :             :                * at this point, so the request can safely be auto-accepted.
     819                 :             :                *
     820                 :             :                * In protocol v7, pair request and accept packets are identical;
     821                 :             :                * if both devices interpret unsolicited accept packets as
     822                 :             :                * requests, they will get caught in an infinite loop.
     823                 :             :                */
     824         [ #  # ]:           0 :               if (device->pair_timestamp > 0)
     825                 :             :                 {
     826                 :           0 :                   g_debug ("Accepting pair request from paired device \"%s\"",
     827                 :             :                            device->name);
     828                 :           0 :                   valent_device_send_pair (device, TRUE);
     829                 :           0 :                   valent_device_set_paired (device, TRUE);
     830                 :           0 :                   VALENT_EXIT;
     831                 :             :                 }
     832                 :             :               else
     833                 :             :                 {
     834                 :           0 :                   g_warning ("Pairing requested by paired device \"%s\"",
     835                 :             :                              device->name);
     836                 :           0 :                   valent_device_set_paired (device, FALSE);
     837                 :             :                 }
     838                 :             :             }
     839                 :             : 
     840                 :           2 :           device->incoming_pair = g_timeout_add_seconds (PAIR_REQUEST_TIMEOUT,
     841                 :             :                                                          valent_device_cancel_pair,
     842                 :             :                                                          device);
     843                 :           2 :           g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
     844                 :           2 :           valent_device_notify_pair_requested (device);
     845                 :             :         }
     846                 :             :     }
     847                 :             :   else
     848                 :             :     {
     849                 :           1 :       g_debug ("Pairing rejected by \"%s\"", device->name);
     850                 :           1 :       valent_device_set_paired (device, FALSE);
     851                 :             :     }
     852                 :             : 
     853                 :           8 :   VALENT_EXIT;
     854                 :             : }
     855                 :             : 
     856                 :             : /*
     857                 :             :  * Private identity methods
     858                 :             :  */
     859                 :             : static char *
     860                 :         176 : valent_device_sanitize_name (const char *name,
     861                 :             :                              const char *fallback)
     862                 :             : {
     863                 :         176 :   static const uint8_t forbidden_chars[256] = {
     864                 :             :     ['"'] = 1, ['\''] = 1,
     865                 :             :     [','] = 1, ['.'] = 1,
     866                 :             :     [';'] = 1, [':'] = 1,
     867                 :             :     ['!'] = 1, ['?'] = 1,
     868                 :             :     ['('] = 1, [')'] = 1,
     869                 :             :     ['['] = 1, [']'] = 1,
     870                 :             :     ['<'] = 1, ['>'] = 1,
     871                 :             :   };
     872                 :         176 :   g_autoptr (GString) ret = NULL;
     873                 :             : 
     874   [ +  -  +  - ]:         176 :   g_assert (name != NULL && *name != '\0');
     875                 :             : 
     876                 :         176 :   ret = g_string_new (NULL);
     877         [ +  + ]:        2112 :   for (const char *p = name; *p != '\0'; p = g_utf8_next_char (p))
     878                 :             :     {
     879                 :        1936 :       gunichar ch = g_utf8_get_char (p);
     880   [ +  -  +  - ]:        1936 :       if (ch > 127 || !forbidden_chars[(uint8_t)ch])
     881                 :        1936 :         g_string_append_unichar (ret, ch);
     882                 :             : 
     883         [ +  - ]:        1936 :       if (ret->len == 32)
     884                 :             :         break;
     885                 :             :     }
     886                 :             : 
     887         [ -  + ]:         176 :   if (ret->len == 0)
     888                 :             :     {
     889                 :           0 :       g_warning ("%s(): device name \"%s\" could not be sanitized",
     890                 :             :                  G_STRFUNC,
     891                 :             :                  name);
     892         [ #  # ]:           0 :       return g_strdup (fallback);
     893                 :             :     }
     894                 :             : 
     895                 :         176 :   return g_string_free_and_steal (g_steal_pointer (&ret));
     896                 :             : }
     897                 :             : 
     898                 :             : static void
     899                 :         176 : valent_device_handle_identity (ValentDevice *device,
     900                 :             :                                JsonNode     *packet)
     901                 :             : {
     902                 :         176 :   const char *device_id;
     903                 :         176 :   const char *device_name;
     904                 :         176 :   const char *device_type;
     905                 :         352 :   g_autofree char *sanitized_name = NULL;
     906                 :             : 
     907                 :         176 :   VALENT_ENTRY;
     908                 :             : 
     909         [ -  + ]:         176 :   g_assert (VALENT_IS_DEVICE (device));
     910         [ +  - ]:         176 :   g_assert (VALENT_IS_PACKET (packet));
     911                 :             : 
     912                 :             :   /* The ID must match the construct-time value.
     913                 :             :    */
     914         [ +  - ]:         176 :   if (!valent_packet_get_string (packet, "deviceId", &device_id) ||
     915         [ -  + ]:         176 :       !g_str_equal (device->id, device_id))
     916                 :             :     {
     917                 :           0 :       g_critical ("%s(): expected \"deviceId\" field holding \"%s\"",
     918                 :             :                   G_STRFUNC,
     919                 :             :                   device->id);
     920                 :           0 :       VALENT_EXIT;
     921                 :             :     }
     922                 :             : 
     923                 :             :   /* If the device name is invalid, try removing the offended characters,
     924                 :             :    * falling back to the device ID.
     925                 :             :    */
     926         [ -  + ]:         176 :   if (!valent_packet_get_string (packet, "deviceName", &device_name))
     927                 :             :     {
     928                 :           0 :       g_critical ("%s(): expected \"deviceName\" field holding a string",
     929                 :             :                   G_STRFUNC);
     930                 :           0 :       VALENT_EXIT;
     931                 :             :     }
     932                 :             : 
     933                 :         176 :   sanitized_name = valent_device_sanitize_name (device_name, device_id);
     934         [ +  + ]:         176 :   if (g_set_str (&device->name, sanitized_name))
     935                 :          96 :     g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_NAME]);
     936                 :             : 
     937                 :             :   /* The device type is only used to generate an icon name.
     938                 :             :    */
     939         [ -  + ]:         176 :   if (!valent_packet_get_string (packet, "deviceType", &device_type))
     940                 :             :     {
     941                 :           0 :       g_warning ("%s(): expected \"deviceType\" field holding a string",
     942                 :             :                   G_STRFUNC);
     943                 :           0 :       device_type = DEVICE_TYPE_DESKTOP;
     944                 :             :     }
     945                 :             : 
     946         [ +  + ]:         176 :   if (g_set_str (&device->type, device_type))
     947                 :             :     {
     948                 :          96 :       const char *device_icon = "computer-symbolic";
     949                 :             : 
     950         [ +  + ]:          96 :       if (g_str_equal (device_type, DEVICE_TYPE_DESKTOP))
     951                 :             :         device_icon = "computer-symbolic";
     952         [ +  - ]:          93 :       else if (g_str_equal (device_type, DEVICE_TYPE_LAPTOP))
     953                 :             :         device_icon = "laptop-symbolic";
     954         [ -  + ]:          93 :       else if (g_str_equal (device_type, DEVICE_TYPE_PHONE))
     955                 :             :         device_icon = "phone-symbolic";
     956         [ #  # ]:           0 :       else if (g_str_equal (device_type, DEVICE_TYPE_TABLET))
     957                 :             :         device_icon = "tablet-symbolic";
     958         [ #  # ]:           0 :       else if (g_str_equal (device_type, DEVICE_TYPE_TV))
     959                 :          96 :         device_icon = "tv-symbolic";
     960                 :             : 
     961         [ +  - ]:          96 :       if (g_set_str (&device->icon_name, device_icon))
     962                 :          96 :         g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_ICON_NAME]);
     963                 :             :     }
     964                 :             : 
     965                 :             :   /* In practice these are static, but in principle could change with the
     966                 :             :    * channel (e.g. TCP and Bluetooth).
     967                 :             :    */
     968         [ +  + ]:         176 :   g_clear_pointer (&device->incoming_capabilities, g_strfreev);
     969                 :         352 :   device->incoming_capabilities =
     970                 :         176 :     valent_packet_dup_strv (packet, "incomingCapabilities");
     971                 :             : 
     972         [ +  + ]:         176 :   g_clear_pointer (&device->outgoing_capabilities, g_strfreev);
     973                 :         352 :   device->outgoing_capabilities =
     974                 :         176 :     valent_packet_dup_strv (packet, "outgoingCapabilities");
     975                 :             : 
     976                 :             :   /* It's not clear if this is only a required field for TLS connections,
     977                 :             :    * or if it applies to Bluetooth as well.
     978                 :             :    */
     979         [ -  + ]:         176 :   if (!valent_packet_get_int (packet, "protocolVersion", &device->protocol_version))
     980                 :           0 :     device->protocol_version = VALENT_NETWORK_PROTOCOL_V8;
     981                 :             : 
     982                 :             :   /* Recheck plugins against current capabilities
     983                 :             :    */
     984                 :         176 :   valent_device_reload_plugins (device);
     985                 :             : 
     986                 :         352 :   VALENT_EXIT;
     987                 :             : }
     988                 :             : 
     989                 :             : static void
     990                 :         128 : valent_device_handle_packet (ValentDevice *self,
     991                 :             :                              JsonNode     *packet)
     992                 :             : {
     993                 :         128 :   GPtrArray *handlers = NULL;
     994                 :         128 :   const char *type;
     995                 :             : 
     996         [ -  + ]:         128 :   g_assert (VALENT_IS_DEVICE (self));
     997         [ +  - ]:         128 :   g_assert (VALENT_IS_PACKET (packet));
     998                 :             : 
     999                 :         128 :   VALENT_JSON (packet, self->name);
    1000                 :             : 
    1001                 :         128 :   type = valent_packet_get_type (packet);
    1002         [ +  + ]:         128 :   if G_UNLIKELY (g_str_equal (type, "kdeconnect.pair"))
    1003                 :             :     {
    1004                 :           8 :       valent_device_handle_pair (self, packet);
    1005                 :           8 :       return;
    1006                 :             :     }
    1007                 :             : 
    1008         [ +  + ]:         120 :   if G_UNLIKELY (!self->paired)
    1009                 :             :     {
    1010                 :           1 :       g_debug ("%s(): unexpected \"%s\" packet from unpaired device %s",
    1011                 :             :                G_STRFUNC, type, self->name);
    1012                 :           1 :       valent_device_send_pair (self, FALSE);
    1013                 :           1 :       return;
    1014                 :             :     }
    1015                 :             : 
    1016                 :         119 :   handlers = g_hash_table_lookup (self->handlers, type);
    1017         [ -  + ]:         119 :   if G_UNLIKELY (handlers == NULL)
    1018                 :             :     {
    1019                 :           0 :       g_debug ("%s(): unsupported \"%s\" packet from %s",
    1020                 :             :                G_STRFUNC, type, self->name);
    1021                 :           0 :       return;
    1022                 :             :     }
    1023                 :             : 
    1024         [ +  + ]:         238 :   for (unsigned int i = 0, len = handlers->len; i < len; i++)
    1025                 :             :     {
    1026                 :         119 :       ValentDevicePlugin *handler = g_ptr_array_index (handlers, i);
    1027                 :             : 
    1028                 :         119 :       valent_device_plugin_handle_packet (handler, type, packet);
    1029                 :             :     }
    1030                 :             : }
    1031                 :             : 
    1032                 :             : /*
    1033                 :             :  * GActions
    1034                 :             :  */
    1035                 :             : static void
    1036                 :           8 : pair_action (GSimpleAction *action,
    1037                 :             :              GVariant      *parameter,
    1038                 :             :              gpointer       user_data)
    1039                 :             : {
    1040                 :           8 :   ValentDevice *device = VALENT_DEVICE (user_data);
    1041                 :             : 
    1042         [ +  + ]:           8 :   if (device->incoming_pair > 0)
    1043                 :             :     {
    1044                 :           1 :       g_debug ("Accepting pair request from \"%s\"", device->name);
    1045                 :             : 
    1046                 :           1 :       valent_device_send_pair (device, TRUE);
    1047                 :           1 :       valent_device_set_paired (device, TRUE);
    1048                 :             :     }
    1049         [ -  + ]:           7 :   else if (device->outgoing_pair > 0)
    1050                 :             :     {
    1051                 :           0 :       g_warning ("Pairing request with \"%s\" already in progress",
    1052                 :             :                  device->name);
    1053                 :             :     }
    1054         [ -  + ]:           7 :   else if (device->paired)
    1055                 :             :     {
    1056                 :           0 :       g_warning ("Ignoring attempt to pair with paired device \"%s\"",
    1057                 :             :                  device->name);
    1058                 :             :     }
    1059                 :             :   else
    1060                 :             :     {
    1061                 :           7 :       g_debug ("Sending pair request to \"%s\"", device->name);
    1062                 :             : 
    1063                 :           7 :       valent_device_send_pair (device, TRUE);
    1064                 :           7 :       device->outgoing_pair = g_timeout_add_seconds (PAIR_REQUEST_TIMEOUT,
    1065                 :             :                                                      valent_device_cancel_pair,
    1066                 :             :                                                      device);
    1067                 :           7 :       g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
    1068                 :             :     }
    1069                 :           8 : }
    1070                 :             : 
    1071                 :             : static void
    1072                 :           6 : unpair_action (GSimpleAction *action,
    1073                 :             :                GVariant      *parameter,
    1074                 :             :                gpointer       user_data)
    1075                 :             : {
    1076                 :           6 :   ValentDevice *device = VALENT_DEVICE (user_data);
    1077                 :             : 
    1078                 :           6 :   valent_device_send_pair (device, FALSE);
    1079                 :           6 :   valent_device_set_paired (device, FALSE);
    1080                 :           6 : }
    1081                 :             : 
    1082                 :             : /*
    1083                 :             :  * ValentObject
    1084                 :             :  */
    1085                 :             : static void
    1086                 :         102 : valent_device_destroy (ValentObject *object)
    1087                 :             : {
    1088                 :         102 :   ValentDevice *self = VALENT_DEVICE (object);
    1089                 :         102 :   unsigned int n_items = 0;
    1090                 :             : 
    1091                 :         102 :   valent_device_reset_pair (self);
    1092                 :             : 
    1093                 :             :   /* Destroy all connections associated with this device,
    1094                 :             :    * ensuring a state change is propagated to plugins
    1095                 :             :    */
    1096                 :         102 :   n_items = g_list_model_get_n_items (self->channels);
    1097                 :         102 :   for (unsigned int i = n_items; i--;)
    1098                 :             :     {
    1099         [ -  + ]:         102 :       g_autoptr (ValentObject) item = NULL;
    1100                 :             : 
    1101                 :           0 :       item = g_list_model_get_item (self->channels, i);
    1102         [ #  # ]:           0 :       valent_object_destroy (item);
    1103                 :             :     }
    1104                 :             : 
    1105                 :         102 :   g_signal_handlers_disconnect_by_data (self->engine, self);
    1106                 :         102 :   g_hash_table_remove_all (self->plugins);
    1107                 :         102 :   g_hash_table_remove_all (self->actions);
    1108                 :         102 :   g_hash_table_remove_all (self->handlers);
    1109                 :             : 
    1110                 :         102 :   VALENT_OBJECT_CLASS (valent_device_parent_class)->destroy (object);
    1111                 :         102 : }
    1112                 :             : 
    1113                 :             : /*
    1114                 :             :  * GObject
    1115                 :             :  */
    1116                 :             : static void
    1117                 :          97 : valent_device_constructed (GObject *object)
    1118                 :             : {
    1119                 :          97 :   ValentDevice *self = VALENT_DEVICE (object);
    1120                 :         194 :   g_autoptr (GFile) certificate_file = NULL;
    1121                 :          97 :   unsigned int n_plugins = 0;
    1122                 :             : 
    1123                 :          97 :   G_OBJECT_CLASS (valent_device_parent_class)->constructed (object);
    1124                 :             : 
    1125                 :             :   /* We must at least have a device ID */
    1126         [ -  + ]:          97 :   g_assert (self->id != NULL);
    1127                 :             : 
    1128         [ +  - ]:          97 :   if (self->context == NULL)
    1129                 :             :     {
    1130                 :          97 :       g_autoptr (ValentContext) context = NULL;
    1131                 :          97 :       ValentObject *parent = NULL;
    1132                 :             : 
    1133                 :          97 :       parent = valent_object_get_parent (VALENT_OBJECT (self));
    1134         [ +  + ]:          97 :       if (parent != NULL)
    1135                 :           6 :         g_object_get (parent, "context", &context, NULL);
    1136                 :             : 
    1137         [ -  + ]:          97 :       self->context = valent_context_new (context, "device", self->id);
    1138                 :             :     }
    1139                 :             : 
    1140                 :          97 :   certificate_file = valent_context_get_config_file (self->context,
    1141                 :             :                                                      "certificate.pem");
    1142                 :          97 :   self->paired = g_file_query_exists (certificate_file, NULL);
    1143                 :             : 
    1144                 :          97 :   self->engine = valent_get_plugin_engine ();
    1145                 :          97 :   g_signal_connect_object (self->engine,
    1146                 :             :                            "load-plugin",
    1147                 :             :                            G_CALLBACK (on_load_plugin),
    1148                 :             :                            self,
    1149                 :             :                            G_CONNECT_AFTER);
    1150                 :             : 
    1151                 :          97 :   g_signal_connect_object (self->engine,
    1152                 :             :                            "unload-plugin",
    1153                 :             :                            G_CALLBACK (on_unload_plugin),
    1154                 :             :                            self,
    1155                 :             :                            G_CONNECT_DEFAULT);
    1156                 :             : 
    1157                 :          97 :   n_plugins = g_list_model_get_n_items (G_LIST_MODEL (self->engine));
    1158         [ +  + ]:         434 :   for (unsigned int i = 0; i < n_plugins; i++)
    1159                 :             :     {
    1160                 :         337 :       g_autoptr (PeasPluginInfo) info = NULL;
    1161                 :             : 
    1162                 :         337 :       info = g_list_model_get_item (G_LIST_MODEL (self->engine), i);
    1163         [ +  + ]:         337 :       if (peas_plugin_info_is_loaded (info))
    1164                 :         336 :         on_load_plugin (self->engine, info, self);
    1165                 :             :     }
    1166                 :          97 : }
    1167                 :             : 
    1168                 :             : static void
    1169                 :          95 : valent_device_finalize (GObject *object)
    1170                 :             : {
    1171                 :          95 :   ValentDevice *self = VALENT_DEVICE (object);
    1172                 :             : 
    1173         [ +  - ]:          95 :   g_clear_object (&self->context);
    1174                 :             : 
    1175                 :             :   /* Properties */
    1176         [ +  + ]:          95 :   g_clear_pointer (&self->icon_name, g_free);
    1177         [ +  - ]:          95 :   g_clear_pointer (&self->id, g_free);
    1178         [ +  + ]:          95 :   g_clear_pointer (&self->name, g_free);
    1179         [ +  + ]:          95 :   g_clear_pointer (&self->type, g_free);
    1180         [ +  + ]:          95 :   g_clear_pointer (&self->incoming_capabilities, g_strfreev);
    1181         [ +  + ]:          95 :   g_clear_pointer (&self->outgoing_capabilities, g_strfreev);
    1182                 :             : 
    1183                 :             :   /* State */
    1184         [ +  - ]:          95 :   g_clear_object (&self->channels);
    1185         [ -  + ]:          95 :   g_clear_object (&self->channel);
    1186                 :             : 
    1187                 :             :   /* Plugins */
    1188         [ +  - ]:          95 :   g_clear_pointer (&self->plugins, g_hash_table_unref);
    1189         [ +  - ]:          95 :   g_clear_pointer (&self->actions, g_hash_table_unref);
    1190         [ +  - ]:          95 :   g_clear_pointer (&self->handlers, g_hash_table_unref);
    1191         [ +  - ]:          95 :   g_clear_object (&self->menu);
    1192                 :             : 
    1193                 :          95 :   G_OBJECT_CLASS (valent_device_parent_class)->finalize (object);
    1194                 :          95 : }
    1195                 :             : 
    1196                 :             : static void
    1197                 :          32 : valent_device_get_property (GObject    *object,
    1198                 :             :                             guint       prop_id,
    1199                 :             :                             GValue     *value,
    1200                 :             :                             GParamSpec *pspec)
    1201                 :             : {
    1202                 :          32 :   ValentDevice *self = VALENT_DEVICE (object);
    1203                 :             : 
    1204   [ +  +  +  +  :          32 :   switch ((ValentDeviceProperty)prop_id)
                   +  - ]
    1205                 :             :     {
    1206                 :          11 :     case PROP_CONTEXT:
    1207                 :          11 :       g_value_set_object (value, self->context);
    1208                 :          11 :       break;
    1209                 :             : 
    1210                 :           4 :     case PROP_ICON_NAME:
    1211                 :           4 :       g_value_set_string (value, self->icon_name);
    1212                 :           4 :       break;
    1213                 :             : 
    1214                 :           5 :     case PROP_ID:
    1215                 :           5 :       g_value_set_string (value, self->id);
    1216                 :           5 :       break;
    1217                 :             : 
    1218                 :          10 :     case PROP_NAME:
    1219                 :          10 :       g_value_set_string (value, self->name);
    1220                 :          10 :       break;
    1221                 :             : 
    1222                 :           2 :     case PROP_STATE:
    1223                 :           2 :       g_value_set_flags (value, valent_device_get_state (self));
    1224                 :           2 :       break;
    1225                 :             : 
    1226                 :           0 :     default:
    1227                 :           0 :       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    1228                 :             :     }
    1229                 :          32 : }
    1230                 :             : 
    1231                 :             : static void
    1232                 :         194 : valent_device_set_property (GObject      *object,
    1233                 :             :                             guint         prop_id,
    1234                 :             :                             const GValue *value,
    1235                 :             :                             GParamSpec   *pspec)
    1236                 :             : {
    1237                 :         194 :   ValentDevice *self = VALENT_DEVICE (object);
    1238                 :             : 
    1239      [ +  +  - ]:         194 :   switch ((ValentDeviceProperty)prop_id)
    1240                 :             :     {
    1241                 :          97 :     case PROP_CONTEXT:
    1242                 :          97 :       self->context = g_value_dup_object (value);
    1243                 :          97 :       break;
    1244                 :             : 
    1245                 :          97 :     case PROP_ID:
    1246                 :          97 :       self->id = g_value_dup_string (value);
    1247                 :          97 :       break;
    1248                 :             : 
    1249                 :           0 :     case PROP_ICON_NAME:
    1250                 :             :     case PROP_NAME:
    1251                 :             :     case PROP_STATE:
    1252                 :             :     default:
    1253                 :           0 :       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    1254                 :             :     }
    1255                 :         194 : }
    1256                 :             : 
    1257                 :             : static void
    1258                 :          97 : valent_device_init (ValentDevice *self)
    1259                 :             : {
    1260                 :          97 :   GSimpleAction *action = NULL;
    1261                 :             : 
    1262                 :          97 :   self->channels = G_LIST_MODEL (g_list_store_new (VALENT_TYPE_CHANNEL));
    1263                 :          97 :   self->plugins = g_hash_table_new_full (NULL, NULL, NULL, valent_plugin_free);
    1264                 :          97 :   self->handlers = g_hash_table_new_full (g_str_hash,
    1265                 :             :                                           g_str_equal,
    1266                 :             :                                           g_free,
    1267                 :             :                                           (GDestroyNotify)g_ptr_array_unref);
    1268                 :          97 :   self->actions = g_hash_table_new_full (g_str_hash,
    1269                 :             :                                          g_str_equal,
    1270                 :             :                                          g_free,
    1271                 :             :                                          g_object_unref);
    1272                 :          97 :   self->menu = g_menu_new ();
    1273                 :             : 
    1274                 :             :   /* Stock Actions */
    1275                 :          97 :   action = g_simple_action_new ("pair", NULL);
    1276                 :          97 :   g_signal_connect_object (action,
    1277                 :             :                            "activate",
    1278                 :             :                            G_CALLBACK (pair_action),
    1279                 :             :                            self,
    1280                 :             :                            G_CONNECT_DEFAULT);
    1281                 :          97 :   g_hash_table_replace (self->actions,
    1282                 :          97 :                         g_strdup ("pair"),
    1283                 :             :                         g_steal_pointer (&action));
    1284                 :             : 
    1285                 :          97 :   action = g_simple_action_new ("unpair", NULL);
    1286                 :          97 :   g_signal_connect_object (action,
    1287                 :             :                            "activate",
    1288                 :             :                            G_CALLBACK (unpair_action),
    1289                 :             :                            self,
    1290                 :             :                            G_CONNECT_DEFAULT);
    1291                 :          97 :   g_hash_table_replace (self->actions,
    1292                 :          97 :                         g_strdup ("unpair"),
    1293                 :             :                         g_steal_pointer (&action));
    1294                 :          97 : }
    1295                 :             : 
    1296                 :             : static void
    1297                 :          32 : valent_device_class_init (ValentDeviceClass *klass)
    1298                 :             : {
    1299                 :          32 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
    1300                 :          32 :   ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
    1301                 :             : 
    1302                 :          32 :   object_class->constructed = valent_device_constructed;
    1303                 :          32 :   object_class->finalize = valent_device_finalize;
    1304                 :          32 :   object_class->get_property = valent_device_get_property;
    1305                 :          32 :   object_class->set_property = valent_device_set_property;
    1306                 :             : 
    1307                 :          32 :   vobject_class->destroy = valent_device_destroy;
    1308                 :             : 
    1309                 :             :   /**
    1310                 :             :    * ValentDevice:context: (getter get_context)
    1311                 :             :    *
    1312                 :             :    * The data context.
    1313                 :             :    *
    1314                 :             :    * Since: 1.0
    1315                 :             :    */
    1316                 :          64 :   properties [PROP_CONTEXT] =
    1317                 :          32 :     g_param_spec_object ("context", NULL, NULL,
    1318                 :             :                          VALENT_TYPE_CONTEXT,
    1319                 :             :                          (G_PARAM_READWRITE |
    1320                 :             :                           G_PARAM_CONSTRUCT_ONLY |
    1321                 :             :                           G_PARAM_EXPLICIT_NOTIFY |
    1322                 :             :                           G_PARAM_STATIC_STRINGS));
    1323                 :             : 
    1324                 :             :   /**
    1325                 :             :    * ValentDevice:icon-name: (getter get_icon_name)
    1326                 :             :    *
    1327                 :             :    * A symbolic icon name for the device.
    1328                 :             :    *
    1329                 :             :    * Since: 1.0
    1330                 :             :    */
    1331                 :          64 :   properties [PROP_ICON_NAME] =
    1332                 :          32 :     g_param_spec_string ("icon-name", NULL, NULL,
    1333                 :             :                          "computer-symbolic",
    1334                 :             :                          (G_PARAM_READABLE |
    1335                 :             :                           G_PARAM_EXPLICIT_NOTIFY |
    1336                 :             :                           G_PARAM_STATIC_STRINGS));
    1337                 :             : 
    1338                 :             :   /**
    1339                 :             :    * ValentDevice:id: (getter get_id)
    1340                 :             :    *
    1341                 :             :    * A unique ID for the device.
    1342                 :             :    *
    1343                 :             :    * By convention, the single source of truth for a device ID in KDE Connect is
    1344                 :             :    * the common name of its TLS certificate. It is not well-defined how this ID
    1345                 :             :    * is generated, however.
    1346                 :             :    *
    1347                 :             :    * Since: 1.0
    1348                 :             :    */
    1349                 :          64 :   properties [PROP_ID] =
    1350                 :          32 :     g_param_spec_string ("id", NULL, NULL,
    1351                 :             :                          NULL,
    1352                 :             :                          (G_PARAM_READWRITE |
    1353                 :             :                           G_PARAM_CONSTRUCT_ONLY |
    1354                 :             :                           G_PARAM_EXPLICIT_NOTIFY |
    1355                 :             :                           G_PARAM_STATIC_STRINGS));
    1356                 :             : 
    1357                 :             :   /**
    1358                 :             :    * ValentDevice:name: (getter get_name)
    1359                 :             :    *
    1360                 :             :    * A display name for the device.
    1361                 :             :    *
    1362                 :             :    * Since: 1.0
    1363                 :             :    */
    1364                 :          64 :   properties [PROP_NAME] =
    1365                 :          32 :     g_param_spec_string ("name", NULL, NULL,
    1366                 :             :                          NULL,
    1367                 :             :                          (G_PARAM_READABLE |
    1368                 :             :                           G_PARAM_EXPLICIT_NOTIFY |
    1369                 :             :                           G_PARAM_STATIC_STRINGS));
    1370                 :             : 
    1371                 :             :   /**
    1372                 :             :    * ValentDevice:state: (getter get_state)
    1373                 :             :    *
    1374                 :             :    * The state of the device.
    1375                 :             :    *
    1376                 :             :    * Since: 1.0
    1377                 :             :    */
    1378                 :          64 :   properties [PROP_STATE] =
    1379                 :          32 :     g_param_spec_flags ("state", NULL, NULL,
    1380                 :             :                         VALENT_TYPE_DEVICE_STATE,
    1381                 :             :                         VALENT_DEVICE_STATE_NONE,
    1382                 :             :                         (G_PARAM_READABLE |
    1383                 :             :                          G_PARAM_EXPLICIT_NOTIFY |
    1384                 :             :                          G_PARAM_STATIC_STRINGS));
    1385                 :             : 
    1386                 :          32 :   g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
    1387                 :          32 : }
    1388                 :             : 
    1389                 :             : /*< private >
    1390                 :             :  * valent_device_new_full:
    1391                 :             :  * @parent: (nullable): a `ValentObject`
    1392                 :             :  * @identity: a KDE Connect identity packet
    1393                 :             :  *
    1394                 :             :  * Create a new device for @identity.
    1395                 :             :  *
    1396                 :             :  * Returns: (transfer full) (nullable): a new `ValentDevice`
    1397                 :             :  */
    1398                 :             : ValentDevice *
    1399                 :          96 : valent_device_new_full (ValentObject *parent,
    1400                 :             :                         JsonNode     *identity)
    1401                 :             : {
    1402                 :          96 :   ValentDevice *ret;
    1403                 :          96 :   const char *id;
    1404                 :             : 
    1405   [ +  +  -  + ]:          96 :   g_return_val_if_fail (parent == NULL || VALENT_IS_OBJECT (parent), NULL);
    1406         [ +  - ]:          96 :   g_return_val_if_fail (VALENT_IS_PACKET (identity), NULL);
    1407                 :             : 
    1408         [ -  + ]:          96 :   if (!valent_packet_get_string (identity, "deviceId", &id))
    1409                 :             :     {
    1410                 :           0 :       g_critical ("%s(): missing \"deviceId\" field", G_STRFUNC);
    1411                 :           0 :       return NULL;
    1412                 :             :     }
    1413                 :             : 
    1414         [ -  + ]:          96 :   if (!valent_device_validate_id (id))
    1415                 :             :     {
    1416                 :           0 :       g_critical ("%s(): invalid device ID \"%s\"", G_STRFUNC, id);
    1417                 :           0 :       return NULL;
    1418                 :             :     }
    1419                 :             : 
    1420                 :          96 :   ret = g_object_new (VALENT_TYPE_DEVICE,
    1421                 :             :                       "id",     id,
    1422                 :             :                       "parent", parent,
    1423                 :             :                       NULL);
    1424                 :          96 :   valent_device_handle_identity (ret, identity);
    1425                 :             : 
    1426                 :          96 :   return ret;
    1427                 :             : }
    1428                 :             : 
    1429                 :             : static void
    1430                 :         145 : valent_device_send_packet_cb (ValentChannel *channel,
    1431                 :             :                               GAsyncResult  *result,
    1432                 :             :                               gpointer       user_data)
    1433                 :             : {
    1434                 :         290 :   g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
    1435                 :         145 :   ValentDevice *self = g_task_get_source_object (task);
    1436                 :         145 :   GCancellable *cancellable = g_task_get_cancellable (task);
    1437                 :         145 :   JsonNode *packet = g_task_get_task_data (task);
    1438   [ +  -  +  - ]:         145 :   g_autoptr (GError) error = NULL;
    1439                 :             : 
    1440         [ +  + ]:         145 :   if (valent_channel_write_packet_finish (channel, result, &error))
    1441                 :             :     {
    1442                 :         140 :       VALENT_JSON (packet, self->name);
    1443                 :         140 :       g_task_return_boolean (task, TRUE);
    1444                 :         140 :       return;
    1445                 :             :     }
    1446                 :             : 
    1447                 :             :   /* Unless the operation was cancelled, destroy the defunct
    1448                 :             :    * channel and retry if possible
    1449                 :             :    */
    1450         [ +  + ]:           5 :   if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
    1451                 :             :     {
    1452                 :           3 :       valent_object_destroy (VALENT_OBJECT (channel));
    1453         [ -  + ]:           3 :       if (self->channel != NULL)
    1454                 :             :         {
    1455                 :           0 :           valent_channel_write_packet (self->channel,
    1456                 :             :                                        packet,
    1457                 :             :                                        cancellable,
    1458                 :             :                                        (GAsyncReadyCallback)valent_device_send_packet_cb,
    1459                 :             :                                        g_object_ref (task));
    1460                 :           0 :           return;
    1461                 :             :         }
    1462                 :             :     }
    1463                 :             : 
    1464         [ -  + ]:           5 :   g_task_return_error (task, g_steal_pointer (&error));
    1465                 :             : }
    1466                 :             : 
    1467                 :             : /**
    1468                 :             :  * valent_device_send_packet:
    1469                 :             :  * @device: a `ValentDevice`
    1470                 :             :  * @packet: a KDE Connect packet
    1471                 :             :  * @cancellable: (nullable): a `GCancellable`
    1472                 :             :  * @callback: (scope async): a `GAsyncReadyCallback`
    1473                 :             :  * @user_data: user supplied data
    1474                 :             :  *
    1475                 :             :  * Send a KDE Connect packet to the device.
    1476                 :             :  *
    1477                 :             :  * Call [method@Valent.Device.send_packet_finish] to get the result.
    1478                 :             :  *
    1479                 :             :  * If @device is disconnected or unpaired when this method is called,
    1480                 :             :  * %G_IO_ERROR_NOT_CONNECTED or %G_IO_ERROR_PERMISSION_DENIED will be set on the
    1481                 :             :  * result, respectively.
    1482                 :             :  *
    1483                 :             :  * Since: 1.0
    1484                 :             :  */
    1485                 :             : void
    1486                 :         147 : valent_device_send_packet (ValentDevice        *device,
    1487                 :             :                            JsonNode            *packet,
    1488                 :             :                            GCancellable        *cancellable,
    1489                 :             :                            GAsyncReadyCallback  callback,
    1490                 :             :                            gpointer             user_data)
    1491                 :             : {
    1492                 :         145 :   g_autoptr (GTask) task = NULL;
    1493                 :             : 
    1494         [ -  + ]:         147 :   g_return_if_fail (VALENT_IS_DEVICE (device));
    1495         [ +  - ]:         147 :   g_return_if_fail (VALENT_IS_PACKET (packet));
    1496                 :             : 
    1497         [ +  + ]:         147 :   if G_UNLIKELY (device->channel == NULL)
    1498                 :             :     {
    1499                 :           1 :       g_task_report_new_error (device,
    1500                 :             :                                callback,
    1501                 :             :                                user_data,
    1502                 :             :                                valent_device_send_packet,
    1503                 :             :                                G_IO_ERROR,
    1504                 :             :                                G_IO_ERROR_NOT_CONNECTED,
    1505                 :             :                                "%s is disconnected", device->name);
    1506                 :           1 :       return;
    1507                 :             :     }
    1508                 :             : 
    1509         [ +  + ]:         146 :   if G_UNLIKELY (!device->paired)
    1510                 :             :     {
    1511                 :           1 :       g_task_report_new_error (device,
    1512                 :             :                                callback,
    1513                 :             :                                user_data,
    1514                 :             :                                valent_device_send_packet,
    1515                 :             :                                G_IO_ERROR,
    1516                 :             :                                G_IO_ERROR_PERMISSION_DENIED,
    1517                 :             :                                "%s is unpaired", device->name);
    1518                 :           1 :       return;
    1519                 :             :     }
    1520                 :             : 
    1521                 :         145 :   task = g_task_new (device, cancellable, callback, user_data);
    1522         [ +  - ]:         145 :   g_task_set_source_tag (task, valent_device_send_packet);
    1523                 :         145 :   g_task_set_task_data (task,
    1524                 :         145 :                         json_node_ref (packet),
    1525                 :             :                         (GDestroyNotify)json_node_unref);
    1526                 :         145 :   valent_channel_write_packet (device->channel,
    1527                 :             :                                packet,
    1528                 :             :                                cancellable,
    1529                 :             :                                (GAsyncReadyCallback)valent_device_send_packet_cb,
    1530                 :             :                                g_steal_pointer (&task));
    1531                 :             : }
    1532                 :             : 
    1533                 :             : /**
    1534                 :             :  * valent_device_send_packet_finish:
    1535                 :             :  * @device: a `ValentDevice`
    1536                 :             :  * @result: a `GAsyncResult`
    1537                 :             :  * @error: (nullable): a `GError`
    1538                 :             :  *
    1539                 :             :  * Finish an operation started by [method@Valent.Device.send_packet].
    1540                 :             :  *
    1541                 :             :  * Returns: %TRUE if successful, or %FALSE with @error set
    1542                 :             :  *
    1543                 :             :  * Since: 1.0
    1544                 :             :  */
    1545                 :             : gboolean
    1546                 :         126 : valent_device_send_packet_finish (ValentDevice  *device,
    1547                 :             :                                   GAsyncResult  *result,
    1548                 :             :                                   GError       **error)
    1549                 :             : {
    1550         [ -  + ]:         126 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), FALSE);
    1551         [ +  - ]:         126 :   g_return_val_if_fail (g_task_is_valid (result, device), FALSE);
    1552   [ +  -  +  - ]:         126 :   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
    1553                 :             : 
    1554                 :         126 :   return g_task_propagate_boolean (G_TASK (result), error);
    1555                 :             : }
    1556                 :             : 
    1557                 :             : static void
    1558                 :         208 : read_packet_cb (ValentChannel *channel,
    1559                 :             :                 GAsyncResult  *result,
    1560                 :             :                 ValentDevice  *device)
    1561                 :             : {
    1562                 :         416 :   g_autoptr (GError) error = NULL;
    1563         [ +  + ]:         208 :   g_autoptr (JsonNode) packet = NULL;
    1564                 :             : 
    1565         [ -  + ]:         208 :   g_assert (VALENT_IS_CHANNEL (channel));
    1566         [ +  - ]:         208 :   g_assert (VALENT_IS_DEVICE (device));
    1567                 :             : 
    1568                 :             :   /* If successful, queue another read before handling the packet. Otherwise
    1569                 :             :    * drop our reference if it's still the active channel.
    1570                 :             :    */
    1571                 :         208 :   packet = valent_channel_read_packet_finish (channel, result, &error);
    1572         [ +  + ]:         208 :   if (packet != NULL)
    1573                 :             :     {
    1574                 :         128 :       valent_channel_read_packet (channel,
    1575                 :             :                                   g_task_get_cancellable (G_TASK (result)),
    1576                 :             :                                   (GAsyncReadyCallback)read_packet_cb,
    1577                 :             :                                   g_object_ref (device));
    1578                 :         128 :       valent_device_handle_packet (device, packet);
    1579                 :             :     }
    1580                 :             :   else
    1581                 :             :     {
    1582                 :          80 :       VALENT_NOTE ("%s: %s", device->name, error->message);
    1583                 :          80 :       valent_object_destroy (VALENT_OBJECT (channel));
    1584                 :             :     }
    1585                 :             : 
    1586         [ +  + ]:         208 :   g_object_unref (device);
    1587                 :         208 : }
    1588                 :             : 
    1589                 :             : /**
    1590                 :             :  * valent_device_get_channels: (get-property channels)
    1591                 :             :  * @device: a `ValentDevice`
    1592                 :             :  *
    1593                 :             :  * Get the active channels.
    1594                 :             :  *
    1595                 :             :  * Returns: (transfer none): a `GListModel`
    1596                 :             :  *
    1597                 :             :  * Since: 1.0
    1598                 :             :  */
    1599                 :             : GListModel *
    1600                 :          40 : valent_device_get_channels (ValentDevice *device)
    1601                 :             : {
    1602         [ -  + ]:          40 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1603                 :             : 
    1604                 :          40 :   return device->channels;
    1605                 :             : }
    1606                 :             : 
    1607                 :             : static void
    1608                 :          80 : on_channel_destroy (ValentObject *object,
    1609                 :             :                     ValentDevice *self)
    1610                 :             : {
    1611                 :          80 :   ValentChannel *channel = VALENT_CHANNEL (object);
    1612                 :          80 :   unsigned int position = 0;
    1613                 :             : 
    1614         [ +  - ]:          80 :   if (g_list_store_find (G_LIST_STORE (self->channels), object, &position))
    1615                 :             :     {
    1616                 :          80 :       g_list_store_remove (G_LIST_STORE (self->channels), position);
    1617                 :             : 
    1618                 :             :       /* Drop the extra reference if this was the highest priority channel
    1619                 :             :        */
    1620         [ +  - ]:          80 :       if (self->channel == channel)
    1621                 :             :         {
    1622         [ -  + ]:          80 :           g_warn_if_fail (position == 0);
    1623         [ +  - ]:          80 :           g_clear_object (&self->channel);
    1624                 :             :         }
    1625                 :             : 
    1626                 :             :       /* Notify of the state change if this was the last channel,
    1627                 :             :        * or ensure a reference to the channel with the highest priority.
    1628                 :             :        */
    1629         [ +  - ]:          80 :       if (g_list_model_get_n_items (self->channels) == 0)
    1630                 :          80 :         g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STATE]);
    1631         [ #  # ]:           0 :       else if (self->channel == NULL)
    1632                 :           0 :         self->channel = g_list_model_get_item (self->channels, 0);
    1633                 :             :     }
    1634                 :          80 : }
    1635                 :             : 
    1636                 :             : static inline int
    1637                 :           0 : channel_sort_func (gconstpointer a,
    1638                 :             :                   gconstpointer b,
    1639                 :             :                   gpointer      user_data)
    1640                 :             : {
    1641         [ #  # ]:           0 :   if (g_str_equal (G_OBJECT_TYPE_NAME (a), G_OBJECT_TYPE_NAME (b)))
    1642                 :             :     return 0;
    1643                 :             : 
    1644         [ #  # ]:           0 :   return g_str_equal (G_OBJECT_TYPE_NAME (a), "ValentLanChannel") ? -1 : 1;
    1645                 :             : }
    1646                 :             : 
    1647                 :             : /**
    1648                 :             :  * valent_device_add_channel:
    1649                 :             :  * @device: a `ValentDevice`
    1650                 :             :  * @channel: a `ValentChannel`
    1651                 :             :  *
    1652                 :             :  * Add @channel to the list of active channels.
    1653                 :             :  *
    1654                 :             :  * Since: 1.0
    1655                 :             :  */
    1656                 :             : void
    1657                 :          80 : valent_device_add_channel (ValentDevice  *device,
    1658                 :             :                            ValentChannel *channel)
    1659                 :             : {
    1660                 :          80 :   JsonNode *peer_identity;
    1661                 :         160 :   g_autoptr (GCancellable) cancellable = NULL;
    1662                 :          80 :   unsigned int n_channels = 0;
    1663                 :          80 :   unsigned int position = 0;
    1664                 :             : 
    1665         [ -  + ]:          80 :   g_return_if_fail (VALENT_IS_DEVICE (device));
    1666         [ +  - ]:          80 :   g_return_if_fail (VALENT_IS_CHANNEL (channel));
    1667                 :             : 
    1668                 :          80 :   g_signal_connect_object (channel,
    1669                 :             :                            "destroy",
    1670                 :             :                            G_CALLBACK (on_channel_destroy),
    1671                 :             :                            device,
    1672                 :             :                            G_CONNECT_DEFAULT);
    1673                 :             : 
    1674                 :          80 :   n_channels = g_list_model_get_n_items (device->channels);
    1675                 :          80 :   position = g_list_store_insert_sorted (G_LIST_STORE (device->channels),
    1676                 :             :                                          channel,
    1677                 :             :                                          channel_sort_func,
    1678                 :             :                                          NULL);
    1679                 :             : 
    1680                 :             :   /* Handle the peer identity and start reading the first packet
    1681                 :             :    * before notifying of the state change
    1682                 :             :    */
    1683                 :          80 :   peer_identity = valent_channel_get_peer_identity (channel);
    1684                 :          80 :   valent_device_handle_identity (device, peer_identity);
    1685                 :             : 
    1686                 :          80 :   cancellable = valent_object_ref_cancellable (VALENT_OBJECT (device));
    1687                 :          80 :   valent_channel_read_packet (channel,
    1688                 :             :                               cancellable,
    1689                 :             :                               (GAsyncReadyCallback)read_packet_cb,
    1690                 :             :                               g_object_ref (device));
    1691                 :             : 
    1692                 :             :   /* Hold a reference to the channel if it has the highest priority,
    1693                 :             :    * and notify of the state change if it's the first channel
    1694                 :             :    */
    1695         [ +  - ]:          80 :   if (position == 0)
    1696                 :          80 :     g_set_object (&device->channel, channel);
    1697                 :             : 
    1698         [ +  - ]:          80 :   if (n_channels == 0)
    1699                 :             :     {
    1700                 :          80 :       valent_device_update_plugins (device);
    1701                 :          80 :       g_object_notify_by_pspec (G_OBJECT (device), properties[PROP_STATE]);
    1702                 :             :     }
    1703                 :             : }
    1704                 :             : 
    1705                 :             : /**
    1706                 :             :  * valent_device_get_context: (get-property context)
    1707                 :             :  * @device: a `ValentDevice`
    1708                 :             :  *
    1709                 :             :  * Get the data context.
    1710                 :             :  *
    1711                 :             :  * Returns: (transfer full): a `ValentContext`
    1712                 :             :  *
    1713                 :             :  * Since: 1.0
    1714                 :             :  */
    1715                 :             : ValentContext *
    1716                 :          48 : valent_device_get_context (ValentDevice *device)
    1717                 :             : {
    1718         [ -  + ]:          48 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1719                 :             : 
    1720                 :          48 :   return device->context;
    1721                 :             : }
    1722                 :             : 
    1723                 :             : /**
    1724                 :             :  * valent_device_get_icon_name: (get-property icon-name)
    1725                 :             :  * @device: a `ValentDevice`
    1726                 :             :  *
    1727                 :             :  * Get the symbolic icon name.
    1728                 :             :  *
    1729                 :             :  * Returns: (transfer none): the icon name.
    1730                 :             :  *
    1731                 :             :  * Since: 1.0
    1732                 :             :  */
    1733                 :             : const char *
    1734                 :           3 : valent_device_get_icon_name (ValentDevice *device)
    1735                 :             : {
    1736         [ -  + ]:           3 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), "computer-symbolic");
    1737                 :             : 
    1738                 :           3 :   return device->icon_name;
    1739                 :             : }
    1740                 :             : 
    1741                 :             : /**
    1742                 :             :  * valent_device_get_id: (get-property id)
    1743                 :             :  * @device: a `ValentDevice`
    1744                 :             :  *
    1745                 :             :  * Get the unique ID.
    1746                 :             :  *
    1747                 :             :  * Returns: (transfer none): the device id.
    1748                 :             :  *
    1749                 :             :  * Since: 1.0
    1750                 :             :  */
    1751                 :             : const char *
    1752                 :          68 : valent_device_get_id (ValentDevice *device)
    1753                 :             : {
    1754         [ -  + ]:          68 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1755         [ -  + ]:          68 :   g_return_val_if_fail (device->id != NULL, NULL);
    1756                 :             : 
    1757                 :             :   return device->id;
    1758                 :             : }
    1759                 :             : 
    1760                 :             : /**
    1761                 :             :  * valent_device_get_menu:
    1762                 :             :  * @device: a `ValentDevice`
    1763                 :             :  *
    1764                 :             :  * Get the [class@Gio.MenuModel] of the device.
    1765                 :             :  *
    1766                 :             :  * Plugins may add items and submenus to this when they want to expose actions
    1767                 :             :  * with presentation details like a label or icon.
    1768                 :             :  *
    1769                 :             :  * Returns: (transfer none): a `GMenuModel`
    1770                 :             :  *
    1771                 :             :  * Since: 1.0
    1772                 :             :  */
    1773                 :             : GMenuModel *
    1774                 :          94 : valent_device_get_menu (ValentDevice *device)
    1775                 :             : {
    1776         [ -  + ]:          94 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1777                 :             : 
    1778                 :          94 :   return G_MENU_MODEL (device->menu);
    1779                 :             : }
    1780                 :             : 
    1781                 :             : /**
    1782                 :             :  * valent_device_get_name:
    1783                 :             :  * @device: a `ValentDevice`
    1784                 :             :  *
    1785                 :             :  * Get the display name of the device.
    1786                 :             :  *
    1787                 :             :  * Returns: (transfer none) (nullable): a display name, or %NULL if unset
    1788                 :             :  *
    1789                 :             :  * Since: 1.0
    1790                 :             :  */
    1791                 :             : const char *
    1792                 :          39 : valent_device_get_name (ValentDevice *device)
    1793                 :             : {
    1794         [ -  + ]:          39 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1795                 :             : 
    1796                 :          39 :   return device->name;
    1797                 :             : }
    1798                 :             : 
    1799                 :             : /**
    1800                 :             :  * valent_device_get_state: (get-property state)
    1801                 :             :  * @device: a `ValentDevice`
    1802                 :             :  *
    1803                 :             :  * Get the state of the device.
    1804                 :             :  *
    1805                 :             :  * Returns: `ValentDeviceState` flags describing the state of the device
    1806                 :             :  *
    1807                 :             :  * Since: 1.0
    1808                 :             :  */
    1809                 :             : ValentDeviceState
    1810                 :         301 : valent_device_get_state (ValentDevice *device)
    1811                 :             : {
    1812                 :         301 :   ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
    1813                 :             : 
    1814         [ -  + ]:         301 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), state);
    1815                 :             : 
    1816         [ +  + ]:         301 :   if (device->channel != NULL)
    1817                 :         138 :     state |= VALENT_DEVICE_STATE_CONNECTED;
    1818                 :             : 
    1819         [ +  + ]:         301 :   if (device->paired)
    1820                 :         199 :     state |= VALENT_DEVICE_STATE_PAIRED;
    1821                 :             : 
    1822         [ +  + ]:         301 :   if (device->incoming_pair > 0)
    1823                 :           2 :     state |= VALENT_DEVICE_STATE_PAIR_INCOMING;
    1824                 :             : 
    1825         [ +  + ]:         301 :   if (device->outgoing_pair > 0)
    1826                 :           1 :     state |= VALENT_DEVICE_STATE_PAIR_OUTGOING;
    1827                 :             : 
    1828                 :             :   return state;
    1829                 :             : }
    1830                 :             : 
    1831                 :             : /**
    1832                 :             :  * valent_device_get_verification_key:
    1833                 :             :  * @device: a `ValentDevice`
    1834                 :             :  *
    1835                 :             :  * Get a verification key for the device connection.
    1836                 :             :  *
    1837                 :             :  * Returns: (nullable) (transfer full): a verification key
    1838                 :             :  *
    1839                 :             :  * Since: 1.0
    1840                 :             :  */
    1841                 :             : char *
    1842                 :           4 : valent_device_get_verification_key (ValentDevice *device)
    1843                 :             : {
    1844                 :           4 :   char *verification_key = NULL;
    1845                 :             : 
    1846                 :           4 :   VALENT_ENTRY;
    1847                 :             : 
    1848         [ -  + ]:           4 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1849                 :             : 
    1850         [ +  + ]:           4 :   if (device->channel != NULL)
    1851                 :             :     {
    1852                 :           6 :       g_autoptr (GChecksum) checksum = NULL;
    1853         [ +  - ]:           2 :       g_autoptr (GTlsCertificate) cert = NULL;
    1854   [ +  -  -  - ]:           2 :       g_autoptr (GTlsCertificate) peer_cert = NULL;
    1855                 :           2 :       GByteArray *pubkey;
    1856                 :           2 :       GByteArray *peer_pubkey;
    1857                 :           2 :       size_t cmplen;
    1858                 :             : 
    1859                 :           2 :       cert = valent_channel_ref_certificate (device->channel);
    1860                 :           2 :       peer_cert = valent_channel_ref_peer_certificate (device->channel);
    1861   [ -  +  -  - ]:           2 :       g_return_val_if_fail (cert != NULL || peer_cert != NULL, NULL);
    1862                 :             : 
    1863                 :           2 :       pubkey = valent_certificate_get_public_key (cert);
    1864                 :           2 :       peer_pubkey = valent_certificate_get_public_key (peer_cert);
    1865         [ +  - ]:           2 :       g_return_val_if_fail (pubkey != NULL || peer_pubkey != NULL, NULL);
    1866                 :             : 
    1867                 :           2 :       checksum = g_checksum_new (G_CHECKSUM_SHA256);
    1868                 :           2 :       cmplen = MIN (pubkey->len, peer_pubkey->len);
    1869         [ +  + ]:           2 :       if (memcmp (pubkey->data, peer_pubkey->data, cmplen) > 0)
    1870                 :             :         {
    1871                 :           1 :           g_checksum_update (checksum, pubkey->data, pubkey->len);
    1872                 :           1 :           g_checksum_update (checksum, peer_pubkey->data, peer_pubkey->len);
    1873                 :             :         }
    1874                 :             :       else
    1875                 :             :         {
    1876                 :           1 :           g_checksum_update (checksum, peer_pubkey->data, peer_pubkey->len);
    1877                 :           1 :           g_checksum_update (checksum, pubkey->data, pubkey->len);
    1878                 :             :         }
    1879                 :             : 
    1880         [ +  - ]:           2 :       if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8)
    1881                 :             :         {
    1882                 :           2 :           g_autofree char *timestamp_str = NULL;
    1883                 :             : 
    1884                 :           2 :           timestamp_str = g_strdup_printf ("%"PRId64, device->pair_timestamp);
    1885                 :           2 :           g_checksum_update (checksum, (const unsigned char *)timestamp_str, -1);
    1886                 :             :         }
    1887                 :             : 
    1888         [ +  - ]:           2 :       verification_key = g_ascii_strup (g_checksum_get_string (checksum), 8);
    1889                 :             :     }
    1890                 :             : 
    1891                 :           4 :   VALENT_RETURN (verification_key);
    1892                 :             : }
    1893                 :             : 
    1894                 :             : /**
    1895                 :             :  * valent_device_generate_id:
    1896                 :             :  *
    1897                 :             :  * Generate a new KDE Connect device ID.
    1898                 :             :  *
    1899                 :             :  * See [func@Valent.Device.validate_id] for a description of valid device ID.
    1900                 :             :  *
    1901                 :             :  * Returns: (transfer full): a new KDE Connect device ID
    1902                 :             :  *
    1903                 :             :  * Since: 1.0
    1904                 :             :  */
    1905                 :             : char *
    1906                 :         206 : valent_device_generate_id (void)
    1907                 :             : {
    1908                 :         206 :   static const char hex_digits[16 + 1] = "0123456789abcdef";
    1909                 :         206 :   char *id = g_new (char, 32 + 1);
    1910                 :             : 
    1911         [ +  + ]:        6798 :   for (size_t i = 0; i < 32; i++)
    1912                 :        6592 :     id[i] = hex_digits[g_random_int_range (0, 16)];
    1913                 :         206 :   id[32] = '\0';
    1914                 :             : 
    1915                 :         206 :   return g_steal_pointer (&id);
    1916                 :             : }
    1917                 :             : 
    1918                 :             : /**
    1919                 :             :  * valent_device_validate_id:
    1920                 :             :  * @id: (nullable): a KDE Connect device ID
    1921                 :             :  *
    1922                 :             :  * Validate a KDE Connect device ID.
    1923                 :             :  *
    1924                 :             :  * A compliant device ID matches the pattern `/^[a-zA-Z0-9]{32,38}$/`, being
    1925                 :             :  * alphanumeric with a length of 32-38 characters. However, for backwards
    1926                 :             :  * compatibility, implementations must accept device IDs that include hyphens
    1927                 :             :  * and underscores.
    1928                 :             :  *
    1929                 :             :  * This became a requirement in version 8 of the KDE Connect protocol.
    1930                 :             :  *
    1931                 :             :  * Returns: %TRUE if valid, or %FALSE
    1932                 :             :  *
    1933                 :             :  * Since: 1.0
    1934                 :             :  */
    1935                 :             : gboolean
    1936                 :         153 : valent_device_validate_id (const char *id)
    1937                 :             : {
    1938                 :         153 :   size_t len = 0;
    1939                 :             : 
    1940   [ +  -  +  + ]:         153 :   if G_UNLIKELY (id == NULL || *id == '\0')
    1941                 :             :     return FALSE;
    1942                 :             : 
    1943         [ +  + ]:        5013 :   while (id[len] != '\0')
    1944                 :             :     {
    1945                 :        4864 :       char c = id[len];
    1946                 :             : 
    1947   [ +  +  +  + ]:        4864 :       if (!g_ascii_isalnum (c) && c != '_' && c != '-')
    1948                 :             :         return FALSE;
    1949                 :             : 
    1950         [ +  + ]:        4862 :       if (++len > 38)
    1951                 :             :         return FALSE;
    1952                 :             :     }
    1953                 :             : 
    1954                 :         149 :   return len >= 32;
    1955                 :             : }
    1956                 :             : 
    1957                 :             : /**
    1958                 :             :  * valent_device_validate_name:
    1959                 :             :  * @name: (nullable): a KDE Connect device name
    1960                 :             :  *
    1961                 :             :  * Validate a KDE Connect device name.
    1962                 :             :  *
    1963                 :             :  * A compliant device name matches the pattern `/^[^"',;:.!?()\[\]<>]{1,32}$/`,
    1964                 :             :  * containing none of `"',;:.!?()[]<>` with a length of 1-32 characters, with
    1965                 :             :  * at least one non-whitespace character.
    1966                 :             :  *
    1967                 :             :  * This became a requirement in version 8 or the KDE Connect protocol.
    1968                 :             :  *
    1969                 :             :  * Returns: %TRUE if valid, or %FALSE
    1970                 :             :  *
    1971                 :             :  * Since: 1.0
    1972                 :             :  */
    1973                 :             : gboolean
    1974                 :          13 : valent_device_validate_name (const char *name)
    1975                 :             : {
    1976                 :          13 :   static const uint8_t forbidden_chars[256] = {
    1977                 :             :     ['"'] = 1, ['\''] = 1,
    1978                 :             :     [','] = 1, ['.'] = 1,
    1979                 :             :     [';'] = 1, [':'] = 1,
    1980                 :             :     ['!'] = 1, ['?'] = 1,
    1981                 :             :     ['('] = 1, [')'] = 1,
    1982                 :             :     ['['] = 1, [']'] = 1,
    1983                 :             :     ['<'] = 1, ['>'] = 1,
    1984                 :             :   };
    1985                 :          13 :   size_t len = 0;
    1986                 :          13 :   gboolean has_nonwhitespace = FALSE;
    1987                 :             : 
    1988   [ +  -  +  - ]:          13 :   if G_UNLIKELY (name == NULL || *name == '\0')
    1989                 :             :     return FALSE;
    1990                 :             : 
    1991         [ +  + ]:         135 :   for (const char *p = name; *p != '\0'; p = g_utf8_next_char (p))
    1992                 :             :     {
    1993                 :         123 :       gunichar ch = g_utf8_get_char (p);
    1994   [ +  -  +  + ]:         123 :       if (ch <= 127 && forbidden_chars[(uint8_t)ch])
    1995                 :             :         return FALSE;
    1996                 :             : 
    1997         [ +  + ]:         122 :       if (!has_nonwhitespace)
    1998                 :          15 :         has_nonwhitespace = !g_unichar_isspace (ch);
    1999                 :             : 
    2000         [ +  - ]:         122 :       if (++len > 32)
    2001                 :             :         return FALSE;
    2002                 :             :     }
    2003                 :             : 
    2004                 :             :   return has_nonwhitespace;
    2005                 :             : }
    2006                 :             : 
        

Generated by: LCOV version 2.0-1