/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details:
 *
 * 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"

static gint
_enum_from_string (GType type,
                   const gchar *str,
                   gint error_value,
                   GError **error)
{
    GEnumClass *enum_class;
    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;
            g_type_class_unref (enum_class);
            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));
    g_type_class_unref (enum_class);
    return error_value;
}

static guint
_flags_from_string (GType type,
                    const gchar *str,
                    guint error_value,
                    GError **error)
{
    GFlagsClass *flags_class;
    guint value;
    guint i;

    flags_class = G_FLAGS_CLASS (g_type_class_ref (type));

    for (i = 0; flags_class->values[i].value_nick; i++) {
        if (!g_ascii_strcasecmp (str, flags_class->values[i].value_nick)) {
            value = flags_class->values[i].value;
            g_type_class_unref (flags_class);
            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));
    g_type_class_unref (flags_class);
    return error_value;
}

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);
}

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;
}

MMSmsStorage *
mm_common_sms_storages_variant_to_array (GVariant *variant,
                                         guint *n_storages)
{
    GArray *array;

    array = mm_common_sms_storages_variant_to_garray (variant);
    if (n_storages)
        *n_storages = array->len;
    return (MMSmsStorage *) g_array_free (array, FALSE);
}

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);
}

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;
}

MMModemPortInfo *
mm_common_ports_variant_to_array (GVariant *variant,
                                  guint *n_ports)
{
    GArray *array;

    array = mm_common_ports_variant_to_garray (variant);
    if (n_ports)
        *n_ports = array->len;
    return (MMModemPortInfo *) g_array_free (array, FALSE);
}

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);
}

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

    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;
    }

    g_type_class_unref (flags_class);
    g_strfreev (capability_strings);
    return capabilities;
}

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

    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;
    }

    g_type_class_unref (flags_class);
    g_strfreev (mode_strings);
    return modes;
}

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;
}

MMModemCapability *
mm_common_capability_combinations_variant_to_array (GVariant *variant,
                                                    guint *n_capabilities)
{
    GArray *array;

    array = mm_common_capability_combinations_variant_to_garray (variant);
    if (n_capabilities)
        *n_capabilities = array->len;
    return (MMModemCapability *) g_array_free (array, FALSE);
}

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);
}

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

    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;
    } else {
        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);
    }

    g_type_class_unref (enum_class);
    g_strfreev (band_strings);
}

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;
}

MMModemBand *
mm_common_bands_variant_to_array (GVariant *variant,
                                  guint *n_bands)
{
    GArray *array;

    array = mm_common_bands_variant_to_garray (variant);
    if (n_bands)
        *n_bands = array->len;
    return (MMModemBand *) g_array_free (array, FALSE);
}

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);
}

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);
}

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;
}

MMModemModeCombination *
mm_common_mode_combinations_variant_to_array (GVariant *variant,
                                              guint *n_modes)
{
    GArray *array;

    array = mm_common_mode_combinations_variant_to_garray (variant);
    if (n_modes)
        *n_modes = array->len;
    return (MMModemModeCombination *) g_array_free (array, FALSE);
}

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);
}

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);
}

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;
}

MMOmaPendingNetworkInitiatedSession *
mm_common_oma_pending_network_initiated_sessions_variant_to_array (GVariant *variant,
                                                                   guint *n_sessions)
{
    GArray *array;

    array = mm_common_oma_pending_network_initiated_sessions_variant_to_garray (variant);
    if (n_sessions)
        *n_sessions = array->len;
    return (MMOmaPendingNetworkInitiatedSession *) g_array_free (array, FALSE);
}

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);
}

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;
    gchar **strings;
    GFlagsClass *flags_class;

    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;
    }

    g_type_class_unref (flags_class);
    g_strfreev (strings);
    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;
    gchar **feature_strings;
    GFlagsClass *flags_class;

    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;
    }

    g_type_class_unref (flags_class);
    g_strfreev (feature_strings);
    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);
}

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);
}

/* 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;
}

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

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);
}

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

/* 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;
}
