LCOV - code coverage report
Current view: top level - libmalcontent - session-limits.c (source / functions) Coverage Total Hit
Test: 2 coverage DB files Lines: 99.1 % 214 212
Test Date: 2025-11-20 19:57:30 Functions: 100.0 % 23 23
Branches: 82.3 % 96 79

             Branch data     Line data    Source code
       1                 :             : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
       2                 :             :  *
       3                 :             :  * Copyright © 2019 Endless Mobile, Inc.
       4                 :             :  * Copyright 2025 GNOME Foundation, Inc.
       5                 :             :  *
       6                 :             :  * SPDX-License-Identifier: LGPL-2.1-or-later
       7                 :             :  *
       8                 :             :  * This library is free software; you can redistribute it and/or
       9                 :             :  * modify it under the terms of the GNU Lesser General Public
      10                 :             :  * License as published by the Free Software Foundation; either
      11                 :             :  * version 2.1 of the License, or (at your option) any later version.
      12                 :             :  *
      13                 :             :  * This library is distributed in the hope that it will be useful,
      14                 :             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      15                 :             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      16                 :             :  * Lesser General Public License for more details.
      17                 :             :  *
      18                 :             :  * You should have received a copy of the GNU Lesser General Public
      19                 :             :  * License along with this library; if not, write to the Free Software
      20                 :             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
      21                 :             :  *
      22                 :             :  * Authors:
      23                 :             :  *  - Philip Withnall <withnall@endlessm.com>
      24                 :             :  */
      25                 :             : 
      26                 :             : #include "config.h"
      27                 :             : 
      28                 :             : #include <glib.h>
      29                 :             : #include <glib-object.h>
      30                 :             : #include <glib/gi18n-lib.h>
      31                 :             : #include <gio/gio.h>
      32                 :             : #include <libmalcontent/manager.h>
      33                 :             : #include <libmalcontent/session-limits.h>
      34                 :             : 
      35                 :             : #include "libmalcontent/session-limits-private.h"
      36                 :             : 
      37                 :             : 
      38                 :             : /* struct _MctSessionLimits is defined in session-limits-private.h */
      39                 :             : 
      40   [ +  -  +  -  :           6 : G_DEFINE_BOXED_TYPE (MctSessionLimits, mct_session_limits,
                   +  - ]
      41                 :             :                      mct_session_limits_ref, mct_session_limits_unref)
      42                 :             : 
      43                 :             : /**
      44                 :             :  * mct_session_limits_ref:
      45                 :             :  * @limits: (transfer none): a [struct@Malcontent.SessionLimits]
      46                 :             :  *
      47                 :             :  * Increment the reference count of @limits, and return the same pointer to it.
      48                 :             :  *
      49                 :             :  * Returns: (transfer full): the same pointer as @limits
      50                 :             :  * Since: 0.5.0
      51                 :             :  */
      52                 :             : MctSessionLimits *
      53                 :           4 : mct_session_limits_ref (MctSessionLimits *limits)
      54                 :             : {
      55                 :           4 :   g_return_val_if_fail (limits != NULL, NULL);
      56                 :           4 :   g_return_val_if_fail (limits->ref_count >= 1, NULL);
      57                 :           4 :   g_return_val_if_fail (limits->ref_count <= G_MAXINT - 1, NULL);
      58                 :             : 
      59                 :           4 :   limits->ref_count++;
      60                 :           4 :   return limits;
      61                 :             : }
      62                 :             : 
      63                 :             : /**
      64                 :             :  * mct_session_limits_unref:
      65                 :             :  * @limits: (transfer full): a [struct@Malcontent.SessionLimits]
      66                 :             :  *
      67                 :             :  * Decrement the reference count of @limits. If the reference count reaches
      68                 :             :  * zero, free the @limits and all its resources.
      69                 :             :  *
      70                 :             :  * Since: 0.5.0
      71                 :             :  */
      72                 :             : void
      73                 :          81 : mct_session_limits_unref (MctSessionLimits *limits)
      74                 :             : {
      75                 :          81 :   g_return_if_fail (limits != NULL);
      76                 :          81 :   g_return_if_fail (limits->ref_count >= 1);
      77                 :             : 
      78                 :          81 :   limits->ref_count--;
      79                 :             : 
      80         [ +  + ]:          81 :   if (limits->ref_count <= 0)
      81                 :             :     {
      82                 :          77 :       g_free (limits);
      83                 :             :     }
      84                 :             : }
      85                 :             : 
      86                 :             : /**
      87                 :             :  * mct_session_limits_get_user_id:
      88                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
      89                 :             :  *
      90                 :             :  * Get the user ID of the user this [struct@Malcontent.SessionLimits] is for.
      91                 :             :  *
      92                 :             :  * Returns: user ID of the relevant user, or `(uid_t) -1` if unknown
      93                 :             :  * Since: 0.5.0
      94                 :             :  */
      95                 :             : uid_t
      96                 :           3 : mct_session_limits_get_user_id (MctSessionLimits *limits)
      97                 :             : {
      98                 :           3 :   g_return_val_if_fail (limits != NULL, (uid_t) -1);
      99                 :           3 :   g_return_val_if_fail (limits->ref_count >= 1, (uid_t) -1);
     100                 :             : 
     101                 :           3 :   return limits->user_id;
     102                 :             : }
     103                 :             : 
     104                 :             : /**
     105                 :             :  * mct_session_limits_is_enabled:
     106                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
     107                 :             :  *
     108                 :             :  * Check whether any session limits are enabled and are going to impose at least
     109                 :             :  * one restriction on the user.
     110                 :             :  *
     111                 :             :  * This gives a high level view of whether session limit parental controls are
     112                 :             :  * ‘enabled’ for the given user.
     113                 :             :  *
     114                 :             :  * This function is equivalent to the value returned by the
     115                 :             :  * `time_limit_enabled_out` argument of
     116                 :             :  * [method@Malcontent.SessionLimits.check_time_remaining].
     117                 :             :  *
     118                 :             :  * Returns: true if the session limits object contains at least one restrictive
     119                 :             :  *   session limit, false if there are no limits in place
     120                 :             :  * Since: 0.7.0
     121                 :             :  */
     122                 :             : gboolean
     123                 :           5 : mct_session_limits_is_enabled (MctSessionLimits *limits)
     124                 :             : {
     125                 :           5 :   g_return_val_if_fail (limits != NULL, FALSE);
     126                 :           5 :   g_return_val_if_fail (limits->ref_count >= 1, FALSE);
     127                 :             : 
     128                 :           5 :   return (limits->limit_type != MCT_SESSION_LIMITS_TYPE_NONE);
     129                 :             : }
     130                 :             : 
     131                 :             : /**
     132                 :             :  * mct_session_limits_get_daily_schedule:
     133                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
     134                 :             :  * @out_start_time_secs: (out) (optional): return location for the earliest
     135                 :             :  *   allowable session start time for the user, in seconds since midnight
     136                 :             :  * @out_end_time_secs: (out) (optional): return location for the latest
     137                 :             :  *   allowable session end time for the user, in seconds since midnight
     138                 :             :  *
     139                 :             :  * Get the daily schedule session limits, if set.
     140                 :             :  *
     141                 :             :  * If set, sessions are allowed between the given start and end time every day.
     142                 :             :  * The times are given as offsets from the start of the day, in seconds.
     143                 :             :  *
     144                 :             :  * @out_end_time_secs is guaranteed to be greater than @out_start_time_secs, if
     145                 :             :  * they are set. @out_end_time_secs is guaranteed to be at most `24 * 60 * 60`
     146                 :             :  * if set.
     147                 :             :  *
     148                 :             :  * Returns: true if a daily schedule is set, false otherwise
     149                 :             :  * Since: 0.14.0
     150                 :             :  */
     151                 :             : gboolean
     152                 :          16 : mct_session_limits_get_daily_schedule (MctSessionLimits *limits,
     153                 :             :                                        unsigned int     *out_start_time_secs,
     154                 :             :                                        unsigned int     *out_end_time_secs)
     155                 :             : {
     156                 :          16 :   g_return_val_if_fail (limits != NULL, FALSE);
     157                 :          16 :   g_return_val_if_fail (limits->ref_count >= 1, FALSE);
     158                 :             : 
     159         [ +  - ]:          16 :   if (out_start_time_secs != NULL)
     160         [ +  + ]:          16 :     *out_start_time_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) ? limits->daily_start_time : 0;
     161         [ +  - ]:          16 :   if (out_end_time_secs != NULL)
     162         [ +  + ]:          16 :     *out_end_time_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE) ? limits->daily_end_time : 0;
     163                 :             : 
     164                 :          16 :   return (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE);
     165                 :             : }
     166                 :             : 
     167                 :             : /**
     168                 :             :  * mct_session_limits_get_daily_limit:
     169                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
     170                 :             :  * @out_daily_limit_secs: (out) (optional): return location for the maximum
     171                 :             :  *   amount of active session time allowed per day for the user, in seconds
     172                 :             :  *
     173                 :             :  * Get the daily limit, if set.
     174                 :             :  *
     175                 :             :  * If set, sessions are allowed to be up to the given limit in length every day.
     176                 :             :  *
     177                 :             :  * @out_daily_limit_secs is guaranteed to be at most `24 * 60 * 60` if set.
     178                 :             :  *
     179                 :             :  * Returns: true if a daily limit is set, false otherwise
     180                 :             :  * Since: 0.14.0
     181                 :             :  */
     182                 :             : gboolean
     183                 :           4 : mct_session_limits_get_daily_limit (MctSessionLimits *limits,
     184                 :             :                                     unsigned int     *out_daily_limit_secs)
     185                 :             : {
     186                 :           4 :   g_return_val_if_fail (limits != NULL, FALSE);
     187                 :           4 :   g_return_val_if_fail (limits->ref_count >= 1, FALSE);
     188                 :             : 
     189         [ +  - ]:           4 :   if (out_daily_limit_secs != NULL)
     190         [ +  - ]:           4 :     *out_daily_limit_secs = (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT) ? limits->daily_limit_secs : 0;
     191                 :             : 
     192                 :           4 :   return (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT);
     193                 :             : }
     194                 :             : 
     195                 :             : /**
     196                 :             :  * mct_session_limits_check_time_remaining:
     197                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
     198                 :             :  * @now_dt: current time in the user’s timezone, typically queried using
     199                 :             :  *   `g_date_time_new_now_local()`
     200                 :             :  * @active_session_time_today_secs: total time the user has spent in an active
     201                 :             :  *   session so far today, in seconds
     202                 :             :  * @time_remaining_secs_out: (out) (optional): return location for the number
     203                 :             :  *   of seconds remaining before the user’s session has to end, if limits are
     204                 :             :  *   in force
     205                 :             :  * @time_limit_enabled_out: (out) (optional): return location for whether time
     206                 :             :  *   limits are enabled for this user
     207                 :             :  *
     208                 :             :  * Check whether the user has time remaining in which they are allowed to use
     209                 :             :  * the computer.
     210                 :             :  *
     211                 :             :  * This assumes that @now_dt is the current time and
     212                 :             :  * @active_session_time_today_secs is the total amount of time the user has
     213                 :             :  * spent in an active session up to that point today, and applies the
     214                 :             :  * session limit policy from @limits to them.
     215                 :             :  *
     216                 :             :  * This will return whether the user is allowed to use the computer now; further
     217                 :             :  * information about the policy and remaining time is provided in
     218                 :             :  * @time_remaining_secs_out and @time_limit_enabled_out.
     219                 :             :  *
     220                 :             :  * Returns: true if the user this @limits corresponds to is allowed to be in
     221                 :             :  *   an active session at the given time; false otherwise
     222                 :             :  * Since: 0.14.0
     223                 :             :  */
     224                 :             : gboolean
     225                 :         143 : mct_session_limits_check_time_remaining (MctSessionLimits *limits,
     226                 :             :                                          GDateTime        *now_dt,
     227                 :             :                                          uint64_t          active_session_time_today_secs,
     228                 :             :                                          guint64          *time_remaining_secs_out,
     229                 :             :                                          gboolean         *time_limit_enabled_out)
     230                 :             : {
     231                 :             :   guint64 time_remaining_secs;
     232                 :         143 :   gboolean time_limit_enabled = FALSE;
     233                 :         143 :   gboolean user_allowed_now = TRUE;
     234                 :             :   guint64 now_time_of_day_secs;
     235                 :             : 
     236                 :         143 :   g_return_val_if_fail (limits != NULL, FALSE);
     237                 :         143 :   g_return_val_if_fail (limits->ref_count >= 1, FALSE);
     238                 :         143 :   g_return_val_if_fail (now_dt != NULL, FALSE);
     239                 :             : 
     240                 :             :   /* Helper calculations. If we end up with a @now_dt before the UNIX epoch,
     241                 :             :    * the caller has provided a date very far in the future which we don’t yet
     242                 :             :    * support.
     243                 :             :    *
     244                 :             :    * This needs to be in the user’s local timezone, because `DAILY_SCHEDULE`
     245                 :             :    * limits are essentially in the local timezone by virtue of being wall clock
     246                 :             :    * times. */
     247         [ +  + ]:         143 :   if (g_date_time_to_unix (now_dt) < 0)
     248                 :             :     {
     249                 :           2 :       time_remaining_secs = 0;
     250                 :           2 :       time_limit_enabled = TRUE;
     251                 :           2 :       user_allowed_now = FALSE;
     252                 :           2 :       goto out;
     253                 :             :     }
     254                 :             : 
     255                 :         141 :   now_time_of_day_secs = ((g_date_time_get_hour (now_dt) * 60 +
     256                 :         141 :                            g_date_time_get_minute (now_dt)) * 60 +
     257                 :         141 :                           g_date_time_get_second (now_dt));
     258                 :         141 :   time_remaining_secs = 24 * 60 * 60 - now_time_of_day_secs;
     259                 :             : 
     260                 :             :   /* Work out the limits. */
     261         [ +  + ]:         141 :   if (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
     262                 :             :     {
     263         [ +  - ]:         178 :       user_allowed_now = user_allowed_now &&
     264         [ +  + ]:          89 :                          (now_time_of_day_secs >= limits->daily_start_time &&
     265         [ +  + ]:          59 :                           now_time_of_day_secs < limits->daily_end_time);
     266         [ +  + ]:          89 :       time_remaining_secs = user_allowed_now ? MIN (time_remaining_secs, limits->daily_end_time - now_time_of_day_secs) : 0;
     267                 :          89 :       time_limit_enabled = TRUE;
     268                 :             : 
     269                 :          89 :       g_debug ("%s: Daily schedule limit allowed in %u–%u (now is %"
     270                 :             :                G_GUINT64_FORMAT "); %" G_GUINT64_FORMAT " seconds remaining",
     271                 :             :                G_STRFUNC, limits->daily_start_time, limits->daily_end_time,
     272                 :             :                now_time_of_day_secs, time_remaining_secs);
     273                 :             :     }
     274                 :             : 
     275         [ +  + ]:         141 :   if (limits->limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
     276                 :             :     {
     277         [ +  + ]:          52 :       user_allowed_now = (user_allowed_now &&
     278         [ +  + ]:          24 :                           active_session_time_today_secs < limits->daily_limit_secs);
     279         [ +  + ]:          28 :       time_remaining_secs = user_allowed_now ? MIN (time_remaining_secs, limits->daily_limit_secs - active_session_time_today_secs) : 0;
     280                 :          28 :       time_limit_enabled = TRUE;
     281                 :             : 
     282                 :          28 :       g_debug ("%s: Daily limit allowed up to %u (currently used %"
     283                 :             :                G_GUINT64_FORMAT "); %" G_GUINT64_FORMAT " seconds remaining",
     284                 :             :                G_STRFUNC, limits->daily_limit_secs,
     285                 :             :                active_session_time_today_secs, time_remaining_secs);
     286                 :             :     }
     287                 :             : 
     288         [ +  + ]:         141 :   if (limits->limit_type == MCT_SESSION_LIMITS_TYPE_NONE)
     289                 :             :     {
     290                 :          40 :       user_allowed_now = TRUE;
     291                 :          40 :       time_remaining_secs = G_MAXUINT64;
     292                 :          40 :       time_limit_enabled = FALSE;
     293                 :             : 
     294                 :          40 :       g_debug ("%s: No limit enabled", G_STRFUNC);
     295                 :             :     }
     296                 :             : 
     297                 :         101 : out:
     298                 :             :   /* Postconditions. */
     299                 :         143 :   g_assert (!user_allowed_now || time_remaining_secs > 0);
     300                 :         143 :   g_assert (user_allowed_now || time_remaining_secs == 0);
     301                 :         143 :   g_assert (time_limit_enabled || time_remaining_secs == G_MAXUINT64);
     302                 :             : 
     303                 :             :   /* Output. */
     304         [ +  + ]:         143 :   if (time_remaining_secs_out != NULL)
     305                 :          43 :     *time_remaining_secs_out = time_remaining_secs;
     306         [ +  + ]:         143 :   if (time_limit_enabled_out != NULL)
     307                 :          43 :     *time_limit_enabled_out = time_limit_enabled;
     308                 :             : 
     309                 :         143 :   return user_allowed_now;
     310                 :             : }
     311                 :             : 
     312                 :             : /**
     313                 :             :  * mct_session_limits_serialize:
     314                 :             :  * @limits: a [struct@Malcontent.SessionLimits]
     315                 :             :  *
     316                 :             :  * Build a [struct@GLib.Variant] which contains the session limits from @limits,
     317                 :             :  * in an opaque variant format.
     318                 :             :  *
     319                 :             :  * This format may change in future, but
     320                 :             :  * [func@Malcontent.SessionLimits.deserialize] is guaranteed to always be able
     321                 :             :  * to load any variant produced by the current or any previous version of
     322                 :             :  * [method@Malcontent.SessionLimits.serialize].
     323                 :             :  *
     324                 :             :  * Returns: (transfer floating): a new, floating [struct@GLib.Variant]
     325                 :             :  *   containing the session limits
     326                 :             :  * Since: 0.7.0
     327                 :             :  */
     328                 :             : GVariant *
     329                 :           9 : mct_session_limits_serialize (MctSessionLimits *limits)
     330                 :             : {
     331                 :          18 :   g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a{sv}"));
     332                 :             : 
     333                 :           9 :   g_return_val_if_fail (limits != NULL, NULL);
     334                 :           9 :   g_return_val_if_fail (limits->ref_count >= 1, NULL);
     335                 :             : 
     336                 :             :   /* The serialisation format is exactly the
     337                 :             :    * `com.endlessm.ParentalControls.SessionLimits` D-Bus interface. */
     338         [ +  + ]:           9 :   if (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
     339                 :           4 :     g_variant_builder_add (&builder, "{sv}", "DailySchedule",
     340                 :             :                            g_variant_new ("(uu)",
     341                 :             :                                           limits->daily_start_time,
     342                 :             :                                           limits->daily_end_time));
     343                 :             : 
     344         [ -  + ]:           9 :   if (limits->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
     345                 :           0 :     g_variant_builder_add (&builder, "{sv}", "DailyLimit",
     346                 :             :                            g_variant_new ("u", limits->daily_limit_secs));
     347                 :             : 
     348                 :           9 :   g_variant_builder_add (&builder, "{sv}", "LimitType",
     349                 :           9 :                          g_variant_new_uint32 (limits->limit_type));
     350                 :             : 
     351                 :           9 :   return g_variant_builder_end (&builder);
     352                 :             : }
     353                 :             : 
     354                 :             : /**
     355                 :             :  * mct_session_limits_deserialize:
     356                 :             :  * @variant: a serialized session limits variant
     357                 :             :  * @user_id: the ID of the user the session limits relate to
     358                 :             :  * @error: return location for a [type@GLib.Error], or `NULL`
     359                 :             :  *
     360                 :             :  * Deserialize a set of session limits previously serialized with
     361                 :             :  * [method@Malcontent.SessionLimits.serialize].
     362                 :             :  *
     363                 :             :  * This function guarantees to be able to deserialize any serialized form from
     364                 :             :  * this version or older versions of libmalcontent.
     365                 :             :  *
     366                 :             :  * If deserialization fails, [error@Malcontent.ManagerError.INVALID_DATA] will
     367                 :             :  * be returned.
     368                 :             :  *
     369                 :             :  * Returns: (transfer full): deserialized session limits
     370                 :             :  * Since: 0.7.0
     371                 :             :  */
     372                 :             : MctSessionLimits *
     373                 :          43 : mct_session_limits_deserialize (GVariant  *variant,
     374                 :             :                                 uid_t      user_id,
     375                 :             :                                 GError   **error)
     376                 :             : {
     377                 :          43 :   g_autoptr(MctSessionLimits) session_limits = NULL;
     378                 :             :   guint32 limit_type;
     379                 :          43 :   MctSessionLimitsType set_limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
     380                 :             :   guint32 daily_start_time, daily_end_time;
     381                 :             :   uint32_t daily_limit_secs;
     382                 :             : 
     383                 :          43 :   g_return_val_if_fail (variant != NULL, NULL);
     384                 :          43 :   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
     385                 :             : 
     386                 :             :   /* Check the overall type. */
     387         [ +  + ]:          43 :   if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("a{sv}")))
     388                 :             :     {
     389                 :           4 :       g_set_error (error, MCT_MANAGER_ERROR,
     390                 :             :                    MCT_MANAGER_ERROR_INVALID_DATA,
     391                 :             :                    _("Session limit for user %u was in an unrecognized format"),
     392                 :             :                    (guint) user_id);
     393                 :           4 :       return NULL;
     394                 :             :     }
     395                 :             : 
     396                 :             :   /* Extract the properties we care about. The default values here should be
     397                 :             :    * kept in sync with those in the `com.endlessm.ParentalControls.SessionLimits`
     398                 :             :    * D-Bus interface. */
     399         [ +  + ]:          39 :   if (!g_variant_lookup (variant, "LimitType", "u",
     400                 :             :                          &limit_type))
     401                 :             :     {
     402                 :             :       /* Default value. */
     403                 :          20 :       limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
     404                 :             :     }
     405                 :             : 
     406                 :             :   /* Check that the limit type is something we support. */
     407                 :             :   G_STATIC_ASSERT (sizeof (limit_type) >= sizeof (MctSessionLimitsType));
     408                 :             : 
     409         [ +  + ]:          39 :   if (((guint) limit_type & ~MCT_SESSION_LIMITS_TYPE_MASK) != 0)
     410                 :             :     {
     411                 :           2 :       g_set_error (error, MCT_MANAGER_ERROR,
     412                 :             :                    MCT_MANAGER_ERROR_INVALID_DATA,
     413                 :             :                    _("Session limit for user %u has an unrecognized type ‘%u’"),
     414                 :             :                    (guint) user_id, limit_type);
     415                 :           2 :       return NULL;
     416                 :             :     }
     417                 :             : 
     418                 :             :   /* Daily schedule */
     419         [ +  + ]:          37 :   if (!g_variant_lookup (variant, "DailySchedule", "(uu)",
     420                 :             :                          &daily_start_time, &daily_end_time))
     421                 :             :     {
     422                 :             :       /* Default value. */
     423                 :          16 :       daily_start_time = 0;
     424                 :          16 :       daily_end_time = 24 * 60 * 60;
     425                 :             :     }
     426                 :             :   else
     427                 :             :     {
     428                 :          21 :       set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
     429                 :             :     }
     430                 :             : 
     431         [ +  + ]:          37 :   if (daily_start_time >= daily_end_time ||
     432         [ +  + ]:          35 :       daily_end_time > 24 * 60 * 60)
     433                 :             :     {
     434                 :           4 :       g_set_error (error, MCT_MANAGER_ERROR,
     435                 :             :                    MCT_MANAGER_ERROR_INVALID_DATA,
     436                 :             :                    _("Session limit for user %u has invalid daily schedule %u–%u"),
     437                 :             :                    (guint) user_id, daily_start_time, daily_end_time);
     438                 :           4 :       return NULL;
     439                 :             :     }
     440                 :             : 
     441                 :             :   /* Daily limit */
     442         [ +  + ]:          33 :   if (!g_variant_lookup (variant, "DailyLimit", "u", &daily_limit_secs))
     443                 :             :     {
     444                 :             :       /* Default value. */
     445                 :          21 :       daily_limit_secs = 24 * 60 * 60;
     446                 :             :     }
     447                 :             :   else
     448                 :             :     {
     449                 :          12 :       set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
     450                 :             :     }
     451                 :             : 
     452         [ +  + ]:          33 :   if (daily_limit_secs > 24 * 60 * 60)
     453                 :             :     {
     454                 :           2 :       g_set_error (error, MCT_MANAGER_ERROR,
     455                 :             :                    MCT_MANAGER_ERROR_INVALID_DATA,
     456                 :             :                    _("Session limit for user %u has invalid daily limit %u"),
     457                 :             :                    (guint) user_id, daily_limit_secs);
     458                 :           2 :       return NULL;
     459                 :             :     }
     460                 :             : 
     461                 :             :   /* Ensure @set_limit_type is equal to, or a superset of, @limit_type. */
     462                 :          31 :   set_limit_type |= limit_type;
     463                 :             : 
     464                 :             :   /* Success. Create an `MctSessionLimits` object to contain the results. */
     465                 :          31 :   session_limits = g_new0 (MctSessionLimits, 1);
     466                 :          31 :   session_limits->ref_count = 1;
     467                 :          31 :   session_limits->user_id = user_id;
     468                 :          31 :   session_limits->limit_type = limit_type;
     469                 :          31 :   session_limits->set_limit_type = set_limit_type;
     470                 :          31 :   session_limits->daily_start_time = daily_start_time;
     471                 :          31 :   session_limits->daily_end_time = daily_end_time;
     472                 :          31 :   session_limits->daily_limit_secs = daily_limit_secs;
     473                 :             : 
     474                 :          31 :   return g_steal_pointer (&session_limits);
     475                 :             : }
     476                 :             : 
     477                 :             : /**
     478                 :             :  * mct_session_limits_equal:
     479                 :             :  * @a: (not nullable): a session limits configuration
     480                 :             :  * @b: (not nullable): a session limits configuration
     481                 :             :  *
     482                 :             :  * Check whether session limits configurations @a and @b are equal.
     483                 :             :  *
     484                 :             :  * Returns: true if @a and @b are equal, false otherwise
     485                 :             :  * Since: 0.14.0
     486                 :             :  */
     487                 :             : gboolean
     488                 :         134 : mct_session_limits_equal (MctSessionLimits *a,
     489                 :             :                           MctSessionLimits *b)
     490                 :             : {
     491                 :         134 :   g_return_val_if_fail (a != NULL, FALSE);
     492                 :         134 :   g_return_val_if_fail (a->ref_count >= 1, FALSE);
     493                 :         134 :   g_return_val_if_fail (b != NULL, FALSE);
     494                 :         134 :   g_return_val_if_fail (b->ref_count >= 1, FALSE);
     495                 :             : 
     496                 :         240 :   return (a->user_id == b->user_id &&
     497         [ +  + ]:         106 :           a->limit_type == b->limit_type &&
     498         [ +  + ]:          50 :           a->set_limit_type == b->set_limit_type &&
     499         [ +  - ]:          30 :           a->daily_start_time == b->daily_start_time &&
     500   [ +  +  +  + ]:         262 :           a->daily_end_time == b->daily_end_time &&
     501         [ +  - ]:          22 :           a->daily_limit_secs == b->daily_limit_secs);
     502                 :             : }
     503                 :             : 
     504                 :             : /*
     505                 :             :  * Actual implementation of `MctSessionLimitsBuilder`.
     506                 :             :  *
     507                 :             :  * All members are `NULL` if un-initialised, cleared, or ended.
     508                 :             :  */
     509                 :             : typedef struct
     510                 :             : {
     511                 :             :   MctSessionLimitsType limit_type;
     512                 :             :   MctSessionLimitsType set_limit_type;
     513                 :             : 
     514                 :             :   struct
     515                 :             :     {
     516                 :             :       guint start_time;  /* seconds since midnight */
     517                 :             :       guint end_time;  /* seconds since midnight */
     518                 :             :     } daily_schedule;
     519                 :             : 
     520                 :             :   unsigned int daily_limit_secs;
     521                 :             : 
     522                 :             :   /*< private >*/
     523                 :             :   gpointer padding[9];
     524                 :             : } MctSessionLimitsBuilderReal;
     525                 :             : 
     526                 :             : G_STATIC_ASSERT (sizeof (MctSessionLimitsBuilderReal) ==
     527                 :             :                  sizeof (MctSessionLimitsBuilder));
     528                 :             : G_STATIC_ASSERT (__alignof__ (MctSessionLimitsBuilderReal) ==
     529                 :             :                  __alignof__ (MctSessionLimitsBuilder));
     530                 :             : 
     531   [ +  -  +  -  :           6 : G_DEFINE_BOXED_TYPE (MctSessionLimitsBuilder, mct_session_limits_builder,
                   +  - ]
     532                 :             :                      mct_session_limits_builder_copy, mct_session_limits_builder_free)
     533                 :             : 
     534                 :             : /**
     535                 :             :  * mct_session_limits_builder_init:
     536                 :             :  * @builder: an uninitialised [struct@Malcontent.SessionLimitsBuilder]
     537                 :             :  *
     538                 :             :  * Initialise the given @builder so it can be used to construct a new
     539                 :             :  * [struct@Malcontent.SessionLimits].
     540                 :             :  *
     541                 :             :  * @builder must have been allocated on the stack, and must not already be
     542                 :             :  * initialised.
     543                 :             :  *
     544                 :             :  * Construct the [struct@Malcontent.SessionLimits] by calling methods on
     545                 :             :  * @builder, followed by [method@Malcontent.SessionLimitsBuilder.end]. To abort
     546                 :             :  * construction, use [method@Malcontent.SessionLimitsBuilder.clear].
     547                 :             :  *
     548                 :             :  * Since: 0.5.0
     549                 :             :  */
     550                 :             : void
     551                 :          26 : mct_session_limits_builder_init (MctSessionLimitsBuilder *builder)
     552                 :             : {
     553                 :          26 :   MctSessionLimitsBuilder local_builder = MCT_SESSION_LIMITS_BUILDER_INIT ();
     554                 :          26 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     555                 :             : 
     556                 :          26 :   g_return_if_fail (_builder != NULL);
     557                 :          26 :   g_return_if_fail (_builder->limit_type == MCT_SESSION_LIMITS_TYPE_NONE);
     558                 :          26 :   g_return_if_fail (_builder->set_limit_type == MCT_SESSION_LIMITS_TYPE_NONE);
     559                 :             : 
     560                 :          26 :   memcpy (builder, &local_builder, sizeof (local_builder));
     561                 :             : }
     562                 :             : 
     563                 :             : /**
     564                 :             :  * mct_session_limits_builder_clear:
     565                 :             :  * @builder: a [struct@Malcontent.SessionLimitsBuilder]
     566                 :             :  *
     567                 :             :  * Clear @builder, freeing any internal state in it.
     568                 :             :  *
     569                 :             :  * This will not free the top-level storage for @builder itself, which is
     570                 :             :  * assumed to be allocated on the stack.
     571                 :             :  *
     572                 :             :  * If called on an already-cleared [struct@Malcontent.SessionLimitsBuilder],
     573                 :             :  * this function is idempotent.
     574                 :             :  *
     575                 :             :  * Since: 0.5.0
     576                 :             :  */
     577                 :             : void
     578                 :         100 : mct_session_limits_builder_clear (MctSessionLimitsBuilder *builder)
     579                 :             : {
     580                 :         100 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     581                 :             : 
     582                 :         100 :   g_return_if_fail (_builder != NULL);
     583                 :             : 
     584                 :             :   /* Nothing to free here for now. */
     585                 :         100 :   _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
     586                 :         100 :   _builder->set_limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
     587                 :             : }
     588                 :             : 
     589                 :             : /**
     590                 :             :  * mct_session_limits_builder_new:
     591                 :             :  *
     592                 :             :  * Construct a new [struct@Malcontent.SessionLimitsBuilder] on the heap.
     593                 :             :  *
     594                 :             :  * This is intended for language bindings. The returned builder must eventually
     595                 :             :  * be freed with [method@Malcontent.SessionLimitsBuilder.free], but can be
     596                 :             :  * cleared zero or more times with
     597                 :             :  * [method@Malcontent.SessionLimitsBuilder.clear] first.
     598                 :             :  *
     599                 :             :  * Returns: (transfer full): a new heap-allocated
     600                 :             :  *   [struct@Malcontent.SessionLimitsBuilder]
     601                 :             :  * Since: 0.5.0
     602                 :             :  */
     603                 :             : MctSessionLimitsBuilder *
     604                 :          18 : mct_session_limits_builder_new (void)
     605                 :             : {
     606                 :          18 :   g_autoptr(MctSessionLimitsBuilder) builder = NULL;
     607                 :             : 
     608                 :          18 :   builder = g_new0 (MctSessionLimitsBuilder, 1);
     609                 :          18 :   mct_session_limits_builder_init (builder);
     610                 :             : 
     611                 :          18 :   return g_steal_pointer (&builder);
     612                 :             : }
     613                 :             : 
     614                 :             : /**
     615                 :             :  * mct_session_limits_builder_copy:
     616                 :             :  * @builder: a [struct@Malcontent.SessionLimitsBuilder]
     617                 :             :  *
     618                 :             :  * Copy the given @builder to a newly-allocated
     619                 :             :  * [struct@Malcontent.SessionLimitsBuilder] on the heap.
     620                 :             :  *
     621                 :             :  * This is safe to use with cleared, stack-allocated
     622                 :             :  * [struct@Malcontent.SessionLimitsBuilder]s.
     623                 :             :  *
     624                 :             :  * Returns: (transfer full): a copy of @builder
     625                 :             :  * Since: 0.5.0
     626                 :             :  */
     627                 :             : MctSessionLimitsBuilder *
     628                 :           4 : mct_session_limits_builder_copy (MctSessionLimitsBuilder *builder)
     629                 :             : {
     630                 :           4 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     631                 :           4 :   g_autoptr(MctSessionLimitsBuilder) copy = NULL;
     632                 :             :   MctSessionLimitsBuilderReal *_copy;
     633                 :             : 
     634                 :           4 :   g_return_val_if_fail (builder != NULL, NULL);
     635                 :             : 
     636                 :           4 :   copy = mct_session_limits_builder_new ();
     637                 :           4 :   _copy = (MctSessionLimitsBuilderReal *) copy;
     638                 :             : 
     639                 :           4 :   mct_session_limits_builder_clear (copy);
     640                 :           4 :   _copy->limit_type = _builder->limit_type;
     641                 :           4 :   _copy->set_limit_type = _builder->set_limit_type;
     642                 :             : 
     643         [ +  + ]:           4 :   if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
     644                 :             :     {
     645                 :           2 :       _copy->daily_schedule.start_time = _builder->daily_schedule.start_time;
     646                 :           2 :       _copy->daily_schedule.end_time = _builder->daily_schedule.end_time;
     647                 :             :     }
     648                 :             : 
     649         [ -  + ]:           4 :   if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
     650                 :           0 :     _copy->daily_limit_secs = _builder->daily_limit_secs;
     651                 :             : 
     652                 :           4 :   return g_steal_pointer (&copy);
     653                 :             : }
     654                 :             : 
     655                 :             : /**
     656                 :             :  * mct_session_limits_builder_free:
     657                 :             :  * @builder: a heap-allocated [struct@Malcontent.SessionLimitsBuilder]
     658                 :             :  *
     659                 :             :  * Free an [struct@Malcontent.SessionLimitsBuilder] originally allocated using
     660                 :             :  * [ctor@Malcontent.SessionLimitsBuilder.new].
     661                 :             :  *
     662                 :             :  * This must not be called on stack-allocated builders initialised using
     663                 :             :  * [method@Malcontent.SessionLimitsBuilder.init].
     664                 :             :  *
     665                 :             :  * Since: 0.5.0
     666                 :             :  */
     667                 :             : void
     668                 :          18 : mct_session_limits_builder_free (MctSessionLimitsBuilder *builder)
     669                 :             : {
     670                 :          18 :   g_return_if_fail (builder != NULL);
     671                 :             : 
     672                 :          18 :   mct_session_limits_builder_clear (builder);
     673                 :          18 :   g_free (builder);
     674                 :             : }
     675                 :             : 
     676                 :             : /**
     677                 :             :  * mct_session_limits_builder_end:
     678                 :             :  * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
     679                 :             :  *
     680                 :             :  * Finish constructing an [struct@Malcontent.SessionLimits] with the given
     681                 :             :  * @builder, and return it.
     682                 :             :  *
     683                 :             :  * The [struct@Malcontent.SessionLimitsBuilder] will be cleared as if
     684                 :             :  * [method@Malcontent.SessionLimitsBuilder.clear] had been called.
     685                 :             :  *
     686                 :             :  * Returns: (transfer full): a newly constructed
     687                 :             :  *   [struct@Malcontent.SessionLimits]
     688                 :             :  * Since: 0.5.0
     689                 :             :  */
     690                 :             : MctSessionLimits *
     691                 :          46 : mct_session_limits_builder_end (MctSessionLimitsBuilder *builder)
     692                 :             : {
     693                 :          46 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     694                 :          46 :   g_autoptr(MctSessionLimits) session_limits = NULL;
     695                 :             : 
     696                 :          46 :   g_return_val_if_fail (_builder != NULL, NULL);
     697                 :             : 
     698                 :             :   /* Build the `MctSessionLimits`. */
     699                 :          46 :   session_limits = g_new0 (MctSessionLimits, 1);
     700                 :          46 :   session_limits->ref_count = 1;
     701                 :          46 :   session_limits->user_id = -1;
     702                 :          46 :   session_limits->limit_type = _builder->limit_type;
     703                 :          46 :   session_limits->set_limit_type = _builder->limit_type;
     704                 :             : 
     705         [ +  + ]:          46 :   if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE)
     706                 :             :     {
     707                 :          25 :       session_limits->daily_start_time = _builder->daily_schedule.start_time;
     708                 :          25 :       session_limits->daily_end_time = _builder->daily_schedule.end_time;
     709                 :             :     }
     710                 :             :   else
     711                 :             :     {
     712                 :             :       /* Defaults: */
     713                 :          21 :       session_limits->daily_start_time = 0;
     714                 :          21 :       session_limits->daily_end_time = 24 * 60 * 60;
     715                 :             :     }
     716                 :             : 
     717         [ +  + ]:          46 :   if (_builder->set_limit_type & MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT)
     718                 :           6 :     session_limits->daily_limit_secs = _builder->daily_limit_secs;
     719                 :             :   else
     720                 :          40 :     session_limits->daily_limit_secs = 24 * 60 * 60;  /* default */
     721                 :             : 
     722                 :          46 :   mct_session_limits_builder_clear (builder);
     723                 :             : 
     724                 :          46 :   return g_steal_pointer (&session_limits);
     725                 :             : }
     726                 :             : 
     727                 :             : /**
     728                 :             :  * mct_session_limits_builder_set_none:
     729                 :             :  * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
     730                 :             :  *
     731                 :             :  * Unset any session limits currently set in the @builder.
     732                 :             :  *
     733                 :             :  * Since: 0.5.0
     734                 :             :  */
     735                 :             : void
     736                 :           2 : mct_session_limits_builder_set_none (MctSessionLimitsBuilder *builder)
     737                 :             : {
     738                 :           2 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     739                 :             : 
     740                 :           2 :   g_return_if_fail (_builder != NULL);
     741                 :             : 
     742                 :             :   /* This will need to free other limit types’ data first in future. */
     743                 :           2 :   _builder->limit_type = MCT_SESSION_LIMITS_TYPE_NONE;
     744                 :             : }
     745                 :             : 
     746                 :             : /**
     747                 :             :  * mct_session_limits_builder_set_daily_schedule:
     748                 :             :  * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
     749                 :             :  * @enforced: true if the limit type is to be enforced, false if the value is
     750                 :             :  *   just being stored for future use without being enforced
     751                 :             :  * @start_time_secs: number of seconds since midnight when the user’s session
     752                 :             :  *   can first start
     753                 :             :  * @end_time_secs: number of seconds since midnight when the user’s session can
     754                 :             :  *   last end
     755                 :             :  *
     756                 :             :  * Set the session limits in @builder to be a daily schedule, where sessions are
     757                 :             :  * allowed between @start_time_secs and @end_time_secs every day.
     758                 :             :  *
     759                 :             :  * @start_time_secs and @end_time_secs are given as offsets from the start of
     760                 :             :  * the day, in seconds. @end_time_secs must be greater than @start_time_secs.
     761                 :             :  * @end_time_secs must be at most `24 * 60 * 60`.
     762                 :             :  *
     763                 :             :  * This will act in addition to any other session limits.
     764                 :             :  *
     765                 :             :  * Since: 0.14.0
     766                 :             :  */
     767                 :             : void
     768                 :          27 : mct_session_limits_builder_set_daily_schedule (MctSessionLimitsBuilder *builder,
     769                 :             :                                                gboolean                 enforced,
     770                 :             :                                                guint                    start_time_secs,
     771                 :             :                                                guint                    end_time_secs)
     772                 :             : {
     773                 :          27 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     774                 :             : 
     775                 :          27 :   g_return_if_fail (_builder != NULL);
     776                 :          27 :   g_return_if_fail (start_time_secs < end_time_secs);
     777                 :          27 :   g_return_if_fail (end_time_secs <= 24 * 60 * 60);
     778                 :             : 
     779                 :          27 :   _builder->set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
     780         [ +  - ]:          27 :   if (enforced)
     781                 :          27 :     _builder->limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_SCHEDULE;
     782                 :          27 :   _builder->daily_schedule.start_time = start_time_secs;
     783                 :          27 :   _builder->daily_schedule.end_time = end_time_secs;
     784                 :             : }
     785                 :             : 
     786                 :             : /**
     787                 :             :  * mct_session_limits_builder_set_daily_limit:
     788                 :             :  * @builder: an initialised [struct@Malcontent.SessionLimitsBuilder]
     789                 :             :  * @enforced: true if the limit type is to be enforced, false if the value is
     790                 :             :  *   just being stored for future use without being enforced
     791                 :             :  * @daily_limit_secs: maximum length for the user’s active session time each
     792                 :             :  *   day, in seconds
     793                 :             :  *
     794                 :             :  * Set the session limits in @builder to be a daily limit, where the total
     795                 :             :  * active session time for the user has a given limit each day.
     796                 :             :  *
     797                 :             :  * @daily_limit_secs must be at most `24 * 60 * 60`.
     798                 :             :  *
     799                 :             :  * This will act in addition to any other session limits.
     800                 :             :  *
     801                 :             :  * Since: 0.14.0
     802                 :             :  */
     803                 :             : void
     804                 :           6 : mct_session_limits_builder_set_daily_limit (MctSessionLimitsBuilder *builder,
     805                 :             :                                             gboolean                 enforced,
     806                 :             :                                             unsigned int             daily_limit_secs)
     807                 :             : {
     808                 :           6 :   MctSessionLimitsBuilderReal *_builder = (MctSessionLimitsBuilderReal *) builder;
     809                 :             : 
     810                 :           6 :   g_return_if_fail (_builder != NULL);
     811                 :           6 :   g_return_if_fail (daily_limit_secs <= 24 * 60 * 60);
     812                 :             : 
     813                 :           6 :   _builder->set_limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
     814         [ +  - ]:           6 :   if (enforced)
     815                 :           6 :     _builder->limit_type |= MCT_SESSION_LIMITS_TYPE_DAILY_LIMIT;
     816                 :           6 :   _builder->daily_limit_secs = daily_limit_secs;
     817                 :             : }
        

Generated by: LCOV version 2.0-1