/* -*- 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) 2013-2021 Aleksander Morgado <aleksander@aleksander.es>
 */

#include "mm-modem-helpers-mbim.h"
#include "mm-modem-helpers.h"
#include "mm-enums-types.h"
#include "mm-flags-types.h"
#include "mm-errors-types.h"
#include "mm-error-helpers.h"
#include "mm-log-object.h"

#include <string.h>

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

typedef struct {
    const gchar         *custom_class;
    const MbimDataClass  primary_data_class;
    const MbimDataClass  secondary_data_class;
} CustomDataClass;

static const CustomDataClass custom_data_classes[] = {
    /* "5GSA/TDS": Quectel RM502Q */
    { "5GSA",    MBIM_DATA_CLASS_5G_SA,                        MBIM_DATA_CLASS_NONE },
    /* "5G/TDS": Telit FN990, Quectel RM502Q, Gosuncn GM800
     * "5G":     Dell Snapdragon X55/Foxconn T99W175
     */
    { "5G",    MBIM_DATA_CLASS_5G_NSA,                        MBIM_DATA_CLASS_5G_SA },
    /* "HSPA+": Dell DW5821e/Foxconn T77W968, Huawei EM820W */
    { "HSPA+", MBIM_DATA_CLASS_HSDPA | MBIM_DATA_CLASS_HSUPA, MBIM_DATA_CLASS_NONE },
};

MbimDataClass
mm_mbim_data_class_from_custom_caps (MbimDataClass  orig_data_class,
                                     const gchar   *custom_data_class)
{
    guint i;

    if (orig_data_class & MBIM_DATA_CLASS_CUSTOM) {
        for (i = 0; i < G_N_ELEMENTS (custom_data_classes); i++) {
            if (strstr (custom_data_class, custom_data_classes[i].custom_class)) {
                /* If the original data class already includes the primary custom
                 * class add the secondary instead. Devices sometimes report a variant
                 * of the custom class depending on MBIMex version.
                 *
                 * For example, Foxconn X55 supports SA but reports "...,lte,custom"
                 * with MBIMex1 and "...,lte,5g-nsa,custom" with MBIMex2. Fix that
                 * up with the highest level we can be sure the device supports.
                 */
                if (orig_data_class & custom_data_classes[i].primary_data_class)
                    return custom_data_classes[i].secondary_data_class;
                else
                    return custom_data_classes[i].primary_data_class;
            }
        }
    }
    return MBIM_DATA_CLASS_NONE;
}

MbimDataClass
mm_modem_mbim_normalize_data_class_mask (MbimDataClass orig_data_class,
                                         MbimDataClass custom_data_class)
{
    if (orig_data_class & MBIM_DATA_CLASS_CUSTOM) {
        orig_data_class |= custom_data_class;
        orig_data_class &= ~MBIM_DATA_CLASS_CUSTOM;
    }
    return orig_data_class;
}

MbimDataClass
mm_modem_mbim_denormalize_data_class_mask (MbimDataClass orig_data_class,
                                           MbimDataClass custom_data_class)
{
    if (orig_data_class & custom_data_class) {
        orig_data_class &= ~custom_data_class;
        orig_data_class |= MBIM_DATA_CLASS_CUSTOM;
    }
    return orig_data_class;
}

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

MMModemCapability
mm_modem_capability_from_mbim_device_caps (MbimCellularClass  caps_cellular_class,
                                           MbimDataClass      caps_data_class)
{
    MMModemCapability mask = 0;

    if (caps_cellular_class & MBIM_CELLULAR_CLASS_GSM)
        mask |= MM_MODEM_CAPABILITY_GSM_UMTS;

#if 0  /* Disable until we add MBIM CDMA support */
    if (caps_cellular_class & MBIM_CELLULAR_CLASS_CDMA)
        mask |= MM_MODEM_CAPABILITY_CDMA_EVDO;
#endif

    if (caps_data_class & MBIM_DATA_CLASS_LTE)
        mask |= MM_MODEM_CAPABILITY_LTE;

    /* Support for devices with Microsoft extensions */
    if (caps_data_class & (MBIM_DATA_CLASS_5G_NSA | MBIM_DATA_CLASS_5G_SA))
        mask |= MM_MODEM_CAPABILITY_5GNR;

    return mask;
}

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

MMModemLock
mm_modem_lock_from_mbim_pin_type (MbimPinType pin_type)
{
    switch (pin_type) {
    case MBIM_PIN_TYPE_PIN1:
        return MM_MODEM_LOCK_SIM_PIN;
    case MBIM_PIN_TYPE_PIN2:
        return MM_MODEM_LOCK_SIM_PIN2;
    case MBIM_PIN_TYPE_DEVICE_SIM_PIN:
        return MM_MODEM_LOCK_PH_SIM_PIN;
    case MBIM_PIN_TYPE_DEVICE_FIRST_SIM_PIN:
        return MM_MODEM_LOCK_PH_FSIM_PIN;
    case MBIM_PIN_TYPE_NETWORK_PIN:
        return MM_MODEM_LOCK_PH_NET_PIN;
    case MBIM_PIN_TYPE_NETWORK_SUBSET_PIN:
        return MM_MODEM_LOCK_PH_NETSUB_PIN;
    case MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN:
        return MM_MODEM_LOCK_PH_SP_PIN;
    case MBIM_PIN_TYPE_CORPORATE_PIN:
        return MM_MODEM_LOCK_PH_CORP_PIN;
    case MBIM_PIN_TYPE_PUK1:
        return MM_MODEM_LOCK_SIM_PUK;
    case MBIM_PIN_TYPE_PUK2:
        return MM_MODEM_LOCK_SIM_PUK2;
    case MBIM_PIN_TYPE_DEVICE_FIRST_SIM_PUK:
        return MM_MODEM_LOCK_PH_FSIM_PUK;
    case MBIM_PIN_TYPE_NETWORK_PUK:
        return MM_MODEM_LOCK_PH_NET_PUK;
    case MBIM_PIN_TYPE_NETWORK_SUBSET_PUK:
        return MM_MODEM_LOCK_PH_NETSUB_PIN;
    case MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK:
        return MM_MODEM_LOCK_PH_SP_PIN;
    case MBIM_PIN_TYPE_CORPORATE_PUK:
        return MM_MODEM_LOCK_PH_CORP_PUK;
    case MBIM_PIN_TYPE_SUBSIDY_PIN:
    case MBIM_PIN_TYPE_ADM:
    case MBIM_PIN_TYPE_NEV:
    case MBIM_PIN_TYPE_UNKNOWN:
    case MBIM_PIN_TYPE_CUSTOM:
    default:
        break;
    }

    return MM_MODEM_LOCK_UNKNOWN;
}

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

MMModem3gppRegistrationState
mm_modem_3gpp_registration_state_from_mbim_register_state (MbimRegisterState state)
{
    switch (state) {
    case MBIM_REGISTER_STATE_DEREGISTERED:
        return MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
    case MBIM_REGISTER_STATE_SEARCHING:
        return MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING;
    case MBIM_REGISTER_STATE_HOME:
        return MM_MODEM_3GPP_REGISTRATION_STATE_HOME;
    case MBIM_REGISTER_STATE_ROAMING:
    case MBIM_REGISTER_STATE_PARTNER:
        return MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING;
    case MBIM_REGISTER_STATE_DENIED:
        return MM_MODEM_3GPP_REGISTRATION_STATE_DENIED;
    case MBIM_REGISTER_STATE_UNKNOWN:
    default:
        return MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
    }
}

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

MMModem3gppPacketServiceState
mm_modem_3gpp_packet_service_state_from_mbim_packet_service_state (MbimPacketServiceState state)
{
    switch (state) {
    case MBIM_PACKET_SERVICE_STATE_ATTACHED:
        return MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED;
    case MBIM_PACKET_SERVICE_STATE_ATTACHING:
    case MBIM_PACKET_SERVICE_STATE_DETACHING:
    case MBIM_PACKET_SERVICE_STATE_DETACHED:
        return MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED;
    case MBIM_PACKET_SERVICE_STATE_UNKNOWN:
    default:
        return MM_MODEM_3GPP_PACKET_SERVICE_STATE_UNKNOWN;
    }
}

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

MMModemMode
mm_modem_mode_from_mbim_data_class (MbimDataClass  data_class)
{
    MMModemMode mask = MM_MODEM_MODE_NONE;

    /* 3GPP... */
    if (data_class & (MBIM_DATA_CLASS_GPRS |
                      MBIM_DATA_CLASS_EDGE))
        mask |= MM_MODEM_MODE_2G;
    if (data_class & (MBIM_DATA_CLASS_UMTS  |
                      MBIM_DATA_CLASS_HSDPA |
                      MBIM_DATA_CLASS_HSUPA))
        mask |= MM_MODEM_MODE_3G;
    if (data_class & MBIM_DATA_CLASS_LTE)
        mask |= MM_MODEM_MODE_4G;
    if (data_class & (MBIM_DATA_CLASS_5G_NSA |
                      MBIM_DATA_CLASS_5G_SA))
        mask |= MM_MODEM_MODE_5G;

    /* 3GPP2... */
    if (data_class & MBIM_DATA_CLASS_1XRTT)
        mask |= MM_MODEM_MODE_2G;
    if (data_class & (MBIM_DATA_CLASS_1XEVDO |
                      MBIM_DATA_CLASS_1XEVDO_REVA |
                      MBIM_DATA_CLASS_1XEVDV |
                      MBIM_DATA_CLASS_3XRTT |
                      MBIM_DATA_CLASS_1XEVDO_REVB))
        mask |= MM_MODEM_MODE_3G;
    if (data_class & MBIM_DATA_CLASS_UMB)
        mask |= MM_MODEM_MODE_4G;

    return mask;
}

MbimDataClass
mm_mbim_data_class_from_modem_mode (MMModemMode modem_mode,
                                    gboolean    is_3gpp,
                                    gboolean    is_cdma)
{
    MbimDataClass mask = 0;

    /* 3GPP... */
    if (is_3gpp) {
        if (modem_mode & MM_MODEM_MODE_2G)
            mask |= (MBIM_DATA_CLASS_GPRS |
                     MBIM_DATA_CLASS_EDGE);
        if (modem_mode & MM_MODEM_MODE_3G)
            mask |= (MBIM_DATA_CLASS_UMTS |
                     MBIM_DATA_CLASS_HSDPA |
                     MBIM_DATA_CLASS_HSUPA);
        if (modem_mode & MM_MODEM_MODE_4G)
            mask |= MBIM_DATA_CLASS_LTE;
        if (modem_mode & MM_MODEM_MODE_5G)
            mask |= (MBIM_DATA_CLASS_5G_NSA |
                     MBIM_DATA_CLASS_5G_SA);
    }

    /* 3GPP2... */
    if (is_cdma) {
        if (modem_mode & MM_MODEM_MODE_2G)
            mask |= MBIM_DATA_CLASS_1XRTT;
        if (modem_mode & MM_MODEM_MODE_3G)
            mask |= (MBIM_DATA_CLASS_1XEVDO |
                     MBIM_DATA_CLASS_1XEVDO_REVA |
                     MBIM_DATA_CLASS_1XEVDV |
                     MBIM_DATA_CLASS_3XRTT |
                     MBIM_DATA_CLASS_1XEVDO_REVB);
        if (modem_mode & MM_MODEM_MODE_4G)
            mask |= MBIM_DATA_CLASS_UMB;
    }

    return mask;
}

MbimDataClass
mm_mbim_data_class_from_mbim_data_class_v3_and_subclass (MbimDataClassV3  data_class_v3,
                                                         MbimDataSubclass data_subclass)
{
    MbimDataClass data_class;

    data_class = data_class_v3 & ~(MBIM_DATA_CLASS_5G_NSA | MBIM_DATA_CLASS_5G_SA);
    if (data_class_v3 & MBIM_DATA_CLASS_V3_5G) {
        if (data_subclass & MBIM_DATA_SUBCLASS_5G_NR)
            data_class |= MBIM_DATA_CLASS_5G_SA;
        else if (data_subclass & (MBIM_DATA_SUBCLASS_5G_ENDC |
                                  MBIM_DATA_SUBCLASS_5G_NEDC |
                                  MBIM_DATA_SUBCLASS_5G_NGENDC))
            data_class |= (MBIM_DATA_CLASS_5G_NSA | MBIM_DATA_CLASS_LTE);
        else if (data_subclass & MBIM_DATA_SUBCLASS_5G_ELTE)
            data_class |= MBIM_DATA_CLASS_LTE;
    }

    return data_class;
}

MMModemAccessTechnology
mm_modem_access_technology_from_mbim_data_class (MbimDataClass data_class)
{
    MMModemAccessTechnology mask = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;

    if (data_class & MBIM_DATA_CLASS_GPRS)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
    if (data_class & MBIM_DATA_CLASS_EDGE)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
    if (data_class & MBIM_DATA_CLASS_UMTS)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
    if (data_class & MBIM_DATA_CLASS_HSDPA)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
    if (data_class & MBIM_DATA_CLASS_HSUPA)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
    if (data_class & MBIM_DATA_CLASS_LTE)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_LTE;
    if (data_class & MBIM_DATA_CLASS_5G_NSA)
        mask |= (MM_MODEM_ACCESS_TECHNOLOGY_LTE | MM_MODEM_ACCESS_TECHNOLOGY_5GNR);
    if (data_class & MBIM_DATA_CLASS_5G_SA)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_5GNR;

    if (data_class & MBIM_DATA_CLASS_1XRTT)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
    if (data_class & MBIM_DATA_CLASS_1XEVDO)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
    if (data_class & MBIM_DATA_CLASS_1XEVDO_REVA)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
    if (data_class & MBIM_DATA_CLASS_1XEVDO_REVB)
        mask |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOB;

    /* Skip:
     *  MBIM_DATA_CLASS_1XEVDV
     *  MBIM_DATA_CLASS_3XRTT
     *  MBIM_DATA_CLASS_UMB
     *  MBIM_DATA_CLASS_CUSTOM
     */

    return mask;
}

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

MMModem3gppNetworkAvailability
mm_modem_3gpp_network_availability_from_mbim_provider_state (MbimProviderState state)
{
    /* MbimProviderState is a bitmask!
     *
     * We don't explicitly process MBIM_PROVIDER_STATE_PREFERRED,
     * MBIM_PROVIDER_STATE_PREFERRED_MULTICARRIER or MBIM_PROVIDER_STATE_HOME,
     * so we don't report at MM level the type of operator it is (home,
     * preferred or non-preferred), just its availability.
     */
    if (state & MBIM_PROVIDER_STATE_REGISTERED)
        return MM_MODEM_3GPP_NETWORK_AVAILABILITY_CURRENT;

    if (state & MBIM_PROVIDER_STATE_FORBIDDEN)
        return MM_MODEM_3GPP_NETWORK_AVAILABILITY_FORBIDDEN;

    if (state & MBIM_PROVIDER_STATE_VISIBLE)
        return MM_MODEM_3GPP_NETWORK_AVAILABILITY_AVAILABLE;

    return MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN;
}

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

GList *
mm_3gpp_network_info_list_from_mbim_providers (const MbimProvider *const *providers, guint n_providers)
{
    GList *info_list = NULL;
    guint i;

    g_return_val_if_fail (providers != NULL, NULL);

    for (i = 0; i < n_providers; i++) {
        MM3gppNetworkInfo *info;

        info = g_new0 (MM3gppNetworkInfo, 1);
        info->status = mm_modem_3gpp_network_availability_from_mbim_provider_state (providers[i]->provider_state);
        info->operator_long = g_strdup (providers[i]->provider_name);
        info->operator_short = g_strdup (providers[i]->provider_name);
        info->operator_code = g_strdup (providers[i]->provider_id);
        info->access_tech = mm_modem_access_technology_from_mbim_data_class (providers[i]->cellular_class);

        info_list = g_list_append (info_list, info);
    }

    return info_list;
}

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

MbimPinType
mbim_pin_type_from_mm_modem_3gpp_facility (MMModem3gppFacility facility)
{
    switch (facility) {
    case MM_MODEM_3GPP_FACILITY_NET_PERS:
        return MBIM_PIN_TYPE_NETWORK_PIN;
    case MM_MODEM_3GPP_FACILITY_NET_SUB_PERS:
        return MBIM_PIN_TYPE_NETWORK_SUBSET_PIN;
    case MM_MODEM_3GPP_FACILITY_PROVIDER_PERS:
        return MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN;
    case MM_MODEM_3GPP_FACILITY_CORP_PERS:
        return MBIM_PIN_TYPE_CORPORATE_PIN;
    case MM_MODEM_3GPP_FACILITY_SIM:
        return MBIM_PIN_TYPE_PIN1;
    case MM_MODEM_3GPP_FACILITY_FIXED_DIALING:
        return MBIM_PIN_TYPE_PIN2;
    case MM_MODEM_3GPP_FACILITY_PH_SIM:
        return MBIM_PIN_TYPE_DEVICE_SIM_PIN;
    case MM_MODEM_3GPP_FACILITY_PH_FSIM:
        return MBIM_PIN_TYPE_DEVICE_FIRST_SIM_PIN;
    case MM_MODEM_3GPP_FACILITY_NONE:
    default:
        return MBIM_PIN_TYPE_UNKNOWN;
    }
}

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

MMModem3gppFacility
mm_modem_3gpp_facility_from_mbim_pin_type (MbimPinType pin_type)
{
    switch (pin_type) {
    case MBIM_PIN_TYPE_PIN1:
    case MBIM_PIN_TYPE_PUK1:
        return MM_MODEM_3GPP_FACILITY_SIM;
    case MBIM_PIN_TYPE_PIN2:
    case MBIM_PIN_TYPE_PUK2:
        return MM_MODEM_3GPP_FACILITY_FIXED_DIALING;
    case MBIM_PIN_TYPE_DEVICE_SIM_PIN:
        return MM_MODEM_3GPP_FACILITY_PH_SIM;
    case MBIM_PIN_TYPE_DEVICE_FIRST_SIM_PIN:
    case MBIM_PIN_TYPE_DEVICE_FIRST_SIM_PUK:
        return MM_MODEM_3GPP_FACILITY_PH_FSIM;
    case MBIM_PIN_TYPE_NETWORK_PIN:
    case MBIM_PIN_TYPE_NETWORK_PUK:
        return MM_MODEM_3GPP_FACILITY_NET_PERS;
    case MBIM_PIN_TYPE_NETWORK_SUBSET_PIN:
    case MBIM_PIN_TYPE_NETWORK_SUBSET_PUK:
        return MM_MODEM_3GPP_FACILITY_NET_SUB_PERS;
    case MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN:
    case MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK:
        return MM_MODEM_3GPP_FACILITY_PROVIDER_PERS;
    case MBIM_PIN_TYPE_CORPORATE_PIN:
    case MBIM_PIN_TYPE_CORPORATE_PUK:
        return MM_MODEM_3GPP_FACILITY_CORP_PERS;
    case MBIM_PIN_TYPE_SUBSIDY_PIN:
    case MBIM_PIN_TYPE_ADM:
    case MBIM_PIN_TYPE_NEV:
    case MBIM_PIN_TYPE_UNKNOWN:
    case MBIM_PIN_TYPE_CUSTOM:
    default:
        return MM_MODEM_3GPP_FACILITY_NONE;
    }
}

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

MMBearerAllowedAuth
mm_bearer_allowed_auth_from_mbim_auth_protocol (MbimAuthProtocol auth_protocol)
{
    switch (auth_protocol) {
    case MBIM_AUTH_PROTOCOL_NONE:
        return MM_BEARER_ALLOWED_AUTH_NONE;
    case MBIM_AUTH_PROTOCOL_PAP:
        return MM_BEARER_ALLOWED_AUTH_PAP;
    case MBIM_AUTH_PROTOCOL_CHAP:
        return MM_BEARER_ALLOWED_AUTH_CHAP;
    case MBIM_AUTH_PROTOCOL_MSCHAPV2:
        return MM_BEARER_ALLOWED_AUTH_MSCHAPV2;
    default:
        return MM_BEARER_ALLOWED_AUTH_UNKNOWN;
    }
}

MbimAuthProtocol
mm_bearer_allowed_auth_to_mbim_auth_protocol (MMBearerAllowedAuth   bearer_auth,
                                              gpointer              log_object,
                                              GError              **error)
{
    gchar *str;

    /* NOTE: the input is a BITMASK, so we try to find a "best match" */

    if (bearer_auth == MM_BEARER_ALLOWED_AUTH_UNKNOWN) {
        mm_obj_dbg (log_object, "using default (CHAP) authentication method");
        return MBIM_AUTH_PROTOCOL_CHAP;
    }
    if (bearer_auth & MM_BEARER_ALLOWED_AUTH_CHAP)
        return MBIM_AUTH_PROTOCOL_CHAP;
    if (bearer_auth & MM_BEARER_ALLOWED_AUTH_PAP)
        return MBIM_AUTH_PROTOCOL_PAP;
    if (bearer_auth & MM_BEARER_ALLOWED_AUTH_MSCHAPV2)
        return MBIM_AUTH_PROTOCOL_MSCHAPV2;
    if (bearer_auth & MM_BEARER_ALLOWED_AUTH_NONE)
        return MBIM_AUTH_PROTOCOL_NONE;

    str = mm_bearer_allowed_auth_build_string_from_mask (bearer_auth);
    g_set_error (error,
                 MM_CORE_ERROR,
                 MM_CORE_ERROR_UNSUPPORTED,
                 "Unsupported authentication methods (%s)",
                 str);
    g_free (str);
    return MBIM_AUTH_PROTOCOL_NONE;
}

gchar *
mm_mbim_auth_protocol_get_printable (MbimAuthProtocol auth_protocol)
{
    const gchar *str;

    str = mbim_auth_protocol_get_string (auth_protocol);
    if (str)
        return g_strdup (str);

    return g_strdup_printf ("unknown (0x%x)", auth_protocol);
}

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

MMBearerApnType
mm_bearer_apn_type_from_mbim_context_type (MbimContextType context_type)
{
    switch (context_type) {
        case MBIM_CONTEXT_TYPE_INTERNET:
            return MM_BEARER_APN_TYPE_DEFAULT;
        case MBIM_CONTEXT_TYPE_VPN:
            return MM_BEARER_APN_TYPE_PRIVATE;
        case MBIM_CONTEXT_TYPE_VOICE:
            return MM_BEARER_APN_TYPE_VOICE;
        case MBIM_CONTEXT_TYPE_VIDEO_SHARE:
            return MM_BEARER_APN_TYPE_VIDEO_SHARE;
        case MBIM_CONTEXT_TYPE_PURCHASE:
            return MM_BEARER_APN_TYPE_PURCHASE;
        case MBIM_CONTEXT_TYPE_IMS:
            return MM_BEARER_APN_TYPE_IMS;
        case MBIM_CONTEXT_TYPE_MMS:
            return MM_BEARER_APN_TYPE_MMS;
        case MBIM_CONTEXT_TYPE_LOCAL:
            return MM_BEARER_APN_TYPE_LOCAL;
        case MBIM_CONTEXT_TYPE_ADMIN:
            return MM_BEARER_APN_TYPE_MANAGEMENT;
        case MBIM_CONTEXT_TYPE_APP:
            return MM_BEARER_APN_TYPE_APP;
        case MBIM_CONTEXT_TYPE_XCAP:
            return MM_BEARER_APN_TYPE_XCAP;
        case MBIM_CONTEXT_TYPE_TETHERING:
            return MM_BEARER_APN_TYPE_TETHERING;
        case MBIM_CONTEXT_TYPE_EMERGENCY_CALLING:
            return MM_BEARER_APN_TYPE_EMERGENCY;
            /* some types unused right now */
        case MBIM_CONTEXT_TYPE_INVALID:
        case MBIM_CONTEXT_TYPE_NONE:
        default:
            return MM_BEARER_APN_TYPE_NONE;
    }
}

MbimContextType
mm_bearer_apn_type_to_mbim_context_type (MMBearerApnType   apn_type,
                                         gboolean          mbim_extensions_supported,
                                         gpointer          log_object,
                                         GError          **error)
{
    g_autofree gchar *str = NULL;

    /* NOTE: the input is a BITMASK, so we try to find a "best match" */

    if (apn_type == MM_BEARER_APN_TYPE_NONE) {
        mm_obj_dbg (log_object, "using default (internet) APN type");
        return MBIM_CONTEXT_TYPE_INTERNET;
    }

    if (apn_type & MM_BEARER_APN_TYPE_DEFAULT)
        return MBIM_CONTEXT_TYPE_INTERNET;
    if (apn_type & MM_BEARER_APN_TYPE_IMS)
        return MBIM_CONTEXT_TYPE_IMS;
    if (apn_type & MM_BEARER_APN_TYPE_MMS)
        return MBIM_CONTEXT_TYPE_MMS;
    if (apn_type & MM_BEARER_APN_TYPE_VOICE)
        return MBIM_CONTEXT_TYPE_VOICE;
    if (apn_type & MM_BEARER_APN_TYPE_PRIVATE)
        return MBIM_CONTEXT_TYPE_VPN;
    if (apn_type & MM_BEARER_APN_TYPE_PURCHASE)
        return MBIM_CONTEXT_TYPE_PURCHASE;
    if (apn_type & MM_BEARER_APN_TYPE_VIDEO_SHARE)
        return MBIM_CONTEXT_TYPE_VIDEO_SHARE;
    if (apn_type & MM_BEARER_APN_TYPE_LOCAL)
        return MBIM_CONTEXT_TYPE_LOCAL;

    if (mbim_extensions_supported) {
        if (apn_type & MM_BEARER_APN_TYPE_MANAGEMENT)
            return MBIM_CONTEXT_TYPE_ADMIN;
        if (apn_type & MM_BEARER_APN_TYPE_APP)
            return MBIM_CONTEXT_TYPE_APP;
        if (apn_type & MM_BEARER_APN_TYPE_XCAP)
            return MBIM_CONTEXT_TYPE_XCAP;
        if (apn_type & MM_BEARER_APN_TYPE_TETHERING)
            return MBIM_CONTEXT_TYPE_TETHERING;
        if (apn_type & MM_BEARER_APN_TYPE_EMERGENCY)
            return MBIM_CONTEXT_TYPE_EMERGENCY_CALLING;
    } else {
        if ((apn_type & MM_BEARER_APN_TYPE_MANAGEMENT) ||
            (apn_type & MM_BEARER_APN_TYPE_APP)        ||
            (apn_type & MM_BEARER_APN_TYPE_XCAP)       ||
            (apn_type & MM_BEARER_APN_TYPE_TETHERING)  ||
            (apn_type & MM_BEARER_APN_TYPE_EMERGENCY)) {
            mm_obj_dbg (log_object,
                        "MS extensions unsupported: "
                        "fallback to using default (internet) APN type");
            return MBIM_CONTEXT_TYPE_INTERNET;
        }
    }

    str = mm_bearer_apn_type_build_string_from_mask (apn_type);
    g_set_error (error,
                 MM_CORE_ERROR,
                 MM_CORE_ERROR_UNSUPPORTED,
                 "Unsupported APN types (%s)",
                 str);
    return MBIM_CONTEXT_TYPE_NONE;
}

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

MMBearerIpFamily
mm_bearer_ip_family_from_mbim_context_ip_type (MbimContextIpType ip_type)
{
    switch (ip_type) {
    case MBIM_CONTEXT_IP_TYPE_IPV4:
        return MM_BEARER_IP_FAMILY_IPV4;
    case MBIM_CONTEXT_IP_TYPE_IPV6:
        return MM_BEARER_IP_FAMILY_IPV6;
    case MBIM_CONTEXT_IP_TYPE_IPV4V6:
        return MM_BEARER_IP_FAMILY_IPV4V6;
    case MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6:
        return MM_BEARER_IP_FAMILY_IPV4 | MM_BEARER_IP_FAMILY_IPV6;
    case MBIM_CONTEXT_IP_TYPE_DEFAULT:
    default:
        return MM_BEARER_IP_FAMILY_NONE;
    }
}

MbimContextIpType
mm_bearer_ip_family_to_mbim_context_ip_type (MMBearerIpFamily   ip_family,
                                             GError           **error)
{
    gchar *str;

    /* NOTE: the input is a BITMASK, so we try to find a "best match" */

    switch ((guint)ip_family) {
    case MM_BEARER_IP_FAMILY_IPV4:
        return MBIM_CONTEXT_IP_TYPE_IPV4;
    case MM_BEARER_IP_FAMILY_IPV6:
        return MBIM_CONTEXT_IP_TYPE_IPV6;
    case  MM_BEARER_IP_FAMILY_IPV4V6:
        return MBIM_CONTEXT_IP_TYPE_IPV4V6;
    case (MM_BEARER_IP_FAMILY_IPV4 | MM_BEARER_IP_FAMILY_IPV6):
        return MBIM_CONTEXT_IP_TYPE_IPV4_AND_IPV6;
    case MM_BEARER_IP_FAMILY_NONE:
    case MM_BEARER_IP_FAMILY_ANY:
        /* A valid default IP family should have been specified */
        g_assert_not_reached ();
    default:
        break;
    }

    str = mm_bearer_ip_family_build_string_from_mask (ip_family);
    g_set_error (error,
                 MM_CORE_ERROR,
                 MM_CORE_ERROR_UNSUPPORTED,
                 "Unsupported IP type configuration: '%s'",
                 str);
    g_free (str);
    return MBIM_CONTEXT_IP_TYPE_DEFAULT;
}

gchar *
mm_mbim_context_ip_type_get_printable (MbimContextIpType ip_type)
{
    const gchar *str;

    str = mbim_context_ip_type_get_string (ip_type);
    if (str)
        return g_strdup (str);

    return g_strdup_printf ("unknown (0x%x)", ip_type);
}

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

gboolean
mm_bearer_roaming_allowance_to_mbim_context_roaming_control (MMBearerRoamingAllowance    mask,
                                                             gpointer                    log_object,
                                                             MbimContextRoamingControl  *out_value,
                                                             GError                    **error)
{
    if (mask == MM_BEARER_ROAMING_ALLOWANCE_NONE) {
        mm_obj_dbg (log_object, "using default (all) roaming allowance");
        *out_value = MBIM_CONTEXT_ROAMING_CONTROL_ALLOW_ALL;
    } else if (mask == MM_BEARER_ROAMING_ALLOWANCE_HOME)
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_HOME_ONLY;
    else if (mask == MM_BEARER_ROAMING_ALLOWANCE_PARTNER)
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_PARTNER_ONLY;
    else if (mask == MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER)
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_NON_PARTNER_ONLY;
    else if (mask == (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_PARTNER))
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_HOME_AND_PARTNER;
    else if (mask == (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER))
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_HOME_AND_NON_PARTNER;
    else if (mask == (MM_BEARER_ROAMING_ALLOWANCE_PARTNER | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER))
        *out_value =MBIM_CONTEXT_ROAMING_CONTROL_PARTNER_AND_NON_PARTNER;
    else if (mask == (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_PARTNER | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER))
        *out_value = MBIM_CONTEXT_ROAMING_CONTROL_ALLOW_ALL;
    else {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported roaming allowance mask: 0x%x", mask);
        return FALSE;
    }

    return TRUE;
}

MMBearerRoamingAllowance
mm_bearer_roaming_allowance_from_mbim_context_roaming_control (MbimContextRoamingControl   value,
                                                               GError                    **error)
{
    switch (value) {
    case MBIM_CONTEXT_ROAMING_CONTROL_HOME_ONLY:
        return MM_BEARER_ROAMING_ALLOWANCE_HOME;
    case MBIM_CONTEXT_ROAMING_CONTROL_PARTNER_ONLY:
        return MM_BEARER_ROAMING_ALLOWANCE_PARTNER;
    case MBIM_CONTEXT_ROAMING_CONTROL_NON_PARTNER_ONLY:
        return MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER;
    case MBIM_CONTEXT_ROAMING_CONTROL_HOME_AND_PARTNER:
        return (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_PARTNER);
    case MBIM_CONTEXT_ROAMING_CONTROL_HOME_AND_NON_PARTNER:
        return (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER);
    case MBIM_CONTEXT_ROAMING_CONTROL_PARTNER_AND_NON_PARTNER:
        return (MM_BEARER_ROAMING_ALLOWANCE_PARTNER | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER);
    case MBIM_CONTEXT_ROAMING_CONTROL_ALLOW_ALL:
        return (MM_BEARER_ROAMING_ALLOWANCE_HOME | MM_BEARER_ROAMING_ALLOWANCE_PARTNER | MM_BEARER_ROAMING_ALLOWANCE_NON_PARTNER);
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported roaming control value: 0x%x", value);
        return MM_BEARER_ROAMING_ALLOWANCE_NONE;
    }
}

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

gboolean
mm_bearer_access_type_preference_to_mbim_context_media_type (MMBearerAccessTypePreference   value,
                                                             gpointer                       log_object,
                                                             MbimContextMediaType          *out_value,
                                                             GError                       **error)
{
    switch (value) {
    case MM_BEARER_ACCESS_TYPE_PREFERENCE_NONE:
        mm_obj_dbg (log_object, "using default (cellular only) context media type");
        *out_value = MBIM_CONTEXT_MEDIA_TYPE_CELLULAR_ONLY;
        return TRUE;
    case MM_BEARER_ACCESS_TYPE_PREFERENCE_3GPP_ONLY:
        *out_value = MBIM_CONTEXT_MEDIA_TYPE_CELLULAR_ONLY;
        return TRUE;
    case MM_BEARER_ACCESS_TYPE_PREFERENCE_3GPP_PREFERRED:
        *out_value = MBIM_CONTEXT_MEDIA_TYPE_ALL;
        return TRUE;
    case MM_BEARER_ACCESS_TYPE_PREFERENCE_NON_3GPP_ONLY:
        *out_value = MBIM_CONTEXT_MEDIA_TYPE_WIFI_ONLY;
        return TRUE;
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported roaming control value: 0x%x", value);
        return FALSE;
    }
}

gboolean
mm_bearer_access_type_preference_from_mbim_context_media_type (MbimContextMediaType           value,
                                                               MMBearerAccessTypePreference  *out_value,
                                                               GError                       **error)
{
    switch (value) {
    case MBIM_CONTEXT_MEDIA_TYPE_CELLULAR_ONLY:
        *out_value =  MM_BEARER_ACCESS_TYPE_PREFERENCE_3GPP_ONLY;
        return TRUE;
    case MBIM_CONTEXT_MEDIA_TYPE_WIFI_ONLY:
        *out_value = MM_BEARER_ACCESS_TYPE_PREFERENCE_NON_3GPP_ONLY;
        return TRUE;
    case MBIM_CONTEXT_MEDIA_TYPE_ALL:
        *out_value = MM_BEARER_ACCESS_TYPE_PREFERENCE_3GPP_PREFERRED;
        return TRUE;
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported roaming control value: 0x%x", value);
        return FALSE;
    }
}

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

gboolean
mm_boolean_from_mbim_context_state (MbimContextState   value,
                                    gboolean          *out_value,
                                    GError           **error)
{
    switch (value) {
    case MBIM_CONTEXT_STATE_DISABLED:
        *out_value = FALSE;
        return TRUE;
    case MBIM_CONTEXT_STATE_ENABLED:
        *out_value = TRUE;
        return TRUE;
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported context state value: 0x%x", value);
        return FALSE;
    }
}

MbimContextState
mm_boolean_to_mbim_context_state (gboolean value)
{
    return (value ? MBIM_CONTEXT_STATE_ENABLED: MBIM_CONTEXT_STATE_DISABLED);
}

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

MMBearerProfileSource
mm_bearer_profile_source_from_mbim_context_source (MbimContextSource   value,
                                                   GError            **error)
{
    switch (value) {
    case MBIM_CONTEXT_SOURCE_ADMIN:
        return MM_BEARER_PROFILE_SOURCE_ADMIN;
    case MBIM_CONTEXT_SOURCE_USER:
        return MM_BEARER_PROFILE_SOURCE_USER;
    case MBIM_CONTEXT_SOURCE_OPERATOR:
        return MM_BEARER_PROFILE_SOURCE_OPERATOR;
    case MBIM_CONTEXT_SOURCE_MODEM:
        return MM_BEARER_PROFILE_SOURCE_MODEM;
    case MBIM_CONTEXT_SOURCE_DEVICE:
        return MM_BEARER_PROFILE_SOURCE_DEVICE;
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported context source value: 0x%x", value);
        return MM_BEARER_PROFILE_SOURCE_UNKNOWN;
    }
}

gboolean
mm_bearer_profile_source_to_mbim_context_source (MMBearerProfileSource   value,
                                                 gpointer                log_object,
                                                 MbimContextSource      *out_value,
                                                 GError                **error)
{
    switch (value) {
    case MM_BEARER_PROFILE_SOURCE_UNKNOWN:
        mm_obj_dbg (log_object, "using default (admin) context source");
        *out_value = MBIM_CONTEXT_SOURCE_ADMIN;
        return TRUE;
    case MM_BEARER_PROFILE_SOURCE_ADMIN:
        *out_value = MBIM_CONTEXT_SOURCE_ADMIN;
        return TRUE;
    case MM_BEARER_PROFILE_SOURCE_USER:
        *out_value = MBIM_CONTEXT_SOURCE_USER;
        return TRUE;
    case MM_BEARER_PROFILE_SOURCE_OPERATOR:
        *out_value = MBIM_CONTEXT_SOURCE_OPERATOR;
        return TRUE;
    case MM_BEARER_PROFILE_SOURCE_MODEM:
        *out_value = MBIM_CONTEXT_SOURCE_MODEM;
        return TRUE;
    case MM_BEARER_PROFILE_SOURCE_DEVICE:
        *out_value = MBIM_CONTEXT_SOURCE_DEVICE;
        return TRUE;
    default:
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported profile source value: 0x%x", value);
        return FALSE;
    }
}

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

/* index in the array is the code point (8 possible values), and the actual
 * value is the lower limit of the error rate range. */
static const gdouble bit_error_rate_ranges[] =   { 0.00, 0.20, 0.40, 0.80, 1.60, 3.20, 6.40, 12.80 };
static const gdouble frame_error_rate_ranges[] = { 0.00, 0.01, 0.10, 0.50, 1.00, 2.00, 4.00,  8.00 };

gboolean
mm_signal_error_rate_percentage_from_coded_value (guint      coded_value,
                                                  gdouble   *out_percentage,
                                                  gboolean   is_gsm,
                                                  GError   **error)
{
    if ((is_gsm && (coded_value >= G_N_ELEMENTS (bit_error_rate_ranges))) ||
        (!is_gsm && (coded_value >= G_N_ELEMENTS (frame_error_rate_ranges)))) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "error rate coded value out of range: %u", coded_value);
        return FALSE;
    }

    *out_percentage = (is_gsm ? bit_error_rate_ranges[coded_value] : frame_error_rate_ranges[coded_value]);
    return TRUE;
}

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

gboolean
mm_signal_rssi_from_coded_value (guint      coded_value,
                                 gdouble   *out_rssi,
                                 GError   **error)
{
    /* expected values between 0 and 31 */
    if (coded_value > 31) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "rssi coded value out of range: %u", coded_value);
        return FALSE;
    }

    *out_rssi = (gdouble)coded_value - 113;
    return TRUE;
}

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

gboolean
mm_signal_rsrp_from_coded_value (guint     coded_value,
                                 gdouble  *out_rsrp,
                                 GError  **error)
{
    /* expected values between 0 and 126 */
    if (coded_value > 126) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "rsrp coded value out of range: %u", coded_value);
        return FALSE;
    }

    *out_rsrp = (gdouble)coded_value - 156;
    return TRUE;
}

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

gboolean
mm_signal_snr_from_coded_value (guint     coded_value,
                                gdouble  *out_snr,
                                GError  **error)
{
    /* expected values between 0 and 126 */
    if (coded_value > 127) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "snr coded value out of range: %u", coded_value);
        return FALSE;
    }

    *out_snr = ((gdouble)coded_value)/2 - 23;
    return TRUE;
}

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

MMModem3gppMicoMode
mm_modem_3gpp_mico_mode_from_mbim_mico_mode (MbimMicoMode mico_mode)
{
    switch (mico_mode) {
    case MBIM_MICO_MODE_DISABLED:
        return MM_MODEM_3GPP_MICO_MODE_DISABLED;
    case MBIM_MICO_MODE_ENABLED:
        return MM_MODEM_3GPP_MICO_MODE_ENABLED;
    case MBIM_MICO_MODE_UNSUPPORTED:
        return MM_MODEM_3GPP_MICO_MODE_UNSUPPORTED;
    case MBIM_MICO_MODE_DEFAULT:
        /* default expected only in set requests */
    default:
        return MM_MODEM_3GPP_MICO_MODE_UNKNOWN;
    }
}

MbimMicoMode
mm_modem_3gpp_mico_mode_to_mbim_mico_mode (MMModem3gppMicoMode mico_mode)
{
    switch (mico_mode) {
    case MM_MODEM_3GPP_MICO_MODE_DISABLED:
        return MBIM_MICO_MODE_DISABLED;
    case MM_MODEM_3GPP_MICO_MODE_ENABLED:
        return MBIM_MICO_MODE_ENABLED;
    case MM_MODEM_3GPP_MICO_MODE_UNSUPPORTED:
        return MBIM_MICO_MODE_UNSUPPORTED;
    case MM_MODEM_3GPP_MICO_MODE_UNKNOWN:
    default:
        return MBIM_MICO_MODE_DEFAULT;
    }
}

MMModem3gppDrxCycle
mm_modem_3gpp_drx_cycle_from_mbim_drx_cycle (MbimDrxCycle drx_cycle)
{
    switch (drx_cycle) {
    case MBIM_DRX_CYCLE_NOT_SUPPORTED:
        return MM_MODEM_3GPP_DRX_CYCLE_UNSUPPORTED;
    case MBIM_DRX_CYCLE_32:
        return MM_MODEM_3GPP_DRX_CYCLE_32;
    case MBIM_DRX_CYCLE_64:
        return MM_MODEM_3GPP_DRX_CYCLE_64;
    case MBIM_DRX_CYCLE_128:
        return MM_MODEM_3GPP_DRX_CYCLE_128;
    case MBIM_DRX_CYCLE_256:
        return MM_MODEM_3GPP_DRX_CYCLE_256;
    case MBIM_DRX_CYCLE_NOT_SPECIFIED:
    default:
        return MM_MODEM_3GPP_DRX_CYCLE_UNKNOWN;
    }
}

MbimDrxCycle
mm_modem_3gpp_drx_cycle_to_mbim_drx_cycle (MMModem3gppDrxCycle drx_cycle)
{
    switch (drx_cycle) {
    case MM_MODEM_3GPP_DRX_CYCLE_UNSUPPORTED:
        return MBIM_DRX_CYCLE_NOT_SUPPORTED;
    case MM_MODEM_3GPP_DRX_CYCLE_32:
        return MBIM_DRX_CYCLE_32;
    case MM_MODEM_3GPP_DRX_CYCLE_64:
        return MBIM_DRX_CYCLE_64;
    case MM_MODEM_3GPP_DRX_CYCLE_128:
        return MBIM_DRX_CYCLE_128;
    case MM_MODEM_3GPP_DRX_CYCLE_256:
        return MBIM_DRX_CYCLE_256;
    case MM_MODEM_3GPP_DRX_CYCLE_UNKNOWN:
    default:
        return MBIM_DRX_CYCLE_NOT_SPECIFIED;
    }
}

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

MMSmsState
mm_sms_state_from_mbim_message_status (MbimSmsStatus status)
{
    switch (status) {
    case MBIM_SMS_STATUS_NEW:
        return MM_SMS_STATE_RECEIVED;
    case MBIM_SMS_STATUS_OLD:
        return MM_SMS_STATE_RECEIVED;
    case MBIM_SMS_STATUS_DRAFT:
        return MM_SMS_STATE_STORED;
    case MBIM_SMS_STATUS_SENT:
        return MM_SMS_STATE_SENT;
    default:
        break;
    }

    return MM_SMS_STATE_UNKNOWN;
}

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

guint
mm_signal_quality_from_mbim_signal_state (guint                 rssi,
                                          MbimRsrpSnrInfoArray *rsrp_snr,
                                          guint32               rsrp_snr_count,
                                          gpointer              log_object)
{
    guint quality;

    /* When MBIMEx is enabled we may get RSSI unset, but per access technology
     * RSRP available. When more than one access technology in use (e.g. 4G+5G in
     * 5G NSA), take the highest RSRP value reported. */
    if (rssi == 99 && rsrp_snr && rsrp_snr_count) {
        guint i;
        gint  max_rsrp = G_MININT;

        for (i = 0; i < rsrp_snr_count; i++) {
            MbimRsrpSnrInfo  *info;

            info = rsrp_snr[i];
            /* scale the value to dBm */
            if (info->rsrp < 127) {
                gint rsrp;

                rsrp = -157 + info->rsrp;
                if (rsrp > max_rsrp)
                    max_rsrp = rsrp;
            }
        }
        quality = MM_RSRP_TO_QUALITY (max_rsrp);
        mm_obj_dbg (log_object, "signal state update: %ddBm --> %u%%", max_rsrp, quality);
    } else {
        /* Normalize the quality. 99 means unknown, we default it to 0 */
        quality = MM_CLAMP_HIGH (rssi == 99 ? 0 : rssi, 31) * 100 / 31;
        mm_obj_dbg (log_object, "signal state update: %u --> %u%%", rssi, quality);
    }

    return quality;
}

static MMSignal **
select_mbim_signal_with_data_class (MbimDataClass   data_class,
                                    MMSignal      **cdma,
                                    MMSignal      **evdo,
                                    MMSignal      **gsm,
                                    MMSignal      **umts,
                                    MMSignal      **lte,
                                    MMSignal      **nr5g)
{
    if (data_class & (MBIM_DATA_CLASS_5G_NSA |
                      MBIM_DATA_CLASS_5G_SA))
        return nr5g;
    if (data_class & (MBIM_DATA_CLASS_LTE))
        return lte;
    if (data_class & (MBIM_DATA_CLASS_UMTS |
                      MBIM_DATA_CLASS_HSDPA |
                      MBIM_DATA_CLASS_HSUPA))
        return umts;
    if (data_class & (MBIM_DATA_CLASS_GPRS |
                      MBIM_DATA_CLASS_EDGE))
        return gsm;
    if (data_class & (MBIM_DATA_CLASS_1XEVDO |
                      MBIM_DATA_CLASS_1XEVDO_REVA |
                      MBIM_DATA_CLASS_1XEVDV |
                      MBIM_DATA_CLASS_3XRTT |
                      MBIM_DATA_CLASS_1XEVDO_REVB))
        return evdo;
    if (data_class & MBIM_DATA_CLASS_1XRTT)
        return cdma;
    return NULL;
}

gboolean
mm_signal_from_mbim_signal_state (MbimDataClass          data_class,
                                  guint                  coded_rssi,
                                  guint                  coded_error_rate,
                                  MbimRsrpSnrInfoArray  *rsrp_snr,
                                  guint32                rsrp_snr_count,
                                  gpointer               log_object,
                                  MMSignal             **out_cdma,
                                  MMSignal             **out_evdo,
                                  MMSignal             **out_gsm,
                                  MMSignal             **out_umts,
                                  MMSignal             **out_lte,
                                  MMSignal             **out_nr5g)
{
    MMSignal **tmp;
    MMSignal **last_updated = NULL;
    guint      n_out_updated = 0;

    if (out_cdma)
        *out_cdma = NULL;
    if (out_evdo)
        *out_evdo = NULL;
    if (out_gsm)
        *out_gsm = NULL;
    if (out_umts)
        *out_umts = NULL;
    if (out_lte)
        *out_lte = NULL;
    if (out_nr5g)
        *out_nr5g = NULL;

    /* When MBIMEx v2.0 is available, we get LTE+5GNR information reported
     * in the RSRP/SNR list of items. */
    if (rsrp_snr && rsrp_snr_count) {
        guint i;

        for (i = 0; i < rsrp_snr_count; i++) {
            MbimRsrpSnrInfo *info;

            info = rsrp_snr[i];

            tmp = select_mbim_signal_with_data_class (info->system_type,
                                                      out_cdma, out_evdo,
                                                      out_gsm, out_umts, out_lte, out_nr5g);
            if (!tmp || ((info->rsrp == 0xFFFFFFFF) && (info->snr == 0xFFFFFFFF)))
                continue;

            last_updated = tmp;
            n_out_updated++;

            *tmp = mm_signal_new ();

            mm_signal_set_rsrp (*tmp, MM_SIGNAL_UNKNOWN);
            if (info->rsrp != 0xFFFFFFFF) {
                g_autoptr(GError) error = NULL;
                gdouble           rsrp;

                if (!mm_signal_rsrp_from_coded_value (info->rsrp, &rsrp, &error))
                    mm_obj_dbg (log_object, "couldn't convert RSRP coded value '%u': %s", info->rsrp, error->message);
                else
                    mm_signal_set_rsrp (*tmp, rsrp);
            }

            mm_signal_set_snr (*tmp, MM_SIGNAL_UNKNOWN);
            if (info->snr != 0xFFFFFFFF) {
                g_autoptr(GError) error = NULL;
                gdouble           snr;

                if (!mm_signal_snr_from_coded_value (info->snr, &snr, &error))
                    mm_obj_dbg (log_object, "couldn't convert SNR coded value '%u': %s", info->snr, error->message);
                else
                    mm_signal_set_snr (*tmp, snr);
            }
        }
    }

    /* The MBIM v1.0 details (RSSI, error rate) will only be set if
     * the target access technology is known without any doubt.
     * E.g. if we are in 5GNSA (4G+5G), we will only set the fields
     * if one of them has valid values. If both have valid values,
     * we'll skip updating RSSI and error rate, as we wouldn't know
     * to which of them applies. */
    if (n_out_updated > 1)
        return TRUE;

    if (n_out_updated == 0) {
        tmp = select_mbim_signal_with_data_class (data_class,
                                                  out_cdma, out_evdo,
                                                  out_gsm, out_umts, out_lte, out_nr5g);
        if (!tmp)
            return FALSE;
        *tmp = mm_signal_new ();
    } else {
        tmp = last_updated;
        g_assert (tmp && *tmp);
    }

    mm_signal_set_error_rate (*tmp, MM_SIGNAL_UNKNOWN);
    if (coded_error_rate != 99) {
        g_autoptr(GError) error = NULL;
        gdouble           error_rate;

        if (!mm_signal_error_rate_percentage_from_coded_value (coded_error_rate,
                                                               &error_rate,
                                                               data_class == (MBIM_DATA_CLASS_GPRS | MBIM_DATA_CLASS_EDGE),
                                                               &error))
            mm_obj_dbg (log_object, "couldn't convert error rate coded value '%u': %s", coded_error_rate, error->message);
        else
            mm_signal_set_error_rate (*tmp, error_rate);
    }

    mm_signal_set_rssi (*tmp, MM_SIGNAL_UNKNOWN);
    if (coded_rssi != 99) {
        g_autoptr(GError) error = NULL;
        gdouble           rssi;

        if (!mm_signal_rssi_from_coded_value (coded_rssi, &rssi, &error))
            mm_obj_dbg (log_object, "couldn't convert RSSI coded value '%u': %s", coded_rssi, error->message);
        else
            mm_signal_set_rssi (*tmp, rssi);
    }

    return TRUE;
}

gboolean
mm_signal_from_atds_signal_response (guint32    rssi,
                                     guint32    rscp,
                                     guint32    ecno,
                                     guint32    rsrq,
                                     guint32    rsrp,
                                     guint32    snr,
                                     MMSignal **out_gsm,
                                     MMSignal **out_umts,
                                     MMSignal **out_lte)
{

    if (rscp <= 96) {
        *out_umts = mm_signal_new ();
        mm_signal_set_rscp (*out_umts, -120.0 + rscp);
    }

    if (ecno <= 49) {
        if (!*out_umts)
            *out_umts = mm_signal_new ();
        mm_signal_set_ecio (*out_umts, -24.0 + ((gdouble) ecno / 2));
    }

    if (rsrq <= 34) {
        *out_lte = mm_signal_new ();
        mm_signal_set_rsrq (*out_lte, -19.5 + ((gdouble) rsrq / 2));
    }

    if (rsrp <= 97) {
        if (!*out_lte)
            *out_lte = mm_signal_new ();
        mm_signal_set_rsrp (*out_lte, -140.0 + rsrp);
    }

    if (snr <= 35) {
        if (!*out_lte)
            *out_lte = mm_signal_new ();
        mm_signal_set_snr (*out_lte, -5.0 + snr);
    }

    /* RSSI may be given for all 2G, 3G or 4G so we detect to which one applies */
    if (rssi <= 31) {
        gdouble value;

        value = -113.0 + (2 * rssi);
        if (*out_lte)
            mm_signal_set_rssi (*out_lte, value);
        else if (*out_umts)
            mm_signal_set_rssi (*out_umts, value);
        else {
            *out_gsm = mm_signal_new ();
            mm_signal_set_rssi (*out_gsm, value);
        }
    }

    if (!out_gsm && !out_umts && !out_lte) {
        return FALSE;
    }

    return TRUE;
}

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

void
mm_rf_info_free (MMRfInfo *rf_data)
{
    g_free (rf_data);
}

void
mm_rfim_info_list_free (GList *rfim_info_list)
{
    g_list_free_full (rfim_info_list, (GDestroyNotify) mm_rf_info_free);
}

GList *
mm_rfim_info_list_from_mbim_intel_rfim_frequency_value_array (MbimIntelRfimFrequencyValueArray *freq_info,
                                                              guint                             freq_count,
                                                              gpointer                          log_object)
{
    GList *info_list = NULL;
    guint i;

    for (i = 0; i < freq_count; i++) {
        MMRfInfo *info;

        /* If Cell info value indicates radio off, then other parameters are invalid.
         * So those data will be ignored. */
        if (freq_info[i]->serving_cell_info == MBIM_INTEL_SERVING_CELL_INFO_RADIO_OFF)
            continue;

        info = g_new0 (MMRfInfo, 1);
        info->serving_cell_type = MM_SERVING_CELL_TYPE_UNKNOWN;
        switch (freq_info[i]->serving_cell_info) {
            case MBIM_INTEL_SERVING_CELL_INFO_PCELL:
                info->serving_cell_type = MM_SERVING_CELL_TYPE_PCELL;
                break;
            case MBIM_INTEL_SERVING_CELL_INFO_SCELL:
                info->serving_cell_type = MM_SERVING_CELL_TYPE_SCELL;
                break;
            case MBIM_INTEL_SERVING_CELL_INFO_PSCELL:
                info->serving_cell_type = MM_SERVING_CELL_TYPE_PSCELL;
                break;
            case MBIM_INTEL_SERVING_CELL_INFO_SSCELL:
                info->serving_cell_type = MM_SERVING_CELL_TYPE_SSCELL;
                break;
            case MBIM_INTEL_SERVING_CELL_INFO_RADIO_OFF:
            default:
                info->serving_cell_type = MM_SERVING_CELL_TYPE_INVALID;
                break;
        }
        info->bandwidth = freq_info[i]->bandwidth;
        info->center_frequency = freq_info[i]->center_frequency;
        info_list = g_list_append (info_list, info);
    }

    return info_list;
}

typedef struct {
    guint8  band;
    gdouble fdl_low;
    guint32 n_offs_dl;
    guint32 range_dl1;
    guint32 range_dl2;
} LteDlRangeData;

static LteDlRangeData lte_dl_range_data [] = {
    { 1,  2110,      0,      0,   599 },
    { 2,  1930,    600,    600,  1199 },
    { 3,  1805,   1200,   1200,  1949 },
    { 4,  2110,   1950,   1950,  2399 },
    { 5,   869,   2400,   2400,  2649 },
    { 6,   875,   2650,   2650,  2749 },
    { 7,  2620,   2750,   2750,  3449 },
    { 8,   925,   3450,   3450,  3799 },
    { 9,  1844.9, 3800,   3800,  4149 },
    { 10, 2110,   4150,   4150,  4749 },
    { 11, 1475.9, 4750,   4750,  4949 },
    { 12,  728,   5000,   5000,  5179 },
    { 13,  746,   5180,   5180,  5279 },
    { 14,  758,   5280,   5280,  5379 },
    { 17,  734,   5730,   5730,  5849 },
    { 18,  860,   5850,   5850,  5999 },
    { 19,  875,   6000,   6000,  6149 },
    { 20,  791,   6150,   6150,  6449 },
    { 21, 1495.9, 6450,   6450,  6599 },
    { 33, 1900,   36000, 36000, 36199 },
    { 34, 2010,   36200, 36200, 36349 },
    { 35, 1850,   36350, 36350, 36949 },
    { 36, 1930,   36950, 36950, 37549 },
    { 37, 1910,   37550, 37550, 37749 },
    { 38, 2570,   37750, 37750, 38249 },
    { 39, 1880,   38250, 38250, 38649 },
    { 40, 2300,   38650, 38650, 39649 },
};

static gint
earfcn_to_band_index (guint32  earfcn,
                      gpointer log_object)
{
    guint i;

    for (i = 0; i < G_N_ELEMENTS (lte_dl_range_data); i++) {
        if (lte_dl_range_data[i].range_dl1 <= earfcn && lte_dl_range_data[i].range_dl2 >= earfcn) {
            mm_obj_dbg (log_object, "found matching band index %u for earfcn %u", i, earfcn);
            return i;
        }
    }
    mm_obj_dbg (log_object, "earfcn %u not matched to any band index", earfcn);
    return -1;
}

gdouble
mm_earfcn_to_frequency (guint32  earfcn,
                        gpointer log_object)
{
    gint i;

    i = earfcn_to_band_index (earfcn, log_object);
    if (i < 0)
        return 0.0;

    return 1.0e6 * (lte_dl_range_data[i].fdl_low + 0.1 * (earfcn - lte_dl_range_data[i].n_offs_dl));
}

typedef struct {
    guint global_khz;
    guint range_offset;
    guint nrarfcn_offset;
    guint range_first;
    guint range_last;
} NrRangeData ;

static NrRangeData nr_range_data [] = {
    { 5,         0,       0,       0,  599999 },
    { 15,  3000000,  600000,  600000, 2016666 },
    { 60, 24250080, 2016667, 2016667, 3279165 },
};

static gint
nrarfcn_to_range_index (guint32  nrarfcn,
                        gpointer log_object)
{
    guint i;

    for (i = 0; i < G_N_ELEMENTS (nr_range_data); i++) {
        if (nr_range_data[i].range_first <= nrarfcn &&  nr_range_data[i].range_last >= nrarfcn) {
            mm_obj_dbg (log_object, "found matching range index %u for nrarfcn %u", i, nrarfcn);
            return i;
        }
    }
    mm_obj_dbg (log_object, "nrarfcn %u not matched to any range index", nrarfcn);
    return -1;
}

gdouble
mm_nrarfcn_to_frequency (guint32  nrarfcn,
                         gpointer log_object)
{
    gint i;

    i = nrarfcn_to_range_index (nrarfcn, log_object);
    if (i < 0)
        return 0.0;

    return 1.0e3 * (nr_range_data[i].range_offset + nr_range_data[i].global_khz * (nrarfcn - nr_range_data[i].nrarfcn_offset));
}

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

static const MMMobileEquipmentError mbim_nw_errors[] = {
    [MBIM_NW_ERROR_IMSI_UNKNOWN_IN_HLR] = MM_MOBILE_EQUIPMENT_ERROR_IMSI_UNKNOWN_IN_HSS,
    [MBIM_NW_ERROR_ILLEGAL_MS] = MM_MOBILE_EQUIPMENT_ERROR_ILLEGAL_UE,
    [MBIM_NW_ERROR_IMSI_UNKNOWN_IN_VLR] = MM_MOBILE_EQUIPMENT_ERROR_IMSI_UNKNOWN_IN_VLR,
    [MBIM_NW_ERROR_ILLEGAL_ME] = MM_MOBILE_EQUIPMENT_ERROR_ILLEGAL_ME,
    [MBIM_NW_ERROR_GPRS_NOT_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_PS_SERVICES_NOT_ALLOWED,
    [MBIM_NW_ERROR_GPRS_AND_NON_GPRS_NOT_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_PS_AND_NON_PS_SERVICES_NOT_ALLOWED,
    [MBIM_NW_ERROR_PLMN_NOT_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_PLMN_NOT_ALLOWED,
    [MBIM_NW_ERROR_LOCATION_AREA_NOT_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_AREA_NOT_ALLOWED,
    [MBIM_NW_ERROR_ROAMING_NOT_ALLOWED_IN_LOCATION_AREA] = MM_MOBILE_EQUIPMENT_ERROR_ROAMING_NOT_ALLOWED_IN_AREA,
    [MBIM_NW_ERROR_GPRS_NOT_ALLOWED_IN_PLMN] = MM_MOBILE_EQUIPMENT_ERROR_PS_SERVICES_NOT_ALLOWED_IN_PLMN,
    [MBIM_NW_ERROR_NO_CELLS_IN_LOCATION_AREA] = MM_MOBILE_EQUIPMENT_ERROR_NO_CELLS_IN_AREA,
    [MBIM_NW_ERROR_NETWORK_FAILURE] = MM_MOBILE_EQUIPMENT_ERROR_NETWORK_FAILURE_ATTACH,
    [MBIM_NW_ERROR_CONGESTION] = MM_MOBILE_EQUIPMENT_ERROR_CONGESTION,
    [MBIM_NW_ERROR_GSM_AUTHENTICATION_UNACCEPTABLE] = MM_MOBILE_EQUIPMENT_ERROR_USER_AUTHENTICATION_FAILED,
    [MBIM_NW_ERROR_NOT_AUTHORIZED_FOR_CSG] = MM_MOBILE_EQUIPMENT_ERROR_NOT_AUTHORIZED_FOR_CSG,
    [MBIM_NW_ERROR_INSUFFICIENT_RESOURCES] = MM_MOBILE_EQUIPMENT_ERROR_INSUFFICIENT_RESOURCES,
    [MBIM_NW_ERROR_MISSING_OR_UNKNOWN_APN] = MM_MOBILE_EQUIPMENT_ERROR_MISSING_OR_UNKNOWN_APN,
    [MBIM_NW_ERROR_UNKNOWN_PDP_ADDRESS_OR_TYPE] = MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN_PDP_ADDRESS_OR_TYPE,
    [MBIM_NW_ERROR_USER_AUTHENTICATION_FAILED] = MM_MOBILE_EQUIPMENT_ERROR_USER_AUTHENTICATION_FAILED,
    [MBIM_NW_ERROR_ACTIVATION_REJECTED_BY_GGSN_OR_GW] = MM_MOBILE_EQUIPMENT_ERROR_ACTIVATION_REJECTED_BY_GGSN_OR_GW,
    [MBIM_NW_ERROR_ACTIVATION_REJECTED_UNSPECIFIED] = MM_MOBILE_EQUIPMENT_ERROR_ACTIVATION_REJECTED_UNSPECIFIED,
    [MBIM_NW_ERROR_SERVICE_OPTION_NOT_SUPPORTED] = MM_MOBILE_EQUIPMENT_ERROR_SERVICE_OPTION_NOT_SUPPORTED,
    [MBIM_NW_ERROR_REQUESTED_SERVICE_OPTION_NOT_SUBSCRIBED] = MM_MOBILE_EQUIPMENT_ERROR_SERVICE_OPTION_NOT_SUBSCRIBED,
    [MBIM_NW_ERROR_SERVICE_OPTION_TEMPORARILY_OUT_OF_ORDER] = MM_MOBILE_EQUIPMENT_ERROR_SERVICE_OPTION_OUT_OF_ORDER,
    [MBIM_NW_ERROR_MAXIMUM_NUMBER_OF_PDP_CONTEXTS_REACHED] = MM_MOBILE_EQUIPMENT_ERROR_MAXIMUM_NUMBER_OF_BEARERS_REACHED,
    [MBIM_NW_ERROR_REQUESTED_APN_NOT_SUPPORTED_IN_CURRENT_RAT_AND_PLMN] = MM_MOBILE_EQUIPMENT_ERROR_REQUESTED_APN_NOT_SUPPORTED,
    [MBIM_NW_ERROR_SEMANTICALLY_INCORRECT_MESSAGE] = MM_MOBILE_EQUIPMENT_ERROR_SEMANTICALLY_INCORRECT_MESSAGE,
    [MBIM_NW_ERROR_PROTOCOL_ERROR_UNSPECIFIED] = MM_MOBILE_EQUIPMENT_ERROR_UNSPECIFIED_PROTOCOL_ERROR,
    [MBIM_NW_ERROR_IMEI_NOT_ACCEPTED] = MM_MOBILE_EQUIPMENT_ERROR_IMEI_NOT_ACCEPTED,
    [MBIM_NW_ERROR_MS_IDENTITY_NOT_DERIVED_BY_NETWORK] = MM_MOBILE_EQUIPMENT_ERROR_UE_IDENTITY_NOT_DERIVED_FROM_NETWORK,
    [MBIM_NW_ERROR_IMPLICITLY_DETACHED] = MM_MOBILE_EQUIPMENT_ERROR_IMPLICITLY_DETACHED,
    [MBIM_NW_ERROR_MSC_TEMPORARILY_NOT_REACHABLE] = MM_MOBILE_EQUIPMENT_ERROR_MSC_TEMPORARILY_NOT_REACHABLE,
    [MBIM_NW_ERROR_NO_PDP_CONTEXT_ACTIVATED] = MM_MOBILE_EQUIPMENT_ERROR_NO_BEARER_ACTIVATED,
    [MBIM_NW_ERROR_PDP_TYPE_IPV4_ONLY_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_IPV4_ONLY_ALLOWED,
    [MBIM_NW_ERROR_PDP_TYPE_IPV6_ONLY_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_IPV6_ONLY_ALLOWED,
    [MBIM_NW_ERROR_INVALID_MANDATORY_INFORMATION] = MM_MOBILE_EQUIPMENT_ERROR_INVALID_MANDATORY_INFORMATION,
    [MBIM_NW_ERROR_MESSAGE_TYPE_NON_EXISTENT_OR_NOT_IMPLEMENTED] = MM_MOBILE_EQUIPMENT_ERROR_MESSAGE_TYPE_NOT_IMPLEMENTED,
    [MBIM_NW_ERROR_MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE] = MM_MOBILE_EQUIPMENT_ERROR_MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE,
    [MBIM_NW_ERROR_INFORMATION_ELEMENT_NON_EXISTENT_OR_NOT_IMPLEMENTED] = MM_MOBILE_EQUIPMENT_ERROR_IE_NOT_IMPLEMENTED,
    [MBIM_NW_ERROR_CONDITIONAL_IE_ERROR] = MM_MOBILE_EQUIPMENT_ERROR_CONDITIONAL_IE_ERROR,
    [MBIM_NW_ERROR_MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE] = MM_MOBILE_EQUIPMENT_ERROR_MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE,
    [MBIM_NW_ERROR_APN_RESTRICTION_VALUE_INCOMPATIBLE_WITH_ACTIVE_PDP_CONTEXT] = MM_MOBILE_EQUIPMENT_ERROR_APN_RESTRICTION_INCOMPATIBLE,
    [MBIM_NW_ERROR_MULTIPLE_ACCESSES_TO_A_PDN_CONNECTION_NOT_ALLOWED] = MM_MOBILE_EQUIPMENT_ERROR_MULTIPLE_ACCESS_TO_PDN_CONNECTION_NOT_ALLOWED,
    [MBIM_NW_ERROR_NONE] = MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN,
    /* known unmapped errors */
    /* MBIM_NW_ERROR_MAC_FAILURE */
    /* MBIM_NW_ERROR_SYNCH_FAILURE */
};

GError *
mm_error_from_mbim_nw_error (MbimNwError nw_error,
                             gpointer    log_object)
{
    const gchar *msg;

    if (nw_error < G_N_ELEMENTS (mbim_nw_errors)) {
        MMMobileEquipmentError  error_code;

        /* convert to mobile equipment error */
        error_code = mbim_nw_errors[nw_error];
        if (error_code)
            return mm_mobile_equipment_error_for_code (error_code, log_object);

        /* provide a nicer error message on unmapped errors */
        msg = mbim_nw_error_get_string (nw_error);
        if (msg)
            return g_error_new (MM_MOBILE_EQUIPMENT_ERROR,
                                MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN,
                                "Unsupported error (%u): %s",
                                nw_error, msg);
    }

    /* fallback */
    return g_error_new_literal (MM_MOBILE_EQUIPMENT_ERROR,
                                MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN,
                                "Unknown error");
}

static const MMNetworkError mbim_mm_nw_errors[] = {
    [MBIM_NW_ERROR_NONE] = MM_NETWORK_ERROR_NONE,
    [MBIM_NW_ERROR_IMSI_UNKNOWN_IN_HLR] = MM_NETWORK_ERROR_IMSI_UNKNOWN_IN_HLR,
    [MBIM_NW_ERROR_ILLEGAL_MS] = MM_NETWORK_ERROR_ILLEGAL_MS,
    [MBIM_NW_ERROR_IMSI_UNKNOWN_IN_VLR] = MM_NETWORK_ERROR_IMSI_UNKNOWN_IN_VLR,
    [MBIM_NW_ERROR_IMEI_NOT_ACCEPTED] = MM_NETWORK_ERROR_IMEI_NOT_ACCEPTED,
    [MBIM_NW_ERROR_ILLEGAL_ME] = MM_NETWORK_ERROR_ILLEGAL_ME,
    [MBIM_NW_ERROR_GPRS_NOT_ALLOWED] = MM_NETWORK_ERROR_GPRS_NOT_ALLOWED,
    [MBIM_NW_ERROR_GPRS_AND_NON_GPRS_NOT_ALLOWED] = MM_NETWORK_ERROR_GPRS_AND_NON_GPRS_NOT_ALLOWED,
    [MBIM_NW_ERROR_MS_IDENTITY_NOT_DERIVED_BY_NETWORK] = MM_NETWORK_ERROR_MS_IDENTITY_NOT_DERIVED_BY_NETWORK,
    [MBIM_NW_ERROR_IMPLICITLY_DETACHED] = MM_NETWORK_ERROR_IMPLICITLY_DETACHED,
    [MBIM_NW_ERROR_PLMN_NOT_ALLOWED] = MM_NETWORK_ERROR_PLMN_NOT_ALLOWED,
    [MBIM_NW_ERROR_LOCATION_AREA_NOT_ALLOWED] = MM_NETWORK_ERROR_LOCATION_AREA_NOT_ALLOWED,
    [MBIM_NW_ERROR_ROAMING_NOT_ALLOWED_IN_LOCATION_AREA] = MM_NETWORK_ERROR_ROAMING_NOT_ALLOWED_IN_LOCATION_AREA,
    [MBIM_NW_ERROR_GPRS_NOT_ALLOWED_IN_PLMN] = MM_NETWORK_ERROR_GPRS_NOT_ALLOWED_IN_PLMN,
    [MBIM_NW_ERROR_NO_CELLS_IN_LOCATION_AREA] = MM_NETWORK_ERROR_NO_CELLS_IN_LOCATION_AREA,
    [MBIM_NW_ERROR_MSC_TEMPORARILY_NOT_REACHABLE] = MM_NETWORK_ERROR_MSC_TEMPORARILY_NOT_REACHABLE,
    [MBIM_NW_ERROR_NETWORK_FAILURE] = MM_NETWORK_ERROR_NETWORK_FAILURE,
    [MBIM_NW_ERROR_MAC_FAILURE] = MM_NETWORK_ERROR_MAC_FAILURE,
    [MBIM_NW_ERROR_SYNCH_FAILURE] = MM_NETWORK_ERROR_SYNCH_FAILURE,
    [MBIM_NW_ERROR_CONGESTION] = MM_NETWORK_ERROR_CONGESTION,
    [MBIM_NW_ERROR_GSM_AUTHENTICATION_UNACCEPTABLE] = MM_NETWORK_ERROR_GSM_AUTHENTICATION_UNACCEPTABLE,
    [MBIM_NW_ERROR_NOT_AUTHORIZED_FOR_CSG] = MM_NETWORK_ERROR_NOT_AUTHORIZED_FOR_CSG,
    [MBIM_NW_ERROR_INSUFFICIENT_RESOURCES] = MM_NETWORK_ERROR_INSUFFICIENT_RESOURCES,
    [MBIM_NW_ERROR_MISSING_OR_UNKNOWN_APN] = MM_NETWORK_ERROR_MISSING_OR_UNKNOWN_APN,
    [MBIM_NW_ERROR_UNKNOWN_PDP_ADDRESS_OR_TYPE] = MM_NETWORK_ERROR_UNKNOWN_PDP_ADDRESS_OR_TYPE,
    [MBIM_NW_ERROR_USER_AUTHENTICATION_FAILED] = MM_NETWORK_ERROR_USER_AUTHENTICATION_FAILED,
    [MBIM_NW_ERROR_ACTIVATION_REJECTED_BY_GGSN_OR_GW] = MM_NETWORK_ERROR_ACTIVATION_REJECTED_BY_GGSN_OR_GW,
    [MBIM_NW_ERROR_ACTIVATION_REJECTED_UNSPECIFIED] = MM_NETWORK_ERROR_REDIRECTION_TO_5GCN_REQUIRED,
    [MBIM_NW_ERROR_SERVICE_OPTION_NOT_SUPPORTED] = MM_NETWORK_ERROR_SERVICE_OPTION_NOT_SUPPORTED,
    [MBIM_NW_ERROR_REQUESTED_SERVICE_OPTION_NOT_SUBSCRIBED] = MM_NETWORK_ERROR_REQUESTED_SERVICE_OPTION_NOT_SUBSCRIBED,
    [MBIM_NW_ERROR_SERVICE_OPTION_TEMPORARILY_OUT_OF_ORDER] = MM_NETWORK_ERROR_SERVICE_OPTION_TEMPORARILY_OUT_OF_ORDER,
    [MBIM_NW_ERROR_NO_PDP_CONTEXT_ACTIVATED] = MM_NETWORK_ERROR_NO_PDP_CONTEXT_ACTIVATED,
    [MBIM_NW_ERROR_SEMANTIC_ERROR_IN_THE_TFT_OPERATION] = MM_NETWORK_ERROR_SEMANTIC_ERROR_IN_THE_TFT_OPERATION,
    [MBIM_NW_ERROR_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION] = MM_NETWORK_ERROR_SYNTACTICAL_ERROR_IN_THE_TFT_OPERATION,
    [MBIM_NW_ERROR_UNKNOWN_PDP_CONTEXT] = MM_NETWORK_ERROR_UNKNOWN_PDP_CONTEXT,
    [MBIM_NW_ERROR_SEMANTIC_ERRORS_IN_PACKET_FILTER] = MM_NETWORK_ERROR_SEMANTIC_ERRORS_IN_PACKET_FILTER,
    [MBIM_NW_ERROR_SYNTACTICAL_ERRORS_IN_PACKET_FILTER] = MM_NETWORK_ERROR_SYNTACTICAL_ERRORS_IN_PACKET_FILTER,
    [MBIM_NW_ERROR_PDP_CONTEXT_WITHOUT_TFT_ALREADY_ACTIVATED] = MM_NETWORK_ERROR_PDP_CONTEXT_WITHOUT_TFT_ALREADY_ACTIVATED,
    [MBIM_NW_ERROR_REQUEST_REJECTED_OR_BEARER_CONTROL_MODE_VIOLATION] = MM_NETWORK_ERROR_REQUEST_REJECTED_OR_BEARER_CONTROL_MODE_VIOLATION,
    [MBIM_NW_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED] = MM_NETWORK_ERROR_LAST_PDN_DISCONNECTION_NOT_ALLOWED,
    [MBIM_NW_ERROR_PDP_TYPE_IPV4_ONLY_ALLOWED] = MM_NETWORK_ERROR_PDP_TYPE_IPV4_ONLY_ALLOWED,
    [MBIM_NW_ERROR_PDP_TYPE_IPV6_ONLY_ALLOWED] = MM_NETWORK_ERROR_PDP_TYPE_IPV6_ONLY_ALLOWED,
    [MBIM_NW_ERROR_NO_NETWORK_SLICES_AVAILABLE] = MM_NETWORK_ERROR_NO_NETWORK_SLICES_AVAILABLE,
    [MBIM_NW_ERROR_MAXIMUM_NUMBER_OF_PDP_CONTEXTS_REACHED] = MM_NETWORK_ERROR_MAXIMUM_NUMBER_OF_PDP_CONTEXTS_REACHED,
    [MBIM_NW_ERROR_REQUESTED_APN_NOT_SUPPORTED_IN_CURRENT_RAT_AND_PLMN] = MM_NETWORK_ERROR_REQUESTED_APN_NOT_SUPPORTED_IN_CURRENT_RAT_AND_PLMN,
    [MBIM_NW_ERROR_INSUFFICIENT_RESOURCES_FOR_SPECIFIC_SLICE_AND_DNN] = MM_NETWORK_ERROR_INSUFFICIENT_RESOURCES_FOR_SPECIFIC_SLICE_AND_DNN,
    [MBIM_NW_ERROR_INSUFFICIENT_RESOURCES_FOR_SPECIFIC_SLICE] = MM_NETWORK_ERROR_INSUFFICIENT_RESOURCES_FOR_SPECIFIC_SLICE,
    [MBIM_NW_ERROR_NGKSI_ALREADY_IN_USE] = MM_NETWORK_ERROR_NGKSI_ALREADY_IN_USE,
    [MBIM_NW_ERROR_NON_3GPP_ACCESS_TO_5GCN_NOT_ALLOWED] = MM_NETWORK_ERROR_NON_3GPP_ACCESS_TO_5GCN_NOT_ALLOWED,
    [MBIM_NW_ERROR_SERVING_NETWORK_NOT_AUTHORIZED] = MM_NETWORK_ERROR_SERVING_NETWORK_NOT_AUTHORIZED,
    [MBIM_NW_ERROR_TEMPORARILY_NOT_AUTHORIZED_FOR_THIS_SNPN] = MM_NETWORK_ERROR_TEMPORARILY_NOT_AUTHORIZED_FOR_THIS_SNPN,
    [MBIM_NW_ERROR_PERMANENTLY_NOT_AUTHORIZED_FOR_THIS_SNPN] = MM_NETWORK_ERROR_PERMANENTLY_NOT_AUTHORIZED_FOR_THIS_SNPN,
    [MBIM_NW_ERROR_NOT_AUTHORIZED_FOR_THIS_CAG_OR_AUTHORIZED_FOR_CAG_CELLS_ONLY] = MM_NETWORK_ERROR_NOT_AUTHORIZED_FOR_THIS_CAG_OR_AUTHORIZED_FOR_CAG_CELLS_ONLY,
    [MBIM_NW_ERROR_WIRELINE_ACCESS_AREA_NOT_ALLOWED] = MM_NETWORK_ERROR_WIRELINE_ACCESS_AREA_NOT_ALLOWED,
    [MBIM_NW_ERROR_PAYLOAD_WAS_NOT_FORWARDED] = MM_NETWORK_ERROR_PAYLOAD_WAS_NOT_FORWARDED,
    [MBIM_NW_ERROR_DNN_NOT_SUPPORTED_OR_NOT_SUBSCRIBED_IN_THE_SLICE] = MM_NETWORK_ERROR_DNN_NOT_SUPPORTED_OR_NOT_SUBSCRIBED_IN_THE_SLICE,
    [MBIM_NW_ERROR_INSUFFICIENT_USER_PLANE_RESOURCES_FOR_THE_PDU_SESSION] = MM_NETWORK_ERROR_INSUFFICIENT_USER_PLANE_RESOURCES_FOR_THE_PDU_SESSION,
    [MBIM_NW_ERROR_SEMANTICALLY_INCORRECT_MESSAGE] = MM_NETWORK_ERROR_SEMANTICALLY_INCORRECT_MESSAGE,
    [MBIM_NW_ERROR_INVALID_MANDATORY_INFORMATION] = MM_NETWORK_ERROR_INVALID_MANDATORY_INFORMATION,
    [MBIM_NW_ERROR_MESSAGE_TYPE_NON_EXISTENT_OR_NOT_IMPLEMENTED] = MM_NETWORK_ERROR_MESSAGE_TYPE_NON_EXISTENT_OR_NOT_IMPLEMENTED,
    [MBIM_NW_ERROR_MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE] = MM_NETWORK_ERROR_MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE,
    [MBIM_NW_ERROR_INFORMATION_ELEMENT_NON_EXISTENT_OR_NOT_IMPLEMENTED] = MM_NETWORK_ERROR_INFORMATION_ELEMENT_NON_EXISTENT_OR_NOT_IMPLEMENTED,
    [MBIM_NW_ERROR_CONDITIONAL_IE_ERROR] = MM_NETWORK_ERROR_CONDITIONAL_IE_ERROR,
    [MBIM_NW_ERROR_MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE] = MM_NETWORK_ERROR_MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE,
    [MBIM_NW_ERROR_PROTOCOL_ERROR_UNSPECIFIED] = MM_NETWORK_ERROR_PROTOCOL_ERROR_UNSPECIFIED,
    [MBIM_NW_ERROR_APN_RESTRICTION_VALUE_INCOMPATIBLE_WITH_ACTIVE_PDP_CONTEXT] = MM_NETWORK_ERROR_APN_RESTRICTION_VALUE_INCOMPATIBLE_WITH_ACTIVE_PDP_CONTEXT,
    [MBIM_NW_ERROR_MULTIPLE_ACCESSES_TO_A_PDN_CONNECTION_NOT_ALLOWED] = MM_NETWORK_ERROR_MULTIPLE_ACCESSES_TO_A_PDN_CONNECTION_NOT_ALLOWED,
};

MMNetworkError
mm_modem_nw_error_from_mbim_nw_error (MbimNwError nw_error)
{
    if (nw_error < G_N_ELEMENTS (mbim_mm_nw_errors)) {
        /* convert to nw error */
        return mbim_mm_nw_errors[nw_error];
    }

    /* fallback */
    return MM_NETWORK_ERROR_UNKNOWN;
}

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

void
mm_register_mbim_errors (void)
{
    static gsize mbim_errors_registered = 0;

    if (!g_once_init_enter (&mbim_errors_registered))
        return;

    /* MBIM core errors */
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_FAILED,             MM_CORE_ERROR, MM_CORE_ERROR_FAILED);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_WRONG_STATE,        MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_TIMEOUT,            MM_CORE_ERROR, MM_CORE_ERROR_TIMEOUT);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_INVALID_ARGS,       MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_INVALID_MESSAGE,    MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_UNSUPPORTED,        MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_ABORTED,            MM_CORE_ERROR, MM_CORE_ERROR_ABORTED);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_UNKNOWN_STATE,      MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE);
    mm_register_error_mapping (MBIM_CORE_ERROR, MBIM_CORE_ERROR_INCOMPLETE_MESSAGE, MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);

    /* MBIM protocol errors */
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_INVALID,                  MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_TIMEOUT_FRAGMENT,         MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_FRAGMENT_OUT_OF_SEQUENCE, MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_LENGTH_MISMATCH,          MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_DUPLICATED_TID,           MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_NOT_OPENED,               MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_UNKNOWN,                  MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_CANCEL,                   MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);
    mm_register_error_mapping (MBIM_PROTOCOL_ERROR, MBIM_PROTOCOL_ERROR_MAX_TRANSFER,             MM_CORE_ERROR, MM_CORE_ERROR_PROTOCOL);

    /* MBIM status errors */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_BUSY,                             MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE,                          MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_PHONE_FAILURE);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SIM_NOT_INSERTED,                 MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_BAD_SIM,                          MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PIN_REQUIRED,                     MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_SIM_PIN);
    /* MBIM_STATUS_ERROR_PIN_DISABLED */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NOT_REGISTERED,                   MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PROVIDERS_NOT_FOUND,              MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NO_DEVICE_SUPPORT,                MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PROVIDER_NOT_VISIBLE,             MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_NETWORK);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_DATA_CLASS_NOT_AVAILABLE,         MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INVALID_MOBILE_CLASS);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PACKET_SERVICE_DETACHED,          MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_MAX_ACTIVATED_CONTEXTS,           MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NOT_INITIALIZED,                  MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_CONNECTION);
    /* MBIM_STATUS_ERROR_VOICE_CALL_IN_PROGRESS */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_CONTEXT_NOT_ACTIVATED,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_BEARER_ACTIVATED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SERVICE_NOT_ACTIVATED,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NO_BEARER_ACTIVATED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_ACCESS_STRING,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_MISSING_OR_UNKNOWN_APN);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_USER_NAME_PWD,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INCORRECT_PASSWORD);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_RADIO_POWER_OFF,                  MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_PARAMETERS,               MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INVALID_MANDATORY_INFORMATION);
    /* MBIM_STATUS_ERROR_READ_FAILURE */
    /* MBIM_STATUS_ERROR_WRITE_FAILURE */
    /* MBIM_STATUS_ERROR_NO_PHONEBOOK */
    /* MBIM_STATUS_ERROR_PARAMETER_TOO_LONG */
    /* MBIM_STATUS_ERROR_STK_BUSY */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_OPERATION_NOT_ALLOWED,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_ALLOWED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_MEMORY_FAILURE,                   MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FAILURE);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_MEMORY_INDEX,             MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_INVALID_INDEX);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_MEMORY_FULL,                      MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_MEMORY_FULL);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FILTER_NOT_SUPPORTED,             MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED);
    /* MBIM_STATUS_ERROR_DSS_INSTANCE_LIMIT */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_DEVICE_SERVICE_OPERATION, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_AUTH_INCORRECT_AUTN,              MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_USER_AUTHENTICATION_FAILED);
    /* MBIM_STATUS_ERROR_AUTH_SYNC_FAILURE */
    /* MBIM_STATUS_ERROR_AUTH_AMF_NOT_SET */
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_CONTEXT_NOT_SUPPORTED,            MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED);

    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SMS_UNKNOWN_SMSC_ADDRESS,         MM_MESSAGE_ERROR,          MM_MESSAGE_ERROR_SMSC_ADDRESS_UNKNOWN);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SMS_NETWORK_TIMEOUT,              MM_MESSAGE_ERROR,          MM_MESSAGE_ERROR_NETWORK_TIMEOUT);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SMS_LANG_NOT_SUPPORTED,           MM_MESSAGE_ERROR,          MM_MESSAGE_ERROR_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SMS_ENCODING_NOT_SUPPORTED,       MM_MESSAGE_ERROR,          MM_MESSAGE_ERROR_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SMS_FORMAT_NOT_SUPPORTED,         MM_MESSAGE_ERROR,          MM_MESSAGE_ERROR_NOT_SUPPORTED);

    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_SIGNATURE,                 MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_INVALID_SIGNATURE);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_IMEI,                      MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_INVALID_IMEI);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_INVALID_TIMESTAMP,                 MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_INVALID_TIMESTAMP);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NETWORK_LIST_TOO_LARGE,            MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_NETWORK_LIST_TOO_LARGE);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_SIGNATURE_ALGORITHM_NOT_SUPPORTED, MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_SIGNATURE_ALGORITHM_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FEATURE_NOT_SUPPORTED,             MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_FEATURE_NOT_SUPPORTED);
    mm_register_error_mapping (MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_DECODE_OR_PARSING_ERROR,           MM_CARRIER_LOCK_ERROR,    MM_CARRIER_LOCK_ERROR_DECODE_OR_PARSING_ERROR);

    g_once_init_leave (&mbim_errors_registered, 1);
}
