/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * libmm-glib -- Access modem status & information from glib applications
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 * Copyright (C) 2010 - 2012 Red Hat, Inc.
 * Copyright (C) 2011 - 2012 Google, Inc.
 */

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <gio/gio.h>

#include <ModemManager.h>

#include "mm-enums-types.h"
#include "mm-errors-types.h"
#include "mm-common-helpers.h"

#if (!GLIB_CHECK_VERSION (2, 58, 0))
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GEnumClass, g_type_class_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GFlagsClass, g_type_class_unref)
#endif

/******************************************************************************/
/* Enums/flags to string builders */

gchar *
mm_common_build_capabilities_string (const MMModemCapability *capabilities,
                                     guint                    n_capabilities)
{
    gboolean first = TRUE;
    GString *str;
    guint i;

    if (!capabilities || !n_capabilities)
        return g_strdup ("none");

    str = g_string_new ("");
    for (i = 0; i < n_capabilities; i++) {
        gchar *tmp;

        tmp = mm_modem_capability_build_string_from_mask (capabilities[i]);
        g_string_append_printf (str, "%s%s",
                                first ? "" : "\n",
                                tmp);
        g_free (tmp);

        if (first)
            first = FALSE;
    }

    return g_string_free (str, FALSE);
}

gchar *
mm_common_build_bands_string (const MMModemBand *bands,
                              guint              n_bands)
{
    gboolean first = TRUE;
    GString *str;
    guint i;

    if (!bands || !n_bands)
        return g_strdup ("none");

    str = g_string_new ("");
    for (i = 0; i < n_bands; i++) {
        g_string_append_printf (str, "%s%s",
                                first ? "" : ", ",
                                mm_modem_band_get_string (bands[i]));

        if (first)
            first = FALSE;
    }

    return g_string_free (str, FALSE);
}

gchar *
mm_common_build_ports_string (const MMModemPortInfo *ports,
                              guint                  n_ports)
{
    gboolean first = TRUE;
    GString *str;
    guint i;

    if (!ports || !n_ports)
        return g_strdup ("none");

    str = g_string_new ("");
    for (i = 0; i < n_ports; i++) {
        g_string_append_printf (str, "%s%s (%s)",
                                first ? "" : ", ",
                                ports[i].name,
                                mm_modem_port_type_get_string (ports[i].type));

        if (first)
            first = FALSE;
    }

    return g_string_free (str, FALSE);
}

gchar *
mm_common_build_sms_storages_string (const MMSmsStorage *storages,
                                     guint               n_storages)
{
    gboolean first = TRUE;
    GString *str;
    guint i;

    if (!storages || !n_storages)
        return g_strdup ("none");

    str = g_string_new ("");
    for (i = 0; i < n_storages; i++) {
        g_string_append_printf (str, "%s%s",
                                first ? "" : ", ",
                                mm_sms_storage_get_string (storages[i]));

        if (first)
            first = FALSE;
    }

    return g_string_free (str, FALSE);
}

gchar *
mm_common_build_mode_combinations_string (const MMModemModeCombination *modes,
                                          guint                         n_modes)
{
    gboolean first = TRUE;
    GString *str;
    guint i;

    if (!modes || !n_modes)
        return g_strdup ("none");

    str = g_string_new ("");
    for (i = 0; i < n_modes; i++) {
        gchar *allowed;
        gchar *preferred;

        allowed = mm_modem_mode_build_string_from_mask (modes[i].allowed);
        preferred = mm_modem_mode_build_string_from_mask (modes[i].preferred);
        g_string_append_printf (str, "%sallowed: %s; preferred: %s",
                                first ? "" : "\n",
                                allowed,
                                preferred);
        g_free (allowed);
        g_free (preferred);

        if (first)
            first = FALSE;
    }

    return g_string_free (str, FALSE);
}

/******************************************************************************/
/* String to enums/flags parsers */


static gint
_enum_from_string (GType         type,
                   const gchar  *str,
                   gint          error_value,
                   GError      **error)
{
    g_autoptr(GEnumClass) enum_class = NULL;
    gint                  value;
    guint                 i;

    enum_class = G_ENUM_CLASS (g_type_class_ref (type));

    for (i = 0; enum_class->values[i].value_nick; i++) {
        if (!g_ascii_strcasecmp (str, enum_class->values[i].value_nick)) {
            value = enum_class->values[i].value;
            return value;
        }
    }

    g_set_error (error,
                 MM_CORE_ERROR,
                 MM_CORE_ERROR_INVALID_ARGS,
                 "Couldn't match '%s' with a valid %s value",
                 str,
                 g_type_name (type));
    return error_value;
}

static guint
_flags_from_string (GType         type,
                    const gchar  *str,
                    guint         error_value,
                    GError      **error)
{
    g_auto(GStrv)          flags_strings = NULL;
    g_autoptr(GFlagsClass) flags_class = NULL;
    guint                  value = 0;
    guint                  i;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (type));
    flags_strings = g_strsplit (str, "|", -1);

    for (i = 0; flags_strings[i]; i++) {
        guint j;
        gboolean found = FALSE;

        for (j = 0; flags_class->values[j].value_nick; j++) {
            if (!g_ascii_strcasecmp (flags_strings[i], flags_class->values[j].value_nick)) {
                value |= flags_class->values[j].value;
                found = TRUE;
            }
        }

        if (!found) {
            g_set_error (error,
                MM_CORE_ERROR,
                MM_CORE_ERROR_INVALID_ARGS,
                "Couldn't match '%s' with a valid %s value",
                flags_strings[i],
                g_type_name (type));
            return error_value;
        }
    }

    return value;
}

MMModemCapability
mm_common_get_capabilities_from_string (const gchar  *str,
                                        GError      **error)
{
    GError                 *inner_error = NULL;
    MMModemCapability       capabilities;
    g_auto(GStrv)           capability_strings = NULL;
    g_autoptr(GFlagsClass)  flags_class = NULL;

    capabilities = MM_MODEM_CAPABILITY_NONE;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_MODEM_CAPABILITY));
    capability_strings = g_strsplit (str, "|", -1);

    if (capability_strings) {
        guint i;

        for (i = 0; capability_strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; flags_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (capability_strings[i], flags_class->values[j].value_nick)) {
                    capabilities |= flags_class->values[j].value;
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (
                    MM_CORE_ERROR,
                    MM_CORE_ERROR_INVALID_ARGS,
                    "Couldn't match '%s' with a valid MMModemCapability value",
                    capability_strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        capabilities = MM_MODEM_CAPABILITY_NONE;
    }
    return capabilities;
}

MMModemMode
mm_common_get_modes_from_string (const gchar  *str,
                                 GError      **error)
{
    GError                 *inner_error = NULL;
    MMModemMode             modes;
    g_auto(GStrv)           mode_strings = NULL;
    g_autoptr(GFlagsClass)  flags_class = NULL;

    modes = MM_MODEM_MODE_NONE;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_MODEM_MODE));
    mode_strings = g_strsplit (str, "|", -1);

    if (mode_strings) {
        guint i;

        for (i = 0; mode_strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; flags_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (mode_strings[i], flags_class->values[j].value_nick)) {
                    modes |= flags_class->values[j].value;
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (
                    MM_CORE_ERROR,
                    MM_CORE_ERROR_INVALID_ARGS,
                    "Couldn't match '%s' with a valid MMModemMode value",
                    mode_strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        modes = MM_MODEM_MODE_NONE;
    }
    return modes;
}

gboolean
mm_common_get_bands_from_string (const gchar  *str,
                                 MMModemBand **bands,
                                 guint        *n_bands,
                                 GError      **error)
{
    GError                *inner_error = NULL;
    GArray                *array;
    g_auto(GStrv)          band_strings = NULL;
    g_autoptr(GEnumClass)  enum_class = NULL;

    array = g_array_new (FALSE, FALSE, sizeof (MMModemBand));

    enum_class = G_ENUM_CLASS (g_type_class_ref (MM_TYPE_MODEM_BAND));
    band_strings = g_strsplit (str, "|", -1);

    if (band_strings) {
        guint i;

        for (i = 0; band_strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; enum_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (band_strings[i], enum_class->values[j].value_nick)) {
                    g_array_append_val (array, enum_class->values[j].value);
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (MM_CORE_ERROR,
                                           MM_CORE_ERROR_INVALID_ARGS,
                                           "Couldn't match '%s' with a valid MMModemBand value",
                                           band_strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        g_array_free (array, TRUE);
        *n_bands = 0;
        *bands = NULL;
        return FALSE;
    }

    if (!array->len) {
        GEnumValue *value;

        value = g_enum_get_value (enum_class, MM_MODEM_BAND_UNKNOWN);
        g_array_append_val (array, value->value);
    }

    *n_bands = array->len;
    *bands = (MMModemBand *)g_array_free (array, FALSE);
    return TRUE;
}

gboolean
mm_common_get_boolean_from_string (const gchar  *value,
                                   GError      **error)
{
    if (!g_ascii_strcasecmp (value, "true") || g_str_equal (value, "1") || !g_ascii_strcasecmp (value, "yes"))
        return TRUE;

    if (!g_ascii_strcasecmp (value, "false") || g_str_equal (value, "0") || !g_ascii_strcasecmp (value, "no"))
        return FALSE;

    g_set_error (error,
                 MM_CORE_ERROR,
                 MM_CORE_ERROR_INVALID_ARGS,
                 "Cannot get boolean from string '%s'", value);
    return FALSE;
}

MMModemCdmaRmProtocol
mm_common_get_rm_protocol_from_string (const gchar  *str,
                                       GError      **error)
{
    return _enum_from_string (MM_TYPE_MODEM_CDMA_RM_PROTOCOL,
                              str,
                              MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN,
                              error);
}

MMBearerIpFamily
mm_common_get_ip_type_from_string (const gchar  *str,
                                   GError      **error)
{
    return _flags_from_string (MM_TYPE_BEARER_IP_FAMILY,
                               str,
                               MM_BEARER_IP_FAMILY_NONE,
                               error);
}

MMBearerAllowedAuth
mm_common_get_allowed_auth_from_string (const gchar  *str,
                                        GError      **error)
{
    GError                 *inner_error = NULL;
    MMBearerAllowedAuth     allowed_auth;
    g_auto(GStrv)           strings = NULL;
    g_autoptr(GFlagsClass)  flags_class = NULL;

    allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_BEARER_ALLOWED_AUTH));
    strings = g_strsplit (str, "|", -1);

    if (strings) {
        guint i;

        for (i = 0; strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; flags_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (strings[i], flags_class->values[j].value_nick)) {
                    allowed_auth |= flags_class->values[j].value;
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (
                    MM_CORE_ERROR,
                    MM_CORE_ERROR_INVALID_ARGS,
                    "Couldn't match '%s' with a valid MMBearerAllowedAuth value",
                    strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        allowed_auth = MM_BEARER_ALLOWED_AUTH_UNKNOWN;
    }
    return allowed_auth;
}

MMSmsStorage
mm_common_get_sms_storage_from_string (const gchar  *str,
                                       GError      **error)
{
    return _enum_from_string (MM_TYPE_SMS_STORAGE,
                              str,
                              MM_SMS_STORAGE_UNKNOWN,
                              error);
}

MMSmsCdmaTeleserviceId
mm_common_get_sms_cdma_teleservice_id_from_string (const gchar  *str,
                                                   GError      **error)
{
    return _enum_from_string (MM_TYPE_SMS_CDMA_TELESERVICE_ID,
                              str,
                              MM_SMS_CDMA_TELESERVICE_ID_UNKNOWN,
                              error);
}

MMSmsCdmaServiceCategory
mm_common_get_sms_cdma_service_category_from_string (const gchar  *str,
                                                     GError      **error)
{
    return _enum_from_string (MM_TYPE_SMS_CDMA_SERVICE_CATEGORY,
                              str,
                              MM_SMS_CDMA_SERVICE_CATEGORY_UNKNOWN,
                              error);
}

MMCallDirection
mm_common_get_call_direction_from_string (const gchar  *str,
                                          GError      **error)
{
    return _enum_from_string (MM_TYPE_CALL_DIRECTION,
                              str,
                              MM_CALL_DIRECTION_UNKNOWN,
                              error);
}

MMCallState
mm_common_get_call_state_from_string (const gchar  *str,
                                      GError      **error)
{
    return _enum_from_string (MM_TYPE_CALL_STATE,
                              str,
                              MM_CALL_STATE_UNKNOWN,
                              error);
}

MMCallStateReason
mm_common_get_call_state_reason_from_string (const gchar  *str,
                                             GError      **error)
{
    return _enum_from_string (MM_TYPE_CALL_STATE_REASON,
                              str,
                              MM_CALL_STATE_REASON_UNKNOWN,
                              error);
}

MMOmaFeature
mm_common_get_oma_features_from_string (const gchar  *str,
                                        GError      **error)
{
    GError                 *inner_error = NULL;
    MMOmaFeature            features;
    g_auto(GStrv)           feature_strings = NULL;
    g_autoptr(GFlagsClass)  flags_class = NULL;

    features = MM_OMA_FEATURE_NONE;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_OMA_FEATURE));
    feature_strings = g_strsplit (str, "|", -1);

    if (feature_strings) {
        guint i;

        for (i = 0; feature_strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; flags_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (feature_strings[i], flags_class->values[j].value_nick)) {
                    features |= flags_class->values[j].value;
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (
                    MM_CORE_ERROR,
                    MM_CORE_ERROR_INVALID_ARGS,
                    "Couldn't match '%s' with a valid MMOmaFeature value",
                    feature_strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        features = MM_OMA_FEATURE_NONE;
    }
    return features;
}

MMOmaSessionType
mm_common_get_oma_session_type_from_string (const gchar  *str,
                                            GError      **error)
{
    return _enum_from_string (MM_TYPE_OMA_SESSION_TYPE,
                              str,
                              MM_OMA_SESSION_TYPE_UNKNOWN,
                              error);
}

MMModem3gppEpsUeModeOperation
mm_common_get_eps_ue_mode_operation_from_string (const gchar  *str,
                                                 GError      **error)
{
    return _enum_from_string (MM_TYPE_MODEM_3GPP_EPS_UE_MODE_OPERATION,
                              str,
                              MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_UNKNOWN,
                              error);
}

MMModemAccessTechnology
mm_common_get_access_technology_from_string (const gchar  *str,
                                             GError      **error)
{
    GError                  *inner_error = NULL;
    MMModemAccessTechnology  technologies;
    g_auto(GStrv)            technology_strings = NULL;
    g_autoptr(GFlagsClass)   flags_class = NULL;

    technologies = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_MODEM_ACCESS_TECHNOLOGY));
    technology_strings = g_strsplit (str, "|", -1);

    if (technology_strings) {
        guint i;

        for (i = 0; technology_strings[i]; i++) {
            guint j;
            gboolean found = FALSE;

            for (j = 0; flags_class->values[j].value_nick; j++) {
                if (!g_ascii_strcasecmp (technology_strings[i], flags_class->values[j].value_nick)) {
                    technologies |= flags_class->values[j].value;
                    found = TRUE;
                    break;
                }
            }

            if (!found) {
                inner_error = g_error_new (
                    MM_CORE_ERROR,
                    MM_CORE_ERROR_INVALID_ARGS,
                    "Couldn't match '%s' with a valid MMModemAccessTechnology value",
                    technology_strings[i]);
                break;
            }
        }
    }

    if (inner_error) {
        g_propagate_error (error, inner_error);
        technologies = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
    }
    return technologies;
}

MMBearerMultiplexSupport
mm_common_get_multiplex_support_from_string (const gchar  *str,
                                             GError      **error)
{
    return _enum_from_string (MM_TYPE_BEARER_MULTIPLEX_SUPPORT,
                              str,
                              MM_BEARER_MULTIPLEX_SUPPORT_UNKNOWN,
                              error);
}

MMBearerApnType
mm_common_get_apn_type_from_string (const gchar  *str,
                                    GError      **error)
{
    return _flags_from_string (MM_TYPE_BEARER_APN_TYPE,
                               str,
                               MM_BEARER_APN_TYPE_NONE,
                               error);
}

MMModem3gppFacility
mm_common_get_3gpp_facility_from_string (const gchar  *str,
                                         GError      **error)
{
    return _flags_from_string (MM_TYPE_MODEM_3GPP_FACILITY,
                               str,
                               MM_MODEM_3GPP_FACILITY_NONE,
                               error);
}

MMModem3gppPacketServiceState
mm_common_get_3gpp_packet_service_state_from_string (const gchar  *str,
                                                     GError      **error)
{
    return _enum_from_string (MM_TYPE_MODEM_3GPP_PACKET_SERVICE_STATE,
                              str,
                              MM_MODEM_3GPP_PACKET_SERVICE_STATE_UNKNOWN,
                              error);
}

MMModem3gppMicoMode
mm_common_get_3gpp_mico_mode_from_string (const gchar  *str,
                                          GError      **error)
{
    return _enum_from_string (MM_TYPE_MODEM_3GPP_MICO_MODE,
                              str,
                              MM_MODEM_3GPP_MICO_MODE_UNKNOWN,
                              error);
}

MMModem3gppDrxCycle
mm_common_get_3gpp_drx_cycle_from_string (const gchar  *str,
                                          GError      **error)
{
    return _enum_from_string (MM_TYPE_MODEM_3GPP_DRX_CYCLE,
                              str,
                              MM_MODEM_3GPP_DRX_CYCLE_UNKNOWN,
                              error);
}

MMBearerAccessTypePreference
mm_common_get_access_type_preference_from_string (const gchar  *str,
                                                  GError      **error)
{
    return _enum_from_string (MM_TYPE_BEARER_ACCESS_TYPE_PREFERENCE,
                              str,
                              MM_BEARER_ACCESS_TYPE_PREFERENCE_NONE,
                              error);
}

MMBearerProfileSource
mm_common_get_profile_source_from_string (const gchar  *str,
                                          GError      **error)
{
    return _enum_from_string (MM_TYPE_BEARER_PROFILE_SOURCE,
                              str,
                              MM_BEARER_PROFILE_SOURCE_UNKNOWN,
                              error);
}

/******************************************************************************/
/* MMModemPortInfo array management */

static void
clear_modem_port_info (MMModemPortInfo *info)
{
    g_free (info->name);
}

GArray *
mm_common_ports_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        guint i;
        guint n;

        n = g_variant_n_children (variant);

        if (n > 0) {
            array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemPortInfo), n);
            g_array_set_clear_func (array, (GDestroyNotify) clear_modem_port_info);
            for (i = 0; i < n; i++) {
                MMModemPortInfo info;

                g_variant_get_child (variant, i, "(su)", &info.name, &info.type);
                g_array_append_val (array, info);
            }
        }
    }

    return array;
}

GVariant *
mm_common_ports_array_to_variant (const MMModemPortInfo *ports,
                                  guint                  n_ports)
{
    GVariantBuilder builder;
    guint i;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(su)"));

    for (i = 0; i < n_ports; i++) {
        GVariant *tuple[2];

        tuple[0] = g_variant_new_string (ports[i].name);
        tuple[1] = g_variant_new_uint32 ((guint32)ports[i].type);
        g_variant_builder_add_value (&builder, g_variant_new_tuple (tuple, 2));
    }
    return g_variant_builder_end (&builder);
}

GVariant *
mm_common_ports_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_ports_array_to_variant ((const MMModemPortInfo *)array->data,
                                                 array->len);

    return mm_common_ports_array_to_variant (NULL, 0);
}

gboolean
mm_common_ports_garray_to_array (GArray           *array,
                                 MMModemPortInfo **ports,
                                 guint            *n_ports)
{
    if (!array)
        return FALSE;

    *ports = NULL;
    *n_ports = array->len;
    if (array->len > 0) {
        guint i;

        *ports = g_malloc (sizeof (MMModemPortInfo) * array->len);

        /* Deep-copy the array */
        for (i = 0; i < array->len; i++) {
            MMModemPortInfo *src;

            src = &g_array_index (array, MMModemPortInfo, i);
            (*ports)[i].name = g_strdup (src->name);
            (*ports)[i].type = src->type;
        }
    }
    return TRUE;
}

/******************************************************************************/
/* MMSmsStorage array management */

GArray *
mm_common_sms_storages_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        GVariantIter iter;
        guint n;

        g_variant_iter_init (&iter, variant);
        n = g_variant_iter_n_children (&iter);

        if (n > 0) {
            guint32 storage;

            array = g_array_sized_new (FALSE, FALSE, sizeof (MMSmsStorage), n);
            while (g_variant_iter_loop (&iter, "u", &storage))
                g_array_append_val (array, storage);
        }
    }

    return array;
}

GVariant *
mm_common_sms_storages_array_to_variant (const MMSmsStorage *storages,
                                         guint               n_storages)
{
    GVariantBuilder builder;
    guint i;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));

    for (i = 0; i < n_storages; i++)
        g_variant_builder_add_value (&builder,
                                     g_variant_new_uint32 ((guint32)storages[i]));
    return g_variant_builder_end (&builder);
}

GVariant *
mm_common_sms_storages_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_sms_storages_array_to_variant ((const MMSmsStorage *)array->data,
                                                        array->len);

    return mm_common_sms_storages_array_to_variant (NULL, 0);
}

/******************************************************************************/
/* MMModemCapability array management */

GArray *
mm_common_capability_combinations_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        GVariantIter iter;
        guint n;

        g_variant_iter_init (&iter, variant);
        n = g_variant_iter_n_children (&iter);

        if (n > 0) {
            guint32 capability;

            array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), n);
            while (g_variant_iter_loop (&iter, "u", &capability))
                g_array_append_val (array, capability);
        }
    }

    /* If nothing set, fallback to default */
    if (!array) {
        guint32 capability = MM_MODEM_CAPABILITY_NONE;

        array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 1);
        g_array_append_val (array, capability);
    }

    return array;
}

GVariant *
mm_common_capability_combinations_array_to_variant (const MMModemCapability *capabilities,
                                                    guint                    n_capabilities)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));

    if (n_capabilities > 0) {
        guint i;

        for (i = 0; i < n_capabilities; i++)
            g_variant_builder_add_value (&builder,
                                         g_variant_new_uint32 ((guint32)capabilities[i]));
    } else
        g_variant_builder_add_value (&builder,
                                     g_variant_new_uint32 (MM_MODEM_CAPABILITY_NONE));

    return g_variant_builder_end (&builder);
}

GVariant *
mm_common_capability_combinations_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_capability_combinations_array_to_variant ((const MMModemCapability *)array->data,
                                                                   array->len);

    return mm_common_capability_combinations_array_to_variant (NULL, 0);
}

GVariant *
mm_common_build_capability_combinations_none (void)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));
    g_variant_builder_add_value (&builder,
                                 g_variant_new_uint32 (MM_MODEM_CAPABILITY_NONE));
    return g_variant_builder_end (&builder);
}

/******************************************************************************/
/* MMModemModeCombination array management */

GArray *
mm_common_mode_combinations_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        GVariantIter iter;
        guint n;

        g_variant_iter_init (&iter, variant);
        n = g_variant_iter_n_children (&iter);

        if (n > 0) {
            MMModemModeCombination mode;

            array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), n);
            while (g_variant_iter_loop (&iter, "(uu)", &mode.allowed, &mode.preferred))
                g_array_append_val (array, mode);
        }
    }

    /* If nothing set, fallback to default */
    if (!array) {
        MMModemModeCombination default_mode;

        default_mode.allowed = MM_MODEM_MODE_ANY;
        default_mode.preferred = MM_MODEM_MODE_NONE;
        array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
        g_array_append_val (array, default_mode);
    }

    return array;
}

GVariant *
mm_common_mode_combinations_array_to_variant (const MMModemModeCombination *modes,
                                              guint                         n_modes)
{
    if (n_modes > 0) {
        GVariantBuilder builder;
        guint i;

        g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));

        for (i = 0; i < n_modes; i++)
            g_variant_builder_add_value (&builder,
                                         g_variant_new ("(uu)",
                                                        ((guint32)modes[i].allowed),
                                                        ((guint32)modes[i].preferred)));
        return g_variant_builder_end (&builder);
    }

    return mm_common_build_mode_combinations_default ();
}

GVariant *
mm_common_mode_combinations_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_mode_combinations_array_to_variant ((const MMModemModeCombination *)array->data,
                                                             array->len);

    return mm_common_mode_combinations_array_to_variant (NULL, 0);
}

GVariant *
mm_common_build_mode_combinations_default (void)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));
    g_variant_builder_add_value (&builder,
                                 g_variant_new ("(uu)",
                                                MM_MODEM_MODE_ANY,
                                                MM_MODEM_MODE_NONE));
    return g_variant_builder_end (&builder);
}

/******************************************************************************/
/* MMModemBand array management */

GArray *
mm_common_bands_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        GVariantIter iter;
        guint n;

        g_variant_iter_init (&iter, variant);
        n = g_variant_iter_n_children (&iter);

        if (n > 0) {
            guint32 band;

            array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), n);
            while (g_variant_iter_loop (&iter, "u", &band))
                g_array_append_val (array, band);
        }
    }

    /* If nothing set, fallback to default */
    if (!array) {
        guint32 band = MM_MODEM_BAND_UNKNOWN;

        array = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 1);
        g_array_append_val (array, band);
    }

    return array;
}

GVariant *
mm_common_bands_array_to_variant (const MMModemBand *bands,
                                  guint              n_bands)
{
    if (n_bands > 0) {
        GVariantBuilder builder;
        guint i;

        g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));

        for (i = 0; i < n_bands; i++)
            g_variant_builder_add_value (&builder,
                                         g_variant_new_uint32 ((guint32)bands[i]));
        return g_variant_builder_end (&builder);
    }

    return mm_common_build_bands_unknown ();
}

GVariant *
mm_common_bands_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_bands_array_to_variant ((const MMModemBand *)array->data,
                                                 array->len);

    return mm_common_bands_array_to_variant (NULL, 0);
}

GVariant *
mm_common_build_bands_unknown (void)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));
    g_variant_builder_add_value (&builder,
                                 g_variant_new_uint32 (MM_MODEM_BAND_UNKNOWN));
    return g_variant_builder_end (&builder);
}

GVariant *
mm_common_build_bands_any (void)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("au"));
    g_variant_builder_add_value (&builder,
                                 g_variant_new_uint32 (MM_MODEM_BAND_ANY));
    return g_variant_builder_end (&builder);
}

static guint
cmp_band (MMModemBand *a, MMModemBand *b)
{
    return (*a - *b);
}

gboolean
mm_common_bands_garray_cmp (GArray *a, GArray *b)
{
    GArray *dup_a;
    GArray *dup_b;
    guint i;
    gboolean different;

    if (a->len != b->len)
        return FALSE;

    dup_a = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), a->len);
    g_array_append_vals (dup_a, a->data, a->len);

    dup_b = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), b->len);
    g_array_append_vals (dup_b, b->data, b->len);

    g_array_sort (dup_a, (GCompareFunc)cmp_band);
    g_array_sort (dup_b, (GCompareFunc)cmp_band);

    different = FALSE;
    for (i = 0; !different && i < a->len; i++) {
        if (g_array_index (dup_a, MMModemBand, i) != g_array_index (dup_b, MMModemBand, i))
            different = TRUE;
    }

    g_array_unref (dup_a);
    g_array_unref (dup_b);

    return !different;
}

gboolean
mm_common_bands_garray_lookup (GArray      *array,
                               MMModemBand  value)
{
    guint i;

    for (i = 0; i < array->len; i++) {
        if (value == g_array_index (array, MMModemBand, i))
            return TRUE;
    }

    return FALSE;
}

void
mm_common_bands_garray_sort (GArray *array)
{
    g_array_sort (array, (GCompareFunc) cmp_band);
}

gboolean
mm_common_band_is_gsm (MMModemBand band)
{
    return ((band >= MM_MODEM_BAND_EGSM && band <= MM_MODEM_BAND_G850) ||
            (band >= MM_MODEM_BAND_G450 && band <= MM_MODEM_BAND_G810));
}

gboolean
mm_common_band_is_utran (MMModemBand band)
{
    return ((band >= MM_MODEM_BAND_UTRAN_1 && band <= MM_MODEM_BAND_UTRAN_7) ||
            (band >= MM_MODEM_BAND_UTRAN_10 && band <= MM_MODEM_BAND_UTRAN_32));
}

gboolean
mm_common_band_is_eutran (MMModemBand band)
{
    return (band >= MM_MODEM_BAND_EUTRAN_1 && band <= MM_MODEM_BAND_EUTRAN_71);
}

gboolean
mm_common_band_is_cdma (MMModemBand band)
{
    return (band >= MM_MODEM_BAND_CDMA_BC0 && band <= MM_MODEM_BAND_CDMA_BC19);
}

/******************************************************************************/
/* MMOmaPendingNetworkInitiatedSession array management */

GArray *
mm_common_oma_pending_network_initiated_sessions_variant_to_garray (GVariant *variant)
{
    GArray *array = NULL;

    if (variant) {
        GVariantIter iter;
        guint n;

        g_variant_iter_init (&iter, variant);
        n = g_variant_iter_n_children (&iter);

        if (n > 0) {
            MMOmaPendingNetworkInitiatedSession session;

            array = g_array_sized_new (FALSE, FALSE, sizeof (MMOmaPendingNetworkInitiatedSession), n);
            while (g_variant_iter_loop (&iter, "(uu)", &session.session_type, &session.session_id))
                g_array_append_val (array, session);
        }
    }

    /* If nothing set, fallback to empty */
    if (!array)
        array = g_array_new (FALSE, FALSE, sizeof (MMOmaPendingNetworkInitiatedSession));

    return array;
}

GVariant *
mm_common_oma_pending_network_initiated_sessions_array_to_variant (const MMOmaPendingNetworkInitiatedSession *sessions,
                                                                   guint                                      n_sessions)
{
    if (n_sessions > 0) {
        GVariantBuilder builder;
        guint i;

        g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));

        for (i = 0; i < n_sessions; i++)
            g_variant_builder_add_value (&builder,
                                         g_variant_new ("(uu)",
                                                        ((guint32)sessions[i].session_type),
                                                        ((guint32)sessions[i].session_id)));
        return g_variant_builder_end (&builder);
    }

    return mm_common_build_oma_pending_network_initiated_sessions_default ();
}

GVariant *
mm_common_oma_pending_network_initiated_sessions_garray_to_variant (GArray *array)
{
    if (array)
        return mm_common_oma_pending_network_initiated_sessions_array_to_variant ((const MMOmaPendingNetworkInitiatedSession *)array->data,
                                                                                  array->len);

    return mm_common_oma_pending_network_initiated_sessions_array_to_variant (NULL, 0);
}

GVariant *
mm_common_build_oma_pending_network_initiated_sessions_default (void)
{
    GVariantBuilder builder;

    g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));
    return g_variant_builder_end (&builder);
}

/******************************************************************************/
/* Common parsers */

/* Expecting input as:
 *   key1=string,key2=true,key3=false...
 * Strings may also be passed enclosed between double or single quotes, like:
 *   key1="this is a string", key2='and so is this'
 */
gboolean
mm_common_parse_key_value_string (const gchar *str,
                                  GError **error,
                                  MMParseKeyValueForeachFn callback,
                                  gpointer user_data)
{
    GError *inner_error = NULL;
    gchar *dup, *p, *key, *key_end, *value, *value_end, quote;

    g_return_val_if_fail (callback != NULL, FALSE);
    g_return_val_if_fail (str != NULL, FALSE);

    /* Allow empty strings, we'll just return with success */
    while (g_ascii_isspace (*str))
        str++;
    if (!str[0])
        return TRUE;

    dup = g_strdup (str);
    p = dup;

    while (TRUE) {
        gboolean keep_iteration = FALSE;

        /* Skip leading spaces */
        while (g_ascii_isspace (*p))
            p++;

        /* Key start */
        key = p;
        if (!g_ascii_isalnum (*key)) {
            inner_error = g_error_new (MM_CORE_ERROR,
                                       MM_CORE_ERROR_FAILED,
                                       "Key must start with alpha/num, starts with '%c'",
                                       *key);
            break;
        }

        /* Key end */
        while (g_ascii_isalnum (*p) || (*p == '-') || (*p == '_'))
            p++;
        key_end = p;
        if (key_end == key) {
            inner_error = g_error_new (MM_CORE_ERROR,
                                       MM_CORE_ERROR_FAILED,
                                       "Couldn't find a proper key");
            break;
        }

        /* Skip whitespaces, if any */
        while (g_ascii_isspace (*p))
            p++;

        /* Equal sign must be here */
        if (*p != '=') {
            inner_error = g_error_new (MM_CORE_ERROR,
                                       MM_CORE_ERROR_FAILED,
                                       "Couldn't find equal sign separator");
            break;
        }
        /* Skip the equal */
        p++;

        /* Skip whitespaces, if any */
        while (g_ascii_isspace (*p))
            p++;

        /* Do we have a quote-enclosed string? */
        if (*p == '\"' || *p == '\'') {
            quote = *p;
            /* Skip the quote */
            p++;
            /* Value start */
            value = p;
            /* Find the closing quote */
            p = strchr (p, quote);
            if (!p) {
                inner_error = g_error_new (MM_CORE_ERROR,
                                           MM_CORE_ERROR_FAILED,
                                           "Unmatched quotes in string value");
                break;
            }

            /* Value end */
            value_end = p;
            /* Skip the quote */
            p++;
        } else {
            /* Value start */
            value = p;

            /* Value end */
            while ((*p != ',') && (*p != '\0') && !g_ascii_isspace (*p))
                p++;
            value_end = p;
        }

        /* Note that we allow value == value_end here */

        /* Skip whitespaces, if any */
        while (g_ascii_isspace (*p))
            p++;

        /* If a comma is found, we should keep the iteration */
        if (*p == ',') {
            /* skip the comma */
            p++;
            keep_iteration = TRUE;
        }

        /* Got key and value, prepare them and run the callback */
        *value_end = '\0';
        *key_end = '\0';
        if (!callback (key, value, user_data)) {
            /* We were told to abort */
            break;
        }

        if (keep_iteration)
            continue;

        /* Check if no more key/value pairs expected */
        if (*p == '\0')
            break;

        inner_error = g_error_new (MM_CORE_ERROR,
                                   MM_CORE_ERROR_FAILED,
                                   "Unexpected content (%s) after value",
                                   p);
        break;
    }

    g_free (dup);

    if (inner_error) {
        g_propagate_error (error, inner_error);
        return FALSE;
    }

    return TRUE;
}

/*****************************************************************************/

gboolean
mm_get_int_from_str (const gchar *str,
                     gint        *out)
{
    glong num;
    guint i;
    guint eol = 0;

    if (!str)
        return FALSE;

    /* ignore all leading whitespaces */
    while (str[0] == ' ')
        str++;

    if (!str[0])
        return FALSE;

    for (i = 0; str[i]; i++) {
        if (str[i] != '+' && str[i] != '-' && !g_ascii_isdigit (str[i])) {
            /* ignore \r\n at the end of the string */
            if ((str[i] == '\r') || (str[i] == '\n')) {
                eol++;
                continue;
            }
            return FALSE;
        }
        /* if eol found before a valid char, the string is not parseable */
        if (eol)
            return FALSE;
    }
    /* if all characters were eol, the string is not parseable */
    if (eol == i)
        return FALSE;

    errno = 0;
    num = strtol (str, NULL, 10);
    if (!errno && num >= G_MININT && num <= G_MAXINT) {
        *out = (gint)num;
        return TRUE;
    }
    return FALSE;
}

gboolean
mm_get_int_from_match_info (GMatchInfo *match_info,
                            guint32     match_index,
                            gint       *out)
{
    g_autofree gchar *s = NULL;

    s = mm_get_string_unquoted_from_match_info (match_info, match_index);
    return (s ? mm_get_int_from_str (s, out) : FALSE);
}

gboolean
mm_get_uint_from_str (const gchar *str,
                      guint       *out)
{
    guint64 num;

    if (!mm_get_u64_from_str (str, &num) || num > G_MAXUINT)
        return FALSE;

    *out = (guint)num;
    return TRUE;
}

gboolean
mm_get_u64_from_str (const gchar *str,
                     guint64     *out)
{
    guint64 num;
    guint   eol = 0;

    if (!str)
        return FALSE;

    /* ignore all leading whitespaces */
    while (str[0] == ' ')
        str++;

    if (!str[0])
        return FALSE;

    for (num = 0; str[num]; num++) {
        if (!g_ascii_isdigit (str[num])) {
            /* ignore \r\n at the end of the string */
            if ((str[num] == '\r') || (str[num] == '\n')) {
                eol++;
                continue;
            }
            return FALSE;
        }
        /* if eol found before a valid char, the string is not parseable */
        if (eol)
            return FALSE;
    }
    /* if all characters were eol, the string is not parseable */
    if (eol == num)
        return FALSE;

    errno = 0;
    num = (guint64) strtoull (str, NULL, 10);
    if (!errno) {
        *out = num;
        return TRUE;
    }
    return FALSE;
}

gboolean
mm_get_uint_from_hex_str (const gchar *str,
                          guint       *out)
{
    guint64 num;

    if (!mm_get_u64_from_hex_str (str, &num) || num > G_MAXUINT)
        return FALSE;

    *out = (guint)num;
    return TRUE;
}

gboolean
mm_get_u64_from_hex_str (const gchar *str,
                         guint64     *out)
{
    guint64 num;
    guint   eol = 0;

    if (!str)
        return FALSE;

    /* ignore all leading whitespaces */
    while (str[0] == ' ')
        str++;

    if (g_str_has_prefix (str, "0x"))
        str = &str[2];

    if (!str[0])
        return FALSE;

    for (num = 0; str[num]; num++) {
        if (!g_ascii_isxdigit (str[num])) {
            /* ignore \r\n at the end of the string */
            if ((str[num] == '\r') || (str[num] == '\n')) {
                eol++;
                continue;
            }
            return FALSE;
        }
        /* if eol found before a valid char, the string is not parseable */
        if (eol)
            return FALSE;
    }
    /* if all characters were eol, the string is not parseable */
    if (eol == num)
        return FALSE;

    errno = 0;
    num = (guint64) strtoull (str, NULL, 16);
    if (!errno) {
        *out = num;
        return TRUE;
    }
    return FALSE;
}

gboolean
mm_get_uint_from_match_info (GMatchInfo *match_info,
                             guint32     match_index,
                             guint      *out)
{
    guint64 num;

    if (!mm_get_u64_from_match_info (match_info, match_index, &num) || num > G_MAXUINT)
        return FALSE;

    *out = (guint)num;
    return TRUE;
}

gboolean
mm_get_u64_from_match_info (GMatchInfo *match_info,
                            guint32     match_index,
                            guint64    *out)
{
    g_autofree gchar *s = NULL;

    s = mm_get_string_unquoted_from_match_info (match_info, match_index);
    return (s ? mm_get_u64_from_str (s, out) : FALSE);
}

gboolean
mm_get_uint_from_hex_match_info (GMatchInfo *match_info,
                                 guint32     match_index,
                                 guint      *out)
{
    guint64 num;

    if (!mm_get_u64_from_hex_match_info (match_info, match_index, &num) || num > G_MAXUINT)
        return FALSE;

    *out = (guint)num;
    return TRUE;
}

gboolean
mm_get_u64_from_hex_match_info (GMatchInfo *match_info,
                                guint32     match_index,
                                guint64    *out)
{
    g_autofree gchar *s = NULL;

    s = mm_get_string_unquoted_from_match_info (match_info, match_index);
    return (s ? mm_get_u64_from_hex_str (s, out) : FALSE);
}

gboolean
mm_get_double_from_str (const gchar *str,
                        gdouble     *out)
{
    gdouble  num;
    guint    i;
    guint    eol = 0;

    if (!str || !str[0])
        return FALSE;

    for (i = 0; str[i]; i++) {
        /* we don't really expect numbers in scientific notation, so
         * don't bother looking for exponents and such */
        if ((str[i] != '-') && (str[i] != '.') && !g_ascii_isdigit (str[i])) {
            /* ignore \r\n at the end of the string */
            if ((str[i] == '\r') || (str[i] == '\n')) {
                eol++;
                continue;
            }
            return FALSE;
        }
        /* if eol found before a valid char, the string is not parseable */
        if (eol)
            return FALSE;
    }
    /* if all characters were eol, the string is not parseable */
    if (eol == i)
        return FALSE;

    errno = 0;
    num = g_ascii_strtod (str, NULL);
    if (!errno) {
        *out = num;
        return TRUE;
    }
    return FALSE;
}

gboolean
mm_get_double_from_match_info (GMatchInfo *match_info,
                               guint32     match_index,
                               gdouble    *out)
{
    g_autofree gchar *s = NULL;

    s = mm_get_string_unquoted_from_match_info (match_info, match_index);
    return (s ? mm_get_double_from_str (s, out) : FALSE);
}

gchar *
mm_get_string_unquoted_from_match_info (GMatchInfo *match_info,
                                        guint32     match_index)
{
    gchar *str;
    gsize len;

    str = g_match_info_fetch (match_info, match_index);
    if (!str)
        return NULL;

    len = strlen (str);

    /* Unquote the item if needed */
    if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) {
        str[0] = ' ';
        str[len - 1] = ' ';
        str = g_strstrip (str);
    }

    if (!str[0]) {
        g_free (str);
        return NULL;
    }

    return str;
}

/*
 * The following implementation is taken from glib g_date_time_format_iso8601 code
 * https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gdatetime.c#L3490
 */
static gchar *
date_time_format_iso8601 (GDateTime *dt)
{
#if GLIB_CHECK_VERSION (2, 62, 0)
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
    return g_date_time_format_iso8601 (dt);
    G_GNUC_END_IGNORE_DEPRECATIONS
#else
    GString          *outstr = NULL;
    g_autofree gchar *main_date = NULL;
    gint64            offset = 0;

    main_date = g_date_time_format (dt, "%Y-%m-%dT%H:%M:%S");
    outstr = g_string_new (main_date);

    /* Timezone. Format it as `%:::z` unless the offset is zero, in which case
     * we can simply use `Z`. */
    offset = g_date_time_get_utc_offset (dt);
    if (offset == 0) {
        g_string_append_c (outstr, 'Z');
    } else {
        g_autofree gchar *time_zone = NULL;

        time_zone = g_date_time_format (dt, "%:::z");
        g_string_append (outstr, time_zone);
    }

    return g_string_free (outstr, FALSE);
#endif
}

gchar *
mm_new_iso8601_time_from_unix_time (guint64   timestamp,
                                    GError  **error)
{
    g_autoptr(GDateTime) dt = NULL;

    dt = g_date_time_new_from_unix_utc ((gint64)timestamp);
    if (!dt) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "Invalid unix time: %" G_GUINT64_FORMAT,
                     timestamp);
        return NULL;
    }

    return date_time_format_iso8601 (dt);
}

gchar *
mm_new_iso8601_time (guint    year,
                     guint    month,
                     guint    day,
                     guint    hour,
                     guint    minute,
                     guint    second,
                     gboolean have_offset,
                     gint     offset_minutes,
                     GError **error)
{
    g_autoptr(GDateTime) dt = NULL;

    if (have_offset) {
        g_autoptr(GTimeZone) tz = NULL;
#if GLIB_CHECK_VERSION (2, 58, 0)
        G_GNUC_BEGIN_IGNORE_DEPRECATIONS
        tz = g_time_zone_new_offset (offset_minutes * 60);
        G_GNUC_END_IGNORE_DEPRECATIONS
#else
        g_autofree gchar *identifier = NULL;

        identifier = g_strdup_printf ("%c%02u:%02u:00",
                                      (offset_minutes >= 0) ? '+' : '-',
                                      ABS (offset_minutes) / 60,
                                      ABS (offset_minutes) % 60);

        tz = g_time_zone_new (identifier);
#endif
        dt = g_date_time_new (tz, year, month, day, hour, minute, second);
    } else
        dt = g_date_time_new_utc (year, month, day, hour, minute, second);

    if (!dt) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "Invalid date: year: %u, month: %u, day: %u, hour: %u, minute: %u, second: %u",
                     year, month, day, hour, minute, second);
        return NULL;
    }
    return date_time_format_iso8601 (dt);
}

/*****************************************************************************/

/* From hostap, Copyright (c) 2002-2005, Jouni Malinen <jkmaline@cc.hut.fi> */

static gint
hex2num (gchar c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;
    return -1;
}

gint
mm_utils_hex2byte (const gchar *hex)
{
    gint a, b;

    a = hex2num (*hex++);
    if (a < 0)
        return -1;
    b = hex2num (*hex++);
    if (b < 0)
        return -1;
    return (a << 4) | b;
}

guint8 *
mm_utils_hexstr2bin (const gchar  *hex,
                     gssize        len,
                     gsize        *out_len,
                     GError      **error)
{
    const gchar *ipos = hex;
    g_autofree guint8 *buf = NULL;
    gssize i;
    gint a;
    guint8 *opos;

    if (len < 0)
        len = strlen (hex);

    if (len == 0) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "Hex conversion failed: empty string");
        return NULL;
    }

    /* Length must be a multiple of 2 */
    if ((len % 2) != 0) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "Hex conversion failed: invalid input length");
        return NULL;
    }

    opos = buf = g_malloc0 (len / 2);
    for (i = 0; i < len; i += 2) {
        a = mm_utils_hex2byte (ipos);
        if (a < 0) {
            g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                         "Hex byte conversion from '%c%c' failed",
                         ipos[0], ipos[1]);
            return NULL;
        }
        *opos++ = (guint8)a;
        ipos += 2;
    }
    *out_len = len / 2;
    return g_steal_pointer (&buf);
}

/* End from hostap */

gboolean
mm_utils_ishexstr (const gchar *hex)
{
    gsize len;
    gsize i;

    /* Empty string or length not multiple of 2? */
    len = strlen (hex);
    if (len == 0 || (len % 2) != 0)
        return FALSE;

    for (i = 0; i < len; i++) {
        /* Non-hex char? */
        if (hex[i] >= '0' && hex[i] <= '9')
            continue;
        if (hex[i] >= 'a' && hex[i] <= 'f')
            continue;
        if (hex[i] >= 'A' && hex[i] <= 'F')
            continue;
        return FALSE;
    }

    return TRUE;
}

gchar *
mm_utils_bin2hexstr (const guint8 *bin,
                     gsize         len)
{
    GString *ret;
    gsize i;

    g_return_val_if_fail (bin != NULL, NULL);

    ret = g_string_sized_new (len * 2 + 1);
    for (i = 0; i < len; i++)
        g_string_append_printf (ret, "%.2X", bin[i]);
    return g_string_free (ret, FALSE);
}

gboolean
mm_utils_check_for_single_value (guint32 value)
{
    gboolean found = FALSE;
    guint32 i;

    for (i = 1; i <= 32; i++) {
        if (value & 0x1) {
            if (found)
                return FALSE;  /* More than one bit set */
            found = TRUE;
        }
        value >>= 1;
    }

    return TRUE;
}

/*****************************************************************************/

gboolean
mm_is_string_mccmnc (const gchar *str)
{
    gsize len;
    guint i;

    if (!str)
        return FALSE;

    len = strlen (str);
    if (len < 5 || len > 6)
        return FALSE;

    for (i = 0; i < len; i++)
        if (str[i] < '0' || str[i] > '9')
            return FALSE;

    return TRUE;
}

/*****************************************************************************/

const gchar *
mm_sms_delivery_state_get_string_extended (guint delivery_state)
{
    if (delivery_state > 0x02 && delivery_state < 0x20) {
        if (delivery_state < 0x10)
            return "completed-reason-reserved";
        else
            return "completed-sc-specific-reason";
    }

    if (delivery_state > 0x25 && delivery_state < 0x40) {
        if (delivery_state < 0x30)
            return "temporary-error-reason-reserved";
        else
            return "temporary-error-sc-specific-reason";
    }

    if (delivery_state > 0x49 && delivery_state < 0x60) {
        if (delivery_state < 0x50)
            return "error-reason-reserved";
        else
            return "error-sc-specific-reason";
    }

    if (delivery_state > 0x65 && delivery_state < 0x80) {
        if (delivery_state < 0x70)
            return "temporary-fatal-error-reason-reserved";
        else
            return "temporary-fatal-error-sc-specific-reason";
    }

    if (delivery_state >= 0x80 && delivery_state < 0x100)
        return "unknown-reason-reserved";

    if (delivery_state >= 0x100)
        return "unknown";

    /* Otherwise, use the MMSmsDeliveryState enum as we can match the known
     * value */
    return mm_sms_delivery_state_get_string ((MMSmsDeliveryState)delivery_state);
}

/*****************************************************************************/
/* DBus error handling */

gboolean
mm_common_register_errors (void)
{
    static volatile guint32 aux = 0;

    if (G_LIKELY (aux))
        return FALSE;

    /* Register all known own errors */
    aux |= MM_CORE_ERROR;
    aux |= MM_MOBILE_EQUIPMENT_ERROR;
    aux |= MM_CONNECTION_ERROR;
    aux |= MM_SERIAL_ERROR;
    aux |= MM_MESSAGE_ERROR;
    aux |= MM_CDMA_ACTIVATION_ERROR;

    return TRUE;
}

GError *
mm_common_error_from_tuple (GVariant  *tuple,
                            GError   **error)
{
    g_autoptr(GError)  dbus_error = NULL;
    g_autofree gchar  *error_name = NULL;
    g_autofree gchar  *error_message = NULL;

    mm_common_register_errors ();

    if (!g_variant_is_of_type (tuple, G_VARIANT_TYPE ("(ss)"))) {
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_INVALID_ARGS,
                     "Cannot create error from tuple: "
                     "invalid variant type received");
        return NULL;
    }

    g_variant_get (tuple, "(ss)", &error_name, &error_message);
    if (!error_name || !error_name[0])
        return NULL;

    /* We convert the error name into a proper GError (domain+code), but we
     * don't attempt to give the error message to new_for_dbus_error() as that
     * would generate a string we don't want (e.g. instead of just "Unknown
     * Error" we would get "GDBus.Error:org.freedesktop.ModemManager1.Error.MobileEquipment.Unknown: Unknown error"
     */
    dbus_error = g_dbus_error_new_for_dbus_error (error_name, "");

    /* And now we build a new GError with same domain+code but with the received
     * error message */
    return g_error_new (dbus_error->domain, dbus_error->code, "%s", error_message);
}

GVariant *
mm_common_error_to_tuple (const GError *error)
{
    g_autofree gchar *error_name = NULL;
    GVariant         *tuple[2];

    mm_common_register_errors ();

    error_name = g_dbus_error_encode_gerror (error);
    tuple[0] = g_variant_new_string (error_name);
    tuple[1] = g_variant_new_string (error->message);

    return g_variant_ref_sink (g_variant_new_tuple (tuple, 2));
}
