/* -*- 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 <config.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>

#include "mm-modem-helpers-mbim.h"
#include "mm-broadband-modem-mbim.h"
#include "mm-bearer-mbim.h"
#include "mm-sim-mbim.h"
#include "mm-sms-mbim.h"

#include "ModemManager.h"
#include <ModemManager-tags.h>
#include "mm-context.h"
#include "mm-log-object.h"
#include "mm-errors-types.h"
#include "mm-error-helpers.h"
#include "mm-modem-helpers.h"
#include "mm-bearer-list.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-3gpp-profile-manager.h"
#include "mm-iface-modem-3gpp-ussd.h"
#include "mm-iface-modem-location.h"
#include "mm-iface-modem-firmware.h"
#include "mm-iface-modem-messaging.h"
#include "mm-iface-modem-signal.h"
#include "mm-iface-modem-sar.h"
#include "mm-sms-part-3gpp.h"

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
# include <libqmi-glib.h>
# include "mm-shared-qmi.h"
#endif

static void iface_modem_init                      (MMIfaceModemInterface                   *iface);
static void iface_modem_3gpp_init                 (MMIfaceModem3gppInterface               *iface);
static void iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManagerInterface *iface);
static void iface_modem_3gpp_ussd_init            (MMIfaceModem3gppUssdInterface           *iface);
static void iface_modem_location_init             (MMIfaceModemLocationInterface           *iface);
static void iface_modem_firmware_init             (MMIfaceModemFirmwareInterface           *iface);
static void iface_modem_messaging_init            (MMIfaceModemMessagingInterface          *iface);
static void iface_modem_signal_init               (MMIfaceModemSignalInterface             *iface);
static void iface_modem_sar_init                  (MMIfaceModemSarInterface                *iface);
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
static void shared_qmi_init                       (MMSharedQmi                             *iface);
#endif

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
static MMIfaceModemLocationInterface  *iface_modem_location_parent;
#endif
static MMIfaceModemSignalInterface    *iface_modem_signal_parent;
static MMIfaceModemInterface          *iface_modem_parent;

G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbim, mm_broadband_modem_mbim, MM_TYPE_BROADBAND_MODEM, 0,
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_PROFILE_MANAGER, iface_modem_3gpp_profile_manager_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP_USSD, iface_modem_3gpp_ussd_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_MESSAGING, iface_modem_messaging_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SAR, iface_modem_sar_init)
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
                        G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QMI, shared_qmi_init)
#endif
)

typedef enum {
    PROCESS_NOTIFICATION_FLAG_NONE                 = 0,
    PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY       = 1 << 0,
    PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES = 1 << 1,
    PROCESS_NOTIFICATION_FLAG_SMS_READ             = 1 << 2,
    PROCESS_NOTIFICATION_FLAG_CONNECT              = 1 << 3,
    PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO      = 1 << 4,
    PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE       = 1 << 5,
    PROCESS_NOTIFICATION_FLAG_PCO                  = 1 << 6,
    PROCESS_NOTIFICATION_FLAG_USSD                 = 1 << 7,
    PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO      = 1 << 8,
    PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS = 1 << 9,
    PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS     = 1 << 10,
    PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION     = 1 << 11,
} ProcessNotificationFlag;

enum {
    PROP_0,
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    PROP_QMI_UNSUPPORTED,
#endif
    PROP_INTEL_FIRMWARE_UPDATE_UNSUPPORTED,
    PROP_LAST
};

/* Runtime cache while enabled */
typedef struct {
    gchar                   *current_operator_id;
    gchar                   *current_operator_name;
    GList                   *pco_list;
    MbimDataClass            available_data_classes;
    MbimDataClass            highest_available_data_class;
    MbimRegisterState        reg_state;
    MbimPacketServiceState   packet_service_state;
    guint64                  packet_service_uplink_speed;
    guint64                  packet_service_downlink_speed;
    MbimSubscriberReadyState last_ready_state;
    MbimPinType              last_pin_type;
} EnabledCache;

struct _MMBroadbandModemMbimPrivate {
    /* Queried and cached capabilities */
    MbimCellularClass caps_cellular_class;
    MbimDataClass caps_data_class;
    MbimDataClass caps_custom_data_class;
    MbimSmsCaps caps_sms;
    guint caps_max_sessions;
    gchar *caps_device_id;
    gchar *caps_firmware_info;
    gchar *caps_hardware_info;

    /* Supported features */
    gboolean is_profile_management_supported;
    gboolean is_profile_management_ext_supported;
    gboolean is_context_type_ext_supported;
    gboolean is_pco_supported;
    gboolean is_lte_attach_info_supported;
    gboolean is_nr5g_registration_settings_supported;
    gboolean is_base_stations_info_supported;
    gboolean is_ussd_supported;
    gboolean is_atds_location_supported;
    gboolean is_atds_signal_supported;
    gboolean is_intel_reset_supported;
    gboolean is_slot_info_status_supported;
    gboolean is_ms_sar_supported;
    gboolean is_google_carrier_lock_supported;
    gboolean is_ms_device_reset_supported;

    /* Process unsolicited notifications */
    gulong                  notification_id;
    ProcessNotificationFlag setup_flags;
    ProcessNotificationFlag enable_flags;

    /* State while enabled */
    EnabledCache enabled_cache;

    /* 3GPP registration helpers */
    gulong         enabling_signal_id;
    gchar         *requested_operator_id;
    MbimDataClass  requested_data_class; /* 0 for defaults/auto */

    /* Allowed modes helpers */
    GTask *pending_allowed_modes_action;

    /* USSD helpers */
    GTask *pending_ussd_action;

    /* SIM hot swap setup */
    gboolean sim_hot_swap_configured;

    /* For notifying when the mbim-proxy connection is dead */
    gulong mbim_device_removed_id;

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    gboolean qmi_unsupported;
    /* Flag when QMI-based capability/mode switching is in use */
    gboolean qmi_capability_and_mode_switching;
#endif

    gboolean intel_firmware_update_unsupported;

    /* Multi-SIM support */
    guint32  executor_index;
    guint    active_slot_index;
    gboolean pending_sim_slot_switch_action;

    MMUnlockRetries *unlock_retries;
};

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

static gboolean
peek_device (gpointer              self,
             MbimDevice          **o_device,
             GAsyncReadyCallback   callback,
             gpointer              user_data)
{
    MMPortMbim *port;

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (!port) {
        g_task_report_new_error (self, callback, user_data, peek_device,
                                 MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't peek MBIM port");
        return FALSE;
    }

    *o_device = mm_port_mbim_peek_device (port);
    return TRUE;
}

static gboolean
peek_device_in_task (gpointer     self,
                     MbimDevice **o_device,
                     GTask       *task)

{
    MMPortMbim *port;

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (!port) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't peek MBIM port");
        g_object_unref (task);
        return FALSE;
    }

    *o_device = mm_port_mbim_peek_device (port);
    return TRUE;
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static QmiClient *
shared_qmi_peek_client (MMSharedQmi    *self,
                        QmiService      service,
                        MMPortQmiFlag   flag,
                        GError        **error)
{
    MMPortMbim *port;
    QmiClient  *client;

    g_assert (flag == MM_PORT_QMI_FLAG_DEFAULT);

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (!port) {
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_FAILED,
                     "Couldn't peek MBIM port");
        return NULL;
    }

    if (!mm_port_mbim_supports_qmi (port)) {
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_UNSUPPORTED,
                     "Unsupported");
        return NULL;
    }

    client = mm_port_mbim_peek_qmi_client (port, service);
    if (!client)
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_FAILED,
                     "Couldn't peek client for service '%s'",
                     qmi_service_get_string (service));

    return client;
}

#endif

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

MMPortMbim *
mm_broadband_modem_mbim_get_port_mbim (MMBroadbandModemMbim *self)
{
    MMPortMbim *primary_mbim_port;

    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));

    primary_mbim_port = mm_broadband_modem_mbim_peek_port_mbim (self);
    return (primary_mbim_port ?
            MM_PORT_MBIM (g_object_ref (primary_mbim_port)) :
            NULL);
}

MMPortMbim *
mm_broadband_modem_mbim_peek_port_mbim (MMBroadbandModemMbim *self)
{
    MMPortMbim *primary_mbim_port = NULL;
    GList      *mbim_ports;

    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));

    mbim_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
                                           MM_PORT_SUBSYS_UNKNOWN,
                                           MM_PORT_TYPE_MBIM);

    /* First MBIM port in the list is the primary one always */
    if (mbim_ports)
        primary_mbim_port = MM_PORT_MBIM (mbim_ports->data);

    g_list_free_full (mbim_ports, g_object_unref);

    return primary_mbim_port;
}

MMPortMbim *
mm_broadband_modem_mbim_get_port_mbim_for_data (MMBroadbandModemMbim  *self,
                                                MMPort                *data,
                                                GError               **error)
{
    MMPortMbim *mbim_port;

    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));

    mbim_port = mm_broadband_modem_mbim_peek_port_mbim_for_data (self, data, error);
    return (mbim_port ?
            MM_PORT_MBIM (g_object_ref (mbim_port)) :
            NULL);
}

static MMPortMbim *
peek_port_mbim_for_data (MMBroadbandModemMbim  *self,
                         MMPort                *data,
                         GError               **error)
{
    GList       *cdc_wdm_mbim_ports;
    GList       *l;
    const gchar *net_port_parent_path;
    MMPortMbim  *found = NULL;
    const gchar *net_port_driver;

    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));
    g_assert (mm_port_get_subsys (data) == MM_PORT_SUBSYS_NET);

    net_port_driver = mm_kernel_device_get_driver (mm_port_peek_kernel_device (data));
    if (g_strcmp0 (net_port_driver, "cdc_mbim") != 0 && g_strcmp0 (net_port_driver, "mhi_net")) {
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_FAILED,
                     "Unsupported MBIM kernel driver for 'net/%s': %s",
                     mm_port_get_device (data),
                     net_port_driver);
        return NULL;
    }

    if (!g_strcmp0 (net_port_driver, "mhi_net"))
        return mm_broadband_modem_mbim_peek_port_mbim (self);

    net_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (data));
    if (!net_port_parent_path) {
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_FAILED,
                     "No parent path for 'net/%s'",
                     mm_port_get_device (data));
        return NULL;
    }

    /* Find the CDC-WDM port on the same USB interface as the given net port */
    cdc_wdm_mbim_ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
                                                   MM_PORT_SUBSYS_USBMISC,
                                                   MM_PORT_TYPE_MBIM);

    for (l = cdc_wdm_mbim_ports; l && !found; l = g_list_next (l)) {
        const gchar *wdm_port_parent_path;

        g_assert (MM_IS_PORT_MBIM (l->data));
        wdm_port_parent_path = mm_kernel_device_get_interface_sysfs_path (mm_port_peek_kernel_device (MM_PORT (l->data)));
        if (wdm_port_parent_path && g_str_equal (wdm_port_parent_path, net_port_parent_path))
            found = MM_PORT_MBIM (l->data);
    }

    g_list_free_full (cdc_wdm_mbim_ports, g_object_unref);

    if (!found)
        g_set_error (error,
                     MM_CORE_ERROR,
                     MM_CORE_ERROR_NOT_FOUND,
                     "Couldn't find associated MBIM port for 'net/%s'",
                     mm_port_get_device (data));

    return found;
}

MMPortMbim *
mm_broadband_modem_mbim_peek_port_mbim_for_data (MMBroadbandModemMbim  *self,
                                                 MMPort                *data,
                                                 GError               **error)
{
    g_assert (MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->peek_port_mbim_for_data);

    return MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->peek_port_mbim_for_data (self, data, error);
}

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

guint32
mm_broadband_modem_mbim_normalize_nw_error (MMBroadbandModemMbim *self,
                                            guint32               nw_error)
{
    if (MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->normalize_nw_error)
        return MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->normalize_nw_error (self, nw_error);

    return nw_error;
}

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

gboolean
mm_broadband_modem_mbim_is_context_type_ext_supported (MMBroadbandModemMbim *self)
{
    return self->priv->is_context_type_ext_supported;
}

/*****************************************************************************/
/* Current capabilities (Modem interface) */

typedef struct {
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    MMModemCapability  current_qmi;
#endif
    MbimDevice        *device;
    MMModemCapability  current_mbim;
} LoadCurrentCapabilitiesContext;

static void
load_current_capabilities_context_free (LoadCurrentCapabilitiesContext *ctx)
{
    g_object_unref (ctx->device);
    g_free (ctx);
}

static MMModemCapability
modem_load_current_capabilities_finish (MMIfaceModem *self,
                                        GAsyncResult *res,
                                        GError **error)
{
    GError *inner_error = NULL;
    gssize value;

    value = g_task_propagate_int (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return MM_MODEM_CAPABILITY_NONE;
    }
    return (MMModemCapability)value;
}

static void
complete_current_capabilities (GTask *task)
{
    LoadCurrentCapabilitiesContext *ctx;
    MMModemCapability               result = 0;

    ctx  = g_task_get_task_data (task);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    {
        MMBroadbandModemMbim *self;

        self = g_task_get_source_object (task);
        /* Warn if the MBIM loaded capabilities isn't a subset of the QMI loaded ones */
        if (ctx->current_qmi && ctx->current_mbim) {
            gchar *mbim_caps_str;
            gchar *qmi_caps_str;

            mbim_caps_str = mm_common_build_capabilities_string ((const MMModemCapability *)&(ctx->current_mbim), 1);
            qmi_caps_str = mm_common_build_capabilities_string ((const MMModemCapability *)&(ctx->current_qmi), 1);

            if ((ctx->current_mbim & ctx->current_qmi) != ctx->current_mbim)
                mm_obj_warn (self, "MBIM reported current capabilities (%s) not found in QMI-over-MBIM reported ones (%s)",
                             mbim_caps_str, qmi_caps_str);
            else
                mm_obj_dbg (self, "MBIM reported current capabilities (%s) is a subset of the QMI-over-MBIM reported ones (%s)",
                            mbim_caps_str, qmi_caps_str);
            g_free (mbim_caps_str);
            g_free (qmi_caps_str);

            result = ctx->current_qmi;
            self->priv->qmi_capability_and_mode_switching = TRUE;
        } else if (ctx->current_qmi) {
            result = ctx->current_qmi;
            self->priv->qmi_capability_and_mode_switching = TRUE;
        } else
            result = ctx->current_mbim;

        /* If current capabilities loading is done via QMI, we can safely assume that all the other
         * capability and mode related operations are going to be done via QMI as well, so that we
         * don't mix both logics */
        if (self->priv->qmi_capability_and_mode_switching)
            mm_obj_dbg (self, "QMI-based capability and mode switching support enabled");
    }
#else
    result = ctx->current_mbim;
#endif

    g_task_return_int (task, (gint) result);
    g_object_unref (task);
}

static void
device_caps_query_ready (MbimDevice   *device,
                         GAsyncResult *res,
                         GTask        *task)
{
    g_autoptr(MbimMessage)          response = NULL;
    MMBroadbandModemMbim           *self;
    GError                         *error = NULL;
    LoadCurrentCapabilitiesContext *ctx;
    MbimDataClass                   caps_data_class = MBIM_DATA_CLASS_NONE;
    g_autofree gchar               *caps_custom_data_class_str;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        MbimDataClassV3  data_class_v3;
        MbimDataSubclass data_subclass;

        if (!mbim_message_ms_basic_connect_extensions_v3_device_caps_response_parse (
                response,
                NULL, /* device_type */
                &self->priv->caps_cellular_class,
                NULL, /* voice_class */
                NULL, /* sim_class */
                &data_class_v3,
                &self->priv->caps_sms,
                NULL, /* ctrl_caps */
                &data_subclass,
                &self->priv->caps_max_sessions,
                NULL, /* executor_index */
                NULL, /* wcdma_band_class */
                NULL, /* lte_band_class_array_size */
                NULL, /* lte_band_class_array */
                NULL, /* nr_band_class_array_size */
                NULL, /* nr_band_class_array */
                &caps_custom_data_class_str,
                &self->priv->caps_device_id,
                &self->priv->caps_firmware_info,
                &self->priv->caps_hardware_info,
                &error)) {
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }
        /* Translate data class v3 to standard data class to simplify further usage of the field */
        caps_data_class = mm_mbim_data_class_from_mbim_data_class_v3_and_subclass (data_class_v3, data_subclass);
    } else if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        if (!mbim_message_ms_basic_connect_extensions_device_caps_response_parse (
                response,
                NULL, /* device_type */
                &self->priv->caps_cellular_class,
                NULL, /* voice_class */
                NULL, /* sim_class */
                &caps_data_class,
                &self->priv->caps_sms,
                NULL, /* ctrl_caps */
                &self->priv->caps_max_sessions,
                &caps_custom_data_class_str,
                &self->priv->caps_device_id,
                &self->priv->caps_firmware_info,
                &self->priv->caps_hardware_info,
                NULL, /* executor_index */
                &error)) {
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }
    } else {
        if (!mbim_message_device_caps_response_parse (
                response,
                NULL, /* device_type */
                &self->priv->caps_cellular_class,
                NULL, /* voice_class */
                NULL, /* sim_class */
                &caps_data_class,
                &self->priv->caps_sms,
                NULL, /* ctrl_caps */
                &self->priv->caps_max_sessions,
                &caps_custom_data_class_str,
                &self->priv->caps_device_id,
                &self->priv->caps_firmware_info,
                &self->priv->caps_hardware_info,
                &error)) {
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }
    }

    /* Normalize data class capabilities to include any custom data class */
    self->priv->caps_custom_data_class = mm_mbim_data_class_from_custom_caps (caps_data_class,
                                                                              caps_custom_data_class_str);
    self->priv->caps_data_class = mm_modem_mbim_normalize_data_class_mask (caps_data_class,
                                                                           self->priv->caps_custom_data_class);

    ctx->current_mbim = mm_modem_capability_from_mbim_device_caps (self->priv->caps_cellular_class,
                                                                   self->priv->caps_data_class);
    complete_current_capabilities (task);
}

static void
load_current_capabilities_mbim (GTask *task)
{
    g_autoptr(MbimMessage)          message = NULL;
    MMBroadbandModemMbim           *self;
    LoadCurrentCapabilitiesContext *ctx;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    mm_obj_dbg (self, "loading current capabilities...");
    if (mbim_device_check_ms_mbimex_version (ctx->device, 2, 0))
        message = mbim_message_ms_basic_connect_extensions_device_caps_query_new (NULL);
    else
        message = mbim_message_device_caps_query_new (NULL);
    mbim_device_command (ctx->device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)device_caps_query_ready,
                         task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
qmi_load_current_capabilities_ready (MMIfaceModem *self,
                                     GAsyncResult *res,
                                     GTask        *task)
{
    LoadCurrentCapabilitiesContext *ctx;
    GError                         *error = NULL;

    ctx = g_task_get_task_data (task);

    ctx->current_qmi = mm_shared_qmi_load_current_capabilities_finish (self, res, &error);
    if (error) {
        mm_obj_dbg (self, "couldn't load current capabilities using QMI over MBIM: %s", error->message);
        g_clear_error (&error);
    }

    load_current_capabilities_mbim (task);
}

#endif

static void
modem_load_current_capabilities (MMIfaceModem        *self,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
{
    MbimDevice                     *device;
    GTask                          *task;
    LoadCurrentCapabilitiesContext *ctx;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);
    ctx = g_new0 (LoadCurrentCapabilitiesContext, 1);
    ctx->device = g_object_ref (device);
    g_task_set_task_data (task, ctx, (GDestroyNotify) load_current_capabilities_context_free);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    mm_shared_qmi_load_current_capabilities (self,
                                             (GAsyncReadyCallback)qmi_load_current_capabilities_ready,
                                             task);
#else
    load_current_capabilities_mbim (task);
#endif
}

/*****************************************************************************/
/* Supported Capabilities loading (Modem interface) */

static GArray *
modem_load_supported_capabilities_finish (MMIfaceModem  *self,
                                          GAsyncResult  *res,
                                          GError       **error)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching)
        return mm_shared_qmi_load_supported_capabilities_finish (self, res, error);
#endif
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
load_supported_capabilities_mbim (GTask *task)
{
    MMBroadbandModemMbim *self;
    MMModemCapability     current;
    GArray               *supported = NULL;

    self = g_task_get_source_object (task);

    /* Current capabilities should have been cached already, just assume them */
    current = mm_modem_capability_from_mbim_device_caps (self->priv->caps_cellular_class,
                                                         self->priv->caps_data_class);
    if (current != 0) {
        supported = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 1);
        g_array_append_val (supported, current);
    }

    if (!supported)
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Couldn't load supported capabilities: no previously cached current capabilities");
    else
        g_task_return_pointer (task, supported, (GDestroyNotify) g_array_unref);
    g_object_unref (task);
}

static void
modem_load_supported_capabilities (MMIfaceModem        *self,
                                   GAsyncReadyCallback  callback,
                                   gpointer             user_data)
{
    GTask *task;

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_load_supported_capabilities (self, callback, user_data);
        return;
    }
#endif

    task = g_task_new (self, NULL, callback, user_data);
    load_supported_capabilities_mbim (task);
}

/*****************************************************************************/
/* Capabilities switching (Modem interface) */

static gboolean
modem_set_current_capabilities_finish (MMIfaceModem  *self,
                                       GAsyncResult  *res,
                                       GError       **error)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching)
        return mm_shared_qmi_set_current_capabilities_finish (self, res, error);
#endif
    g_assert (error);
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
modem_set_current_capabilities (MMIfaceModem         *self,
                                MMModemCapability     capabilities,
                                GAsyncReadyCallback   callback,
                                gpointer              user_data)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_set_current_capabilities (self, capabilities, callback, user_data);
        return;
    }
#endif

    g_task_report_new_error (self, callback, user_data,
                             modem_set_current_capabilities,
                             MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                             "Capability switching is not supported");
}

/*****************************************************************************/
/* Manufacturer loading (Modem interface) */

static gchar *
modem_load_manufacturer_finish (MMIfaceModem *self,
                                GAsyncResult *res,
                                GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_load_manufacturer (MMIfaceModem *self,
                         GAsyncReadyCallback callback,
                         gpointer user_data)
{
    GTask *task;
    gchar *manufacturer = NULL;
    MMPortMbim *port;

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (port) {
        manufacturer = g_strdup (mm_kernel_device_get_physdev_manufacturer (
            mm_port_peek_kernel_device (MM_PORT (port))));
    }

    if (!manufacturer)
        manufacturer = g_strdup (mm_base_modem_get_plugin (MM_BASE_MODEM (self)));

    task = g_task_new (self, NULL, callback, user_data);
    g_task_return_pointer (task, manufacturer, g_free);
    g_object_unref (task);
}

/*****************************************************************************/
/* Model loading (Modem interface) */

static gchar *
modem_load_model_default (MMIfaceModem *self)
{
    return g_strdup_printf ("MBIM [%04X:%04X]",
                            (mm_base_modem_get_vendor_id (MM_BASE_MODEM (self)) & 0xFFFF),
                            (mm_base_modem_get_product_id (MM_BASE_MODEM (self)) & 0xFFFF));
}

static gchar *
modem_load_model_finish (MMIfaceModem *self,
                         GAsyncResult *res,
                         GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
qmi_load_model_ready (MMIfaceModem *self,
                      GAsyncResult *res,
                      GTask *task)
{
    gchar  *model = NULL;
    GError *error = NULL;

    model = mm_shared_qmi_load_model_finish (self, res, &error);
    if (!model) {
        mm_obj_dbg (self, "couldn't load model using QMI over MBIM: %s", error->message);
        model = modem_load_model_default (self);
        g_clear_error (&error);
    }

     g_task_return_pointer (task, model, g_free);
     g_object_unref (task);
}

#endif

static void
at_load_model_ready (MMIfaceModem *self,
                     GAsyncResult *res,
                     GTask        *task)
{
    gchar  *model = NULL;
    GError *error = NULL;

    model = iface_modem_parent->load_model_finish (self, res, &error);
    if (!model) {
        mm_obj_dbg (self, "couldn't load model using AT: %s", error->message);
        model = modem_load_model_default (self);
        g_clear_error (&error);
    }

    g_task_return_pointer (task, model, g_free);
    g_object_unref (task);
}

static void
modem_load_model (MMIfaceModem *self,
                  GAsyncReadyCallback callback,
                  gpointer user_data)
{
    gchar      *model = NULL;
    GTask      *task;
    MMPortMbim *port;

    task = g_task_new (self, NULL, callback, user_data);

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (port) {
        model = g_strdup (mm_kernel_device_get_physdev_product (
            mm_port_peek_kernel_device (MM_PORT (port))));
        if (model) {
            g_task_return_pointer (task, model, g_free);
            g_object_unref (task);
            return;
        }
    }

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (!model) {
        mm_shared_qmi_load_model (self, (GAsyncReadyCallback)qmi_load_model_ready, task);
        return;
    }
#endif

    iface_modem_parent->load_model (self, (GAsyncReadyCallback)at_load_model_ready, task);
}

/*****************************************************************************/
/* Revision loading (Modem interface) */

static gchar *
modem_load_revision_finish (MMIfaceModem *self,
                            GAsyncResult *res,
                            GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_load_revision (MMIfaceModem *_self,
                     GAsyncReadyCallback callback,
                     gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->caps_firmware_info)
        g_task_return_pointer (task,
                               g_strdup (self->priv->caps_firmware_info),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Firmware revision information not given in device capabilities");
    g_object_unref (task);
}

/*****************************************************************************/
/* Hardware Revision loading (Modem interface) */

static gchar *
modem_load_hardware_revision_finish (MMIfaceModem *self,
                                     GAsyncResult *res,
                                     GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_load_hardware_revision (MMIfaceModem *_self,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->caps_hardware_info)
        g_task_return_pointer (task,
                               g_strdup (self->priv->caps_hardware_info),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Hardware revision information not given in device capabilities");
    g_object_unref (task);
}

/*****************************************************************************/
/* Equipment Identifier loading (Modem interface) */

static gchar *
modem_load_equipment_identifier_finish (MMIfaceModem *self,
                                        GAsyncResult *res,
                                        GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_load_equipment_identifier (MMIfaceModem *_self,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->caps_device_id)
        g_task_return_pointer (task,
                               g_strdup (self->priv->caps_device_id),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Device ID not given in device capabilities");
    g_object_unref (task);
}

/*****************************************************************************/
/* Device identifier loading (Modem interface) */

static gchar *
modem_load_device_identifier_finish (MMIfaceModem *self,
                                     GAsyncResult *res,
                                     GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_load_device_identifier (MMIfaceModem *self,
                              GAsyncReadyCallback callback,
                              gpointer user_data)
{
    gchar  *device_identifier;
    GTask  *task;
    GError *error = NULL;

    task = g_task_new (self, NULL, callback, user_data);

    /* Just use placeholder ATI/ATI1 replies, all the other internal info should be
     * enough for uniqueness */
    device_identifier = mm_broadband_modem_create_device_identifier (MM_BROADBAND_MODEM (self), "", "", &error);
    if (!device_identifier)
        g_task_return_error (task, error);
    else
        g_task_return_pointer (task, device_identifier, g_free);
    g_object_unref (task);
}

/*****************************************************************************/
/* Supported modes loading (Modem interface) */

static GArray *
modem_load_supported_modes_finish (MMIfaceModem  *self,
                                   GAsyncResult  *res,
                                   GError       **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
load_supported_modes_mbim (GTask      *task,
                           MbimDevice *device)
{
    MMBroadbandModemMbim   *self;
    GArray                 *all;
    GArray                 *filtered;
    MMModemMode             mask_all;
    MMModemModeCombination  mode = {
        .allowed   = MM_MODEM_MODE_NONE,
        .preferred = MM_MODEM_MODE_NONE,
    };

    self = g_task_get_source_object (task);

    if (self->priv->caps_data_class == 0) {
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Data class not given in device capabilities");
        g_object_unref (task);
        return;
    }

    /* Build all */
    mask_all = mm_modem_mode_from_mbim_data_class (self->priv->caps_data_class);
    mode.allowed = mask_all;
    mode.preferred = MM_MODEM_MODE_NONE;
    all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
    g_array_append_val (all, mode);

    /* When using MBIMEx we can enable the mode switching operation because
     * we'll be able to know if the modes requested are the ones configured
     * as preferred after the operation. */
    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        GArray *combinations;

        combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));

#define ADD_MODE_PREFERENCE(MODE_MASK) do {                             \
            mode.allowed = MODE_MASK;                                   \
            mode.preferred = MM_MODEM_MODE_NONE;                        \
            g_array_append_val (combinations, mode);                    \
        } while (0)

        /* 2G, 3G */
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);

        /* +4G */
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);

        /* +5G */
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G);
        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G);

        filtered = mm_filter_supported_modes (all, combinations, self);
        g_array_unref (combinations);
        g_array_unref (all);
#undef ADD_MODE_PREFERENCE
    } else
        filtered = all;

    g_task_return_pointer (task, filtered, (GDestroyNotify)g_array_unref);
    g_object_unref (task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
shared_qmi_load_supported_modes_ready (MMIfaceModem *self,
                                       GAsyncResult *res,
                                       GTask        *task)
{
    GArray *combinations;
    GError *error = NULL;

    combinations = mm_shared_qmi_load_supported_modes_finish (self, res, &error);
    if (!combinations)
        g_task_return_error (task, error);
    else
        g_task_return_pointer (task, combinations, (GDestroyNotify)g_array_unref);
    g_object_unref (task);
}

#endif

static void
modem_load_supported_modes (MMIfaceModem        *self,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
    GTask      *task;
    MbimDevice *device;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_load_supported_modes (self, (GAsyncReadyCallback)shared_qmi_load_supported_modes_ready, task);
        return;
    }
#endif

    load_supported_modes_mbim (task, device);
}

/*****************************************************************************/
/* Current modes loading (Modem interface) */

static gboolean
modem_load_current_modes_finish (MMIfaceModem  *self,
                                 GAsyncResult  *res,
                                 MMModemMode   *allowed,
                                 MMModemMode   *preferred,
                                 GError       **error)
{
    g_autofree MMModemModeCombination *mode = NULL;

    mode = g_task_propagate_pointer (G_TASK (res), error);
    if (!mode)
        return FALSE;

    *allowed   = mode->allowed;
    *preferred = mode->preferred;
    return TRUE;
}

static void
register_state_current_modes_query_ready (MbimDevice   *device,
                                          GAsyncResult *res,
                                          GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    MMModemModeCombination *mode = NULL;
    GError                 *error = NULL;
    MbimDataClass           preferred_data_classes;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_v2_register_state_response_parse (
            response,
            NULL, /* nw_error */
            NULL, /* register_state */
            NULL, /* register_mode */
            NULL, /* available_data_classes */
            NULL, /* current_cellular_class */
            NULL, /* provider_id */
            NULL, /* provider_name */
            NULL, /* roaming_text */
            NULL, /* registration_flag */
            &preferred_data_classes,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Normalize preferred data class to include any custom data class */
    preferred_data_classes = mm_modem_mbim_normalize_data_class_mask (preferred_data_classes,
                                                                      self->priv->caps_custom_data_class);

    mode = g_new0 (MMModemModeCombination, 1);
    mode->allowed = mm_modem_mode_from_mbim_data_class (preferred_data_classes);
    mode->preferred = MM_MODEM_MODE_NONE;
    g_task_return_pointer (task, mode, (GDestroyNotify)g_free);
    g_object_unref (task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
shared_qmi_load_current_modes_ready (MMIfaceModem *self,
                                     GAsyncResult *res,
                                     GTask        *task)
{
    g_autofree MMModemModeCombination *mode = NULL;
    GError                            *error = NULL;

    mode = g_new0 (MMModemModeCombination, 1);
    if (!mm_shared_qmi_load_current_modes_finish (self, res, &mode->allowed, &mode->preferred, &error))
        g_task_return_error (task, error);
    else
        g_task_return_pointer (task, g_steal_pointer (&mode), (GDestroyNotify)g_free);
    g_object_unref (task);
}

#endif

static void
modem_load_current_modes (MMIfaceModem        *self,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
    GTask      *task;
    MbimDevice *device;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_load_current_modes (self, (GAsyncReadyCallback)shared_qmi_load_current_modes_ready, task);
        return;
    }
#endif

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        g_autoptr(MbimMessage) message = NULL;

        message = mbim_message_register_state_query_new (NULL);
        mbim_device_command (device,
                             message,
                             60,
                             NULL,
                             (GAsyncReadyCallback)register_state_current_modes_query_ready,
                             task);
        return;
    }

    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                             "Current mode loading is not supported");
    g_object_unref (task);
}

/*****************************************************************************/
/* Current modes switching (Modem interface) */

static gboolean
modem_set_current_modes_finish (MMIfaceModem  *self,
                                GAsyncResult  *res,
                                GError       **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
complete_pending_allowed_modes_action (MMBroadbandModemMbim *self,
                                       MbimDataClass         preferred_data_classes)
{
    MbimDataClass requested_data_classes;
    MMModemMode   preferred_modes;
    MMModemMode   requested_modes;

    if (!self->priv->pending_allowed_modes_action)
        return;

    /* requested_data_classes is de-normalized (since we just sent it to the modem) */
    requested_data_classes = (MbimDataClass) GPOINTER_TO_UINT (g_task_get_task_data (self->priv->pending_allowed_modes_action));
    requested_modes = mm_modem_mode_from_mbim_data_class (requested_data_classes);

    /* But preferred_data_classes is normalized (since we just pulled it out of
     * an MBIM message). De-normalize preferred_data_classes so we can compare
     * it to requested_data_classes.
     */
    preferred_data_classes = mm_modem_mbim_normalize_data_class_mask (preferred_data_classes,
                                                                      self->priv->caps_custom_data_class);
    preferred_modes = mm_modem_mode_from_mbim_data_class (preferred_data_classes);

    /* only early complete on success, as we don't know if they're going to be
     * intermediate indications emitted before the preference change is valid */
    if (requested_modes == preferred_modes) {
        GTask *task;

        task = g_steal_pointer (&self->priv->pending_allowed_modes_action);
        g_task_return_boolean (task, TRUE);
        g_object_unref (task);
    }
}

static void
register_state_current_modes_set_ready (MbimDevice   *device,
                                        GAsyncResult *res,
                                        GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    MMBroadbandModemMbim   *self;
    GError                 *error = NULL;
    MbimDataClass           preferred_data_classes;
    MMModemMode             preferred_modes;
    MbimDataClass           requested_data_classes;
    MMModemMode             requested_modes;

    self = g_task_get_source_object (task);
    requested_data_classes = (MbimDataClass) GPOINTER_TO_UINT (g_task_get_task_data (task));

    /* If the task is still in the private info, it means it wasn't either
     * cancelled or completed, so we just unref that reference and go on
     * with out response processing. But if the task is no longer in the
     * private info (or if there's a different task), then it means we're
     * either cancelled (by some new incoming user request) or otherwise
     * successfully completed (if completed via a Register State indication).
     * In both those cases, just unref the incoming task and go on. */
    if (self->priv->pending_allowed_modes_action != task) {
        g_assert (g_task_get_completed (task));
        g_object_unref (task);
        return;
    }
    g_clear_object (&self->priv->pending_allowed_modes_action);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_v2_register_state_response_parse (
            response,
            NULL, /* nw_error */
            NULL, /* register_state */
            NULL, /* register_mode */
            NULL, /* available_data_classes */
            NULL, /* current_cellular_class */
            NULL, /* provider_id */
            NULL, /* provider_name */
            NULL, /* roaming_text */
            NULL, /* registration_flag */
            &preferred_data_classes,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    requested_modes = mm_modem_mode_from_mbim_data_class (requested_data_classes);
    preferred_modes = mm_modem_mode_from_mbim_data_class (preferred_data_classes);

    if (requested_modes != preferred_modes) {
        g_autofree gchar *requested_modes_str = NULL;
        g_autofree gchar *preferred_modes_str = NULL;
        g_autofree gchar *requested_data_classes_str = NULL;
        g_autofree gchar *preferred_data_classes_str = NULL;

        requested_modes_str = mm_modem_mode_build_string_from_mask (requested_modes);
        preferred_modes_str = mm_modem_mode_build_string_from_mask (preferred_modes);
        requested_data_classes_str = mbim_data_class_build_string_from_mask (requested_data_classes);
        preferred_data_classes_str = mbim_data_class_build_string_from_mask (preferred_data_classes);

        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Current mode update failed: requested %s (%s) but reported preferred is %s (%s)",
                                 requested_modes_str, requested_data_classes_str,
                                 preferred_modes_str, preferred_data_classes_str);
        g_object_unref (task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
shared_qmi_set_current_modes_ready (MMIfaceModem *self,
                                    GAsyncResult *res,
                                    GTask        *task)
{
    GError *error = NULL;

    if (!mm_shared_qmi_set_current_modes_finish (self, res, &error))
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

#endif

static void
modem_set_current_modes (MMIfaceModem        *_self,
                         MMModemMode          allowed,
                         MMModemMode          preferred,
                         GAsyncReadyCallback  callback,
                         gpointer             user_data)
{
    MMBroadbandModemMbim    *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask                   *task;
    MbimDevice              *device;
    g_autoptr(GCancellable)  cancellable = NULL;
    MbimDataClass            normalized_class;

    if (!peek_device (self, &device, callback, user_data))
        return;

    cancellable = g_cancellable_new ();
    task = g_task_new (self, cancellable, callback, user_data);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (self->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_set_current_modes (MM_IFACE_MODEM (self),
                                         allowed,
                                         preferred,
                                         (GAsyncReadyCallback)shared_qmi_set_current_modes_ready,
                                         task);
        return;
    }
#endif

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        g_autoptr(MbimMessage) message = NULL;

        /* Limit ANY to the currently supported modes */
        if (allowed == MM_MODEM_MODE_ANY)
            allowed = mm_modem_mode_from_mbim_data_class (self->priv->caps_data_class);

        normalized_class = mm_mbim_data_class_from_modem_mode (allowed,
                                                               mm_iface_modem_is_3gpp (_self),
                                                               mm_iface_modem_is_cdma (_self));

        /* Replace any normalized data class with MBIM_DATA_CLASS_CUSTOM
         * before sending back to the modem.
         */
        self->priv->requested_data_class = mm_modem_mbim_denormalize_data_class_mask (normalized_class,
                                                                                      self->priv->caps_custom_data_class);

        /* Store the ongoing allowed modes action, so that we can finish the
         * operation early via indications, instead of waiting for the modem
         * to be registered on the requested access technologies */
        g_task_set_task_data (task, GUINT_TO_POINTER (self->priv->requested_data_class), NULL);
        if (self->priv->pending_allowed_modes_action) {
            /* cancel the task and clear this reference; the _set_ready()
             * will take care of completing the task */
            g_cancellable_cancel (g_task_get_cancellable (self->priv->pending_allowed_modes_action));
            g_task_return_error_if_cancelled (self->priv->pending_allowed_modes_action);
            g_clear_object (&self->priv->pending_allowed_modes_action);
        }
        self->priv->pending_allowed_modes_action = g_object_ref (task);

        /* use the last requested operator id to determine whether the
         * registration should be manual or automatic */
        message = mbim_message_register_state_set_new (
                      self->priv->requested_operator_id ? self->priv->requested_operator_id : "",
                      self->priv->requested_operator_id ? MBIM_REGISTER_ACTION_MANUAL : MBIM_REGISTER_ACTION_AUTOMATIC,
                      self->priv->requested_data_class,
                      NULL);
        mbim_device_command (device,
                             message,
                             60,
                             NULL,
                             (GAsyncReadyCallback)register_state_current_modes_set_ready,
                             task);
        return;
    }

    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                             "Current mode switching is not supported");
    g_object_unref (task);
}

/*****************************************************************************/
/* Load supported IP families (Modem interface) */

static MMBearerIpFamily
modem_load_supported_ip_families_finish (MMIfaceModem *self,
                                         GAsyncResult *res,
                                         GError **error)
{
    GError *inner_error = NULL;
    gssize value;

    value = g_task_propagate_int (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return MM_BEARER_IP_FAMILY_NONE;
    }
    return (MMBearerIpFamily)value;
}

static void
modem_load_supported_ip_families (MMIfaceModem *self,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    /* Assume IPv4 + IPv6 + IPv4v6 supported */
    g_task_return_int (task,
                       MM_BEARER_IP_FAMILY_IPV4 |
                       MM_BEARER_IP_FAMILY_IPV6 |
                       MM_BEARER_IP_FAMILY_IPV4V6);
    g_object_unref (task);
}

/*****************************************************************************/
/* Unlock required loading (Modem interface) */

typedef struct {
    MbimDevice *device;
    gboolean    last_attempt;
} LoadUnlockRequiredContext;

static void
load_unlock_required_context_free (LoadUnlockRequiredContext *ctx)
{
    g_object_unref (ctx->device);
    g_slice_free (LoadUnlockRequiredContext, ctx);
}

static MMModemLock
modem_load_unlock_required_finish (MMIfaceModem  *self,
                                   GAsyncResult  *res,
                                   GError       **error)
{
    GError *inner_error = NULL;
    gssize  value;

    value = g_task_propagate_int (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return MM_MODEM_LOCK_UNKNOWN;
    }
    return (MMModemLock)value;
}

static void
pin_query_ready (MbimDevice   *device,
                 GAsyncResult *res,
                 GTask        *task)
{
    MbimMessage *response;
    GError *error = NULL;
    MbimPinType pin_type;
    MbimPinState pin_state;
    MMBroadbandModemMbim *self;

    self = g_task_get_source_object (task);
    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_pin_response_parse (
            response,
            &pin_type,
            &pin_state,
            NULL,
            &error)) {
        MMModemLock unlock_required;

        if (pin_state == MBIM_PIN_STATE_UNLOCKED)
            unlock_required = MM_MODEM_LOCK_NONE;
        else
            unlock_required = mm_modem_lock_from_mbim_pin_type (pin_type);

        g_task_return_int (task, unlock_required);
        self->priv->enabled_cache.last_pin_type = pin_type;
    }
    /* VZ20M reports an error when SIM-PIN is required... */
    else if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_PIN_REQUIRED)) {
        g_error_free (error);
        g_task_return_int (task, MBIM_PIN_TYPE_PIN1);
    }
    else
        g_task_return_error (task, error);

    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
unlock_required_subscriber_ready_state_ready (MbimDevice   *device,
                                              GAsyncResult *res,
                                              GTask        *task)
{
    LoadUnlockRequiredContext *ctx;
    MMBroadbandModemMbim      *self;
    g_autoptr(MbimMessage)     response = NULL;
    GError                    *error = NULL;
    MbimSubscriberReadyState   ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;
    MbimSubscriberReadyState   prev_ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;

    ctx = g_task_get_task_data (task);
    self = g_task_get_source_object (task);

    /* hold on to the previous ready_state value to determine if the retry logic needs to be reset. */
    prev_ready_state = self->priv->enabled_cache.last_ready_state;
    /* reset to the default if any error happens */
    self->priv->enabled_cache.last_ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_v3_subscriber_ready_status_response_parse (
                response,
                &ready_state,
                NULL, /* flags */
                NULL, /* subscriber id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                NULL, /* telephone_numbers */
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v3.0 subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v3.0 subscriber ready status response");
    } else {
        if (!mbim_message_subscriber_ready_status_response_parse (
                response,
                &ready_state,
                NULL, /* subscriber id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                NULL, /* telephone_numbers */
                &error))
            g_prefix_error (&error, "Failed processing subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed subscriber ready status response");
    }

    if (!error) {
        /* Store last valid ready state until it is NOT_INITIALIZED which is temporary */
        if (ready_state != MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED) {
            self->priv->enabled_cache.last_ready_state = ready_state;
        } else {
            self->priv->enabled_cache.last_ready_state = prev_ready_state;
        }

        switch (ready_state) {
        case MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED:
        case MBIM_SUBSCRIBER_READY_STATE_INITIALIZED:
        case MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED:
        case MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE:
            /* Don't set error */
            break;
        case MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED:
            /* This is an error, but we still want to retry.
             * The MC7710 may use this while the SIM is not ready yet. */
            break;
        case MBIM_SUBSCRIBER_READY_STATE_BAD_SIM:
            error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_WRONG, self);
            break;
        case MBIM_SUBSCRIBER_READY_STATE_FAILURE:
        case MBIM_SUBSCRIBER_READY_STATE_NOT_ACTIVATED:
        default:
            error = mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_FAILURE, self);
            break;
        }
    }

    if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NOT_INITIALIZED)) {
        g_clear_error (&error);
        ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;
    }

    /* Fatal errors are reported right away */
    if (error) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Need to retry? */
    if (ready_state == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED ||
        ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) {
        /* All retries consumed? issue error */
        if (ctx->last_attempt) {
            if (ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED)
                g_task_return_error (task, mm_mobile_equipment_error_for_code (MM_MOBILE_EQUIPMENT_ERROR_SIM_NOT_INSERTED, self));
            else
                g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                         "Error waiting for SIM to get initialized");
        } else {
            /* Start the retry process from the top if the SIM state changes from
             * MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED to
             * MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED
             * This will address the race condition that occurs during rapid hotswap. */
            gboolean retry_reset_needed = FALSE;

            if (prev_ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED &&
                ready_state == MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED)
                retry_reset_needed = TRUE;
            g_task_return_new_error (task,
                                     MM_CORE_ERROR,
                                     retry_reset_needed ? MM_CORE_ERROR_RESET_AND_RETRY : MM_CORE_ERROR_RETRY,
                                     "SIM not ready yet (retry)");
        }
        g_object_unref (task);
        return;
    }

    /* Initialized */
    if (ready_state == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED ||
        ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED) {
        MbimMessage *message;

        /* Query which lock is to unlock */
        message = mbim_message_pin_query_new (NULL);
        mbim_device_command (device,
                             message,
                             10,
                             g_task_get_cancellable (task),
                             (GAsyncReadyCallback)pin_query_ready,
                             task);
        mbim_message_unref (message);
        return;
    }

    /* When initialized but there are not profile set, assume no lock is
     * applied. */
    mm_obj_dbg (self, "eSIM without profiles: assuming no lock is required");
    g_assert (ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE);
    g_task_return_int (task, MM_MODEM_LOCK_NONE);
    g_object_unref (task);
}

static gboolean
wait_for_sim_ready (GTask *task)
{
    LoadUnlockRequiredContext *ctx;
    MbimMessage *message;

    ctx = g_task_get_task_data (task);
    message = mbim_message_subscriber_ready_status_query_new (NULL);
    mbim_device_command (ctx->device,
                         message,
                         10,
                         g_task_get_cancellable (task),
                         (GAsyncReadyCallback)unlock_required_subscriber_ready_state_ready,
                         task);
    mbim_message_unref (message);
    return G_SOURCE_REMOVE;
}

static void
modem_load_unlock_required (MMIfaceModem        *self,
                            gboolean             last_attempt,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
    LoadUnlockRequiredContext *ctx;
    MbimDevice                *device;
    GTask                     *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    ctx = g_slice_new (LoadUnlockRequiredContext);
    ctx->device = g_object_ref (device);
    ctx->last_attempt = last_attempt;

    task = g_task_new (self, cancellable, callback, user_data);
    g_task_set_task_data (task, ctx, (GDestroyNotify)load_unlock_required_context_free);

    wait_for_sim_ready (task);
}

/*****************************************************************************/
/* Unlock retries loading (Modem interface) */

static MMUnlockRetries *
modem_load_unlock_retries_finish (MMIfaceModem *self,
                                  GAsyncResult *res,
                                  GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
pin_query_unlock_retries_ready (MbimDevice *device,
                                GAsyncResult *res,
                                GTask *task)
{
    MbimMessage *response;
    GError *error = NULL;
    MbimPinType pin_type;
    guint32 remaining_attempts;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_pin_response_parse (
            response,
            &pin_type,
            NULL,
            &remaining_attempts,
            &error)) {
        MMBroadbandModemMbim *self;
        MMModemLock lock;

        self = MM_BROADBAND_MODEM_MBIM (g_task_get_source_object (task));
        lock = mm_modem_lock_from_mbim_pin_type (pin_type);

        mm_broadband_modem_mbim_set_unlock_retries (self, lock, remaining_attempts);
        g_task_return_pointer (task, g_object_ref (self->priv->unlock_retries), g_object_unref);
        self->priv->enabled_cache.last_pin_type = pin_type;
    } else
        g_task_return_error (task, error);

    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
modem_load_unlock_retries (MMIfaceModem *self,
                           GAsyncReadyCallback callback,
                           gpointer user_data)
{
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_pin_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)pin_query_unlock_retries_ready,
                         task);
    mbim_message_unref (message);
}

void
mm_broadband_modem_mbim_set_unlock_retries (MMBroadbandModemMbim *self,
                                            MMModemLock           lock_type,
                                            guint32               remaining_attempts)
{
    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));

    if (!self->priv->unlock_retries)
        self->priv->unlock_retries = mm_unlock_retries_new ();

    /* According to the MBIM specification, RemainingAttempts is set to
     * 0xffffffff if the device does not support this information. */
    if (remaining_attempts != G_MAXUINT32)
        mm_unlock_retries_set (self->priv->unlock_retries,
                               lock_type,
                               remaining_attempts);
}

/*****************************************************************************/
/* Own numbers loading */

static GStrv
modem_load_own_numbers_finish (MMIfaceModem  *self,
                               GAsyncResult  *res,
                               GError       **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
own_numbers_subscriber_ready_state_ready (MbimDevice   *device,
                                          GAsyncResult *res,
                                          GTask        *task)
{
    MMBroadbandModemMbim    *self;
    g_autoptr(MbimMessage)   response = NULL;
    GError                  *error = NULL;
    gchar                  **telephone_numbers;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_v3_subscriber_ready_status_response_parse (
                response,
                NULL, /* ready_state */
                NULL, /* flags */
                NULL, /* subscriber id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                &telephone_numbers,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v3.0 subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v3.0 subscriber ready status response");
    } else {
        if (!mbim_message_subscriber_ready_status_response_parse (
                response,
                NULL, /* ready_state */
                NULL, /* subscriber id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                &telephone_numbers,
                &error))
            g_prefix_error (&error, "Failed processing subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed subscriber ready status response");
    }

    if (error)
        g_task_return_error (task, error);
    else
        g_task_return_pointer (task, telephone_numbers, (GDestroyNotify)g_strfreev);
    g_object_unref (task);
}

static void
modem_load_own_numbers (MMIfaceModem        *self,
                        GAsyncReadyCallback  callback,
                        gpointer             user_data)
{
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_subscriber_ready_status_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)own_numbers_subscriber_ready_state_ready,
                         task);
}

/*****************************************************************************/
/* Initial power state loading */

static MMModemPowerState
modem_load_power_state_finish (MMIfaceModem *self,
                               GAsyncResult *res,
                               GError **error)
{
    GError *inner_error = NULL;
    gssize value;

    value = g_task_propagate_int (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return MM_MODEM_POWER_STATE_UNKNOWN;
    }
    return (MMModemPowerState)value;
}

static void
radio_state_query_ready (MbimDevice *device,
                         GAsyncResult *res,
                         GTask *task)
{
    MbimMessage *response;
    GError *error = NULL;
    MbimRadioSwitchState hardware_radio_state;
    MbimRadioSwitchState software_radio_state;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_radio_state_response_parse (
            response,
            &hardware_radio_state,
            &software_radio_state,
            &error)) {
        MMModemPowerState state;

        if (hardware_radio_state == MBIM_RADIO_SWITCH_STATE_OFF ||
            software_radio_state == MBIM_RADIO_SWITCH_STATE_OFF)
            state = MM_MODEM_POWER_STATE_LOW;
        else
            state = MM_MODEM_POWER_STATE_ON;
        g_task_return_int (task, state);
    } else
        g_task_return_error (task, error);
    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
modem_load_power_state (MMIfaceModem *self,
                        GAsyncReadyCallback callback,
                        gpointer user_data)
{
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_radio_state_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)radio_state_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Power up (Modem interface) */

static gboolean
power_up_finish (MMIfaceModem  *self,
                 GAsyncResult  *res,
                 GError       **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
radio_state_set_up_ready (MbimDevice   *device,
                          GAsyncResult *res,
                          GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(GError)       error = NULL;
    MbimRadioSwitchState    hardware_radio_state;
    MbimRadioSwitchState    software_radio_state;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_radio_state_response_parse (
            response,
            &hardware_radio_state,
            &software_radio_state,
            &error)) {
        if (hardware_radio_state == MBIM_RADIO_SWITCH_STATE_OFF)
            error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Cannot power-up: hardware radio switch is OFF");
        else if (software_radio_state == MBIM_RADIO_SWITCH_STATE_OFF)
            error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Cannot power-up: sotware radio switch is OFF");
    }

    /* Nice! we're done, quick exit */
    if (!error) {
        g_task_return_boolean (task, TRUE);
        g_object_unref (task);
        return;
    }

    /* The SDX55 returns "Operation not allowed", but not really sure about other
     * older devices. The original logic in the MBIM implementation triggered a retry
     * for any kind of error, so let's do the same for now. */
    mm_obj_warn (self, "%s", error->message);
    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "Invalid transition");
    g_object_unref (task);
}

static void
modem_power_up (MMIfaceModem        *self,
                GAsyncReadyCallback  callback,
                gpointer             user_data)
{
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_radio_state_set_new (MBIM_RADIO_SWITCH_STATE_ON, NULL);
    mbim_device_command (device,
                         message,
                         20,
                         NULL,
                         (GAsyncReadyCallback)radio_state_set_up_ready,
                         task);
}

/*****************************************************************************/
/* Power down (Modem interface) */

static gboolean
power_down_finish (MMIfaceModem *self,
                   GAsyncResult *res,
                   GError **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
radio_state_set_down_ready (MbimDevice *device,
                            GAsyncResult *res,
                            GTask *task)
{
    MbimMessage *response;
    GError *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response) {
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
        mbim_message_unref (response);
    }

    if (error)
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_power_down (MMIfaceModem *self,
                  GAsyncReadyCallback callback,
                  gpointer user_data)
{
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_radio_state_set_new (MBIM_RADIO_SWITCH_STATE_OFF, NULL);
    mbim_device_command (device,
                         message,
                         20,
                         NULL,
                         (GAsyncReadyCallback)radio_state_set_down_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Signal quality loading (Modem interface) */

static MbimDataClass
enabled_cache_best_available_data_class (MMBroadbandModemMbim *self)
{
    MbimDataClass data_class;

    /* Best guess of current data class */
    data_class = self->priv->enabled_cache.highest_available_data_class;
    if (data_class == 0)
        data_class = self->priv->enabled_cache.available_data_classes;
    return data_class;
}

static guint
modem_load_signal_quality_finish (MMIfaceModem *self,
                                  GAsyncResult *res,
                                  GError **error)
{
    gssize value;

    value = g_task_propagate_int (G_TASK (res), error);
    return value < 0 ? 0 : value;
}

static void
signal_state_query_ready (MbimDevice   *device,
                          GAsyncResult *res,
                          GTask        *task)
{
    MMBroadbandModemMbim            *self;
    GError                          *error = NULL;
    g_autoptr(MbimMessage)           response = NULL;
    g_autoptr(MbimRsrpSnrInfoArray)  rsrp_snr = NULL;
    guint32                          rsrp_snr_count = 0;
    guint32                          rssi;
    guint32                          error_rate = 99;
    guint                            quality;
    MbimDataClass                    data_class;
    g_autoptr(MMSignal)              cdma = NULL;
    g_autoptr(MMSignal)              evdo = NULL;
    g_autoptr(MMSignal)              gsm = NULL;
    g_autoptr(MMSignal)              umts = NULL;
    g_autoptr(MMSignal)              lte = NULL;
    g_autoptr(MMSignal)              nr5g = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        if (!mbim_message_ms_basic_connect_v2_signal_state_response_parse (
                response,
                &rssi,
                &error_rate,
                NULL, /* signal_strength_interval */
                NULL, /* rssi_threshold */
                NULL, /* error_rate_threshold */
                &rsrp_snr_count,
                &rsrp_snr,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v2.0 signal state response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v2.0 signal state response");
    } else {
        if (!mbim_message_signal_state_response_parse (
                response,
                &rssi,
                &error_rate,
                NULL, /* signal_strength_interval */
                NULL, /* rssi_threshold */
                NULL, /* error_rate_threshold */
                &error))
            g_prefix_error (&error, "Failed processing signal state response: ");
        else
            mm_obj_dbg (self, "processed signal state response");
    }

    if (error)
        g_task_return_error (task, error);
    else {
        data_class = enabled_cache_best_available_data_class (self);
        if (mm_signal_from_mbim_signal_state (data_class, rssi, error_rate, rsrp_snr, rsrp_snr_count,
                                              self, &cdma, &evdo, &gsm, &umts, &lte, &nr5g))
            mm_iface_modem_signal_update (MM_IFACE_MODEM_SIGNAL (self), cdma, evdo, gsm, umts, lte, nr5g);

        quality = mm_signal_quality_from_mbim_signal_state (rssi, rsrp_snr, rsrp_snr_count, self);
        g_task_return_int (task, quality);
    }
    g_object_unref (task);
}

static void
modem_load_signal_quality (MMIfaceModem *self,
                           GAsyncReadyCallback callback,
                           gpointer user_data)
{
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_signal_state_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)signal_state_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Reset */

static gboolean
modem_reset_finish (MMIfaceModem  *self,
                    GAsyncResult  *res,
                    GError       **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
reset_operation_unsupported (GTask *task)
{
    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                             "modem reset operation is not supported");
    g_object_unref (task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
shared_qmi_reset_ready (MMIfaceModem *self,
                        GAsyncResult *res,
                        GTask        *task)
{
    g_autoptr(GError) error = NULL;

    if (!mm_shared_qmi_reset_finish (self, res, &error)) {
        mm_obj_dbg (self, "reset using QMI failed: %s", error->message);
        reset_operation_unsupported (task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

#endif

static void
reset_fallback_to_qmi_or_unsupported (GTask *task)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    MMBroadbandModemMbim   *self;

    self = g_task_get_source_object (task);
    mm_obj_dbg (self, "attempting reset using QMI...");
    mm_shared_qmi_reset (MM_IFACE_MODEM (self),
                         (GAsyncReadyCallback)shared_qmi_reset_ready,
                         task);
#else
    reset_operation_unsupported (task);
#endif
}

static void
ms_basic_connect_extensions_device_reset_set_ready (MbimDevice   *device,
                                                    GAsyncResult *res,
                                                    GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(GError)       error = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        mm_obj_dbg (self, "reset using the ms extensions device reset operation failed: %s", error->message);
        reset_fallback_to_qmi_or_unsupported (task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_reset_ms_ext (GTask      *task,
                    MbimDevice *device)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  message = NULL;

    self = g_task_get_source_object (task);

    if (!self->priv->is_ms_device_reset_supported) {
        reset_fallback_to_qmi_or_unsupported (task);
        return;
    }

    mm_obj_dbg (self, "attempting reset using the ms extensions device reset operation...");
    message = mbim_message_ms_basic_connect_extensions_device_reset_set_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)ms_basic_connect_extensions_device_reset_set_ready,
                         task);
}

static void
intel_firmware_update_modem_reboot_set_ready (MbimDevice   *device,
                                              GAsyncResult *res,
                                              GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(GError)       error = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        mm_obj_dbg (self, "reset using the intel firmware update modem reboot operation failed: %s", error->message);
        /* fallback to MS extensions reset */
        modem_reset_ms_ext (task, device);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_reset (MMIfaceModem        *_self,
             GAsyncReadyCallback  callback,
             gpointer             user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask                  *task;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_intel_reset_supported) {
        /* fallback to MS extensions reset otherwise */
        modem_reset_ms_ext (task, device);
        return;
    }

    /* This message is defined in the Intel Firmware Update service, but it
     * really is just a standard modem reboot. */
    mm_obj_dbg (self, "attempting reset using the intel firmware update modem reboot operation...");
    message = mbim_message_intel_firmware_update_modem_reboot_set_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)intel_firmware_update_modem_reboot_set_ready,
                         task);
}

/*****************************************************************************/
/* Cell info retrieval */

typedef enum {
    GET_CELL_INFO_STEP_FIRST,
    GET_CELL_INFO_STEP_RFIM,
    GET_CELL_INFO_STEP_CELL_INFO,
    GET_CELL_INFO_STEP_LAST
} GetCellInfoStep;

typedef struct {
    GetCellInfoStep step;
    GList           *rfim_info_list;
    GList           *cell_info_list;
    GError          *saved_error;
} GetCellInfoContext;

static void
get_cell_info_context_free (GetCellInfoContext *ctx)
{
    mm_rfim_info_list_free (ctx->rfim_info_list);
    g_assert (!ctx->saved_error);
    g_free (ctx);
}

static void get_cell_info_step (MbimDevice *device,
                                GTask      *task);

static GList *
modem_get_cell_info_finish (MMIfaceModem *self,
                            GAsyncResult *res,
                            GError       **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
cell_info_list_free (GList *list)
{
    g_list_free_full (list, (GDestroyNotify)g_object_unref);
}

static void
base_stations_info_query_ready (MbimDevice   *device,
                                GAsyncResult *res,
                                GTask        *task)
{
    MMBroadbandModemMbim                          *self;
    g_autoptr(MbimMessage)                         response = NULL;
    GError                                        *error = NULL;
    GList                                         *list = NULL;
    MMCellInfo                                    *info = NULL;
    g_autoptr(MbimCellInfoServingGsm)              gsm_serving_cell = NULL;
    g_autoptr(MbimCellInfoServingUmts)             umts_serving_cell = NULL;
    g_autoptr(MbimCellInfoServingTdscdma)          tdscdma_serving_cell = NULL;
    g_autoptr(MbimCellInfoServingLte)              lte_serving_cell = NULL;
    guint32                                        gsm_neighboring_cells_count = 0;
    g_autoptr(MbimCellInfoNeighboringGsmArray)     gsm_neighboring_cells = NULL;
    guint32                                        umts_neighboring_cells_count = 0;
    g_autoptr(MbimCellInfoNeighboringUmtsArray)    umts_neighboring_cells = NULL;
    guint32                                        tdscdma_neighboring_cells_count = 0;
    g_autoptr(MbimCellInfoNeighboringTdscdmaArray) tdscdma_neighboring_cells = NULL;
    guint32                                        lte_neighboring_cells_count = 0;
    g_autoptr(MbimCellInfoNeighboringLteArray)     lte_neighboring_cells = NULL;
    guint32                                        cdma_cells_count = 0;
    g_autoptr(MbimCellInfoCdmaArray)               cdma_cells = NULL;
    guint32                                        nr_serving_cells_count = 0;
    g_autoptr(MbimCellInfoServingNrArray)          nr_serving_cells = NULL;
    guint32                                        nr_neighboring_cells_count = 0;
    g_autoptr(MbimCellInfoNeighboringNrArray)      nr_neighboring_cells = NULL;
    GetCellInfoContext                             *ctx;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        ctx->saved_error = error;
        ctx->step = GET_CELL_INFO_STEP_LAST;
        get_cell_info_step (device, task);
        return;
    }

    /* MBIMEx 3.0 support */
    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_extensions_v3_base_stations_info_response_parse (
                response,
                NULL, /* system_type_v3 */
                NULL, /* system_subtype */
                &gsm_serving_cell,
                &umts_serving_cell,
                &tdscdma_serving_cell,
                &lte_serving_cell,
                &gsm_neighboring_cells_count,
                &gsm_neighboring_cells,
                &umts_neighboring_cells_count,
                &umts_neighboring_cells,
                &tdscdma_neighboring_cells_count,
                &tdscdma_neighboring_cells,
                &lte_neighboring_cells_count,
                &lte_neighboring_cells,
                &cdma_cells_count,
                &cdma_cells,
                &nr_serving_cells_count,
                &nr_serving_cells,
                &nr_neighboring_cells_count,
                &nr_neighboring_cells,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v3.0 base stations info response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v3.0 base stations info response");
    }
    /* MBIMEx 1.0 support */
    else {
        if (!mbim_message_ms_basic_connect_extensions_base_stations_info_response_parse (
                response,
                NULL, /* system_type */
                &gsm_serving_cell,
                &umts_serving_cell,
                &tdscdma_serving_cell,
                &lte_serving_cell,
                &gsm_neighboring_cells_count,
                &gsm_neighboring_cells,
                &umts_neighboring_cells_count,
                &umts_neighboring_cells,
                &tdscdma_neighboring_cells_count,
                &tdscdma_neighboring_cells,
                &lte_neighboring_cells_count,
                &lte_neighboring_cells,
                &cdma_cells_count,
                &cdma_cells,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v1.0 base stations info response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v1.0 base stations info response");
    }

    if (error) {
        ctx->saved_error = error;
        ctx->step = GET_CELL_INFO_STEP_LAST;
        get_cell_info_step (device, task);
        return;
    }

#define CELL_INFO_SET_STR(VALUE, SETTER, CELL_TYPE) do {    \
        if (VALUE) {                                        \
            mm_cell_info_##SETTER (CELL_TYPE (info), VALUE); \
        }                                                   \
    } while (0)

#define CELL_INFO_SET_HEXSTR(VALUE, UNKNOWN, MODIFIER, SETTER, CELL_TYPE) do { \
        if (VALUE != UNKNOWN) {                                                \
            g_autofree gchar *str = NULL;                                      \
                                                                               \
            str = g_strdup_printf ("%" MODIFIER "X", VALUE);                   \
            mm_cell_info_##SETTER (CELL_TYPE (info), str);                     \
        }                                                                      \
    } while (0)

#define CELL_INFO_SET_UINT(VALUE, UNKNOWN, SETTER, CELL_TYPE) do { \
        if (VALUE != UNKNOWN) {                                    \
            mm_cell_info_##SETTER (CELL_TYPE (info), VALUE);       \
        }                                                          \
    } while (0)

#define CELL_INFO_SET_INT_DOUBLE(VALUE, UNKNOWN, SETTER, CELL_TYPE) do { \
        if (VALUE != (gint)UNKNOWN) {                                    \
            mm_cell_info_##SETTER (CELL_TYPE (info), (gdouble)VALUE);    \
        }                                                                \
    } while (0)

#define CELL_INFO_SET_UINT_DOUBLE_SCALED(VALUE, UNKNOWN, SCALE, SETTER, CELL_TYPE) do { \
        if (VALUE != UNKNOWN) {                                                  \
            mm_cell_info_##SETTER (CELL_TYPE (info), (gdouble)(VALUE + SCALE));        \
        }                                                                              \
    } while (0)

    if (gsm_serving_cell) {
        info = mm_cell_info_gsm_new_from_dictionary (NULL);
        mm_cell_info_set_serving (info, TRUE);

        CELL_INFO_SET_STR    (gsm_serving_cell->provider_id,                        gsm_set_operator_id,     MM_CELL_INFO_GSM);
        CELL_INFO_SET_HEXSTR (gsm_serving_cell->location_area_code, 0xFFFFFFFF, "", gsm_set_lac,             MM_CELL_INFO_GSM);
        CELL_INFO_SET_HEXSTR (gsm_serving_cell->cell_id,            0xFFFFFFFF, "", gsm_set_ci,              MM_CELL_INFO_GSM);
        CELL_INFO_SET_UINT   (gsm_serving_cell->timing_advance,     0xFFFFFFFF,     gsm_set_timing_advance,  MM_CELL_INFO_GSM);
        CELL_INFO_SET_UINT   (gsm_serving_cell->arfcn,              0xFFFFFFFF,     gsm_set_arfcn,           MM_CELL_INFO_GSM);
        CELL_INFO_SET_HEXSTR (gsm_serving_cell->base_station_id,    0xFFFFFFFF, "", gsm_set_base_station_id, MM_CELL_INFO_GSM);
        CELL_INFO_SET_UINT   (gsm_serving_cell->rx_level,           0xFFFFFFFF,     gsm_set_rx_level,        MM_CELL_INFO_GSM);

        list = g_list_append (list, g_steal_pointer (&info));
    }

    if (gsm_neighboring_cells_count && gsm_neighboring_cells) {
        guint i;

        for (i = 0; i < gsm_neighboring_cells_count; i++) {
            info = mm_cell_info_gsm_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, FALSE);

            CELL_INFO_SET_STR    (gsm_neighboring_cells[i]->provider_id,                        gsm_set_operator_id,     MM_CELL_INFO_GSM);
            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->location_area_code, 0xFFFFFFFF, "", gsm_set_lac,             MM_CELL_INFO_GSM);
            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->cell_id,            0xFFFFFFFF, "", gsm_set_ci,              MM_CELL_INFO_GSM);
            CELL_INFO_SET_UINT   (gsm_neighboring_cells[i]->arfcn,              0xFFFFFFFF,     gsm_set_arfcn,           MM_CELL_INFO_GSM);
            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->base_station_id,    0xFFFFFFFF, "", gsm_set_base_station_id, MM_CELL_INFO_GSM);
            CELL_INFO_SET_UINT   (gsm_neighboring_cells[i]->rx_level,           0xFFFFFFFF,     gsm_set_rx_level,        MM_CELL_INFO_GSM);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (umts_serving_cell) {
        info = mm_cell_info_umts_new_from_dictionary (NULL);
        mm_cell_info_set_serving (info, TRUE);

        CELL_INFO_SET_STR        (umts_serving_cell->provider_id,                             umts_set_operator_id,      MM_CELL_INFO_UMTS);
        CELL_INFO_SET_HEXSTR     (umts_serving_cell->location_area_code,      0xFFFFFFFF, "", umts_set_lac,              MM_CELL_INFO_UMTS);
        CELL_INFO_SET_HEXSTR     (umts_serving_cell->cell_id,                 0xFFFFFFFF, "", umts_set_ci,               MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_ul,       0xFFFFFFFF,     umts_set_frequency_fdd_ul, MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_dl,       0xFFFFFFFF,     umts_set_frequency_fdd_dl, MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_nt,       0xFFFFFFFF,     umts_set_frequency_tdd,    MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->uarfcn,                  0xFFFFFFFF,     umts_set_uarfcn,           MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->primary_scrambling_code, 0xFFFFFFFF,     umts_set_psc,              MM_CELL_INFO_UMTS);
        CELL_INFO_SET_INT_DOUBLE (umts_serving_cell->rscp,                    0,              umts_set_rscp,             MM_CELL_INFO_UMTS);
        CELL_INFO_SET_INT_DOUBLE (umts_serving_cell->ecno,                    1,              umts_set_ecio,             MM_CELL_INFO_UMTS);
        CELL_INFO_SET_UINT       (umts_serving_cell->path_loss,               0xFFFFFFFF,     umts_set_path_loss,        MM_CELL_INFO_UMTS);

        list = g_list_append (list, g_steal_pointer (&info));
    }

    if (umts_neighboring_cells_count && umts_neighboring_cells) {
        guint i;

        for (i = 0; i < umts_neighboring_cells_count; i++) {
            info = mm_cell_info_umts_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, FALSE);

            CELL_INFO_SET_STR        (umts_neighboring_cells[i]->provider_id,                             umts_set_operator_id, MM_CELL_INFO_UMTS);
            CELL_INFO_SET_HEXSTR     (umts_neighboring_cells[i]->location_area_code,      0xFFFFFFFF, "", umts_set_lac,         MM_CELL_INFO_UMTS);
            CELL_INFO_SET_HEXSTR     (umts_neighboring_cells[i]->cell_id,                 0xFFFFFFFF, "", umts_set_ci,          MM_CELL_INFO_UMTS);
            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->uarfcn,                  0xFFFFFFFF,     umts_set_uarfcn,      MM_CELL_INFO_UMTS);
            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->primary_scrambling_code, 0xFFFFFFFF,     umts_set_psc,         MM_CELL_INFO_UMTS);
            CELL_INFO_SET_INT_DOUBLE (umts_neighboring_cells[i]->rscp,                    0,              umts_set_rscp,        MM_CELL_INFO_UMTS);
            CELL_INFO_SET_INT_DOUBLE (umts_neighboring_cells[i]->ecno,                    1,              umts_set_ecio,        MM_CELL_INFO_UMTS);
            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->path_loss,               0xFFFFFFFF,     umts_set_path_loss,   MM_CELL_INFO_UMTS);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (tdscdma_serving_cell) {
        info = mm_cell_info_tdscdma_new_from_dictionary (NULL);
        mm_cell_info_set_serving (info, TRUE);

        CELL_INFO_SET_STR        (tdscdma_serving_cell->provider_id,                        tdscdma_set_operator_id,       MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_HEXSTR     (tdscdma_serving_cell->location_area_code, 0xFFFFFFFF, "", tdscdma_set_lac,               MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_HEXSTR     (tdscdma_serving_cell->cell_id,            0xFFFFFFFF, "", tdscdma_set_ci,                MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_UINT       (tdscdma_serving_cell->uarfcn,             0xFFFFFFFF,     tdscdma_set_uarfcn,            MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_UINT       (tdscdma_serving_cell->cell_parameter_id,  0xFFFFFFFF,     tdscdma_set_cell_parameter_id, MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_UINT       (tdscdma_serving_cell->timing_advance,     0xFFFFFFFF,     tdscdma_set_timing_advance,    MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_INT_DOUBLE (tdscdma_serving_cell->rscp,               0xFFFFFFFF,     tdscdma_set_rscp,              MM_CELL_INFO_TDSCDMA);
        CELL_INFO_SET_UINT       (tdscdma_serving_cell->path_loss,          0xFFFFFFFF,     tdscdma_set_path_loss,         MM_CELL_INFO_TDSCDMA);

        list = g_list_append (list, g_steal_pointer (&info));
    }

    if (tdscdma_neighboring_cells_count && tdscdma_neighboring_cells) {
        guint i;

        for (i = 0; i < tdscdma_neighboring_cells_count; i++) {
            info = mm_cell_info_tdscdma_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, FALSE);

            CELL_INFO_SET_STR        (tdscdma_neighboring_cells[i]->provider_id,                        tdscdma_set_operator_id,       MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_HEXSTR     (tdscdma_neighboring_cells[i]->location_area_code, 0xFFFFFFFF, "", tdscdma_set_lac,               MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_HEXSTR     (tdscdma_neighboring_cells[i]->cell_id,            0xFFFFFFFF, "", tdscdma_set_ci,                MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->uarfcn,             0xFFFFFFFF,     tdscdma_set_uarfcn,            MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->cell_parameter_id,  0xFFFFFFFF,     tdscdma_set_cell_parameter_id, MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->timing_advance,     0xFFFFFFFF,     tdscdma_set_timing_advance,    MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_INT_DOUBLE (tdscdma_neighboring_cells[i]->rscp,               0xFFFFFFFF,     tdscdma_set_rscp,              MM_CELL_INFO_TDSCDMA);
            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->path_loss,          0xFFFFFFFF,     tdscdma_set_path_loss,         MM_CELL_INFO_TDSCDMA);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (lte_serving_cell) {
        GList *l;
        GList *next;

        info = mm_cell_info_lte_new_from_dictionary (NULL);
        mm_cell_info_set_serving (info, TRUE);

        CELL_INFO_SET_STR        (lte_serving_cell->provider_id,                         lte_set_operator_id,      MM_CELL_INFO_LTE);
        CELL_INFO_SET_HEXSTR     (lte_serving_cell->tac,                 0xFFFFFFFF, "", lte_set_tac,              MM_CELL_INFO_LTE);
        CELL_INFO_SET_HEXSTR     (lte_serving_cell->cell_id,             0xFFFFFFFF, "", lte_set_ci,               MM_CELL_INFO_LTE);
        CELL_INFO_SET_HEXSTR     (lte_serving_cell->physical_cell_id,    0xFFFFFFFF, "", lte_set_physical_ci,      MM_CELL_INFO_LTE);
        CELL_INFO_SET_UINT       (lte_serving_cell->earfcn,              0xFFFFFFFF,     lte_set_earfcn,           MM_CELL_INFO_LTE);
        CELL_INFO_SET_INT_DOUBLE (lte_serving_cell->rsrp,                0xFFFFFFFF,     lte_set_rsrp,             MM_CELL_INFO_LTE);
        CELL_INFO_SET_INT_DOUBLE (lte_serving_cell->rsrq,                0xFFFFFFFF,     lte_set_rsrq,             MM_CELL_INFO_LTE);
        CELL_INFO_SET_UINT       (lte_serving_cell->timing_advance,      0xFFFFFFFF,     lte_set_timing_advance,   MM_CELL_INFO_LTE);

        /* Update cell info with the radio frequency information received previously */
        for (l = ctx->rfim_info_list, next = NULL; l; l = next) {
            MMRfInfo *data;

            next = g_list_next (l);

            data = (MMRfInfo *)(l->data);
            if (fabs ((mm_earfcn_to_frequency (lte_serving_cell->earfcn, self)) - data->center_frequency) < FREQUENCY_TOLERANCE_HZ) {
                mm_obj_dbg (self, "Merging radio frequency data with lte serving cell info");
                CELL_INFO_SET_UINT (data->serving_cell_type, MM_SERVING_CELL_TYPE_INVALID, lte_set_serving_cell_type, MM_CELL_INFO_LTE);
                CELL_INFO_SET_UINT (data->bandwidth,         0xFFFFFFFF,                   lte_set_bandwidth,         MM_CELL_INFO_LTE);
                ctx->rfim_info_list = g_list_delete_link (ctx->rfim_info_list, l);
                mm_rf_info_free (data);
            }
        }
        list = g_list_append (list, g_steal_pointer (&info));
    }

    if (lte_neighboring_cells_count && lte_neighboring_cells) {
        guint i;

        for (i = 0; i < lte_neighboring_cells_count; i++) {
            info = mm_cell_info_lte_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, FALSE);

            CELL_INFO_SET_STR        (lte_neighboring_cells[i]->provider_id,                      lte_set_operator_id, MM_CELL_INFO_LTE);
            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->tac,              0xFFFFFFFF, "", lte_set_tac,         MM_CELL_INFO_LTE);
            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->cell_id,          0xFFFFFFFF, "", lte_set_ci,          MM_CELL_INFO_LTE);
            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->physical_cell_id, 0xFFFFFFFF, "", lte_set_physical_ci, MM_CELL_INFO_LTE);
            CELL_INFO_SET_UINT       (lte_neighboring_cells[i]->earfcn,           0xFFFFFFFF,     lte_set_earfcn,      MM_CELL_INFO_LTE);
            CELL_INFO_SET_INT_DOUBLE (lte_neighboring_cells[i]->rsrp,             0xFFFFFFFF,     lte_set_rsrp,        MM_CELL_INFO_LTE);
            CELL_INFO_SET_INT_DOUBLE (lte_neighboring_cells[i]->rsrq,             0xFFFFFFFF,     lte_set_rsrq,        MM_CELL_INFO_LTE);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (cdma_cells_count && cdma_cells) {
        guint i;

        for (i = 0; i < cdma_cells_count; i++) {
            info = mm_cell_info_cdma_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, cdma_cells[i]->serving_cell_flag);

            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->nid,              0xFFFFFFFF, "", cdma_set_nid,             MM_CELL_INFO_CDMA);
            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->sid,              0xFFFFFFFF, "", cdma_set_sid,             MM_CELL_INFO_CDMA);
            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->base_station_id,  0xFFFFFFFF, "", cdma_set_base_station_id, MM_CELL_INFO_CDMA);
            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->ref_pn,           0xFFFFFFFF, "", cdma_set_ref_pn,          MM_CELL_INFO_CDMA);
            CELL_INFO_SET_UINT       (cdma_cells[i]->pilot_strength,   0xFFFFFFFF,     cdma_set_pilot_strength,  MM_CELL_INFO_CDMA);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (nr_serving_cells_count && nr_serving_cells) {
        guint i;

        for (i = 0; i < nr_serving_cells_count; i++) {
            GList *l;
            GList *next;

            info = mm_cell_info_nr5g_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, TRUE);

            CELL_INFO_SET_STR                (nr_serving_cells[i]->provider_id,                                             nr5g_set_operator_id,      MM_CELL_INFO_NR5G);
            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->tac,              0xFFFFFFFF,         "",                nr5g_set_tac,              MM_CELL_INFO_NR5G);
            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->nci,              0xFFFFFFFFFFFFFFFF, G_GINT64_MODIFIER, nr5g_set_ci,               MM_CELL_INFO_NR5G);
            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->physical_cell_id, 0xFFFFFFFF,         "",                nr5g_set_physical_ci,      MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT               (nr_serving_cells[i]->nrarfcn,          0xFFFFFFFF,                            nr5g_set_nrarfcn,          MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->rsrp,             0xFFFFFFFF,         -156,              nr5g_set_rsrp,             MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->rsrq,             0xFFFFFFFF,         -43,               nr5g_set_rsrq,             MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->sinr,             0xFFFFFFFF,         -23,               nr5g_set_sinr,             MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT               (nr_serving_cells[i]->timing_advance,   0xFFFFFFFFFFFFFFFF,                    nr5g_set_timing_advance,   MM_CELL_INFO_NR5G);

            /* Update cell info with the radio frequency information received previously */
            for (l = ctx->rfim_info_list, next = NULL; l; l = next) {
                MMRfInfo *data;

                next = g_list_next (l);

                data = (MMRfInfo *)(l->data);
                /* Comparing the derived frequency value from NRARFCN with received center frequency data to map the NR CELL */
                if (fabs (mm_nrarfcn_to_frequency (nr_serving_cells[i]->nrarfcn, self) - data->center_frequency) < FREQUENCY_TOLERANCE_HZ) {
                    mm_obj_dbg (self, "Merging radio frequency data with 5gnr serving cell info");
                    CELL_INFO_SET_UINT (data->serving_cell_type, MM_SERVING_CELL_TYPE_INVALID, nr5g_set_serving_cell_type, MM_CELL_INFO_NR5G);
                    CELL_INFO_SET_UINT (data->bandwidth,         0xFFFFFFFF,                   nr5g_set_bandwidth,         MM_CELL_INFO_NR5G);
                    ctx->rfim_info_list = g_list_delete_link (ctx->rfim_info_list, l);
                    mm_rf_info_free (data);
                }
            }
            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

    if (nr_neighboring_cells_count && nr_neighboring_cells) {
        guint i;

        for (i = 0; i < nr_neighboring_cells_count; i++) {
            info = mm_cell_info_nr5g_new_from_dictionary (NULL);
            mm_cell_info_set_serving (info, FALSE);

            CELL_INFO_SET_STR                (nr_neighboring_cells[i]->provider_id,                        nr5g_set_operator_id, MM_CELL_INFO_NR5G);
            CELL_INFO_SET_HEXSTR             (nr_neighboring_cells[i]->tac,              0xFFFFFFFF, "",   nr5g_set_tac,         MM_CELL_INFO_NR5G);
            CELL_INFO_SET_STR                (nr_neighboring_cells[i]->cell_id,                            nr5g_set_ci,          MM_CELL_INFO_NR5G);
            CELL_INFO_SET_HEXSTR             (nr_neighboring_cells[i]->physical_cell_id, 0xFFFFFFFF, "",   nr5g_set_physical_ci, MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->rsrp,             0xFFFFFFFF, -156, nr5g_set_rsrp,        MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->rsrq,             0xFFFFFFFF, -43,  nr5g_set_rsrq,        MM_CELL_INFO_NR5G);
            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->sinr,             0xFFFFFFFF, -23,  nr5g_set_sinr,        MM_CELL_INFO_NR5G);

            list = g_list_append (list, g_steal_pointer (&info));
        }
    }

#undef CELL_INFO_SET_STR
#undef CELL_INFO_SET_HEXSTR
#undef CELL_INFO_SET_UINT
#undef CELL_INFO_SET_INT_DOUBLE
#undef CELL_INFO_SET_UINT_DOUBLE_SCALED

    ctx->cell_info_list = list;
    ctx->step++;
    get_cell_info_step (device, task);
}

static void
check_rfim_query_ready (MbimDevice   *device,
                        GAsyncResult *res,
                        GTask        *task)
{
    g_autoptr(MbimMessage)            response = NULL;
    MbimIntelRfimFrequencyValueArray  *freq_info;
    guint                             freq_count;
    GetCellInfoContext                *ctx;
    MMBroadbandModemMbim              *self;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, NULL);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) &&
        mbim_message_intel_thermal_rf_rfim_response_parse (response,
                                                           &freq_count,
                                                           &freq_info,
                                                           NULL)) {

        ctx->rfim_info_list = mm_rfim_info_list_from_mbim_intel_rfim_frequency_value_array (freq_info,
                                                                                            freq_count,
                                                                                            self);
        mbim_intel_rfim_frequency_value_array_free (freq_info);
    } else {
        mm_obj_dbg (self, "Fetching of bandwidth and serving cell type data failed");
    }
    ctx->step++;
    get_cell_info_step (device, task);
}

static void
get_cell_info_step (MbimDevice *device,
                    GTask      *task)
{
    MMBroadbandModemMbim    *self;
    GetCellInfoContext      *ctx;
    g_autoptr(MbimMessage)  message = NULL;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    /* Don't run new steps if we're cancelled */
    if (g_task_return_error_if_cancelled (task)) {
        g_object_unref (task);
        return;
    }

    switch (ctx->step) {
    case GET_CELL_INFO_STEP_FIRST:
    if (!self->priv->is_base_stations_info_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "base stations info is not supported");
        g_object_unref (task);
        return;
    }
    ctx->step++;

    /* fall through */
    case GET_CELL_INFO_STEP_RFIM: {
        mm_obj_dbg (self, "Obtaining RFIM data...");
        message = mbim_message_intel_thermal_rf_rfim_query_new (NULL);
        mbim_device_command (device,
                             message,
                             10,
                             NULL,
                             (GAsyncReadyCallback)check_rfim_query_ready,
                             task);
        return;
    }

    case GET_CELL_INFO_STEP_CELL_INFO: {
        mm_obj_dbg (self, "Obtaining cell info...");
        /* Default capacity is 15 */
        if (mbim_device_check_ms_mbimex_version (device, 3, 0))
            message = mbim_message_ms_basic_connect_extensions_v3_base_stations_info_query_new (15, 15, 15, 15, 15, 15, NULL);
        else
            message = mbim_message_ms_basic_connect_extensions_base_stations_info_query_new (15, 15, 15, 15, 15, NULL);

        mbim_device_command (device,
                             message,
                             300,
                             NULL,
                             (GAsyncReadyCallback)base_stations_info_query_ready,
                             task);
        return;
    }

    case GET_CELL_INFO_STEP_LAST:
        if (ctx->saved_error)
            g_task_return_error (task, g_steal_pointer (&ctx->saved_error));
        else if (ctx->cell_info_list)
            g_task_return_pointer (task, ctx->cell_info_list, (GDestroyNotify)cell_info_list_free);
        else
            g_assert_not_reached ();
        g_object_unref (task);
        return;

    default:
        break;
    }

    g_assert_not_reached ();
}


static void
modem_get_cell_info (MMIfaceModem        *_self,
                     GAsyncReadyCallback callback,
                     gpointer            user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice           *device;
    GTask                *task;
    GetCellInfoContext   *ctx;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);
    ctx = g_new0 (GetCellInfoContext, 1);
    ctx->step = GET_CELL_INFO_STEP_FIRST;
    g_task_set_task_data (task, ctx, (GDestroyNotify)get_cell_info_context_free);
    get_cell_info_step (device, task);
}

/*****************************************************************************/
/* Create Bearer (Modem interface) */

static MMBaseBearer *
modem_create_bearer_finish (MMIfaceModem  *self,
                            GAsyncResult  *res,
                            GError       **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_create_bearer (MMIfaceModem        *self,
                     MMBearerProperties  *properties,
                     GAsyncReadyCallback  callback,
                     gpointer             user_data)
{
    MMBaseBearer *bearer;
    GTask        *task;

    /* Note: the session id to be used by the bearer will always be 0
     * for non-multiplexed sessions, bound to the non-VLAN-tagged traffic
     * managed by the main network interface */

    task = g_task_new (self, NULL, callback, user_data);
    mm_obj_dbg (self, "creating MBIM bearer in MBIM modem");
    bearer = mm_bearer_mbim_new (MM_BROADBAND_MODEM_MBIM (self), properties);
    g_task_return_pointer (task, bearer, g_object_unref);
    g_object_unref (task);
}

/*****************************************************************************/
/* Create Bearer List (Modem interface) */

static MMBearerList *
modem_create_bearer_list (MMIfaceModem *self)
{
    MMPortMbim *port;
    guint       n;
    guint       n_multiplexed;

    /* The maximum number of available/connected modems is guessed from
     * the size of the data ports list. */
    n = g_list_length (mm_base_modem_peek_data_ports (MM_BASE_MODEM (self)));
    mm_obj_dbg (self, "allowed up to %u active bearers", n);

    /* The maximum number of multiplexed links is defined by the MBIM protocol */
    n_multiplexed = (MBIM_DEVICE_SESSION_ID_MAX - MBIM_DEVICE_SESSION_ID_MIN + 1);
    mm_obj_dbg (self, "allowed up to %u active multiplexed bearers", n_multiplexed);

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (port &&
        mm_kernel_device_has_global_property (mm_port_peek_kernel_device (MM_PORT (port)),
                                              ID_MM_MAX_MULTIPLEXED_LINKS)) {
        guint n_multiplexed_limited;

        n_multiplexed_limited = mm_kernel_device_get_global_property_as_int (
            mm_port_peek_kernel_device (MM_PORT (port)),
            ID_MM_MAX_MULTIPLEXED_LINKS);
        if (n_multiplexed_limited < n_multiplexed) {
            n_multiplexed = n_multiplexed_limited;
            mm_obj_dbg (self, "limited to %u active multiplexed bearers", n_multiplexed);
        }
    }

    /* by default, no multiplexing support */
    return mm_bearer_list_new (n, n_multiplexed);
}

/*****************************************************************************/
/* Create SIM (Modem interface) */

static MMBaseSim *
create_sim_finish (MMIfaceModem *self,
                   GAsyncResult *res,
                   GError **error)
{
    return mm_sim_mbim_new_finish (res, error);
}

static void
create_sim (MMIfaceModem *self,
            GAsyncReadyCallback callback,
            gpointer user_data)
{
    /* New MBIM SIM */
    mm_sim_mbim_new (MM_BASE_MODEM (self),
                    NULL, /* cancellable */
                    callback,
                    user_data);
}

/*****************************************************************************/
/* Reset data interfaces during initialization */

typedef struct {
    GList      *ports;
    MMPort     *data;
    MMPortMbim *mbim;
} ResetPortsContext;

static void
reset_ports_context_free (ResetPortsContext *ctx)
{
    g_assert (!ctx->data);
    g_assert (!ctx->mbim);
    g_list_free_full (ctx->ports, g_object_unref);
    g_slice_free (ResetPortsContext, ctx);
}

static gboolean
reset_ports_finish (MMBroadbandModemMbim  *self,
                    GAsyncResult          *res,
                    GError               **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void reset_next_port (GTask *task);

static void
port_mbim_reset_ready (MMPortMbim   *mbim,
                       GAsyncResult *res,
                       GTask        *task)
{
    MMBroadbandModemMbim *self;
    ResetPortsContext   *ctx;
    g_autoptr(GError)    error = NULL;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    if (!mm_port_mbim_reset_finish (mbim, res, &error))
        mm_obj_warn (self, "couldn't reset MBIM port '%s' with data interface '%s': %s",
                     mm_port_get_device (MM_PORT (ctx->mbim)),
                     mm_port_get_device (ctx->data),
                     error->message);

    g_clear_object (&ctx->data);
    g_clear_object (&ctx->mbim);
    reset_next_port (task);
}

static void
reset_next_port (GTask *task)
{
    MMBroadbandModemMbim *self;
    ResetPortsContext    *ctx;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    if (!ctx->ports) {
        g_task_return_boolean (task, TRUE);
        g_object_unref (task);
        return;
    }

    /* steal full data port reference from list head */
    ctx->data = ctx->ports->data;
    ctx->ports = g_list_delete_link (ctx->ports, ctx->ports);

    ctx->mbim = mm_broadband_modem_mbim_get_port_mbim_for_data (self, ctx->data, NULL);
    if (!ctx->mbim) {
        mm_obj_dbg (self, "no MBIM port associated to data port '%s': ignoring data interface reset",
                    mm_port_get_device (ctx->data));
        g_clear_object (&ctx->data);
        reset_next_port (task);
        return;
    }

    mm_obj_dbg (self, "running MBIM port '%s' reset with data interface '%s'",
                mm_port_get_device (MM_PORT (ctx->mbim)), mm_port_get_device (ctx->data));

    mm_port_mbim_reset (ctx->mbim,
                        ctx->data,
                        (GAsyncReadyCallback) port_mbim_reset_ready,
                        task);
}

static void
reset_ports (MMBroadbandModemMbim *self,
             GAsyncReadyCallback   callback,
             gpointer              user_data)
{
    GTask             *task;
    ResetPortsContext *ctx;

    task = g_task_new (self, NULL, callback, user_data);
    ctx = g_slice_new0 (ResetPortsContext);
    g_task_set_task_data (task, ctx, (GDestroyNotify)reset_ports_context_free);

    ctx->ports = mm_base_modem_find_ports (MM_BASE_MODEM (self),
                                           MM_PORT_SUBSYS_UNKNOWN,
                                           MM_PORT_TYPE_NET);

    reset_next_port (task);
}

/*****************************************************************************/
/* First enabling step */

static gboolean
enabling_started_finish (MMBroadbandModem *self,
                         GAsyncResult *res,
                         GError **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
parent_enabling_started_ready (MMBroadbandModem *self,
                               GAsyncResult *res,
                               GTask *task)
{
    GError *error = NULL;

    if (!MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_parent_class)->enabling_started_finish (
            self,
            res,
            &error)) {
        /* Don't treat this as fatal. Parent enabling may fail if it cannot grab a primary
         * AT port, which isn't really an issue in MBIM-based modems */
        mm_obj_dbg (self, "couldn't start parent enabling: %s", error->message);
        g_error_free (error);
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
enabling_started (MMBroadbandModem *self,
                  GAsyncReadyCallback callback,
                  gpointer user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_parent_class)->enabling_started (
        self,
        (GAsyncReadyCallback)parent_enabling_started_ready,
        task);
}

/*****************************************************************************/
/* First initialization step */

typedef struct {
    MMPortMbim *mbim;
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    guint       qmi_service_index;
#endif
} InitializationStartedContext;

static void
initialization_started_context_free (InitializationStartedContext *ctx)
{
    if (ctx->mbim)
        g_object_unref (ctx->mbim);
    g_slice_free (InitializationStartedContext, ctx);
}

static gpointer
initialization_started_finish (MMBroadbandModem  *self,
                               GAsyncResult      *res,
                               GError           **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
parent_initialization_started_ready (MMBroadbandModem *self,
                                     GAsyncResult     *res,
                                     GTask            *task)
{
    gpointer parent_ctx;
    GError *error = NULL;

    parent_ctx = MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_parent_class)->initialization_started_finish (
        self,
        res,
        &error);
    if (error) {
        /* Don't treat this as fatal. Parent initialization may fail if it cannot grab a primary
         * AT port, which isn't really an issue in MBIM-based modems */
        mm_obj_dbg (self, "couldn't start parent initialization: %s", error->message);
        g_error_free (error);
    }

    /* Just parent's pointer passed here */
    g_task_return_pointer (task, parent_ctx, NULL);
    g_object_unref (task);
}

static void
parent_initialization_started (GTask *task)
{
    MMBroadbandModem *self;

    self = g_task_get_source_object (task);
    MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_parent_class)->initialization_started (
        self,
        (GAsyncReadyCallback)parent_initialization_started_ready,
        task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static const QmiService qmi_services[] = {
    QMI_SERVICE_DMS,
    QMI_SERVICE_NAS,
    QMI_SERVICE_PDS,
    QMI_SERVICE_LOC,
    QMI_SERVICE_PDC,
    QMI_SERVICE_UIM,
};

static void allocate_next_qmi_client (GTask *task);

static void
mbim_port_allocate_qmi_client_ready (MMPortMbim   *mbim,
                                     GAsyncResult *res,
                                     GTask        *task)
{
    MMBroadbandModemMbim         *self;
    InitializationStartedContext *ctx;
    GError                       *error = NULL;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    if (!mm_port_mbim_allocate_qmi_client_finish (mbim, res, &error)) {
        mm_obj_dbg (self, "couldn't allocate QMI client for service '%s': %s",
                qmi_service_get_string (qmi_services[ctx->qmi_service_index]),
                error->message);
        g_error_free (error);
    }

    ctx->qmi_service_index++;
    allocate_next_qmi_client (task);
}

static void
allocate_next_qmi_client (GTask *task)
{
    InitializationStartedContext *ctx;

    ctx = g_task_get_task_data (task);

    if (ctx->qmi_service_index == G_N_ELEMENTS (qmi_services)) {
        parent_initialization_started (task);
        return;
    }

    /* Otherwise, allocate next client */
    mm_port_mbim_allocate_qmi_client (ctx->mbim,
                                      qmi_services[ctx->qmi_service_index],
                                      NULL,
                                      (GAsyncReadyCallback)mbim_port_allocate_qmi_client_ready,
                                      task);
}

#endif

static void
query_device_services_ready (MbimDevice   *device,
                             GAsyncResult *res,
                             GTask        *task)
{
    MMBroadbandModemMbim *self;
    MbimMessage *response;
    GError *error = NULL;
    MbimDeviceServiceElement **device_services;
    guint32 device_services_count;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_device_services_response_parse (
            response,
            &device_services_count,
            NULL, /* max_dss_sessions */
            &device_services,
            &error)) {
        guint32 i;

        for (i = 0; i < device_services_count; i++) {
            MbimService service;
            guint32     j;

            service = mbim_uuid_to_service (&device_services[i]->device_service_id);

            if (service == MBIM_SERVICE_BASIC_CONNECT) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_BASIC_CONNECT_PROVISIONED_CONTEXTS) {
                        mm_obj_dbg (self, "Profile management is supported");
                        self->priv->is_profile_management_supported = TRUE;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_MS_BASIC_CONNECT_EXTENSIONS) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_PCO) {
                        mm_obj_dbg (self, "PCO is supported");
                        self->priv->is_pco_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_LTE_ATTACH_INFO) {
                        mm_obj_dbg (self, "LTE attach info is supported");
                        self->priv->is_lte_attach_info_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_SLOT_INFO_STATUS) {
                        mm_obj_dbg (self, "Slot info status is supported");
                        self->priv->is_slot_info_status_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_REGISTRATION_PARAMETERS) {
                        mm_obj_dbg (self, "5GNR registration settings are supported");
                        self->priv->is_nr5g_registration_settings_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_BASE_STATIONS_INFO) {
                        mm_obj_dbg (self, "Base stations info is supported");
                        self->priv->is_base_stations_info_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_PROVISIONED_CONTEXTS) {
                        mm_obj_dbg (self, "Context types extension is supported");
                        self->priv->is_context_type_ext_supported = TRUE;
                        if (mm_context_get_test_mbimex_profile_management ()) {
                            mm_obj_dbg (self, "Profile management extension is supported");
                            self->priv->is_profile_management_ext_supported = TRUE;
                        } else
                            mm_obj_dbg (self, "Profile management extension is supported but not allowed");
                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_DEVICE_RESET) {
                        mm_obj_dbg (self, "Device reset is supported");
                        self->priv->is_ms_device_reset_supported = TRUE;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_USSD) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_USSD) {
                        mm_obj_dbg (self, "USSD is supported");
                        self->priv->is_ussd_supported = TRUE;
                        break;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_ATDS) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_ATDS_LOCATION) {
                        mm_obj_dbg (self, "ATDS location is supported");
                        self->priv->is_atds_location_supported = TRUE;
                    } else if (device_services[i]->cids[j] == MBIM_CID_ATDS_SIGNAL) {
                        mm_obj_dbg (self, "ATDS signal is supported");
                        self->priv->is_atds_signal_supported = TRUE;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_INTEL_FIRMWARE_UPDATE) {
                if (self->priv->intel_firmware_update_unsupported) {
                    mm_obj_dbg (self, "Intel firmware update service is explicitly ignored");
                    continue;
                }
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_INTEL_FIRMWARE_UPDATE_MODEM_REBOOT) {
                        mm_obj_dbg (self, "Intel reset is supported");
                        self->priv->is_intel_reset_supported = TRUE;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_MS_SAR) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_MS_SAR_CONFIG) {
                        mm_obj_dbg (self, "SAR is supported");
                        self->priv->is_ms_sar_supported = TRUE;
                    }
                }
                continue;
            }

            if (service == MBIM_SERVICE_GOOGLE) {
                for (j = 0; j < device_services[i]->cids_count; j++) {
                    if (device_services[i]->cids[j] == MBIM_CID_GOOGLE_CARRIER_LOCK) {
                        mm_obj_dbg (self, "Google carrier lock is supported");
                        self->priv->is_google_carrier_lock_supported = TRUE;
                    }
                }
                continue;
            }

            /* no optional features to check in remaining services */
        }
        mbim_device_service_element_array_free (device_services);
    } else {
        /* Ignore error */
        mm_obj_warn (self, "couldn't query device services: %s", error->message);
        g_error_free (error);
    }

    if (response)
        mbim_message_unref (response);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    allocate_next_qmi_client (task);
#else
    parent_initialization_started (task);
#endif
}

static void
query_device_services (GTask *task)
{
    MMBroadbandModem *self;
    InitializationStartedContext *ctx;
    MbimMessage *message;
    MbimDevice *device;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    device = mm_port_mbim_peek_device (ctx->mbim);
    g_assert (device);

    mm_obj_dbg (self, "querying device services...");

    message = mbim_message_device_services_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)query_device_services_ready,
                         task);
    mbim_message_unref (message);
}

static void
mbim_port_open_ready (MMPortMbim   *mbim,
                      GAsyncResult *res,
                      GTask        *task)
{
    GError *error = NULL;

    if (!mm_port_mbim_open_finish (mbim, res, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    query_device_services (task);
}

static void
initialization_open_port (GTask *task)
{
    InitializationStartedContext *ctx;
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    MMBroadbandModemMbim         *self;
    gboolean                      qmi_unsupported = FALSE;
#endif

    ctx = g_task_get_task_data (task);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    self = g_task_get_source_object (task);
    g_object_get (self,
                  MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, &qmi_unsupported,
                  NULL);
#endif

    /* Now open our MBIM port */
    mm_port_mbim_open (ctx->mbim,
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
                       ! qmi_unsupported, /* With QMI over MBIM support if available */
#endif
                       NULL,
                       (GAsyncReadyCallback)mbim_port_open_ready,
                       task);
}

static void
reset_ports_ready (MMBroadbandModemMbim *self,
                   GAsyncResult         *res,
                   GTask                *task)
{
    GError *error = NULL;

    if (!reset_ports_finish (self, res, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    initialization_open_port (task);
}

static void
initialization_reset_ports (GTask *task)
{
    MMBroadbandModemMbim *self;

    self = g_task_get_source_object (task);

    /* resetting the data interfaces is really only needed if the device
     * hasn't been hotplugged */
    if (mm_base_modem_get_hotplugged (MM_BASE_MODEM (self))) {
        mm_obj_dbg (self, "not running data interface reset procedure: device is hotplugged");
        initialization_open_port (task);
        return;
    }

    reset_ports (self, (GAsyncReadyCallback)reset_ports_ready, task);
}

static void
initialization_started (MMBroadbandModem    *self,
                        GAsyncReadyCallback  callback,
                        gpointer             user_data)
{
    InitializationStartedContext *ctx;
    GTask                        *task;

    ctx = g_slice_new0 (InitializationStartedContext);
    ctx->mbim = mm_broadband_modem_mbim_get_port_mbim (MM_BROADBAND_MODEM_MBIM (self));

    task = g_task_new (self, NULL, callback, user_data);
    g_task_set_task_data (task, ctx, (GDestroyNotify)initialization_started_context_free);

    /* This may happen if we unplug the modem unexpectedly */
    if (!ctx->mbim) {
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Cannot initialize: MBIM port went missing");
        g_object_unref (task);
        return;
    }

    if (mm_port_mbim_is_open (ctx->mbim)) {
        /* Nothing to be done, just connect to a signal and launch parent's
         * callback */
        query_device_services (task);
        return;
    }

    initialization_reset_ports (task);
}

/*****************************************************************************/
/* IMEI loading (3GPP interface) */

static gchar *
modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self,
                             GAsyncResult *res,
                             GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_3gpp_load_imei (MMIfaceModem3gpp *_self,
                      GAsyncReadyCallback callback,
                      gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->caps_device_id)
        g_task_return_pointer (task,
                               g_strdup (self->priv->caps_device_id),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Device doesn't report a valid IMEI");
    g_object_unref (task);
}

/*****************************************************************************/
/* Facility locks status loading (3GPP interface) */

typedef struct {
    MMModem3gppFacility facilities;
} LoadEnabledFacilityLocksContext;

static MMModem3gppFacility
modem_3gpp_load_enabled_facility_locks_finish (MMIfaceModem3gpp *self,
                                               GAsyncResult *res,
                                               GError **error)
{
    GError *inner_error = NULL;
    gssize value;

    value = g_task_propagate_int (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return MM_MODEM_3GPP_FACILITY_NONE;
    }
    return (MMModem3gppFacility)value;
}

static void
pin_list_query_ready (MbimDevice *device,
                      GAsyncResult *res,
                      GTask *task)
{
    LoadEnabledFacilityLocksContext *ctx;
    MbimMessage *response;
    GError *error = NULL;
    MbimPinDesc *pin_desc_pin1;
    MbimPinDesc *pin_desc_pin2;
    MbimPinDesc *pin_desc_device_sim_pin;
    MbimPinDesc *pin_desc_device_first_sim_pin;
    MbimPinDesc *pin_desc_network_pin;
    MbimPinDesc *pin_desc_network_subset_pin;
    MbimPinDesc *pin_desc_service_provider_pin;
    MbimPinDesc *pin_desc_corporate_pin;

    ctx = g_task_get_task_data (task);
    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_pin_list_response_parse (
            response,
            &pin_desc_pin1,
            &pin_desc_pin2,
            &pin_desc_device_sim_pin,
            &pin_desc_device_first_sim_pin,
            &pin_desc_network_pin,
            &pin_desc_network_subset_pin,
            &pin_desc_service_provider_pin,
            &pin_desc_corporate_pin,
            NULL, /* pin_desc_subsidy_lock */
            NULL, /* pin_desc_custom */
            &error)) {
        MMModem3gppFacility mask = ctx->facilities;

        if (pin_desc_pin1->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_SIM;
        mbim_pin_desc_free (pin_desc_pin1);

        if (pin_desc_pin2->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_FIXED_DIALING;
        mbim_pin_desc_free (pin_desc_pin2);

        if (pin_desc_device_sim_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_PH_SIM;
        mbim_pin_desc_free (pin_desc_device_sim_pin);

        if (pin_desc_device_first_sim_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_PH_FSIM;
        mbim_pin_desc_free (pin_desc_device_first_sim_pin);

        if (pin_desc_network_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_NET_PERS;
        mbim_pin_desc_free (pin_desc_network_pin);

        if (pin_desc_network_subset_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_NET_SUB_PERS;
        mbim_pin_desc_free (pin_desc_network_subset_pin);

        if (pin_desc_service_provider_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_PROVIDER_PERS;
        mbim_pin_desc_free (pin_desc_service_provider_pin);

        if (pin_desc_corporate_pin->pin_mode == MBIM_PIN_MODE_ENABLED)
            mask |= MM_MODEM_3GPP_FACILITY_CORP_PERS;
        mbim_pin_desc_free (pin_desc_corporate_pin);

        g_task_return_int (task, mask);
    } else
        g_task_return_error (task, error);

    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
load_enabled_facility_pin_query_ready (MbimDevice *device,
                                       GAsyncResult *res,
                                       GTask *task)
{
    LoadEnabledFacilityLocksContext *ctx;
    MbimMessage *response;
    MbimMessage *message;
    MbimPinType pin_type;
    MbimPinState pin_state;
    MMBroadbandModemMbim *self;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);
    response = mbim_device_command_finish (device, res, NULL);
    if (response) {
        if (mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) &&
            mbim_message_pin_response_parse (response, &pin_type, &pin_state, NULL, NULL)) {
            if (pin_state == MBIM_PIN_STATE_LOCKED)
                ctx->facilities |= mm_modem_3gpp_facility_from_mbim_pin_type (pin_type);

            self->priv->enabled_cache.last_pin_type = pin_type;
        }

        mbim_message_unref (response);
    }

    message = mbim_message_pin_list_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)pin_list_query_ready,
                         task);
    mbim_message_unref (message);
}

static void
modem_3gpp_load_enabled_facility_locks (MMIfaceModem3gpp *self,
                                        GAsyncReadyCallback callback,
                                        gpointer user_data)
{
    LoadEnabledFacilityLocksContext *ctx;
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);
    ctx = g_new0 (LoadEnabledFacilityLocksContext, 1);
    g_task_set_task_data (task, ctx, g_free);

    /* The PIN LIST command returns status of pin locks but omits PUK locked
     * facilities. To workaround this limitation additional PIN command query
     * was added to get currently active PIN or PUK lock.
     */
    message = mbim_message_pin_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)load_enabled_facility_pin_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Facility locks disabling (3GPP interface) */

typedef struct _DisableFacilityLockContext DisableFacilityLockContext;
struct _DisableFacilityLockContext {
    MbimPinType pin_type;
};

static gboolean
modem_3gpp_disable_facility_lock_finish (MMIfaceModem3gpp *self,
                                         GAsyncResult *res,
                                         GError **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
disable_facility_lock_ready (MbimDevice *device,
                             GAsyncResult *res,
                             GTask *task)
{
    DisableFacilityLockContext *ctx;
    MbimMessage *response = NULL;
    guint32 remaining_attempts;
    MbimPinState pin_state;
    MbimPinType pin_type;
    GError *error = NULL;

    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response,
                                                        MBIM_MESSAGE_TYPE_COMMAND_DONE,
                                                        &error)) {
        g_task_return_error (task, error);
    } else if (!mbim_message_pin_response_parse (response,
                                                 &pin_type,
                                                 &pin_state,
                                                 &remaining_attempts,
                                                 &error)) {
        g_task_return_error (task, error);
    } else if (pin_type == ctx->pin_type &&
               pin_state == MBIM_PIN_STATE_LOCKED) {
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Disabling PIN lock %s failed, remaining attempts: %u",
                                 mbim_pin_state_get_string (pin_state),
                                 remaining_attempts);
    } else
        g_task_return_boolean (task, TRUE);

    if (response)
        mbim_message_unref (response);
    g_object_unref (task);
}

static void
modem_3gpp_disable_facility_lock (MMIfaceModem3gpp *self,
                                  MMModem3gppFacility facility,
                                  guint8 slot,
                                  const gchar *key,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data)
{
    DisableFacilityLockContext *ctx;
    MbimMessage *message;
    MbimPinType pin_type;
    MbimDevice *device;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    /* Set type of pin lock to disable */
    pin_type = mbim_pin_type_from_mm_modem_3gpp_facility (facility);
    if (pin_type == MBIM_PIN_TYPE_UNKNOWN) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                                 "Not supported type of facility lock.");
        g_object_unref (task);
        return;
    }

    mm_obj_dbg (self, "Trying to disable %s lock using key: %s",
                mbim_pin_type_get_string (pin_type), key);

    ctx = g_new0 (DisableFacilityLockContext, 1);
    ctx->pin_type = pin_type;
    g_task_set_task_data (task, ctx, g_free);

    message = mbim_message_pin_set_new (pin_type,
                                        MBIM_PIN_OPERATION_DISABLE,
                                        key,
                                        NULL,
                                        NULL);

    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)disable_facility_lock_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Initial EPS bearer info loading */

static MMBearerProperties *
modem_3gpp_load_initial_eps_bearer_finish (MMIfaceModem3gpp  *self,
                                           GAsyncResult      *res,
                                           GError           **error)
{
    return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
}

static MMBearerProperties *
common_process_lte_attach_info (MMBroadbandModemMbim  *self,
                                guint32                lte_attach_state,
                                guint32                ip_type,
                                const gchar           *access_string,
                                const gchar           *user_name,
                                const gchar           *password,
                                guint32                compression,
                                guint32                auth_protocol,
                                GError               **error)
{
    MMBearerProperties  *properties;
    MMBearerIpFamily     ip_family;
    MMBearerAllowedAuth  auth;

    /* Remove LTE attach bearer info */
    if (lte_attach_state == MBIM_LTE_ATTACH_STATE_DETACHED) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Not attached to LTE");
        return NULL;
    }

    properties = mm_bearer_properties_new ();
    if (access_string)
        mm_bearer_properties_set_apn (properties, access_string);
    if (user_name)
        mm_bearer_properties_set_user (properties, user_name);
    if (password)
        mm_bearer_properties_set_password (properties, password);

    ip_family = mm_bearer_ip_family_from_mbim_context_ip_type (ip_type);
    if (ip_family != MM_BEARER_IP_FAMILY_NONE)
        mm_bearer_properties_set_ip_type (properties, ip_family);

    auth = mm_bearer_allowed_auth_from_mbim_auth_protocol (auth_protocol);
    if (auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN)
        mm_bearer_properties_set_allowed_auth (properties, auth);

    /* note: we don't expose compression settings */

    return properties;
}

static void
lte_attach_info_query_ready (MbimDevice   *device,
                             GAsyncResult *res,
                             GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;
    MMBearerProperties     *properties;
    guint32                 lte_attach_state;
    guint32                 ip_type;
    g_autofree gchar       *access_string = NULL;
    g_autofree gchar       *user_name = NULL;
    g_autofree gchar       *password = NULL;
    guint32                 compression;
    guint32                 auth_protocol;
    MbimNwError             nw_error = 0;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_extensions_v3_lte_attach_info_response_parse (
                response,
                &lte_attach_state,
                &nw_error,
                &ip_type,
                &access_string,
                &user_name,
                &password,
                &compression,
                &auth_protocol,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v3.0 LTE attach info response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v3.0 LTE attach info response");
    } else {
        if (!mbim_message_ms_basic_connect_extensions_lte_attach_info_response_parse (
                response,
                &lte_attach_state,
                &ip_type,
                &access_string,
                &user_name,
                &password,
                &compression,
                &auth_protocol,
                &error))
            g_prefix_error (&error, "Failed processing LTE attach info response: ");
        else
            mm_obj_dbg (self, "processed LTE attach info response");
    }

    properties = common_process_lte_attach_info (self,
                                                 lte_attach_state,
                                                 ip_type,
                                                 access_string,
                                                 user_name,
                                                 password,
                                                 compression,
                                                 auth_protocol,
                                                 &error);
    g_assert (properties || error);

    if (!error) {
        /* If network error is reported, then log it */
        if (nw_error) {
            const gchar *nw_error_str;

            nw_error = mm_broadband_modem_mbim_normalize_nw_error (self, nw_error);
            nw_error_str = mbim_nw_error_get_string (nw_error);
            if (nw_error_str)
                mm_obj_dbg (self, "LTE attach info network error reported: %s", nw_error_str);
            else
                mm_obj_dbg (self, "LTE attach info network error reported: 0x%x", nw_error);
        }
        g_task_return_pointer (task, properties, g_object_unref);
    } else {
        g_task_return_error (task, error);
    }
    g_object_unref (task);
}

static void
modem_3gpp_load_initial_eps_bearer (MMIfaceModem3gpp    *_self,
                                    GAsyncReadyCallback  callback,
                                    gpointer             user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice           *device;
    MbimMessage          *message;
    GTask                *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_lte_attach_info_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "LTE attach status is unsupported");
        g_object_unref (task);
        return;
    }

    message = mbim_message_ms_basic_connect_extensions_lte_attach_info_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)lte_attach_info_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Initial EPS bearer settings loading */

static MMBearerProperties *
modem_3gpp_load_initial_eps_bearer_settings_finish (MMIfaceModem3gpp  *self,
                                                    GAsyncResult      *res,
                                                    GError           **error)
{
    return MM_BEARER_PROPERTIES (g_task_propagate_pointer (G_TASK (res), error));
}

static MMBearerProperties *
common_process_lte_attach_configuration (MMBroadbandModemMbim              *self,
                                         const MbimLteAttachConfiguration  *config,
                                         GError                           **error)
{
    MMBearerProperties  *properties;
    MMBearerIpFamily     ip_family = MM_BEARER_IP_FAMILY_NONE;
    MMBearerAllowedAuth  auth;

    properties = mm_bearer_properties_new ();
    if (config->access_string)
        mm_bearer_properties_set_apn (properties, config->access_string);
    if (config->user_name)
        mm_bearer_properties_set_user (properties, config->user_name);
    if (config->password)
        mm_bearer_properties_set_password (properties, config->password);

    ip_family = mm_bearer_ip_family_from_mbim_context_ip_type (config->ip_type);
    if (ip_family != MM_BEARER_IP_FAMILY_NONE)
        mm_bearer_properties_set_ip_type (properties, ip_family);

    auth = mm_bearer_allowed_auth_from_mbim_auth_protocol (config->auth_protocol);
    if (auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN)
        mm_bearer_properties_set_allowed_auth (properties, auth);

    /* note: we don't expose compression settings or the configuration source details */

    return properties;
}

static const MbimLteAttachConfiguration *
lte_attach_configuration_response_detect_home_settings (MMBroadbandModemMbim                    *self,
                                                        const MbimLteAttachConfiguration *const *configurations,
                                                        guint32                                  n_configurations,
                                                        const MbimLteAttachConfiguration       **out_partner,
                                                        const MbimLteAttachConfiguration       **out_non_partner,
                                                        GError                                 **error)
{
    const MbimLteAttachConfiguration *home = NULL;
    guint32                           i;

    if (out_partner)
        *out_partner = NULL;
    if (out_non_partner)
        *out_non_partner = NULL;

    for (i = 0; i < n_configurations; i++) {
        switch (configurations[i]->roaming) {
            case MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_HOME:
                if (home)
                    mm_obj_dbg (self, "duplicated 'home' settings found at configuration index %u: skipping", i);
                else {
                    mm_obj_dbg (self, "'home' settings found at configuration index %u", i);
                    home = configurations[i];
                }
                break;
            case MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_PARTNER:
                if (out_partner) {
                    if (*out_partner)
                        mm_obj_dbg (self, "duplicated 'partner' settings found at configuration index %u: skipping", i);
                    else {
                        mm_obj_dbg (self, "'partner' settings found at configuration index %u", i);
                        *out_partner = configurations[i];
                    }
                }
                break;
            case MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_NON_PARTNER:
                if (out_non_partner) {
                    if (*out_non_partner)
                        mm_obj_dbg (self, "duplicated 'non-partner' settings found at configuration index %u: skipping", i);
                    else {
                        mm_obj_dbg (self, "'non-partner' settings found at configuration index %u", i);
                        *out_non_partner = configurations[i];
                    }
                }
                break;
            default:
                mm_obj_dbg (self, "settings with unknown roaming control configuration (0x%x) found at index %u: skipping",
                            configurations[i]->roaming, i);
                break;
        }
    }

    if (!home) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                     "'home' settings not found");
        return NULL;
    }

    if (out_partner && !*out_partner)
        mm_obj_dbg (self, "'partner' settings not found");
    if (out_non_partner && !*out_non_partner)
        mm_obj_dbg (self, "'non-partner' settings not found");
    return home;
}

static void
lte_attach_configuration_query_ready (MbimDevice   *device,
                                      GAsyncResult *res,
                                      GTask        *task)
{
    MMBroadbandModemMbim                       *self;
    GError                                     *error = NULL;
    g_autoptr(MbimMessage)                      response = NULL;
    MMBearerProperties                         *properties = NULL;
    guint32                                     n_configurations = 0;
    g_autoptr(MbimLteAttachConfigurationArray)  configurations = NULL;
    const MbimLteAttachConfiguration           *home;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_lte_attach_configuration_response_parse (
            response,
            &n_configurations,
            &configurations,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Lookup the home settings, which are the ones we always control via the MM API */
    home = lte_attach_configuration_response_detect_home_settings (self,
                                                                   (const MbimLteAttachConfiguration *const *)configurations,
                                                                   n_configurations,
                                                                   NULL, /* partner */
                                                                   NULL, /* non-partner */
                                                                   &error);
    if (!home) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    properties = common_process_lte_attach_configuration (self, home, &error);
    if (properties)
        g_task_return_pointer (task, properties, g_object_unref);
    else
        g_task_return_error (task, error);
    g_object_unref (task);
}

static void
modem_3gpp_load_initial_eps_bearer_settings (MMIfaceModem3gpp    *_self,
                                             GAsyncReadyCallback  callback,
                                             gpointer             user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice           *device;
    MbimMessage          *message;
    GTask                *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_lte_attach_info_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "LTE attach status info is unsupported");
        g_object_unref (task);
        return;
    }

    message = mbim_message_ms_basic_connect_extensions_lte_attach_configuration_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)lte_attach_configuration_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Set initial EPS bearer settings */

typedef struct {
    MMBroadbandModemMbimSetInitialEpsBearerSettingsFlag mask;
    MbimDevice                 *device;
    MbimLteAttachConfiguration  home;
    MbimLteAttachConfiguration  partner;
    MbimLteAttachConfiguration  non_partner;
} SetInitialEpsBearerSettingsContext;

static void
clear_lte_attach_configuration (MbimLteAttachConfiguration *config)
{
    g_free (config->access_string);
    g_free (config->user_name);
    g_free (config->password);
}

static void
set_initial_eps_bearer_settings_context_free (SetInitialEpsBearerSettingsContext *ctx)
{
    clear_lte_attach_configuration (&ctx->home);
    clear_lte_attach_configuration (&ctx->partner);
    clear_lte_attach_configuration (&ctx->non_partner);
    g_object_unref (ctx->device);
    g_slice_free (SetInitialEpsBearerSettingsContext, ctx);
}

static gboolean
modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp  *self,
                                                   GAsyncResult      *res,
                                                   GError           **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
lte_attach_configuration_set_ready (MbimDevice   *device,
                                    GAsyncResult *res,
                                    GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error))
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
lte_attach_configuration_set (GTask *task)
{
    g_autoptr(MbimMessage)              request = NULL;
    SetInitialEpsBearerSettingsContext *ctx;
    guint32                             n_configurations = 0;
    MbimLteAttachConfiguration         *configurations[3];

    ctx = g_task_get_task_data (task);

    /* Home always added */
    g_assert (ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_UPDATE_HOME);
    g_assert (ctx->home.roaming == MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_HOME);
    configurations[n_configurations++] = &ctx->home;

    /* Partner and non-partner optional */
    if (!(ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_SKIP_PARTNER)) {
        g_assert (ctx->partner.roaming == MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_PARTNER);
        configurations[n_configurations++] = &ctx->partner;
    }
    if (!(ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_SKIP_NON_PARTNER)) {
        g_assert (ctx->non_partner.roaming == MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_NON_PARTNER);
        configurations[n_configurations++] = &ctx->non_partner;
    }

    request = mbim_message_ms_basic_connect_extensions_lte_attach_configuration_set_new (
                  MBIM_LTE_ATTACH_CONTEXT_OPERATION_DEFAULT,
                  n_configurations,
                  (const MbimLteAttachConfiguration * const*)configurations,
                  NULL);
    mbim_device_command (ctx->device,
                         request,
                         10,
                         NULL,
                         (GAsyncReadyCallback)lte_attach_configuration_set_ready,
                         task);
}

static void
update_lte_attach_configuration (MMBroadbandModemMbim               *self,
                                 MbimLteAttachContextRoamingControl  roaming_control,
                                 MbimLteAttachConfiguration         *config,
                                 const MbimLteAttachConfiguration   *previous_config,
                                 const MbimLteAttachConfiguration   *new_config)
{
    guint n_updates = 0;

    /* Log differences between previous and new */

    mm_obj_dbg (self, "updates in the %s LTE attach configuration settings:",
                mbim_lte_attach_context_roaming_control_get_string (roaming_control));

    if (!previous_config && !new_config) {
        mm_obj_dbg (self, "  none (skipped)");
        return;
    }

    /* ignore source and compression on purpose, as we don't care about their values */

    if (!previous_config || !new_config || g_strcmp0 (previous_config->access_string, new_config->access_string)) {
        mm_obj_dbg (self, "  access string: '%s' -> '%s'",
                    (previous_config && previous_config->access_string) ? previous_config->access_string : "",
                    (new_config && new_config->access_string) ? new_config->access_string : "");
        n_updates++;
    }

    if (!previous_config || !new_config || (previous_config->ip_type != new_config->ip_type)) {
        g_autofree gchar *previous_ip_type = NULL;
        g_autofree gchar *new_ip_type = NULL;

        previous_ip_type = previous_config ? mm_mbim_context_ip_type_get_printable (previous_config->ip_type) : NULL;
        new_ip_type = new_config ? mm_mbim_context_ip_type_get_printable (new_config->ip_type) : NULL;
        mm_obj_dbg (self, "  ip type: '%s' -> '%s'",
                    previous_ip_type ? previous_ip_type : "",
                    new_ip_type ? new_ip_type : "");
        n_updates++;
    }

    if (!previous_config || !new_config || (previous_config->auth_protocol != new_config->auth_protocol)) {
        g_autofree gchar *previous_auth_protocol = NULL;
        g_autofree gchar *new_auth_protocol = NULL;

        previous_auth_protocol = previous_config ? mm_mbim_auth_protocol_get_printable (previous_config->auth_protocol) : NULL;
        new_auth_protocol = new_config ? mm_mbim_auth_protocol_get_printable (new_config->auth_protocol) : NULL;
        mm_obj_dbg (self, "  auth protocol: '%s' -> '%s'",
                    previous_auth_protocol ? previous_auth_protocol : "",
                    new_auth_protocol ? new_auth_protocol : "");
        n_updates++;
    }

    if (!previous_config || !new_config || g_strcmp0 (previous_config->user_name, new_config->user_name)) {
        mm_obj_dbg (self, "  user name: '%s' -> '%s'",
                    (previous_config && previous_config->user_name) ? mm_log_str_personal_info (previous_config->user_name) : "",
                    (new_config && new_config->user_name) ? mm_log_str_personal_info (new_config->user_name) : "");
        n_updates++;
    }

    if (!previous_config || !new_config || g_strcmp0 (previous_config->password, new_config->password)) {
        mm_obj_dbg (self, "  password: '%s' -> '%s'",
                    (previous_config && previous_config->password) ? mm_log_str_personal_info (previous_config->password) : "",
                    (new_config && new_config->password) ? mm_log_str_personal_info (new_config->password) : "");
        n_updates++;
    }

    if (!n_updates)
        mm_obj_dbg (self, "  none");

    /* Copy config from the new one, if any */
    if (config && new_config) {
        config->roaming = roaming_control;
        config->source = new_config->source;
        config->compression = new_config->compression;
        config->ip_type = new_config->ip_type;
        config->access_string = g_strdup (new_config->access_string);
        config->user_name = g_strdup (new_config->user_name);
        config->password = g_strdup (new_config->password);
        config->auth_protocol = new_config->auth_protocol;
    }
}

static void
lte_attach_configuration_reload_before_set_ready (MbimDevice   *device,
                                                  GAsyncResult *res,
                                                  GTask        *task)
{
    MMBroadbandModemMbim                       *self;
    g_autoptr(MbimMessage)                      response = NULL;
    SetInitialEpsBearerSettingsContext         *ctx;
    guint32                                     n_configurations = 0;
    g_autoptr(MbimLteAttachConfigurationArray)  configurations = NULL;
    GError                                     *error = NULL;
    const MbimLteAttachConfiguration           *previous_home = NULL;
    const MbimLteAttachConfiguration           *previous_partner = NULL;
    const MbimLteAttachConfiguration           *previous_non_partner = NULL;

    self = g_task_get_source_object (task);
    ctx  = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_lte_attach_configuration_response_parse (
            response,
            &n_configurations,
            &configurations,
            &error)) {
        g_prefix_error (&error, "Failed to load LTE attach configuration before updating: ");
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Lookup the home settings, which are the ones we always control via the MM API, but also
     * detect existing partner and non-partner settings */
    previous_home = lte_attach_configuration_response_detect_home_settings (self,
                                                                            (const MbimLteAttachConfiguration *const *)configurations,
                                                                            n_configurations,
                                                                            &previous_partner,
                                                                            &previous_non_partner,
                                                                            NULL);

    /* Log differences, no need to update anything as the target settings are already set */
    update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_HOME, NULL, previous_home, &ctx->home);

    if (ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_SKIP_PARTNER) {
        /* Partner settings need to be skipped */
        g_assert (!(ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_UPDATE_PARTNER));
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_PARTNER, NULL, previous_partner, NULL);
    } else if (ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_UPDATE_PARTNER) {
        /* Partner settings need to be updated to be equal to home */
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_PARTNER, &ctx->partner, previous_partner, &ctx->home);
    } else {
        /* Partner settings need to be same as they were before */
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_PARTNER, &ctx->partner, previous_partner, previous_partner);
    }

    if (ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_SKIP_NON_PARTNER) {
        /* Non partner settings need to be skipped */
        g_assert (!(ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_UPDATE_NON_PARTNER));
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_NON_PARTNER, NULL, previous_non_partner, NULL);
    } else if (ctx->mask & MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_UPDATE_NON_PARTNER) {
        /* Non partner settings need to be updated to be equal to home */
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_NON_PARTNER, &ctx->non_partner, previous_non_partner, &ctx->home);
    } else {
        /* Non partner settings need to be same as they were before */
        update_lte_attach_configuration (self, MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_NON_PARTNER, &ctx->non_partner, previous_non_partner, previous_non_partner);
    }

    lte_attach_configuration_set (task);
}

static void
init_lte_attach_configuration_from_bearer_properties (MMBroadbandModemMbim               *self,
                                                      MbimLteAttachConfiguration         *lte_attach_config,
                                                      MbimLteAttachContextRoamingControl  roaming_control,
                                                      MMBearerProperties                 *bearer_properties)
{
    GError              *error = NULL;
    MMBearerIpFamily     ip_family;
    MMBearerAllowedAuth  auth;

    g_assert (lte_attach_config);

    lte_attach_config->roaming = roaming_control;
    lte_attach_config->source = MBIM_CONTEXT_SOURCE_USER;
    lte_attach_config->compression = MBIM_COMPRESSION_NONE;

    ip_family = mm_bearer_properties_get_ip_type (bearer_properties);
    if (ip_family == MM_BEARER_IP_FAMILY_NONE || ip_family == MM_BEARER_IP_FAMILY_ANY)
        lte_attach_config->ip_type = MBIM_CONTEXT_IP_TYPE_DEFAULT;
    else {
        lte_attach_config->ip_type = mm_bearer_ip_family_to_mbim_context_ip_type (ip_family, &error);
        if (error) {
            lte_attach_config->ip_type = MBIM_CONTEXT_IP_TYPE_DEFAULT;
            mm_obj_warn (self, "unexpected IP type settings requested: %s", error->message);
            g_clear_error (&error);
        }
    }

    lte_attach_config->access_string = g_strdup (mm_bearer_properties_get_apn (bearer_properties));
    lte_attach_config->user_name = g_strdup (mm_bearer_properties_get_user (bearer_properties));
    lte_attach_config->password = g_strdup (mm_bearer_properties_get_password (bearer_properties));

    auth = mm_bearer_properties_get_allowed_auth (bearer_properties);
    if ((auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN) || lte_attach_config->user_name || lte_attach_config->password) {
        lte_attach_config->auth_protocol = mm_bearer_allowed_auth_to_mbim_auth_protocol (auth, self, &error);
        if (error) {
            lte_attach_config->auth_protocol = MBIM_AUTH_PROTOCOL_NONE;
            mm_obj_warn (self, "unexpected auth settings requested: %s", error->message);
            g_clear_error (&error);
        }
    } else {
        lte_attach_config->auth_protocol = MBIM_AUTH_PROTOCOL_NONE;
    }
}

static void
modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp    *_self,
                                            MMBearerProperties  *config,
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
{
    MMBroadbandModemMbim               *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice                         *device;
    GTask                              *task;
    SetInitialEpsBearerSettingsContext *ctx;
    g_autoptr(MbimMessage)              request = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_lte_attach_info_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "LTE attach configuration is unsupported");
        g_object_unref (task);
        return;
    }

    ctx = g_slice_new0 (SetInitialEpsBearerSettingsContext);
    ctx->device = g_object_ref (device);
    ctx->mask = MM_BROADBAND_MODEM_MBIM_SET_INITIAL_EPS_BEARER_SETTINGS_FLAG_DEFAULT;
    g_task_set_task_data (task, ctx, (GDestroyNotify)set_initial_eps_bearer_settings_context_free);

    /* Load custom settings to use in the operation */
    if (MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->load_set_initial_eps_bearer_settings_mask)
        ctx->mask = MM_BROADBAND_MODEM_MBIM_GET_CLASS (self)->load_set_initial_eps_bearer_settings_mask (self);

    /* Initialize home settings from the config. Home settings will always be unconditionally updated */
    init_lte_attach_configuration_from_bearer_properties (self,
                                                          &ctx->home,
                                                          MBIM_LTE_ATTACH_CONTEXT_ROAMING_CONTROL_HOME,
                                                          config);

    /* Reload existing settings, so that we can log about the changes before the set operation */
    request = mbim_message_ms_basic_connect_extensions_lte_attach_configuration_query_new (NULL);
    mbim_device_command (ctx->device,
                         request,
                         10,
                         NULL,
                         (GAsyncReadyCallback)lte_attach_configuration_reload_before_set_ready,
                         task);
}

/*****************************************************************************/
/* 5GNR registration settings loading */

static MMNr5gRegistrationSettings *
modem_3gpp_load_nr5g_registration_settings_finish (MMIfaceModem3gpp  *self,
                                                   GAsyncResult      *res,
                                                   GError           **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
registration_parameters_query_ready (MbimDevice   *device,
                                     GAsyncResult *res,
                                     GTask        *task)
{
    GError                     *error = NULL;
    MMNr5gRegistrationSettings *settings;
    MbimMicoMode                mico_mode = MBIM_MICO_MODE_DEFAULT;
    MbimDrxCycle                drx_cycle = MBIM_DRX_CYCLE_NOT_SPECIFIED;
    g_autoptr(MbimMessage)      response = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_v3_registration_parameters_response_parse (
            response,
            &mico_mode,
            &drx_cycle,
            NULL, /* ladn info */
            NULL, /* default pdu activation hint */
            NULL, /* reregister if needed */
            NULL, /* unnamed ies */
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    settings = mm_nr5g_registration_settings_new ();
    mm_nr5g_registration_settings_set_mico_mode (settings, mm_modem_3gpp_mico_mode_from_mbim_mico_mode (mico_mode));
    mm_nr5g_registration_settings_set_drx_cycle (settings, mm_modem_3gpp_drx_cycle_from_mbim_drx_cycle (drx_cycle));

    g_task_return_pointer (task, settings, (GDestroyNotify) g_object_unref);
    g_object_unref (task);
}

static void
modem_3gpp_load_nr5g_registration_settings (MMIfaceModem3gpp    *_self,
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask                  *task;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_nr5g_registration_settings_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "5GNR registration settings are unsupported");
        g_object_unref (task);
        return;
    }

    message = mbim_message_ms_basic_connect_extensions_v3_registration_parameters_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)registration_parameters_query_ready,
                         task);
}

/*****************************************************************************/
/* Set 5GNR registration settings */

static gboolean
modem_3gpp_set_nr5g_registration_settings_finish (MMIfaceModem3gpp  *self,
                                                  GAsyncResult      *res,
                                                  GError           **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
set_nr5g_registration_settings_ready (MbimDevice   *device,
                                      GAsyncResult *res,
                                      GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error))
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_3gpp_set_nr5g_registration_settings (MMIfaceModem3gpp           *_self,
                                           MMNr5gRegistrationSettings *settings,
                                           GAsyncReadyCallback         callback,
                                           gpointer                    user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask                  *task;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;
    MbimMicoMode            mico_mode;
    MbimDrxCycle            drx_cycle;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_nr5g_registration_settings_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "5GNR registration settings are unsupported");
        g_object_unref (task);
        return;
    }

    mico_mode = mm_modem_3gpp_mico_mode_to_mbim_mico_mode (mm_nr5g_registration_settings_get_mico_mode (settings));
    drx_cycle = mm_modem_3gpp_drx_cycle_to_mbim_drx_cycle (mm_nr5g_registration_settings_get_drx_cycle (settings));

    message = mbim_message_ms_basic_connect_extensions_v3_registration_parameters_set_new (mico_mode,
                                                                                           drx_cycle,
                                                                                           MBIM_LADN_INFO_NOT_NEEDED,
                                                                                           MBIM_DEFAULT_PDU_ACTIVATION_HINT_LIKELY,
                                                                                           TRUE,
                                                                                           NULL, /* unnamed ies */
                                                                                           NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)set_nr5g_registration_settings_ready,
                         task);
}

/*****************************************************************************/
/* Signal state updates */

static void
atds_signal_query_after_indication_ready (MbimDevice           *device,
                                          GAsyncResult         *res,
                                          MMBroadbandModemMbim *_self) /* full reference! */
{
    g_autoptr(MMBroadbandModemMbim) self = _self;
    g_autoptr(MbimMessage)          response = NULL;
    g_autoptr(MMSignal)             gsm = NULL;
    g_autoptr(MMSignal)             umts = NULL;
    g_autoptr(MMSignal)             lte = NULL;
    g_autoptr(GError)               error = NULL;
    guint32                         rssi;
    guint32                         error_rate;
    guint32                         rscp;
    guint32                         ecno;
    guint32                         rsrq;
    guint32                         rsrp;
    guint32                         snr;

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_atds_signal_response_parse (response, &rssi, &error_rate, &rscp, &ecno, &rsrq, &rsrp, &snr, &error)) {
        mm_obj_warn (self, "ATDS signal query failed: %s", error->message);
        return;
    }

    if (!mm_signal_from_atds_signal_response (rssi, rscp, ecno, rsrq, rsrp, snr, &gsm, &umts, &lte)) {
        mm_obj_warn (self, "No signal details given");
        return;
    }

    mm_iface_modem_signal_update (MM_IFACE_MODEM_SIGNAL (self), NULL, NULL, gsm, umts, lte, NULL);
}

static void
basic_connect_notification_signal_state (MMBroadbandModemMbim *self,
                                         MbimDevice           *device,
                                         MbimMessage          *notification)
{
    g_autoptr(GError)               error = NULL;
    g_autoptr(MbimRsrpSnrInfoArray) rsrp_snr = NULL;
    guint32                         rsrp_snr_count = 0;
    guint32                         coded_rssi;
    guint32                         coded_error_rate = 99;
    guint32                         quality;
    MbimDataClass                   data_class;
    g_autoptr(MMSignal)             cdma = NULL;
    g_autoptr(MMSignal)             evdo = NULL;
    g_autoptr(MMSignal)             gsm = NULL;
    g_autoptr(MMSignal)             umts = NULL;
    g_autoptr(MMSignal)             lte = NULL;
    g_autoptr(MMSignal)             nr5g = NULL;

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        if (!mbim_message_ms_basic_connect_v2_signal_state_notification_parse (
                notification,
                &coded_rssi,
                &coded_error_rate,
                NULL, /* signal_strength_interval */
                NULL, /* rssi_threshold */
                NULL, /* error_rate_threshold */
                &rsrp_snr_count,
                &rsrp_snr,
                &error)) {
            mm_obj_warn (self, "failed processing MBIMEx v2.0 signal state indication: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed MBIMEx v2.0 signal state indication");
    } else {
        if (!mbim_message_signal_state_notification_parse (
                notification,
                &coded_rssi,
                &coded_error_rate,
                NULL, /* signal_strength_interval */
                NULL, /* rssi_threshold */
                NULL, /* error_rate_threshold */
                &error)) {
            mm_obj_warn (self, "failed processing signal state indication: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed signal state indication");
        if (self->priv->is_atds_signal_supported) {
            g_autoptr(MbimMessage) message = NULL;

            mm_obj_dbg (self, "triggering ATDS signal query");
            message = mbim_message_atds_signal_query_new (NULL);
            mbim_device_command (device,
                                 message,
                                 5,
                                 NULL,
                                 (GAsyncReadyCallback)atds_signal_query_after_indication_ready,
                                 g_object_ref (self));
        }
    }

    quality = mm_signal_quality_from_mbim_signal_state (coded_rssi, rsrp_snr, rsrp_snr_count, self);
    mm_iface_modem_update_signal_quality (MM_IFACE_MODEM (self), quality);

    data_class = enabled_cache_best_available_data_class (self);
    if (mm_signal_from_mbim_signal_state (data_class, coded_rssi, coded_error_rate, rsrp_snr, rsrp_snr_count,
                                          self, &cdma, &evdo, &gsm, &umts, &lte, &nr5g))
        mm_iface_modem_signal_update (MM_IFACE_MODEM_SIGNAL (self), cdma, evdo, gsm, umts, lte, nr5g);
}

/*****************************************************************************/
/* ATDS location update */

static void
atds_location_query_ready (MbimDevice           *device,
                           GAsyncResult         *res,
                           MMBroadbandModemMbim *self)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;
    guint32                 lac;
    guint32                 tac;
    guint32                 cid;

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_atds_location_response_parse (response, &lac, &tac, &cid, &error)) {
        mm_obj_warn (self, "failed processing ATDS location query response: %s", error->message);
        mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), 0, 0, 0);
    } else {
        mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), lac, tac, cid);
    }

    g_object_unref (self);
}

static void
update_atds_location (MMBroadbandModemMbim *self)
{
    g_autoptr(MbimMessage)  message = NULL;
    MMPortMbim             *port;
    MbimDevice             *device;

    port = mm_broadband_modem_mbim_peek_port_mbim (self);
    if (!port)
        return;
    device = mm_port_mbim_peek_device (port);
    if (!device)
        return;

    message = mbim_message_atds_location_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)atds_location_query_ready,
                         g_object_ref (self));
}

/*****************************************************************************/
/* Access technology updates */

static void
update_access_technologies (MMBroadbandModemMbim *self)
{
    MMModemAccessTechnology act;
    MbimDataClass           data_class;

    data_class = enabled_cache_best_available_data_class (self);
    act = mm_modem_access_technology_from_mbim_data_class (data_class);
    mm_iface_modem_3gpp_update_access_technologies (MM_IFACE_MODEM_3GPP (self), act);
}

/*****************************************************************************/
/* Packet service updates */

static void update_registration_info (MMBroadbandModemMbim *self,
                                      gboolean              scheduled,
                                      MbimRegisterState     state,
                                      MbimDataClass         available_data_classes,
                                      gchar                *operator_id_take,
                                      gchar                *operator_name_take);

static void
update_packet_service_info (MMBroadbandModemMbim    *self,
                            MbimPacketServiceState   packet_service_state)
{
    MMModem3gppPacketServiceState state;

    /* Report the new value to the 3GPP interface right away, don't assume it has the same
     * cached value. */
    state = mm_modem_3gpp_packet_service_state_from_mbim_packet_service_state (packet_service_state);
    mm_iface_modem_3gpp_update_packet_service_state (MM_IFACE_MODEM_3GPP (self), state);

    if (packet_service_state == self->priv->enabled_cache.packet_service_state)
        return;

    /* PS reg state depends on the packet service state */
    self->priv->enabled_cache.packet_service_state = packet_service_state;
    update_registration_info (self,
                              FALSE,
                              self->priv->enabled_cache.reg_state,
                              self->priv->enabled_cache.available_data_classes,
                              g_strdup (self->priv->enabled_cache.current_operator_id),
                              g_strdup (self->priv->enabled_cache.current_operator_name));
}

/*****************************************************************************/
/* Registration info updates */

static void
enabling_state_changed (MMBroadbandModemMbim *self)
{
    MMModemState state;

    g_object_get (self, MM_IFACE_MODEM_STATE, &state, NULL);

    /* if we've reached a enabled state, we can trigger the update */
    if (state > MM_MODEM_STATE_ENABLING) {
        mm_obj_dbg (self, "triggering 3GPP registration info update");
        update_registration_info (self,
                                  TRUE,
                                  self->priv->enabled_cache.reg_state,
                                  self->priv->enabled_cache.available_data_classes,
                                  g_strdup (self->priv->enabled_cache.current_operator_id),
                                  g_strdup (self->priv->enabled_cache.current_operator_name));
    }
    /* if something bad happened during enabling, we can ignore any pending
     * registration info update */
    else if (state < MM_MODEM_STATE_ENABLING)
        mm_obj_dbg (self, "discarding pending 3GPP registration info update");

    /* this signal is expected to be fired just once */
    g_signal_handler_disconnect (self, self->priv->enabling_signal_id);
    self->priv->enabling_signal_id = 0;
}

static void
update_registration_info (MMBroadbandModemMbim *self,
                          gboolean              scheduled,
                          MbimRegisterState     state,
                          MbimDataClass         available_data_classes,
                          gchar                *operator_id_take,
                          gchar                *operator_name_take)
{
    MMModem3gppRegistrationState reg_state;
    MMModem3gppRegistrationState reg_state_cs;
    MMModem3gppRegistrationState reg_state_ps;
    MMModem3gppRegistrationState reg_state_eps;
    MMModem3gppRegistrationState reg_state_5gs;
    gboolean                     operator_updated = FALSE;
    gboolean                     reg_state_updated = FALSE;
    MMModemState                 modem_state;
    gboolean                     schedule_update_in_enabled = FALSE;

    /* If we're enabling, we will not attempt to update anything yet, we will
     * instead store the info and schedule the updates for when we're enabled */
    g_object_get (self, MM_IFACE_MODEM_STATE, &modem_state, NULL);
    if (modem_state == MM_MODEM_STATE_ENABLING)
        schedule_update_in_enabled = TRUE;

    if (self->priv->enabled_cache.reg_state != state)
        reg_state_updated = TRUE;
    self->priv->enabled_cache.reg_state = state;

    reg_state = mm_modem_3gpp_registration_state_from_mbim_register_state (state);

    if (reg_state == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
        reg_state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
        if (self->priv->enabled_cache.current_operator_id && operator_id_take &&
            g_str_equal (self->priv->enabled_cache.current_operator_id, operator_id_take)) {
            g_free (operator_id_take);
        } else {
            operator_updated = TRUE;
            g_free (self->priv->enabled_cache.current_operator_id);
            self->priv->enabled_cache.current_operator_id = operator_id_take;
        }

        if (self->priv->enabled_cache.current_operator_name && operator_name_take &&
            g_str_equal (self->priv->enabled_cache.current_operator_name, operator_name_take)) {
            g_free (operator_name_take);
        } else {
            operator_updated = TRUE;
            g_free (self->priv->enabled_cache.current_operator_name);
            self->priv->enabled_cache.current_operator_name = operator_name_take;
        }
    } else {
        if (self->priv->enabled_cache.current_operator_id ||
            self->priv->enabled_cache.current_operator_name) {
            operator_updated = TRUE;
        }
        g_clear_pointer (&self->priv->enabled_cache.current_operator_id, g_free);
        g_clear_pointer (&self->priv->enabled_cache.current_operator_name, g_free);
        g_free (operator_id_take);
        g_free (operator_name_take);
        /* Explicitly reset packet service state if we're not registered */
        update_packet_service_info (self, MBIM_PACKET_SERVICE_STATE_UNKNOWN);
    }

    /* If we can update domain registration states right now, do it */
    if (!schedule_update_in_enabled) {
        reg_state_cs = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
        reg_state_ps = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
        reg_state_eps = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;
        reg_state_5gs = MM_MODEM_3GPP_REGISTRATION_STATE_IDLE;

        if (available_data_classes & (MBIM_DATA_CLASS_GPRS  | MBIM_DATA_CLASS_EDGE  |
                                      MBIM_DATA_CLASS_UMTS  | MBIM_DATA_CLASS_HSDPA | MBIM_DATA_CLASS_HSUPA)) {
            reg_state_cs = reg_state;
            if (self->priv->enabled_cache.packet_service_state == MBIM_PACKET_SERVICE_STATE_ATTACHED)
                reg_state_ps = reg_state;
        }

        if (available_data_classes & (MBIM_DATA_CLASS_LTE))
            reg_state_eps = reg_state;

        if (available_data_classes & (MBIM_DATA_CLASS_5G_NSA | MBIM_DATA_CLASS_5G_SA))
            reg_state_5gs = reg_state;

        mm_iface_modem_3gpp_update_cs_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_cs, TRUE);
        mm_iface_modem_3gpp_update_ps_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_ps, TRUE);
        if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self)))
            mm_iface_modem_3gpp_update_eps_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_eps, TRUE);
        if (mm_iface_modem_is_3gpp_5gnr (MM_IFACE_MODEM (self)))
            mm_iface_modem_3gpp_update_5gs_registration_state (MM_IFACE_MODEM_3GPP (self), reg_state_5gs, TRUE);
        mm_iface_modem_3gpp_apply_deferred_registration_state (MM_IFACE_MODEM_3GPP (self));

        /* request to reload operator info explicitly, so that the new
         * operator name and code is propagated to the DBus interface */
        if (operator_updated || scheduled)
            mm_iface_modem_3gpp_reload_current_registration_info (MM_IFACE_MODEM_3GPP (self), NULL, NULL);
    }

    self->priv->enabled_cache.available_data_classes = available_data_classes;
    /* If we can update access technologies right now, do it */
    if (!schedule_update_in_enabled)
        update_access_technologies (self);

    /* request to reload location info */
    if (!schedule_update_in_enabled &&
        self->priv->is_atds_location_supported &&
        (reg_state_updated || scheduled)) {
        if (self->priv->enabled_cache.reg_state < MBIM_REGISTER_STATE_HOME) {
            mm_iface_modem_3gpp_update_location (MM_IFACE_MODEM_3GPP (self), 0, 0, 0);

        } else
            update_atds_location (self);
    }

    if (schedule_update_in_enabled && !self->priv->enabling_signal_id) {
        mm_obj_dbg (self, "Scheduled registration info update once the modem is enabled");
        self->priv->enabling_signal_id = g_signal_connect (self,
                                                           "notify::" MM_IFACE_MODEM_STATE,
                                                           G_CALLBACK (enabling_state_changed),
                                                           NULL);
    }
}

static gboolean
common_process_register_state (MMBroadbandModemMbim  *self,
                               MbimDevice            *device,
                               MbimMessage           *message,
                               MbimNwError           *out_nw_error,
                               GError               **error)
{
    MbimNwError        nw_error = 0;
    MbimRegisterState  register_state = MBIM_REGISTER_STATE_UNKNOWN;
    MbimDataClass      available_data_classes = 0;
    g_autofree gchar  *provider_id = NULL;
    g_autofree gchar  *provider_name = NULL;
    MbimDataClass      preferred_data_classes = 0;
    const gchar       *nw_error_str;
    g_autofree gchar  *available_data_classes_str = NULL;
    g_autofree gchar  *preferred_data_classes_str = NULL;
    gboolean           is_notification;

    is_notification = (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_INDICATE_STATUS);
    g_assert (is_notification || (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_COMMAND_DONE));

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        if (is_notification) {
            if (!mbim_message_ms_basic_connect_v2_register_state_notification_parse (
                    message,
                    &nw_error,
                    &register_state,
                    NULL, /* register_mode */
                    &available_data_classes,
                    NULL, /* current_cellular_class */
                    &provider_id,
                    &provider_name,
                    NULL, /* roaming_text */
                    NULL, /* registration_flag */
                    &preferred_data_classes,
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v2.0 register state indication: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v2.0 register state indication");
        } else {
            if (!mbim_message_ms_basic_connect_v2_register_state_response_parse (
                    message,
                    &nw_error,
                    &register_state,
                    NULL, /* register_mode */
                    &available_data_classes,
                    NULL, /* current_cellular_class */
                    &provider_id,
                    &provider_name,
                    NULL, /* roaming_text */
                    NULL, /* registration_flag */
                    &preferred_data_classes,
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v2.0 register state response: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v2.0 register state indication");
        }
    } else {
        if (is_notification) {
            if (!mbim_message_register_state_notification_parse (
                    message,
                    &nw_error,
                    &register_state,
                    NULL, /* register_mode */
                    &available_data_classes,
                    NULL, /* current_cellular_class */
                    &provider_id,
                    &provider_name,
                    NULL, /* roaming_text */
                    NULL, /* registration_flag */
                    error)) {
                g_prefix_error (error, "Failed processing register state indication: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed register state indication");
        } else {
            if (!mbim_message_register_state_response_parse (
                    message,
                    &nw_error,
                    &register_state,
                    NULL, /* register_mode */
                    &available_data_classes,
                    NULL, /* current_cellular_class */
                    &provider_id,
                    &provider_name,
                    NULL, /* roaming_text */
                    NULL, /* registration_flag */
                    error)) {
                g_prefix_error (error, "Failed processing register state response: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed register state response");
        }
    }

    /* Normalize preferred and available data classes to include any custom data class */
    preferred_data_classes = mm_modem_mbim_normalize_data_class_mask (preferred_data_classes,
                                                                      self->priv->caps_custom_data_class);
    available_data_classes = mm_modem_mbim_normalize_data_class_mask (available_data_classes,
                                                                      self->priv->caps_custom_data_class);


    nw_error = mm_broadband_modem_mbim_normalize_nw_error (self, nw_error);
    nw_error_str = mbim_nw_error_get_string (nw_error);
    available_data_classes_str = mbim_data_class_build_string_from_mask (available_data_classes);
    preferred_data_classes_str = mbim_data_class_build_string_from_mask (preferred_data_classes);

    mm_obj_dbg (self, "register state update:");
    if (nw_error_str)
        mm_obj_dbg (self, "              nw error: '%s'", nw_error_str);
    else
        mm_obj_dbg (self, "              nw error: '0x%x'", nw_error);
    mm_obj_dbg (self, "                 state: '%s'", mbim_register_state_get_string (register_state));
    mm_obj_dbg (self, "           provider id: '%s'", provider_id ? provider_id : "n/a");
    mm_obj_dbg (self, "         provider name: '%s'", provider_name ? provider_name : "n/a");
    mm_obj_dbg (self, "available data classes: '%s'", available_data_classes_str);
    mm_obj_dbg (self, "preferred data classes: '%s'", preferred_data_classes_str);

    if ((register_state != MBIM_REGISTER_STATE_HOME) &&
        (register_state != MBIM_REGISTER_STATE_ROAMING) &&
        mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
        MMNetworkError mm_nw_error;
        MMModemAccessTechnology access_technology;

        mm_nw_error = mm_modem_nw_error_from_mbim_nw_error (nw_error);
        access_technology = mm_modem_access_technology_from_mbim_data_class (available_data_classes);

        mm_iface_modem_3gpp_update_network_rejection (MM_IFACE_MODEM_3GPP (self),
                                                      mm_nw_error,
                                                      provider_id,
                                                      provider_name,
                                                      access_technology);
    }

    update_registration_info (self,
                              FALSE,
                              register_state,
                              available_data_classes,
                              g_steal_pointer (&provider_id),
                              g_steal_pointer (&provider_name));

    if (preferred_data_classes)
        complete_pending_allowed_modes_action (self, preferred_data_classes);

    if (out_nw_error)
        *out_nw_error = nw_error;
    return TRUE;
}

static void
basic_connect_notification_register_state (MMBroadbandModemMbim *self,
                                           MbimDevice           *device,
                                           MbimMessage          *notification)
{
    g_autoptr(GError)  error = NULL;

    if (!common_process_register_state (self, device, notification, NULL, &error))
        mm_obj_warn (self, "%s", error->message);
}

typedef struct {
    MMBroadbandModemMbim *self;
    guint32               session_id;
    GError               *connection_error;
} ReportDisconnectedStatusContext;

static void
bearer_list_report_disconnected_status (MMBaseBearer *bearer,
                                        gpointer user_data)
{
    ReportDisconnectedStatusContext *ctx = user_data;

    if (MM_IS_BEARER_MBIM (bearer) &&
        mm_bearer_mbim_get_session_id (MM_BEARER_MBIM (bearer)) == ctx->session_id) {
        mm_obj_dbg (ctx->self, "bearer '%s' was disconnected.", mm_base_bearer_get_path (bearer));
        mm_base_bearer_report_connection_status_detailed (bearer, MM_BEARER_CONNECTION_STATUS_DISCONNECTED, ctx->connection_error);
    }
}

static void
basic_connect_notification_connect (MMBroadbandModemMbim *self,
                                    MbimDevice           *device,
                                    MbimMessage          *notification)
{
    guint32                  session_id;
    MbimActivationState      activation_state;
    const MbimUuid          *context_type;
    guint32                  nw_error;
    g_autoptr(MMBearerList)  bearer_list = NULL;
    g_autoptr(GError)        error = NULL;

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_v3_connect_notification_parse (
                notification,
                &session_id,
                &activation_state,
                NULL, /* voice_call_state */
                NULL, /* ip_type */
                &context_type, /* context_type */
                &nw_error,
                NULL, /* media_preference */
                NULL, /* access_string */
                NULL, /* unnamed_ies */
                &error)) {
            mm_obj_warn (self, "Failed processing MBIMEx v3.0 connect notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed MBIMEx v3.0 connect notification");
    } else {
        if (!mbim_message_connect_notification_parse (
                notification,
                &session_id,
                &activation_state,
                NULL, /* voice_call_state */
                NULL, /* ip_type */
                &context_type,
                &nw_error,
                &error)) {
            mm_obj_warn (self, "Failed processing connect notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed connect notification");
    }

    g_object_get (self,
                  MM_IFACE_MODEM_BEARER_LIST, &bearer_list,
                  NULL);

    if (!bearer_list)
        return;

    if (activation_state == MBIM_ACTIVATION_STATE_DEACTIVATED) {
        ReportDisconnectedStatusContext ctx;
        g_autoptr(GError)               connection_error = NULL;

        nw_error = mm_broadband_modem_mbim_normalize_nw_error (self, nw_error);
        connection_error = mm_error_from_mbim_nw_error (nw_error, self);

        mm_obj_dbg (self, "session ID '%u' was deactivated: %s", session_id, connection_error->message);

        ctx.self = self;
        ctx.session_id = session_id;
        ctx.connection_error = connection_error;
        mm_bearer_list_foreach (bearer_list,
                                (MMBearerListForeachFunc)bearer_list_report_disconnected_status,
                                &ctx);
    }
}

static void
pin_query_after_subscriber_ready_status_ready (MbimDevice           *device,
                                               GAsyncResult         *res,
                                               MMBroadbandModemMbim *self) /* full reference! */
{
    g_autoptr(MbimMessage) response = NULL;
    g_autoptr(GError)      error = NULL;
    MbimPinType            pin_type;
    MbimPinState           pin_state;
    gboolean               sim_event = FALSE;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_pin_response_parse (
            response,
            &pin_type,
            &pin_state,
            NULL,
            &error)) {
        if (pin_type == MBIM_PIN_TYPE_NETWORK_PIN ||
            pin_type == MBIM_PIN_TYPE_NETWORK_PUK ||
            self->priv->enabled_cache.last_pin_type == MBIM_PIN_TYPE_NETWORK_PIN) {
            sim_event = TRUE;
        }
        self->priv->enabled_cache.last_pin_type = pin_type;
    }

    if (error)
        mm_obj_dbg (self, "PIN query for basic connect failed: %s", error->message);

    if (sim_event)
        mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));

    g_object_unref (self);
}

static void
basic_connect_notification_subscriber_ready_status (MMBroadbandModemMbim *self,
                                                    MbimDevice           *device,
                                                    MbimMessage          *notification)
{
    MbimSubscriberReadyState ready_state;
    g_auto(GStrv)            telephone_numbers = NULL;
    g_autoptr(GError)        error = NULL;
    gboolean                 active_sim_event = FALSE;

    if (self->priv->pending_sim_slot_switch_action) {
        mm_obj_dbg (self, "ignoring slot status change");
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_v3_subscriber_ready_status_notification_parse (
                notification,
                &ready_state,
                NULL, /* flags */
                NULL, /* subscriber id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                &telephone_numbers,
                &error)) {
            mm_obj_warn (self, "Failed processing MBIMEx v3.0 subscriber ready status notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed MBIMEx v3.0 subscriber ready status notification");
    } else {
        if (!mbim_message_subscriber_ready_status_notification_parse (
                notification,
                &ready_state,
                NULL, /* subscriber_id */
                NULL, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                &telephone_numbers,
                &error)) {
            mm_obj_warn (self, "Failed processing subscriber ready status notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "processed subscriber ready status notification");
    }

    if (ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED)
        mm_iface_modem_update_own_numbers (MM_IFACE_MODEM (self), telephone_numbers);

    if ((self->priv->enabled_cache.last_ready_state != MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE &&
         ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE) ||
        (self->priv->enabled_cache.last_ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE &&
         ready_state != MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE)) {
        /* eSIM profiles have been added or removed, re-probe to ensure correct interfaces are exposed */
        mm_obj_dbg (self, "eSIM profile updates detected");
        active_sim_event = TRUE;
    }

    if ((self->priv->enabled_cache.last_ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED &&
         ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) ||
        (self->priv->enabled_cache.last_ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED &&
         ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED)) {
        /* SIM has been removed or reinserted, re-probe to ensure correct interfaces are exposed */
        mm_obj_dbg (self, "SIM hot swap detected");
        active_sim_event = TRUE;
    }

    if ((self->priv->enabled_cache.last_ready_state != MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED &&
         ready_state == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED) ||
        (self->priv->enabled_cache.last_ready_state == MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED &&
         ready_state != MBIM_SUBSCRIBER_READY_STATE_DEVICE_LOCKED)) {
        g_autoptr(MbimMessage) message = NULL;

        /* Query which lock has changed */
        message = mbim_message_pin_query_new (NULL);
        mbim_device_command (device,
                             message,
                             10,
                             NULL,
                             (GAsyncReadyCallback)pin_query_after_subscriber_ready_status_ready,
                             g_object_ref (self));
    }

    /* Ignore NOT_INITIALIZED state when setting the last_ready_state as it is
     * reported regardless of whether SIM was inserted or unlocked */
    if (ready_state != MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED) {
        self->priv->enabled_cache.last_ready_state = ready_state;
    }

    if (active_sim_event) {
        mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
    }
}

/*****************************************************************************/
/* Speed updates */

void
mm_broadband_modem_mbim_get_speeds (MMBroadbandModemMbim *self,
                                    guint64              *uplink_speed,
                                    guint64              *downlink_speed)
{
    if (uplink_speed)
        *uplink_speed = self->priv->enabled_cache.packet_service_uplink_speed;
    if (downlink_speed)
        *downlink_speed = self->priv->enabled_cache.packet_service_downlink_speed;
}

static void
bearer_list_report_speeds (MMBaseBearer         *bearer,
                           MMBroadbandModemMbim *self)
{
    if (!MM_IS_BEARER_MBIM (bearer))
        return;

    /* Update speeds only if connected or connecting */
    if (mm_base_bearer_get_status (bearer) >= MM_BEARER_STATUS_CONNECTING) {
        mm_obj_dbg (self, "bearer '%s' speeds updated", mm_base_bearer_get_path (bearer));
        mm_base_bearer_report_speeds (bearer,
                                      self->priv->enabled_cache.packet_service_uplink_speed,
                                      self->priv->enabled_cache.packet_service_downlink_speed);
    }
}

static void
update_bearer_speeds (MMBroadbandModemMbim *self,
                      guint64               uplink_speed,
                      guint64               downlink_speed)
{
    g_autoptr(MMBearerList) bearer_list = NULL;

    if ((self->priv->enabled_cache.packet_service_uplink_speed == uplink_speed) &&
        (self->priv->enabled_cache.packet_service_downlink_speed == downlink_speed))
        return;

    self->priv->enabled_cache.packet_service_uplink_speed = uplink_speed;
    self->priv->enabled_cache.packet_service_downlink_speed = downlink_speed;

    g_object_get (self,
                  MM_IFACE_MODEM_BEARER_LIST, &bearer_list,
                  NULL);
    if (!bearer_list)
        return;

    mm_bearer_list_foreach (bearer_list,
                            (MMBearerListForeachFunc)bearer_list_report_speeds,
                            self);
}

/*****************************************************************************/
/* Packet service updates */

static gboolean
common_process_packet_service (MMBroadbandModemMbim     *self,
                               MbimDevice               *device,
                               MbimMessage              *message,
                               guint32                  *out_nw_error,
                               MbimPacketServiceState   *out_packet_service_state,
                               GError                  **error)
{
    guint32                 nw_error = 0;
    MbimPacketServiceState  packet_service_state = MBIM_PACKET_SERVICE_STATE_UNKNOWN;
    MbimDataClass           data_class = 0;
    MbimDataClassV3         data_class_v3 = 0;
    MbimDataSubclass        data_subclass = 0;
    guint64                 uplink_speed = 0;
    guint64                 downlink_speed = 0;
    MbimFrequencyRange      frequency_range = MBIM_FREQUENCY_RANGE_UNKNOWN;
    g_autofree gchar       *data_class_str = NULL;
    g_autofree gchar       *data_class_v3_str = NULL;
    g_autofree gchar       *data_subclass_str = NULL;
    g_autofree gchar       *frequency_range_str = NULL;
    const gchar            *nw_error_str;
    gboolean                is_notification;

    is_notification = (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_INDICATE_STATUS);
    g_assert (is_notification || (mbim_message_get_message_type (message) == MBIM_MESSAGE_TYPE_COMMAND_DONE));

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (is_notification) {
            if (!mbim_message_ms_basic_connect_v3_packet_service_notification_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class_v3,
                    &uplink_speed,
                    &downlink_speed,
                    &frequency_range,
                    &data_subclass,
                    NULL, /* tai */
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v3.0 packet service indication: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v3.0 packet service indication");
        } else {
            if (!mbim_message_ms_basic_connect_v3_packet_service_response_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class_v3,
                    &uplink_speed,
                    &downlink_speed,
                    &frequency_range,
                    &data_subclass,
                    NULL, /* tai */
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v3.0 packet service response: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v3.0 packet service response");
        }
        data_class_v3_str = mbim_data_class_v3_build_string_from_mask (data_class_v3);
        data_subclass_str = mbim_data_subclass_build_string_from_mask (data_subclass);
    } else if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        if (is_notification) {
            if (!mbim_message_ms_basic_connect_v2_packet_service_notification_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class, /* current */
                    &uplink_speed,
                    &downlink_speed,
                    &frequency_range,
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v2.0 packet service indication: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v2.0 packet service indication");
        } else {
            if (!mbim_message_ms_basic_connect_v2_packet_service_response_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class, /* current */
                    &uplink_speed,
                    &downlink_speed,
                    &frequency_range,
                    error)) {
                g_prefix_error (error, "Failed processing MBIMEx v2.0 packet service response: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed MBIMEx v2.0 packet service response");
        }
        data_class_str = mbim_data_class_build_string_from_mask (data_class);
    } else {
        if (is_notification) {
            if (!mbim_message_packet_service_notification_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class, /* highest available */
                    &uplink_speed,
                    &downlink_speed,
                    error)) {
                g_prefix_error (error, "Failed processing packet service indication: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed packet service indication");
        } else {
            if (!mbim_message_packet_service_response_parse (
                    message,
                    &nw_error,
                    &packet_service_state,
                    &data_class, /* highest available */
                    &uplink_speed,
                    &downlink_speed,
                    error)) {
                g_prefix_error (error, "Failed processing packet service response: ");
                return FALSE;
            }
            mm_obj_dbg (self, "processed packet service response");
        }
        data_class_str = mbim_data_class_build_string_from_mask (data_class);
    }

    frequency_range_str = mbim_frequency_range_build_string_from_mask (frequency_range);
    nw_error = mm_broadband_modem_mbim_normalize_nw_error (self, nw_error);
    nw_error_str = mbim_nw_error_get_string (nw_error);

    mm_obj_dbg (self, "packet service update:");
    if (nw_error_str)
        mm_obj_dbg (self, "        nw error: '%s'", nw_error_str);
    else
        mm_obj_dbg (self, "        nw error: '0x%x'", nw_error);
    mm_obj_dbg (self, "           state: '%s'", mbim_packet_service_state_get_string (packet_service_state));
    if (data_class_str)
        mm_obj_dbg (self, "      data class: '%s'", data_class_str);
    else if (data_class_v3_str)
        mm_obj_dbg (self, "      data class: '%s'", data_class_v3_str);
    if (data_subclass_str)
        mm_obj_dbg (self, "   data subclass: '%s'", data_subclass_str);
    mm_obj_dbg (self, "          uplink: '%" G_GUINT64_FORMAT "' bps", uplink_speed);
    mm_obj_dbg (self, "        downlink: '%" G_GUINT64_FORMAT "' bps", downlink_speed);
    mm_obj_dbg (self, " frequency range: '%s'", frequency_range_str);

    if (packet_service_state == MBIM_PACKET_SERVICE_STATE_ATTACHED) {
        if (data_class_v3)
            data_class = mm_mbim_data_class_from_mbim_data_class_v3_and_subclass (data_class_v3, data_subclass);

        /* Normalize data class to include any custom data class */
        data_class = mm_modem_mbim_normalize_data_class_mask (data_class,
                                                              self->priv->caps_custom_data_class);
        self->priv->enabled_cache.highest_available_data_class = data_class;
    } else if (packet_service_state == MBIM_PACKET_SERVICE_STATE_DETACHED) {
        self->priv->enabled_cache.highest_available_data_class = 0;
    }
    update_access_technologies (self);

    update_packet_service_info (self, packet_service_state);

    update_bearer_speeds (self, uplink_speed, downlink_speed);

    if (out_nw_error)
        *out_nw_error = nw_error;
    if (out_packet_service_state)
        *out_packet_service_state = packet_service_state;
    return TRUE;
}

static void
basic_connect_notification_packet_service (MMBroadbandModemMbim *self,
                                           MbimDevice           *device,
                                           MbimMessage          *notification)
{
    g_autoptr(GError) error = NULL;

    if (!common_process_packet_service (self, device, notification, NULL, NULL, &error))
        mm_obj_warn (self, "%s", error->message);
}

static void
basic_connect_notification_provisioned_contexts (MMBroadbandModemMbim *self,
                                                 MbimDevice           *device,
                                                 MbimMessage          *notification)
{
    /* We don't even attempt to parse the indication, we just need to notify that
     * something changed to the upper layers */
    mm_iface_modem_3gpp_profile_manager_updated (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self));
}

static gboolean process_pdu_messages (MMBroadbandModemMbim       *self,
                                      MbimSmsFormat               format,
                                      guint32                     messages_count,
                                      MbimSmsPduReadRecordArray  *pdu_messages,
                                      guint                       expected_index,
                                      gboolean                    unstored,
                                      GError                    **error);

static void
sms_notification_read_flash_sms (MMBroadbandModemMbim *self,
                                 MbimDevice           *device,
                                 MbimMessage          *notification)
{
    g_autoptr(MbimSmsPduReadRecordArray) pdu_messages = NULL;
    g_autoptr(GError)                    error = NULL;
    MbimSmsFormat                        format;
    guint32                              messages_count;

    if (!mbim_message_sms_read_notification_parse (
            notification,
            &format,
            &messages_count,
            &pdu_messages,
            NULL, /* cdma_messages */
            &error) ||
        !process_pdu_messages (self,
                               format,
                               messages_count,
                               pdu_messages,
                               SMS_PART_INVALID_INDEX,
                               TRUE,
                               &error))
        mm_obj_dbg (self, "flash SMS message reading failed: %s", error->message);
}

static void
basic_connect_notification (MMBroadbandModemMbim *self,
                            MbimDevice           *device,
                            MbimMessage          *notification)
{
    switch (mbim_message_indicate_status_get_cid (notification)) {
    case MBIM_CID_BASIC_CONNECT_SIGNAL_STATE:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY)
            basic_connect_notification_signal_state (self, device, notification);
        break;
    case MBIM_CID_BASIC_CONNECT_REGISTER_STATE:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES)
            basic_connect_notification_register_state (self, device, notification);
        break;
    case MBIM_CID_BASIC_CONNECT_CONNECT:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_CONNECT)
            basic_connect_notification_connect (self, device, notification);
        break;
    case MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO)
            basic_connect_notification_subscriber_ready_status (self, device, notification);
        break;
    case MBIM_CID_BASIC_CONNECT_PACKET_SERVICE:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE)
            basic_connect_notification_packet_service (self, device, notification);
        break;
    case MBIM_CID_BASIC_CONNECT_PROVISIONED_CONTEXTS:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS)
            basic_connect_notification_provisioned_contexts (self, device, notification);
    case MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION:
        /* Ignored at modem level, only managed by bearer if waiting for async SLAAC results */
    default:
        /* Ignore */
        break;
    }
}

typedef struct {
    MMBroadbandModemMbim *self;
    guint                 expected_index;
} SmsNotificationContext;

static void
sms_notification_context_free (SmsNotificationContext *ctx)
{
    g_object_unref (ctx->self);
    g_slice_free (SmsNotificationContext, ctx);
}

static void
alert_sms_read_query_ready (MbimDevice             *device,
                            GAsyncResult           *res,
                            SmsNotificationContext *ctx)
{
    g_autoptr(MbimMessage)               response = NULL;
    g_autoptr(GError)                    error = NULL;
    g_autoptr(MbimSmsPduReadRecordArray) pdu_messages = NULL;
    MbimSmsFormat                        format;
    guint32                              messages_count;

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_sms_read_response_parse (
            response,
            &format,
            &messages_count,
            &pdu_messages,
            NULL, /* cdma_messages */
            &error) ||
        !process_pdu_messages (ctx->self,
                               format,
                               messages_count,
                               pdu_messages,
                               ctx->expected_index,
                               FALSE,
                               &error))
        mm_obj_dbg (ctx->self, "SMS message reading failed: %s", error->message);

    sms_notification_context_free (ctx);
}

static void
sms_notification_read_stored_sms (MMBroadbandModemMbim *self,
                                  guint32               index)
{
    g_autoptr(MbimMessage)  message = NULL;
    MMPortMbim             *port;
    MbimDevice             *device;
    SmsNotificationContext *ctx;

    port = mm_broadband_modem_mbim_peek_port_mbim (self);
    if (!port)
        return;
    device = mm_port_mbim_peek_device (port);
    if (!device)
        return;

    mm_obj_dbg (self, "reading new SMS at index '%u'", index);
    message = mbim_message_sms_read_query_new (MBIM_SMS_FORMAT_PDU,
                                               MBIM_SMS_FLAG_INDEX,
                                               index,
                                               NULL);
    ctx = g_slice_new0 (SmsNotificationContext);
    ctx->self = g_object_ref (self);
    ctx->expected_index = index;
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)alert_sms_read_query_ready,
                         ctx);
}

static void
sms_notification (MMBroadbandModemMbim *self,
                  MbimDevice           *device,
                  MbimMessage          *notification)
{
    switch (mbim_message_indicate_status_get_cid (notification)) {
    case MBIM_CID_SMS_READ:
        /* New flash/alert message? */
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ)
            sms_notification_read_flash_sms (self, device, notification);
        break;

    case MBIM_CID_SMS_MESSAGE_STORE_STATUS: {
        MbimSmsStatusFlag flags;
        guint32           index;

        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ &&
            mbim_message_sms_message_store_status_notification_parse (
                notification,
                &flags,
                &index,
                NULL)) {
            g_autofree gchar *flags_str = NULL;

            flags_str = mbim_sms_status_flag_build_string_from_mask (flags);
            mm_obj_dbg (self, "received SMS store status update: '%s'", flags_str);
            if (flags & MBIM_SMS_STATUS_FLAG_NEW_MESSAGE)
                sms_notification_read_stored_sms (self, index);
        }
        break;
    }

    default:
        /* Ignore */
        break;
    }
}

static void
ms_basic_connect_extensions_notification_pco (MMBroadbandModemMbim *self,
                                              MbimDevice           *device,
                                              MbimMessage          *notification)
{
    MbimPcoValue *pco_value;
    GError *error = NULL;
    gchar *pco_data_hex;
    MMPco *pco;

    if (!mbim_message_ms_basic_connect_extensions_pco_notification_parse (
            notification,
            &pco_value,
            &error)) {
        mm_obj_warn (self, "couldn't parse PCO notification: %s", error->message);
        g_error_free (error);
        return;
    }

    pco_data_hex = mm_utils_bin2hexstr (pco_value->pco_data_buffer,
                                        pco_value->pco_data_size);
    mm_obj_dbg (self, "received PCO: session ID=%u type=%s size=%u data=%s",
                pco_value->session_id,
                mbim_pco_type_get_string (pco_value->pco_data_type),
                pco_value->pco_data_size,
                pco_data_hex);
    g_free (pco_data_hex);

    pco = mm_pco_new ();
    mm_pco_set_session_id (pco, pco_value->session_id);
    mm_pco_set_complete (pco,
                         pco_value->pco_data_type == MBIM_PCO_TYPE_COMPLETE);
    mm_pco_set_data (pco,
                     pco_value->pco_data_buffer,
                     pco_value->pco_data_size);

    self->priv->enabled_cache.pco_list = mm_pco_list_add (self->priv->enabled_cache.pco_list, pco);
    mm_iface_modem_3gpp_update_pco_list (MM_IFACE_MODEM_3GPP (self),
                                         self->priv->enabled_cache.pco_list);
    g_object_unref (pco);
    mbim_pco_value_free (pco_value);
}

static void
ms_basic_connect_extensions_notification_lte_attach_info (MMBroadbandModemMbim *self,
                                                          MbimDevice           *device,
                                                          MbimMessage          *notification)
{
    g_autoptr(GError)              error = NULL;
    g_autoptr(MMBearerProperties)  properties = NULL;
    guint32                        lte_attach_state;
    guint32                        ip_type;
    g_autofree gchar              *access_string = NULL;
    g_autofree gchar              *user_name = NULL;
    g_autofree gchar              *password = NULL;
    guint32                        compression;
    guint32                        auth_protocol;
    MbimNwError                    nw_error = 0;

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_extensions_v3_lte_attach_info_notification_parse (
                notification,
                &lte_attach_state,
                &nw_error,
                &ip_type,
                &access_string,
                &user_name,
                &password,
                &compression,
                &auth_protocol,
                &error)) {
            mm_obj_warn (self, "Failed processing MBIMEx v3.0 LTE attach info notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "Processed MBIMEx v3.0 LTE attach info notification");
    } else {
        if (!mbim_message_ms_basic_connect_extensions_lte_attach_info_notification_parse (
            notification,
            &lte_attach_state,
            &ip_type,
            &access_string,
            &user_name,
            &password,
            &compression,
            &auth_protocol,
            &error)) {
            mm_obj_warn (self, "Failed processing LTE attach info notification: %s", error->message);
            return;
        }
        mm_obj_dbg (self, "Processed LTE attach info notification");
    }

    properties = common_process_lte_attach_info (self,
                                                 lte_attach_state,
                                                 ip_type,
                                                 access_string,
                                                 user_name,
                                                 password,
                                                 compression,
                                                 auth_protocol,
                                                 NULL);
    mm_iface_modem_3gpp_update_initial_eps_bearer (MM_IFACE_MODEM_3GPP (self), properties);

    /* If network error is reported, then log it */
    if (nw_error) {
        const gchar *nw_error_str;

        nw_error = mm_broadband_modem_mbim_normalize_nw_error (self, nw_error);
        nw_error_str = mbim_nw_error_get_string (nw_error);
        if (nw_error_str)
            mm_obj_dbg (self, "LTE attach info network error reported: %s", nw_error_str);
        else
            mm_obj_dbg (self, "LTE attach info network error reported: 0x%x", nw_error);
    }
}

static MMBaseSim *
create_sim_from_slot_state (MMBroadbandModemMbim *self,
                            gboolean              active,
                            guint                 slot_index,
                            MbimUiccSlotState     slot_state)
{
    MMSimType       sim_type    = MM_SIM_TYPE_UNKNOWN;
    MMSimEsimStatus esim_status = MM_SIM_ESIM_STATUS_UNKNOWN;

    switch (slot_state) {
    case MBIM_UICC_SLOT_STATE_ACTIVE:
        sim_type = MM_SIM_TYPE_PHYSICAL;
        break;
    case MBIM_UICC_SLOT_STATE_ACTIVE_ESIM:
        sim_type = MM_SIM_TYPE_ESIM;
        esim_status = MM_SIM_ESIM_STATUS_WITH_PROFILES;
        break;
    case MBIM_UICC_SLOT_STATE_ACTIVE_ESIM_NO_PROFILES:
        sim_type = MM_SIM_TYPE_ESIM;
        esim_status = MM_SIM_ESIM_STATUS_NO_PROFILES;
        break;
    case MBIM_UICC_SLOT_STATE_NOT_READY:
    case MBIM_UICC_SLOT_STATE_ERROR:
        /* Not fully ready (NOT_READY) or unusable (ERROR) SIM cards should also be
         * reported as being available in the non-active slot. */
        break;
    case MBIM_UICC_SLOT_STATE_UNKNOWN:
    case MBIM_UICC_SLOT_SATE_OFF_EMPTY:
    case MBIM_UICC_SLOT_STATE_OFF:
    case MBIM_UICC_SLOT_STATE_EMPTY:
    default:
        return NULL;
    }

    mm_obj_dbg (self, "found %s SIM in slot %u: %s (%s)",
                active ? "active" : "inactive",
                slot_index,
                mm_sim_type_get_string (sim_type),
                (sim_type == MM_SIM_TYPE_ESIM) ? mm_sim_esim_status_get_string (esim_status) : "n/a");

    return MM_BASE_SIM (mm_sim_mbim_new_initialized (MM_BASE_MODEM (self),
                                                     slot_index,
                                                     active,
                                                     sim_type,
                                                     esim_status,
                                                     NULL,
                                                     NULL,
                                                     NULL,
                                                     NULL,
                                                     NULL,
                                                     NULL));
}

static void
ms_basic_connect_extensions_notification_slot_info_status (MMBroadbandModemMbim *self,
                                                           MbimDevice           *device,
                                                           MbimMessage          *notification)
{
    g_autoptr(GError) error = NULL;
    guint32           slot_index;
    MbimUiccSlotState slot_state;

    if (!mbim_message_ms_basic_connect_extensions_slot_info_status_notification_parse (
            notification,
            &slot_index,
            &slot_state,
            &error)) {
        mm_obj_warn (self, "Couldn't parse slot info status notification: %s", error->message);
        return;
    }

    if (self->priv->pending_sim_slot_switch_action) {
        mm_obj_dbg (self, "ignoring slot status change in SIM slot %d: %s", slot_index + 1, mbim_uicc_slot_state_get_string (slot_state));
        return;
    }

    if (self->priv->active_slot_index != (slot_index + 1)) {
        /* Modifies SIM object at the given slot based on the reported state,
         * when the slot is not the active one. */
        g_autoptr(MMBaseSim) sim = NULL;

        mm_obj_dbg (self, "processing slot status change in non-active SIM slot %d: %s", slot_index + 1, mbim_uicc_slot_state_get_string (slot_state));
        sim = create_sim_from_slot_state (self, FALSE, slot_index, slot_state);
        mm_iface_modem_modify_sim (MM_IFACE_MODEM (self), slot_index, sim);
        return;
    }

    /* Major SIM event on the active slot, will request reprobing the
     * modem from scratch. */
    mm_obj_dbg (self, "processing slot status change in active SIM slot %d: %s", slot_index + 1, mbim_uicc_slot_state_get_string (slot_state));
    mm_iface_modem_process_sim_event (MM_IFACE_MODEM (self));
}

static void
ms_basic_connect_extensions_notification (MMBroadbandModemMbim *self,
                                          MbimDevice           *device,
                                          MbimMessage          *notification)
{
    switch (mbim_message_indicate_status_get_cid (notification)) {
    case MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_PCO:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO)
            ms_basic_connect_extensions_notification_pco (self, device, notification);
        break;
    case MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_LTE_ATTACH_INFO:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO)
            ms_basic_connect_extensions_notification_lte_attach_info (self, device, notification);
        break;
    case MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_SLOT_INFO_STATUS:
        if (self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS)
            ms_basic_connect_extensions_notification_slot_info_status (self, device, notification);
        break;
    default:
        /* Ignore */
        break;
    }
}

static void
process_ussd_notification (MMBroadbandModemMbim *self,
                           MbimMessage          *notification);

static void
ussd_notification (MMBroadbandModemMbim *self,
                   MbimDevice           *device,
                   MbimMessage          *notification)
{
    if (mbim_message_indicate_status_get_cid (notification) != MBIM_CID_USSD) {
        mm_obj_warn (self, "unexpected USSD notification (cid %u)", mbim_message_indicate_status_get_cid (notification));
        return;
    }

    if (!(self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD))
        return;

    process_ussd_notification (self, notification);
}

static void
port_notification_cb (MMPortMbim           *port,
                      MbimMessage          *notification,
                      MMBroadbandModemMbim *self)
{
    MbimService  service;
    MbimDevice  *device;

    /* Only process notifications if the device still exists */
    device = mm_port_mbim_peek_device (port);
    if (!device)
        return;

    service = mbim_message_indicate_status_get_service (notification);
    mm_obj_dbg (self, "received notification (service '%s', command '%s')",
                mbim_service_get_string (service),
                mbim_cid_get_printable (service,
                                        mbim_message_indicate_status_get_cid (notification)));

    if (service == MBIM_SERVICE_BASIC_CONNECT)
        basic_connect_notification (self, device, notification);
    else if (service == MBIM_SERVICE_MS_BASIC_CONNECT_EXTENSIONS)
        ms_basic_connect_extensions_notification (self, device, notification);
    else if (service == MBIM_SERVICE_SMS)
        sms_notification (self, device, notification);
    else if (service == MBIM_SERVICE_USSD)
        ussd_notification (self, device, notification);
}

static void
common_setup_cleanup_unsolicited_events_sync (MMBroadbandModemMbim *self,
                                              MMPortMbim           *port,
                                              gboolean              setup)
{
    if (!port)
        return;

    mm_obj_dbg (self, "supported notifications: signal (%s), registration (%s), sms (%s), "
                "connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), "
                "lte attach info (%s), provisioned contexts (%s), slot info status (%s), "
                "ip configuration (%s)",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS ? "yes" : "no",
                self->priv->setup_flags & PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION ? "yes" : "no");

    if (setup) {
        /* Don't re-enable it if already there */
        if (!self->priv->notification_id)
            self->priv->notification_id =
                g_signal_connect (port,
                                  MM_PORT_MBIM_SIGNAL_NOTIFICATION,
                                  G_CALLBACK (port_notification_cb),
                                  self);
    } else {
        /* Don't remove the signal if there are still listeners interested */
        if (self->priv->setup_flags == PROCESS_NOTIFICATION_FLAG_NONE &&
            self->priv->notification_id &&
            g_signal_handler_is_connected (port, self->priv->notification_id)) {
            g_signal_handler_disconnect (port, self->priv->notification_id);
            self->priv->notification_id = 0;
        }
    }
}

static gboolean
common_setup_cleanup_unsolicited_events_finish (MMBroadbandModemMbim  *self,
                                                GAsyncResult          *res,
                                                GError               **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
common_setup_cleanup_unsolicited_events (MMBroadbandModemMbim *self,
                                         gboolean              setup,
                                         GAsyncReadyCallback   callback,
                                         gpointer              user_data)
{
    GTask      *task;
    MMPortMbim *port;

    task = g_task_new (self, NULL, callback, user_data);

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (!port) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Couldn't peek MBIM port");
        g_object_unref (task);
        return;
    }

    common_setup_cleanup_unsolicited_events_sync (self, port, setup);

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

/*****************************************************************************/
/* Setup/cleanup unsolicited events (3GPP interface) */

static gboolean
common_setup_cleanup_unsolicited_events_3gpp_finish (MMIfaceModem3gpp *self,
                                                     GAsyncResult *res,
                                                     GError **error)
{
    return common_setup_cleanup_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
cleanup_enabled_cache (MMBroadbandModemMbim *self)
{
    g_clear_pointer (&self->priv->enabled_cache.current_operator_id, g_free);
    g_clear_pointer (&self->priv->enabled_cache.current_operator_name, g_free);
    g_list_free_full (self->priv->enabled_cache.pco_list, g_object_unref);
    self->priv->enabled_cache.pco_list = NULL;
    self->priv->enabled_cache.available_data_classes = MBIM_DATA_CLASS_NONE;
    self->priv->enabled_cache.highest_available_data_class = MBIM_DATA_CLASS_NONE;
    self->priv->enabled_cache.reg_state = MBIM_REGISTER_STATE_UNKNOWN;
    self->priv->enabled_cache.packet_service_state = MBIM_PACKET_SERVICE_STATE_UNKNOWN;
    self->priv->enabled_cache.packet_service_uplink_speed = 0;
    self->priv->enabled_cache.packet_service_downlink_speed = 0;
    self->priv->enabled_cache.last_pin_type = MBIM_PIN_TYPE_UNKNOWN;

    /* NOTE: FLAG_SUBSCRIBER_INFO is managed both via 3GPP unsolicited
     * events and via SIM hot swap setup. We only reset the last ready state
     * if SIM hot swap context is not using it. */
    if (!self->priv->sim_hot_swap_configured)
        self->priv->enabled_cache.last_ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;
}

static void
cleanup_unsolicited_events_3gpp (MMIfaceModem3gpp *_self,
                                 GAsyncReadyCallback callback,
                                 gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    /* NOTE: FLAG_SUBSCRIBER_INFO and FLAG_SLOT_INFO_STATUS are managed both
     * via 3GPP unsolicited events and via SIM hot swap setup. We only really
     * cleanup the indication if SIM hot swap context is not using it. */
    if (!self->priv->sim_hot_swap_configured) {
        self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
        if (self->priv->is_slot_info_status_supported)
            self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    }

    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_CONNECT;
    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION;
    if (self->priv->is_pco_supported)
        self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO;
    if (self->priv->is_lte_attach_info_supported)
        self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO;
    common_setup_cleanup_unsolicited_events (self, FALSE, callback, user_data);

    /* Runtime cached state while enabled, to be cleaned up once disabled */
    cleanup_enabled_cache (self);
}

static void
setup_unsolicited_events_3gpp (MMIfaceModem3gpp *_self,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    /* NOTE: FLAG_SUBSCRIBER_INFO is managed both via 3GPP unsolicited
     * events and via SIM hot swap setup. */
    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;

    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_CONNECT;
    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION;
    if (self->priv->is_pco_supported)
        self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PCO;
    if (self->priv->is_lte_attach_info_supported)
        self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO;
    if (self->priv->is_slot_info_status_supported)
        self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    common_setup_cleanup_unsolicited_events (self, TRUE, callback, user_data);
}

/*****************************************************************************/
/* Cleanup/Setup unsolicited registration events */

static void
cleanup_unsolicited_registration_events (MMIfaceModem3gpp *_self,
                                         GAsyncReadyCallback callback,
                                         gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES;
    common_setup_cleanup_unsolicited_events (self, FALSE, callback, user_data);
}

static void
setup_unsolicited_registration_events (MMIfaceModem3gpp *_self,
                                       GAsyncReadyCallback callback,
                                       gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES;
    common_setup_cleanup_unsolicited_events (self, TRUE, callback, user_data);
}

/*****************************************************************************/
/* Enable/disable unsolicited events (common) */

static gboolean
common_enable_disable_unsolicited_events_finish (MMBroadbandModemMbim *self,
                                                 GAsyncResult *res,
                                                 GError **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
subscribe_list_set_ready_cb (MbimDevice *device,
                             GAsyncResult *res,
                             GTask *task)
{
    MbimMessage *response;
    GError *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response) {
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
        mbim_message_unref (response);
    }

    if (error)
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
common_enable_disable_unsolicited_events (MMBroadbandModemMbim *self,
                                          GAsyncReadyCallback callback,
                                          gpointer user_data)
{
    MbimMessage *request;
    MbimDevice *device;
    MbimEventEntry **entries;
    guint n_entries = 0;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    mm_obj_dbg (self, "enabled notifications: signal (%s), registration (%s), sms (%s), "
                "connect (%s), subscriber (%s), packet (%s), pco (%s), ussd (%s), "
                "lte attach info (%s), provisioned contexts (%s), slot info status (%s), "
                "ip configuration (%s)",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_USSD ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS ? "yes" : "no",
                self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION ? "yes" : "no");

    entries = g_new0 (MbimEventEntry *, 5);

    /* Basic connect service */
    if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_CONNECT ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION) {
        entries[n_entries] = g_new (MbimEventEntry, 1);
        memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_BASIC_CONNECT, sizeof (MbimUuid));
        entries[n_entries]->cids_count = 0;
        entries[n_entries]->cids = g_new0 (guint32, 7);
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_SIGNAL_STATE;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_REGISTER_STATE;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_CONNECT)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_CONNECT;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_SUBSCRIBER_READY_STATUS;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_PACKET_SERVICE;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_PROVISIONED_CONTEXTS;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_BASIC_CONNECT_IP_CONFIGURATION;
        n_entries++;
    }

    /* Basic connect extensions service */
    if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO ||
        self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS) {
        entries[n_entries] = g_new (MbimEventEntry, 1);
        memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_MS_BASIC_CONNECT_EXTENSIONS, sizeof (MbimUuid));
        entries[n_entries]->cids_count = 0;
        entries[n_entries]->cids = g_new0 (guint32, 3);
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_PCO)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_PCO;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_LTE_ATTACH_INFO;
        if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS)
            entries[n_entries]->cids[entries[n_entries]->cids_count++] = MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_SLOT_INFO_STATUS;
        n_entries++;
    }

    /* SMS service */
    if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_SMS_READ) {
        entries[n_entries] = g_new (MbimEventEntry, 1);
        memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_SMS, sizeof (MbimUuid));
        entries[n_entries]->cids_count = 2;
        entries[n_entries]->cids = g_new0 (guint32, 2);
        entries[n_entries]->cids[0] = MBIM_CID_SMS_READ;
        entries[n_entries]->cids[1] = MBIM_CID_SMS_MESSAGE_STORE_STATUS;
        n_entries++;
    }

    /* USSD service */
    if (self->priv->enable_flags & PROCESS_NOTIFICATION_FLAG_USSD) {
        entries[n_entries] = g_new (MbimEventEntry, 1);
        memcpy (&(entries[n_entries]->device_service_id), MBIM_UUID_USSD, sizeof (MbimUuid));
        entries[n_entries]->cids_count = 1;
        entries[n_entries]->cids = g_new0 (guint32, 1);
        entries[n_entries]->cids[0] = MBIM_CID_USSD;
        n_entries++;
    }

    task = g_task_new (self, NULL, callback, user_data);

    request = (mbim_message_device_service_subscribe_list_set_new (
                   n_entries,
                   (const MbimEventEntry *const *)entries,
                   NULL));
    mbim_device_command (device,
                         request,
                         10,
                         NULL,
                         (GAsyncReadyCallback)subscribe_list_set_ready_cb,
                         task);
    mbim_message_unref (request);
    mbim_event_entry_array_free (entries);
}

/*****************************************************************************/
/* Enable/Disable unsolicited registration events */

static gboolean
modem_3gpp_common_enable_disable_unsolicited_registration_events_finish (MMIfaceModem3gpp *self,
                                                                         GAsyncResult *res,
                                                                         GError **error)
{
    return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
modem_3gpp_disable_unsolicited_registration_events (MMIfaceModem3gpp *_self,
                                                    gboolean cs_supported,
                                                    gboolean ps_supported,
                                                    gboolean eps_supported,
                                                    GAsyncReadyCallback callback,
                                                    gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}


static void
modem_3gpp_enable_unsolicited_registration_events (MMIfaceModem3gpp *_self,
                                                   gboolean cs_supported,
                                                   gboolean ps_supported,
                                                   gboolean eps_supported,
                                                   GAsyncReadyCallback callback,
                                                   gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_REGISTRATION_UPDATES;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

/*****************************************************************************/
/* Setup SIM hot swap */

typedef struct {
    MMPortMbim *port;
    GError     *subscriber_info_error;
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    GError     *qmi_error;
#endif
} SetupSimHotSwapContext;

static void
setup_sim_hot_swap_context_free (SetupSimHotSwapContext *ctx)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    g_clear_error (&ctx->qmi_error);
#endif
    g_clear_error (&ctx->subscriber_info_error);
    g_clear_object (&ctx->port);
    g_slice_free (SetupSimHotSwapContext, ctx);
}

static gboolean
modem_setup_sim_hot_swap_finish (MMIfaceModem  *_self,
                                 GAsyncResult  *res,
                                 GError       **error)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->sim_hot_swap_configured = g_task_propagate_boolean (G_TASK (res), error);
    return self->priv->sim_hot_swap_configured;
}

static void
sim_hot_swap_complete (GTask *task)
{
    SetupSimHotSwapContext *ctx;

    ctx = g_task_get_task_data (task);

    /* If MBIM based logic worked, success */
    if (!ctx->subscriber_info_error)
        g_task_return_boolean (task, TRUE);
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    /* Otherwise, If QMI-over-MBIM based logic worked, success */
    else if (!ctx->qmi_error)
        g_task_return_boolean (task, TRUE);
#endif
    /* Otherwise, prefer MBIM specific error */
    else
        g_task_return_error (task, g_steal_pointer (&ctx->subscriber_info_error));
    g_object_unref (task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static void
qmi_setup_sim_hot_swap_ready (MMIfaceModem *self,
                              GAsyncResult *res,
                              GTask        *task)
{
    SetupSimHotSwapContext *ctx;

    ctx = g_task_get_task_data (task);
    if (!mm_shared_qmi_setup_sim_hot_swap_finish (self, res, &ctx->qmi_error))
        mm_obj_dbg (self, "couldn't setup SIM hot swap using QMI over MBIM: %s", ctx->qmi_error->message);

    sim_hot_swap_complete (task);
}

#endif

static void
enable_subscriber_info_unsolicited_events_ready (MMBroadbandModemMbim *self,
                                                 GAsyncResult         *res,
                                                 GTask                *task)
{
    SetupSimHotSwapContext *ctx;

    ctx = g_task_get_task_data (task);

    if (!common_enable_disable_unsolicited_events_finish (self, res, &ctx->subscriber_info_error)) {
        mm_obj_dbg (self, "failed to enable subscriber info events: %s", ctx->subscriber_info_error->message);
        /* reset setup flags if enabling failed */
        self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
        if (self->priv->is_slot_info_status_supported)
            self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
        common_setup_cleanup_unsolicited_events_sync (self, ctx->port, FALSE);
        /* and also reset enable flags */
        self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
        if (self->priv->is_slot_info_status_supported)
            self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    }

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    mm_shared_qmi_setup_sim_hot_swap (MM_IFACE_MODEM (self),
                                      (GAsyncReadyCallback)qmi_setup_sim_hot_swap_ready,
                                      task);
#else
    sim_hot_swap_complete (task);
#endif
}

static void
modem_setup_sim_hot_swap (MMIfaceModem        *_self,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MMPortMbim             *port;
    GTask                  *task;
    SetupSimHotSwapContext *ctx;

    task = g_task_new (self, NULL, callback, user_data);

    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
    if (!port) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Couldn't peek MBIM port");
        g_object_unref (task);
        return;
    }

    ctx = g_slice_new0 (SetupSimHotSwapContext);
    ctx->port = g_object_ref (port);
    g_task_set_task_data (task, ctx, (GDestroyNotify)setup_sim_hot_swap_context_free);

    /* Setup flags synchronously, which never fails */
    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
    if (self->priv->is_slot_info_status_supported)
        self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    common_setup_cleanup_unsolicited_events_sync (self, ctx->port, TRUE);

    /* Enable flags asynchronously, which may fail */
    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
    if (self->priv->is_slot_info_status_supported)
        self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    common_enable_disable_unsolicited_events (self,
                                              (GAsyncReadyCallback)enable_subscriber_info_unsolicited_events_ready,
                                              task);
}

/*****************************************************************************/
/* Check basic SIM details */

typedef struct {
    gboolean  sim_inserted;
    gchar    *iccid;
    gchar    *imsi;
} SimDetails;

static void sim_details_free (SimDetails *sim_info) {
    g_free (sim_info->iccid);
    g_free (sim_info->imsi);
    g_slice_free (SimDetails, sim_info);
}

static gboolean
modem_check_basic_sim_details_finish (MMIfaceModem  *self,
                                      GAsyncResult  *res,
                                      gboolean      *sim_inserted,
                                      gchar        **iccid,
                                      gchar        **imsi,
                                      GError       **error)
{
    SimDetails *sim_info;

    sim_info = g_task_propagate_pointer (G_TASK (res), error);
    if (!sim_info)
        return FALSE;

    *sim_inserted = sim_info->sim_inserted;
    if (iccid)
        *iccid = g_steal_pointer (&sim_info->iccid);
    if (imsi)
        *imsi = g_steal_pointer (&sim_info->imsi);
    sim_details_free (sim_info);
    return TRUE;
}

static void
basic_sim_details_subscriber_ready_state_ready (MbimDevice   *device,
                                                GAsyncResult *res,
                                                GTask        *task)
{
    MMBroadbandModemMbim     *self;
    g_autoptr(MbimMessage)    response = NULL;
    GError                   *error = NULL;
    gchar                    *imsi = NULL;
    g_autofree gchar         *raw_iccid = NULL;
    MbimSubscriberReadyState  ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
        if (!mbim_message_ms_basic_connect_v3_subscriber_ready_status_response_parse (
                response,
                &ready_state, /* ready_state */
                NULL, /* flags */
                &imsi, /* subscriber id */
                &raw_iccid, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                NULL,
                &error))
            g_prefix_error (&error, "Failed processing MBIMEx v3.0 subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed MBIMEx v3.0 subscriber ready status response");
    } else {
        if (!mbim_message_subscriber_ready_status_response_parse (
                response,
                &ready_state, /* ready_state */
                &imsi, /* subscriber id */
                &raw_iccid, /* sim_iccid */
                NULL, /* ready_info */
                NULL, /* telephone_numbers_count */
                NULL,
                &error))
            g_prefix_error (&error, "Failed processing subscriber ready status response: ");
        else
            mm_obj_dbg (self, "processed subscriber ready status response");
    }

    if (error)
        g_task_return_error (task, error);
    else {
        SimDetails        *sim_details;
        g_autoptr(GError)  inner_error = NULL;

        sim_details = g_slice_new0 (SimDetails);
        if (ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) {
            sim_details->sim_inserted = TRUE;
            if (raw_iccid) {
                sim_details->iccid = mm_3gpp_parse_iccid (raw_iccid, &inner_error);
                if (!sim_details->iccid) {
                    mm_obj_warn (self, "can not get ICCID info: couldn't parse SIM ICCID: %s", inner_error->message);
                }
            }
            sim_details->imsi = imsi;
        }
        g_task_return_pointer (task, sim_details, (GDestroyNotify)sim_details_free);
    }
    g_object_unref (task);
}

static void
modem_check_basic_sim_details (MMIfaceModem        *self,
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
{
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_subscriber_ready_status_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)basic_sim_details_subscriber_ready_state_ready,
                         task);
}

/*****************************************************************************/
/* Enable/Disable unsolicited events (3GPP interface) */

static gboolean
modem_3gpp_common_enable_disable_unsolicited_events_finish (MMIfaceModem3gpp *self,
                                                            GAsyncResult *res,
                                                            GError **error)
{
    return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
modem_3gpp_disable_unsolicited_events (MMIfaceModem3gpp *_self,
                                       GAsyncReadyCallback callback,
                                       gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    /* NOTE: FLAG_SUBSCRIBER_INFO and FLAG_SLOT_INFO_STATUS are managed both
     * via 3GPP unsolicited events and via SIM hot swap setup. We only really
     * cleanup the indication if SIM hot swap context is not using it. */
    if (!self->priv->sim_hot_swap_configured) {
        self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;
        if (self->priv->is_slot_info_status_supported)
            self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    }

    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_CONNECT;
    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION;
    if (self->priv->is_pco_supported)
        self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PCO;
    if (self->priv->is_lte_attach_info_supported)
        self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

static void
modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *_self,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    /* NOTE: FLAG_SUBSCRIBER_INFO is managed both via 3GPP unsolicited
     * events and via SIM hot swap setup. */
    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SUBSCRIBER_INFO;

    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SIGNAL_QUALITY;
    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_CONNECT;
    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PACKET_SERVICE;
    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_IP_CONFIGURATION;
    if (self->priv->is_pco_supported)
        self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PCO;
    if (self->priv->is_lte_attach_info_supported)
        self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_LTE_ATTACH_INFO;
    if (self->priv->is_slot_info_status_supported)
        self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SLOT_INFO_STATUS;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

/*****************************************************************************/
/* Load operator name (3GPP interface) */

static gchar *
modem_3gpp_load_operator_name_finish (MMIfaceModem3gpp *self,
                                      GAsyncResult *res,
                                      GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_3gpp_load_operator_name (MMIfaceModem3gpp *_self,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->enabled_cache.current_operator_name)
        g_task_return_pointer (task,
                               g_strdup (self->priv->enabled_cache.current_operator_name),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Current operator name is still unknown");
    g_object_unref (task);
}

/*****************************************************************************/
/* Load operator code (3GPP interface) */

static gchar *
modem_3gpp_load_operator_code_finish (MMIfaceModem3gpp *self,
                                      GAsyncResult *res,
                                      GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
modem_3gpp_load_operator_code (MMIfaceModem3gpp *_self,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    if (self->priv->enabled_cache.current_operator_id)
        g_task_return_pointer (task,
                               g_strdup (self->priv->enabled_cache.current_operator_id),
                               g_free);
    else
        g_task_return_new_error (task,
                                 MM_CORE_ERROR,
                                 MM_CORE_ERROR_FAILED,
                                 "Current operator MCC/MNC is still unknown");
    g_object_unref (task);
}

/*****************************************************************************/
/* Registration checks (3GPP interface) */

static gboolean
modem_3gpp_run_registration_checks_finish (MMIfaceModem3gpp  *self,
                                           GAsyncResult      *res,
                                           GError           **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
packet_service_query_ready (MbimDevice   *device,
                            GAsyncResult *res,
                            GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(GError)       error = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        (mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
         g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE))) {
        g_autoptr(GError) inner_error = NULL;

        if (!common_process_packet_service (self, device, response, NULL, NULL, &inner_error))
            mm_obj_warn (self, "%s", inner_error->message);
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
register_state_query_ready (MbimDevice   *device,
                            GAsyncResult *res,
                            GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(MbimMessage)  message = NULL;
    MMBroadbandModemMbim   *self;
    GError                 *error = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (!common_process_register_state (self, device, response, NULL, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Now queue packet service state update */
    message = mbim_message_packet_service_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)packet_service_query_ready,
                         task);
}

static void
modem_3gpp_run_registration_checks (MMIfaceModem3gpp    *self,
                                    gboolean             is_cs_supported,
                                    gboolean             is_ps_supported,
                                    gboolean             is_eps_supported,
                                    gboolean             is_5gs_supported,
                                    GAsyncReadyCallback  callback,
                                    gpointer             user_data)
{
    g_autoptr(MbimMessage)  message = NULL;
    MbimDevice             *device;
    GTask                  *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_register_state_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)register_state_query_ready,
                         task);
}

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

static gboolean
modem_3gpp_register_in_network_finish (MMIfaceModem3gpp *self,
                                       GAsyncResult *res,
                                       GError **error)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->qmi_capability_and_mode_switching)
        return mm_shared_qmi_3gpp_register_in_network_finish (self, res, error);
#endif

    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
register_state_set_ready (MbimDevice   *device,
                          GAsyncResult *res,
                          GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;

    self = g_task_get_source_object (task);

    /* The NwError field is valid if MBIM_SET_REGISTER_STATE response status code
     * equals MBIM_STATUS_FAILURE, so we parse the message both on success and on that
     * specific failure */
    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        (mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
         g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE))) {
        g_autoptr(GError) inner_error = NULL;
        MbimNwError       normalized_nw_error = 0;

        if (!common_process_register_state (self, device, response, &normalized_nw_error, &inner_error)) {
            mm_obj_warn (self, "%s", inner_error->message);
            /* Prefer the error from the result to the parsing error */
            if (!error)
                error = g_steal_pointer (&inner_error);
        } else {
            /* Prefer the NW error if available.
             *
             * NwError "0" is defined in 3GPP TS 24.008 as "Unknown error",
             * not "No error", making it unsuitable as condition for registration check.
             * Still, there are certain modems (e.g. Fibocom NL668) that will
             * report Failure+NwError=0 even after the modem has already reported a
             * successful registration via indications after the set operation. If
             * that is the case, log about it and ignore the error; we are anyway
             * reloading the registration info after the set, so it should not be
             * a big issue. */
            if (normalized_nw_error) {
                g_clear_error (&error);
                error = mm_error_from_mbim_nw_error (normalized_nw_error, self);
            }
        }
    }

    if (error)
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_3gpp_register_in_network (MMIfaceModem3gpp *_self,
                                const gchar *operator_id,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    /* data_class set to 0 in the MBIM register state set message ends up
     * selecting some "auto" mode that would overwrite whatever capabilities
     * and modes we had set. So, if we're using QMI-based capability and
     * mode switching, also use QMI-based network registration. */
    if (self->priv->qmi_capability_and_mode_switching) {
        mm_shared_qmi_3gpp_register_in_network (_self, operator_id, cancellable, callback, user_data);
        return;
    }
#endif

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    /* keep track of which operator id is selected */
    g_clear_pointer (&self->priv->requested_operator_id, g_free);
    if (operator_id && operator_id[0])
        self->priv->requested_operator_id = g_strdup (operator_id);

    message = (mbim_message_register_state_set_new (
                   self->priv->requested_operator_id ? self->priv->requested_operator_id : "",
                   self->priv->requested_operator_id ? MBIM_REGISTER_ACTION_MANUAL : MBIM_REGISTER_ACTION_AUTOMATIC,
                   self->priv->requested_data_class,
                   NULL));
    mbim_device_command (device,
                         message,
                         60,
                         NULL,
                         (GAsyncReadyCallback)register_state_set_ready,
                         task);
}

/*****************************************************************************/
/* Scan networks (3GPP interface) */

static GList *
modem_3gpp_scan_networks_finish (MMIfaceModem3gpp *self,
                                 GAsyncResult *res,
                                 GError **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
visible_providers_query_ready (MbimDevice *device,
                               GAsyncResult *res,
                               GTask *task)
{
    MbimMessage *response;
    MbimProvider **providers;
    guint n_providers;
    GError *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_visible_providers_response_parse (response,
                                                       &n_providers,
                                                       &providers,
                                                       &error)) {
        GList *info_list;

        info_list = mm_3gpp_network_info_list_from_mbim_providers ((const MbimProvider *const *)providers,
                                                                   n_providers);
        mbim_provider_array_free (providers);

        g_task_return_pointer (task, info_list, (GDestroyNotify)mm_3gpp_network_info_list_free);
    } else
        g_task_return_error (task, error);

    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
modem_3gpp_scan_networks (MMIfaceModem3gpp *self,
                          GAsyncReadyCallback callback,
                          gpointer user_data)
{
    MbimDevice *device;
    MbimMessage *message;
    GTask *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    mm_obj_dbg (self, "scanning networks...");
    message = mbim_message_visible_providers_query_new (MBIM_VISIBLE_PROVIDERS_ACTION_FULL_SCAN, NULL);
    mbim_device_command (device,
                         message,
                         300,
                         NULL,
                         (GAsyncReadyCallback)visible_providers_query_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Check support (Signal interface) */

static gboolean
modem_signal_check_support_finish (MMIfaceModemSignal  *self,
                                   GAsyncResult        *res,
                                   GError             **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
parent_signal_check_support_ready (MMIfaceModemSignal *self,
                                   GAsyncResult       *res,
                                   GTask              *task)
{
    gboolean parent_supported;

    parent_supported = iface_modem_signal_parent->check_support_finish (self, res, NULL);
    g_task_return_boolean (task, parent_supported);
    g_object_unref (task);
}

static void
modem_signal_check_support (MMIfaceModemSignal  *self,
                            GAsyncReadyCallback  callback,
                            gpointer             user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);

    /* If ATDS signal is supported, we support the Signal interface */
    if (MM_BROADBAND_MODEM_MBIM (self)->priv->is_atds_signal_supported) {
        g_task_return_boolean (task, TRUE);
        g_object_unref (task);
        return;
    }

    /* Otherwise, check if the parent CESQ-based implementation works */
    g_assert (iface_modem_signal_parent->check_support && iface_modem_signal_parent->check_support_finish);
    iface_modem_signal_parent->check_support (self,
                                              (GAsyncReadyCallback)parent_signal_check_support_ready,
                                              task);
}

/*****************************************************************************/
/* Load extended signal information (Signal interface) */

typedef struct {
    MMSignal *gsm;
    MMSignal *umts;
    MMSignal *lte;
    MMSignal *nr5g;
} SignalLoadValuesResult;

static void
signal_load_values_result_free (SignalLoadValuesResult *result)
{
    g_clear_object (&result->gsm);
    g_clear_object (&result->umts);
    g_clear_object (&result->lte);
    g_clear_object (&result->nr5g);
    g_slice_free (SignalLoadValuesResult, result);
}

static gboolean
modem_signal_load_values_finish (MMIfaceModemSignal  *self,
                                 GAsyncResult        *res,
                                 MMSignal           **cdma,
                                 MMSignal           **evdo,
                                 MMSignal           **gsm,
                                 MMSignal           **umts,
                                 MMSignal           **lte,
                                 MMSignal           **nr5g,
                                 GError             **error)
{
    SignalLoadValuesResult *result;

    result = g_task_propagate_pointer (G_TASK (res), error);
    if (!result)
        return FALSE;

    if (gsm)
        *gsm = g_steal_pointer (&result->gsm);
    if (umts)
        *umts = g_steal_pointer (&result->umts);
    if (lte)
        *lte = g_steal_pointer (&result->lte);
    if (nr5g)
        *nr5g = g_steal_pointer (&result->nr5g);

    signal_load_values_result_free (result);

    /* No 3GPP2 support */
    if (cdma)
        *cdma = NULL;
    if (evdo)
        *evdo = NULL;
    return TRUE;
}

static void
atds_signal_query_ready (MbimDevice   *device,
                         GAsyncResult *res,
                         GTask        *task)
{
    MbimMessage            *response;
    SignalLoadValuesResult *result;
    GError                 *error = NULL;
    guint32                 rssi;
    guint32                 error_rate;
    guint32                 rscp;
    guint32                 ecno;
    guint32                 rsrq;
    guint32                 rsrp;
    guint32                 snr;

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_atds_signal_response_parse (response, &rssi, &error_rate, &rscp, &ecno, &rsrq, &rsrp, &snr, &error)) {
        g_task_return_error (task, error);
        goto out;
    }

    result = g_slice_new0 (SignalLoadValuesResult);

    if (!mm_signal_from_atds_signal_response (rssi, rscp, ecno, rsrq, rsrp, snr, &result->gsm, &result->umts, &result->lte)) {
        signal_load_values_result_free (result);
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "No signal details given");
        goto out;
    }

    g_task_return_pointer (task, result, (GDestroyNotify) signal_load_values_result_free);

out:
    if (response)
        mbim_message_unref (response);
    g_object_unref (task);
}

static void
parent_signal_load_values_ready (MMIfaceModemSignal *self,
                                 GAsyncResult       *res,
                                 GTask              *task)
{
    SignalLoadValuesResult *result;
    GError                 *error = NULL;

    result = g_slice_new0 (SignalLoadValuesResult);
    if (!iface_modem_signal_parent->load_values_finish (self, res,
                                                        NULL, NULL,
                                                        &result->gsm, &result->umts, &result->lte, &result->nr5g,
                                                        &error)) {
        signal_load_values_result_free (result);
        g_task_return_error (task, error);
    } else if (!result->gsm && !result->umts && !result->lte) {
        signal_load_values_result_free (result);
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "No signal details given");
    } else
        g_task_return_pointer (task, result, (GDestroyNotify) signal_load_values_result_free);
    g_object_unref (task);
}

static void
mbimexv2_signal_state_query_ready (MbimDevice   *device,
                                   GAsyncResult *res,
                                   GTask        *task)
{
    MMBroadbandModemMbim            *self;
    GError                          *error = NULL;
    SignalLoadValuesResult          *result;
    g_autoptr(MbimMessage)           response = NULL;
    g_autoptr(MbimRsrpSnrInfoArray)  rsrp_snr = NULL;
    guint32                          rsrp_snr_count = 0;
    guint32                          rssi;
    guint32                          error_rate = 99;
    MbimDataClass                    data_class;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (!mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Failed MBIMEx v2.0 signal state response support check");
        g_object_unref (task);
        return;
    }

    if (!mbim_message_ms_basic_connect_v2_signal_state_response_parse (
            response,
            &rssi,
            &error_rate,
            NULL, /* signal_strength_interval */
            NULL, /* rssi_threshold */
            NULL, /* error_rate_threshold */
            &rsrp_snr_count,
            &rsrp_snr,
            &error)) {
        g_prefix_error (&error, "Failed processing MBIMEx v2.0 signal state response: ");
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    result = g_slice_new0 (SignalLoadValuesResult);

    data_class = enabled_cache_best_available_data_class (self);
    if (!mm_signal_from_mbim_signal_state (
            data_class, rssi, error_rate, rsrp_snr, rsrp_snr_count, self,
            NULL, NULL, &result->gsm, &result->umts, &result->lte, &result->nr5g)) {
        signal_load_values_result_free (result);
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "No signal details given");
        g_object_unref (task);
        return;
    }

    g_task_return_pointer (task, result, (GDestroyNotify) signal_load_values_result_free);
    g_object_unref (task);
}

static void
modem_signal_load_values (MMIfaceModemSignal  *self,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
    MbimDevice  *device;
    MbimMessage *message;
    GTask       *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (mbim_device_check_ms_mbimex_version (device, 2, 0)) {
        message = mbim_message_signal_state_query_new (NULL);
        mbim_device_command (device,
                             message,
                             5,
                             NULL,
                             (GAsyncReadyCallback)mbimexv2_signal_state_query_ready,
                             task);
        mbim_message_unref (message);
        return;
    }

    if (MM_BROADBAND_MODEM_MBIM (self)->priv->is_atds_signal_supported) {
        message = mbim_message_atds_signal_query_new (NULL);
        mbim_device_command (device,
                             message,
                             5,
                             NULL,
                             (GAsyncReadyCallback)atds_signal_query_ready,
                             task);
        mbim_message_unref (message);
        return;
    }

    /* Fallback to parent CESQ based implementation */
    g_assert (iface_modem_signal_parent->load_values && iface_modem_signal_parent->load_values_finish);
    iface_modem_signal_parent->load_values (self,
                                            NULL,
                                            (GAsyncReadyCallback)parent_signal_load_values_ready,
                                            task);
}

/*****************************************************************************/
/* Setup threshold values (Signal interface) */

static gboolean
modem_signal_setup_thresholds_finish (MMIfaceModemSignal  *self,
                                      GAsyncResult        *res,
                                      GError             **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
signal_state_set_thresholds_ready (MbimDevice   *device,
                                   GAsyncResult *res,
                                   GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                  *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_signal_state_response_parse (
            response,
            NULL, /* rssi */
            NULL, /* error_rate */
            NULL, /* signal_strength_interval */
            NULL, /* rssi_threshold */
            NULL, /* error_rate_threshold */
            &error))
        g_task_return_boolean (task, TRUE);
    else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
modem_signal_setup_thresholds (MMIfaceModemSignal  *self,
                               guint                rssi_threshold,
                               gboolean             error_rate_threshold,
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
{
    g_autoptr(MbimMessage)  message = NULL;
    MbimDevice             *device;
    GTask                  *task;
    guint                   coded_rssi_threshold = 0;
    guint                   coded_error_rate_threshold = 0;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    /* the input RSSI threshold difference is given in dBm, and in the MBIM
     * protocol we use a linear scale of coded values that correspond to 2dBm
     * per code point. */
    if (rssi_threshold) {
        coded_rssi_threshold = rssi_threshold / 2;
        if (!coded_rssi_threshold)
            coded_rssi_threshold = 1; /* minimum value when enabled */
    }

    /* the input error rate threshold is given as a boolean to enable or
     * disable, and in the MBIM protocol we have a non-linear scale of
     * coded values. We just select the minimum coded value, so that we
     * get all reports, i.e. every time it changes the coded value */
    if (error_rate_threshold)
        coded_error_rate_threshold = 1; /* minimum value when enabled */

    /* setting signal strength interval to 0 disables threshold-based
     * notifications on certain modems (FM350).
     * hence, it is being set to 5 as per FBC's recommendation.
     * typically, setting this parameter to 0 should make the modem
     * set the value to its internal default as per spec. */
    message = (mbim_message_signal_state_set_new (
                   5, /* non-zero default signal strength interval */
                   coded_rssi_threshold,
                   coded_error_rate_threshold,
                   NULL));

    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)signal_state_set_thresholds_ready,
                         task);
}

/*****************************************************************************/
/* Check support (3GPP profile management interface) */

static gboolean
modem_3gpp_profile_manager_check_support_finish (MMIfaceModem3gppProfileManager  *_self,
                                                 GAsyncResult                    *res,
                                                 gchar                          **index_field,
                                                 GError                         **error)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    g_assert (g_task_propagate_boolean (G_TASK (res), NULL));

    if (mm_iface_modem_is_3gpp (MM_IFACE_MODEM (self))) {
        if (self->priv->is_profile_management_ext_supported) {
            *index_field = g_strdup ("apn-type");
            return TRUE;
        }
        if (self->priv->is_profile_management_supported) {
            *index_field = g_strdup ("profile-id");
            return TRUE;
        }
    }
    return FALSE;

}

static void
modem_3gpp_profile_manager_check_support (MMIfaceModem3gppProfileManager  *self,
                                          GAsyncReadyCallback              callback,
                                          gpointer                         user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

/*****************************************************************************/
/* Setup/Cleanup unsolicited event handlers (3gppProfileManager interface) */

static gboolean
common_setup_cleanup_unsolicited_events_3gpp_profile_manager_finish (MMIfaceModem3gppProfileManager  *self,
                                                                     GAsyncResult                    *res,
                                                                     GError                         **error)
{
    return common_setup_cleanup_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
cleanup_unsolicited_events_3gpp_profile_manager (MMIfaceModem3gppProfileManager *_self,
                                                 GAsyncReadyCallback             callback,
                                                 gpointer                        user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
    common_setup_cleanup_unsolicited_events (self, FALSE, callback, user_data);
}

static void
setup_unsolicited_events_3gpp_profile_manager (MMIfaceModem3gppProfileManager *_self,
                                               GAsyncReadyCallback             callback,
                                               gpointer                        user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
    common_setup_cleanup_unsolicited_events (self, TRUE, callback, user_data);
}

/*****************************************************************************/
/* Enable/Disable unsolicited event handlers (3gppProfileManager interface) */

static gboolean
common_enable_disable_unsolicited_events_3gpp_profile_manager_finish (MMIfaceModem3gppProfileManager  *self,
                                                                      GAsyncResult                    *res,
                                                                      GError                         **error)
{
    return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
disable_unsolicited_events_3gpp_profile_manager (MMIfaceModem3gppProfileManager *_self,
                                                 GAsyncReadyCallback             callback,
                                                 gpointer                        user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

static void
enable_unsolicited_events_3gpp_profile_manager (MMIfaceModem3gppProfileManager *_self,
                                                GAsyncReadyCallback             callback,
                                                gpointer                        user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_PROVISIONED_CONTEXTS;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

/*****************************************************************************/
/* Check format (3gppProfileManager interface) */

static gboolean
modem_3gpp_profile_manager_check_format_finish (MMIfaceModem3gppProfileManager  *_self,
                                                GAsyncResult                    *res,
                                                gboolean                        *new_id,
                                                gint                            *min_profile_id,
                                                gint                            *max_profile_id,
                                                GEqualFunc                      *apn_cmp,
                                                MM3gppProfileCmpFlags           *profile_cmp_flags,
                                                GError                         **error)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    if (!g_task_propagate_boolean (G_TASK (res), error)) {
        g_assert_not_reached ();
        return FALSE;
    }

    if (new_id)
        *new_id = TRUE;
    if (min_profile_id)
        *min_profile_id = 1;
    if (max_profile_id)
        *max_profile_id = G_MAXINT - 1;
    /* use default string comparison method */
    if (apn_cmp)
        *apn_cmp = NULL;
    if (profile_cmp_flags) {
        if (!self->priv->is_profile_management_ext_supported)
            *profile_cmp_flags = (MM_3GPP_PROFILE_CMP_FLAGS_NO_IP_TYPE |
                                  MM_3GPP_PROFILE_CMP_FLAGS_NO_ACCESS_TYPE_PREFERENCE |
                                  MM_3GPP_PROFILE_CMP_FLAGS_NO_ROAMING_ALLOWANCE |
                                  MM_3GPP_PROFILE_CMP_FLAGS_NO_PROFILE_SOURCE);
        else
            /* when using the MS extensions, we support all IP type, access type
             * preference, roaming allowance and profile source */
            *profile_cmp_flags = 0;
    }
    return TRUE;
}

static void
modem_3gpp_profile_manager_check_format (MMIfaceModem3gppProfileManager *self,
                                         MMBearerIpFamily                ip_type,
                                         GAsyncReadyCallback             callback,
                                         gpointer                        user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

/*****************************************************************************/
/* List profiles (3GPP profile management interface) */

typedef struct {
    GList *profiles;
} ListProfilesContext;

static void
list_profiles_context_free (ListProfilesContext *ctx)
{
    mm_3gpp_profile_list_free (ctx->profiles);
    g_slice_free (ListProfilesContext, ctx);
}

static gboolean
modem_3gpp_profile_manager_list_profiles_finish (MMIfaceModem3gppProfileManager  *self,
                                                 GAsyncResult                    *res,
                                                 GList                          **out_profiles,
                                                 GError                         **error)
{
    ListProfilesContext *ctx;

    if (!g_task_propagate_boolean (G_TASK (res), error))
        return FALSE;

    ctx = g_task_get_task_data (G_TASK (res));
    if (out_profiles)
        *out_profiles = g_steal_pointer (&ctx->profiles);
    return TRUE;
}

static MM3gppProfile *
provisioned_context_element_to_3gpp_profile (MbimProvisionedContextElement *element)
{
    MM3gppProfile *profile;

    profile = mm_3gpp_profile_new ();
    mm_3gpp_profile_set_profile_id   (profile, element->context_id);
    mm_3gpp_profile_set_apn          (profile, element->access_string);
    mm_3gpp_profile_set_apn_type     (profile, mm_bearer_apn_type_from_mbim_context_type (mbim_uuid_to_context_type (&element->context_type)));
    mm_3gpp_profile_set_user         (profile, element->user_name);
    mm_3gpp_profile_set_password     (profile, element->password);
    mm_3gpp_profile_set_allowed_auth (profile, (mm_bearer_allowed_auth_from_mbim_auth_protocol (element->auth_protocol)));
    /* compression unused, and ip-type not provided */
    return profile;
}

static MM3gppProfile *
provisioned_context_element_v2_to_3gpp_profile (MMBroadbandModemMbim            *self,
                                                MbimProvisionedContextElementV2 *element)
{
    MM3gppProfile                *profile;
    GError                       *error = NULL;
    gboolean                      enabled;
    MMBearerRoamingAllowance      roaming_allowance;
    MMBearerAccessTypePreference  access_type_preference;
    MMBearerProfileSource         profile_source;

    profile = mm_3gpp_profile_new ();
    mm_3gpp_profile_set_profile_id   (profile, element->context_id);
    mm_3gpp_profile_set_apn          (profile, element->access_string);
    mm_3gpp_profile_set_apn_type     (profile, mm_bearer_apn_type_from_mbim_context_type (mbim_uuid_to_context_type (&element->context_type)));
    mm_3gpp_profile_set_user         (profile, element->user_name);
    mm_3gpp_profile_set_password     (profile, element->password);
    mm_3gpp_profile_set_allowed_auth (profile, (mm_bearer_allowed_auth_from_mbim_auth_protocol (element->auth_protocol)));
    mm_3gpp_profile_set_ip_type      (profile, mm_bearer_ip_family_from_mbim_context_ip_type (element->ip_type));

    if (!mm_boolean_from_mbim_context_state (element->state, &enabled, &error)) {
        mm_obj_dbg (self, "ignoring enable setting: %s", error->message);
        g_clear_error (&error);
    } else
        mm_3gpp_profile_set_enabled (profile, enabled);

    roaming_allowance = mm_bearer_roaming_allowance_from_mbim_context_roaming_control (element->roaming, &error);
    if (roaming_allowance == MM_BEARER_ROAMING_ALLOWANCE_NONE) {
        mm_obj_dbg (self, "ignoring roaming allowance: %s", error->message);
        g_clear_error (&error);
    } else
        mm_3gpp_profile_set_roaming_allowance (profile, roaming_allowance);

    if (!mm_bearer_access_type_preference_from_mbim_context_media_type (element->media_type, &access_type_preference, &error)) {
        mm_obj_dbg (self, "ignoring access type preference: %s", error->message);
        g_clear_error (&error);
    } else
        mm_3gpp_profile_set_access_type_preference (profile, access_type_preference);

    profile_source = mm_bearer_profile_source_from_mbim_context_source (element->source, &error);
    if (profile_source == MM_BEARER_PROFILE_SOURCE_UNKNOWN) {
        mm_obj_dbg (self, "ignoring profile source: %s", error->message);
        g_clear_error (&error);
    } else
        mm_3gpp_profile_set_profile_source (profile, profile_source);

    /* compression unused */
    return profile;
}

static void
profile_manager_provisioned_contexts_query_ready (MbimDevice   *device,
                                                  GAsyncResult *res,
                                                  GTask        *task)
{
    ListProfilesContext    *ctx;
    GError                 *error = NULL;
    guint                   i;
    guint32                 provisioned_contexts_count = 0;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(MbimProvisionedContextElementArray) provisioned_contexts = NULL;

    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_provisioned_contexts_response_parse (response,
                                                           &provisioned_contexts_count,
                                                           &provisioned_contexts,
                                                           &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    for (i = 0; i < provisioned_contexts_count; i++) {
        MM3gppProfile *profile;

        profile = provisioned_context_element_to_3gpp_profile (provisioned_contexts[i]);
        ctx->profiles = g_list_append (ctx->profiles, profile);
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
profile_manager_provisioned_contexts_v2_query_ready (MbimDevice   *device,
                                                     GAsyncResult *res,
                                                     GTask        *task)
{
    MMBroadbandModemMbim   *self;
    ListProfilesContext    *ctx;
    GError                 *error = NULL;
    guint                   i;
    guint32                 provisioned_contexts_count = 0;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(MbimProvisionedContextElementV2Array) provisioned_contexts_v2 = NULL;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_provisioned_contexts_response_parse (response,
                                                                                       &provisioned_contexts_count,
                                                                                       &provisioned_contexts_v2,
                                                                                       &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    for (i = 0; i < provisioned_contexts_count; i++) {
        MM3gppProfile *profile;

        profile = provisioned_context_element_v2_to_3gpp_profile (self, provisioned_contexts_v2[i]);
        ctx->profiles = g_list_append (ctx->profiles, profile);
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
modem_3gpp_profile_manager_list_profiles (MMIfaceModem3gppProfileManager  *_self,
                                          GAsyncReadyCallback              callback,
                                          gpointer                         user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    ListProfilesContext    *ctx;
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);
    ctx = g_slice_new0 (ListProfilesContext);
    g_task_set_task_data (task, ctx, (GDestroyNotify) list_profiles_context_free);

    mm_obj_dbg (self, "querying provisioned contexts...");

    /* We will use the MBIMEx operation not only when testing with MBIMEx profile management
     * support, but also any time the command is supported by the modem, because it gives much
     * more information than the default one. */
    if (self->priv->is_context_type_ext_supported) {
        message = mbim_message_ms_basic_connect_extensions_provisioned_contexts_query_new (NULL);
        mbim_device_command (device,
                             message,
                             10,
                             NULL,
                             (GAsyncReadyCallback)profile_manager_provisioned_contexts_v2_query_ready,
                             task);
        return;
    }

    message = mbim_message_provisioned_contexts_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)profile_manager_provisioned_contexts_query_ready,
                         task);
}

/*****************************************************************************/
/* Store profile (3GPP profile management interface) */

typedef struct {
    gint            profile_id;
    MMBearerApnType apn_type;
} StoreProfileContext;

static gboolean
modem_3gpp_profile_manager_store_profile_finish (MMIfaceModem3gppProfileManager  *self,
                                                 GAsyncResult                    *res,
                                                 gint                            *out_profile_id,
                                                 MMBearerApnType                 *out_apn_type,
                                                 GError                         **error)
{
    StoreProfileContext *ctx;

    if (!g_task_propagate_boolean (G_TASK (res), error))
        return FALSE;

    ctx = g_task_get_task_data (G_TASK (res));
    if (out_profile_id)
        *out_profile_id = ctx->profile_id;
    if (out_apn_type)
        *out_apn_type = ctx->apn_type;
    return TRUE;
}

static void
profile_manager_provisioned_contexts_set_ready (MbimDevice   *device,
                                                GAsyncResult *res,
                                                GTask        *task)
{
    GError                 *error = NULL;
    g_autoptr(MbimMessage)  response = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error))
        g_task_return_boolean (task, TRUE);
    else
        g_task_return_error (task, error);
    g_object_unref (task);
}

static void
modem_3gpp_profile_manager_store_profile (MMIfaceModem3gppProfileManager *_self,
                                          MM3gppProfile                  *profile,
                                          const gchar                    *index_field,
                                          GAsyncReadyCallback             callback,
                                          gpointer                        user_data)
{
    MMBroadbandModemMbim     *self = MM_BROADBAND_MODEM_MBIM (_self);
    StoreProfileContext      *ctx = NULL;
    MbimDevice               *device;
    GTask                    *task;
    GError                   *error = NULL;
    MMBearerAllowedAuth       allowed_auth;
    MbimAuthProtocol          auth_protocol = MBIM_AUTH_PROTOCOL_NONE;
    MbimContextType           context_type;
    const MbimUuid           *context_type_uuid;
    const gchar              *apn;
    const gchar              *user;
    const gchar              *password;
    g_autofree gchar         *apn_type_str = NULL;
    g_autoptr(MbimMessage)    message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    ctx = g_new0 (StoreProfileContext, 1);
    ctx->profile_id = MM_3GPP_PROFILE_ID_UNKNOWN;
    ctx->apn_type = MM_BEARER_APN_TYPE_NONE;

    g_task_set_task_data (task, ctx, (GDestroyNotify) g_free);

    ctx->profile_id = mm_3gpp_profile_get_profile_id (profile);

    ctx->apn_type = mm_3gpp_profile_get_apn_type (profile);
    apn_type_str = mm_bearer_apn_type_build_string_from_mask (ctx->apn_type);
    context_type = mm_bearer_apn_type_to_mbim_context_type (ctx->apn_type,
                                                            self->priv->is_context_type_ext_supported,
                                                            self,
                                                            &error);
    if (error) {
        g_prefix_error (&error, "Failed to convert mbim context type from apn type: ");
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }
    context_type_uuid = mbim_uuid_from_context_type (context_type);

    apn = mm_3gpp_profile_get_apn (profile);
    user = mm_3gpp_profile_get_user (profile);
    password = mm_3gpp_profile_get_password (profile);
    allowed_auth = mm_3gpp_profile_get_allowed_auth (profile);
    if ((allowed_auth != MM_BEARER_ALLOWED_AUTH_UNKNOWN) || user || password) {
        auth_protocol = mm_bearer_allowed_auth_to_mbim_auth_protocol (allowed_auth, self, &error);
        if (error) {
            g_prefix_error (&error, "Failed to convert mbim auth protocol from allowed auth: ");
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }
    }

    if (g_strcmp0 (index_field, "profile-id") == 0) {
        mm_obj_dbg (self, "storing profile '%d': apn '%s', apn type '%s'",
                    ctx->profile_id, apn, apn_type_str);
        message = mbim_message_provisioned_contexts_set_new (ctx->profile_id,
                                                             context_type_uuid,
                                                             apn ? apn : "",
                                                             user ? user : "",
                                                             password ? password : "",
                                                             MBIM_COMPRESSION_NONE,
                                                             auth_protocol,
                                                             "", /* provider id */
                                                             &error);
    } else if (g_strcmp0 (index_field, "apn-type") == 0) {
        MbimContextIpType         ip_type;
        MbimContextState          state;
        MbimContextRoamingControl roaming;
        MbimContextMediaType      media_type;
        MbimContextSource         source;

        g_assert (self->priv->is_profile_management_ext_supported);

        state = mm_boolean_to_mbim_context_state (mm_3gpp_profile_get_enabled (profile));

        ip_type = mm_bearer_ip_family_to_mbim_context_ip_type (mm_3gpp_profile_get_ip_type (profile), &error);
        if (error) {
            g_prefix_error (&error, "Failed to convert mbim context ip type from ip type: ");
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }

        if (!mm_bearer_roaming_allowance_to_mbim_context_roaming_control (mm_3gpp_profile_get_roaming_allowance (profile), self, &roaming, &error)) {
            g_prefix_error (&error, "Failed to convert mbim context roaming control from roaming allowance: ");
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }

        if (!mm_bearer_access_type_preference_to_mbim_context_media_type (mm_3gpp_profile_get_access_type_preference (profile), self, &media_type, &error)) {
            g_prefix_error (&error, "Failed to convert mbim context media type from access type preference: ");
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }

        if (!mm_bearer_profile_source_to_mbim_context_source (mm_3gpp_profile_get_profile_source (profile), self, &source, &error)) {
            g_prefix_error (&error, "Failed to convert mbim context source from profile source: ");
            g_task_return_error (task, error);
            g_object_unref (task);
            return;
        }

        if (ctx->profile_id != MM_3GPP_PROFILE_ID_UNKNOWN)
            mm_obj_warn (self, "ignoring profile id '%d' when storing profile: unsupported", ctx->profile_id);

        mm_obj_dbg (self, "storing profile '%s': apn '%s'", apn_type_str, apn);
        message = mbim_message_ms_basic_connect_extensions_provisioned_contexts_set_new (MBIM_CONTEXT_OPERATION_DEFAULT,
                                                                                         context_type_uuid,
                                                                                         ip_type,
                                                                                         state,
                                                                                         roaming,
                                                                                         media_type,
                                                                                         source,
                                                                                         apn ? apn : "",
                                                                                         user ? user : "",
                                                                                         password ? password : "",
                                                                                         MBIM_COMPRESSION_NONE,
                                                                                         auth_protocol,
                                                                                         &error);
    } else
        g_assert_not_reached ();

    if (error) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)profile_manager_provisioned_contexts_set_ready,
                         task);
}

/*****************************************************************************/
/* Delete profile (3GPP profile management interface) */

static gboolean
modem_3gpp_profile_manager_delete_profile_finish (MMIfaceModem3gppProfileManager  *self,
                                                  GAsyncResult                    *res,
                                                  GError                         **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
profile_manager_provisioned_contexts_reset_ready (MbimDevice   *device,
                                                  GAsyncResult *res,
                                                  GTask        *task)
{
    GError                 *error = NULL;
    g_autoptr(MbimMessage)  response = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error))
        g_task_return_boolean (task, TRUE);
    else
        g_task_return_error (task, error);
    g_object_unref (task);
}

static void
modem_3gpp_profile_manager_delete_profile (MMIfaceModem3gppProfileManager *_self,
                                           MM3gppProfile                  *profile,
                                           const gchar                    *index_field,
                                           GAsyncReadyCallback             callback,
                                           gpointer                        user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice             *device;
    GTask                  *task;
    GError                 *error = NULL;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (g_strcmp0 (index_field, "profile-id") == 0) {
        gint profile_id;

        profile_id = mm_3gpp_profile_get_profile_id (profile);
        g_assert (profile_id != MM_3GPP_PROFILE_ID_UNKNOWN);

        mm_obj_dbg (self, "deleting profile '%d'", profile_id);
        message = mbim_message_provisioned_contexts_set_new (profile_id,
                                                             mbim_uuid_from_context_type (MBIM_CONTEXT_TYPE_NONE),
                                                             "", /* access string */
                                                             "", /* user */
                                                             "", /* pass */
                                                             MBIM_COMPRESSION_NONE,
                                                             MBIM_AUTH_PROTOCOL_NONE,
                                                             "", /* provider id */
                                                             &error);
    } else if (g_strcmp0 (index_field, "apn-type") == 0) {
        MMBearerApnType  apn_type;
        MbimContextType  context_type;

        g_assert (self->priv->is_profile_management_ext_supported);
        g_assert (self->priv->is_context_type_ext_supported);

        apn_type = mm_3gpp_profile_get_apn_type (profile);
        g_assert (apn_type != MM_BEARER_APN_TYPE_NONE);
        context_type = mm_bearer_apn_type_to_mbim_context_type (apn_type, TRUE, self, &error);
        if (error)
            g_prefix_error (&error, "Failed to convert mbim context type from apn type: ");
        else {
            const MbimUuid  *context_type_uuid;

            context_type_uuid = mbim_uuid_from_context_type (context_type);
            message = mbim_message_ms_basic_connect_extensions_provisioned_contexts_set_new (MBIM_CONTEXT_OPERATION_DELETE,
                                                                                             context_type_uuid,
                                                                                             MBIM_CONTEXT_IP_TYPE_DEFAULT,
                                                                                             MBIM_CONTEXT_STATE_DISABLED,
                                                                                             MBIM_CONTEXT_ROAMING_CONTROL_ALLOW_ALL,
                                                                                             MBIM_CONTEXT_MEDIA_TYPE_ALL,
                                                                                             MBIM_CONTEXT_SOURCE_ADMIN,
                                                                                             "", /* access string */
                                                                                             "", /* user */
                                                                                             "", /* password */
                                                                                             MBIM_COMPRESSION_NONE,
                                                                                             MBIM_AUTH_PROTOCOL_NONE,
                                                                                             &error);
        }
    } else
        g_assert_not_reached ();

    if (error) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)profile_manager_provisioned_contexts_reset_ready,
                         task);
}

/*****************************************************************************/
/* Check if USSD supported (3GPP/USSD interface) */

static gboolean
modem_3gpp_ussd_check_support_finish (MMIfaceModem3gppUssd  *self,
                                      GAsyncResult          *res,
                                      GError               **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
modem_3gpp_ussd_check_support (MMIfaceModem3gppUssd *self,
                               GAsyncReadyCallback   callback,
                               gpointer              user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    g_task_return_boolean (task, MM_BROADBAND_MODEM_MBIM (self)->priv->is_ussd_supported);
    g_object_unref (task);
}

/*****************************************************************************/
/* USSD encoding/decoding helpers
 *
 * Note: we don't care about subclassing the ussd_encode/decode methods in the
 * interface, as we're going to use this methods just here.
 */

static GByteArray *
ussd_encode (const gchar  *command,
             guint32      *scheme,
             GError      **error)
{
    g_autoptr(GByteArray) array = NULL;

    if (mm_charset_can_convert_to (command, MM_MODEM_CHARSET_GSM)) {
        g_autoptr(GByteArray)  gsm = NULL;
        guint8                *packed;
        guint32                packed_len = 0;

        *scheme = MM_MODEM_GSM_USSD_SCHEME_7BIT;
        gsm = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_GSM, FALSE, error);
        if (!gsm) {
            g_prefix_error (error, "Failed to encode USSD command in GSM7 charset: ");
            return NULL;
        }
        packed = mm_charset_gsm_pack (gsm->data, gsm->len, 0, &packed_len);
        array = g_byte_array_new_take (packed, packed_len);
    } else {
        *scheme = MM_MODEM_GSM_USSD_SCHEME_UCS2;
        array = mm_modem_charset_bytearray_from_utf8 (command, MM_MODEM_CHARSET_UCS2, FALSE, error);
        if (!array) {
            g_prefix_error (error, "Failed to encode USSD command in UCS2 charset: ");
            return NULL;
        }
    }

    if (array->len > 160) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
                     "Failed to encode USSD command: encoded data too long (%u > 160)", array->len);
        return NULL;
    }

    return g_steal_pointer (&array);
}

static gchar *
ussd_decode (guint32      scheme,
             GByteArray  *data,
             GError     **error)
{
    gchar *decoded = NULL;

    if (scheme == MM_MODEM_GSM_USSD_SCHEME_7BIT) {
        g_autoptr(GByteArray)  unpacked_array = NULL;
        guint8                *unpacked = NULL;
        guint32                unpacked_len;

        unpacked = mm_charset_gsm_unpack ((const guint8 *)data->data, (data->len * 8) / 7, 0, &unpacked_len);
        unpacked_array = g_byte_array_new_take (unpacked, unpacked_len);

        decoded = mm_modem_charset_bytearray_to_utf8 (unpacked_array, MM_MODEM_CHARSET_GSM, FALSE, error);
        if (!decoded)
            g_prefix_error (error, "Error decoding USSD command in 0x%04x scheme (GSM7 charset): ", scheme);
    } else if (scheme == MM_MODEM_GSM_USSD_SCHEME_UCS2) {
        decoded = mm_modem_charset_bytearray_to_utf8 (data, MM_MODEM_CHARSET_UCS2, FALSE, error);
        if (!decoded)
            g_prefix_error (error, "Error decoding USSD command in 0x%04x scheme (UCS2 charset): ", scheme);
    } else
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                     "Failed to decode USSD command in unsupported 0x%04x scheme", scheme);

    return decoded;
}

/*****************************************************************************/
/* USSD notifications */

static void
process_ussd_message (MMBroadbandModemMbim *self,
                      MbimUssdResponse      ussd_response,
                      MbimUssdSessionState  ussd_session_state,
                      guint32               scheme,
                      guint32               data_size,
                      const guint8         *data)
{
    GTask                       *task = NULL;
    MMModem3gppUssdSessionState  ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE;
    GByteArray                  *bytearray;
    gchar                       *converted = NULL;
    GError                      *error = NULL;

    /* Steal task and balance out received reference */
    if (self->priv->pending_ussd_action) {
        task = self->priv->pending_ussd_action;
        self->priv->pending_ussd_action = NULL;
    }

    bytearray = g_byte_array_new ();
    if (data && data_size)
        bytearray = g_byte_array_append (bytearray, data, data_size);

    switch (ussd_response) {
    case MBIM_USSD_RESPONSE_NO_ACTION_REQUIRED:
        /* no further action required */
        converted = ussd_decode (scheme, bytearray, &error);
        if (!converted)
            break;

        /* Response to the user's request? */
        if (task)
            break;

        /* Network-initiated USSD-Notify */
        mm_iface_modem_3gpp_ussd_update_network_notification (MM_IFACE_MODEM_3GPP_USSD (self), converted);
        g_clear_pointer (&converted, g_free);
        break;

    case MBIM_USSD_RESPONSE_ACTION_REQUIRED:
        /* further action required */
        ussd_state = MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE;

        converted = ussd_decode (scheme, bytearray, &error);
        if (!converted)
            break;
        /* Response to the user's request? */
        if (task)
            break;

        /* Network-initiated USSD-Request */
        mm_iface_modem_3gpp_ussd_update_network_request (MM_IFACE_MODEM_3GPP_USSD (self), converted);
        g_clear_pointer (&converted, g_free);
        break;

    case MBIM_USSD_RESPONSE_TERMINATED_BY_NETWORK:
        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED, "USSD terminated by network");
        break;

    case MBIM_USSD_RESPONSE_OTHER_LOCAL_CLIENT:
        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "Another ongoing USSD operation is in progress");
        break;

    case MBIM_USSD_RESPONSE_OPERATION_NOT_SUPPORTED:
        error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NOT_SUPPORTED, "Operation not supported");
        break;

    case MBIM_USSD_RESPONSE_NETWORK_TIMEOUT:
        error = g_error_new (MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_NETWORK_TIMEOUT, "Network timeout");
        break;

    default:
        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown USSD response (%u)", ussd_response);
        break;
    }

    mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self), ussd_state);

    g_byte_array_unref (bytearray);

    /* Complete the pending action */
    if (task) {
        if (error)
            g_task_return_error (task, error);
        else if (converted)
            g_task_return_pointer (task, converted, g_free);
        else
            g_assert_not_reached ();
        g_object_unref (task);
        return;
    }

    /* If no pending task, just report the error */
    if (error) {
        mm_obj_dbg (self, "network reported USSD message: %s", error->message);
        g_error_free (error);
    }

    g_assert (!converted);
}

static void
process_ussd_notification (MMBroadbandModemMbim *self,
                           MbimMessage          *notification)
{
    MbimUssdResponse      ussd_response;
    MbimUssdSessionState  ussd_session_state;
    guint32               scheme;
    guint32               data_size;
    const guint8         *data;

    if (mbim_message_ussd_notification_parse (notification,
                                              &ussd_response,
                                              &ussd_session_state,
                                              &scheme,
                                              &data_size,
                                              &data,
                                              NULL)) {
        mm_obj_dbg (self, "received USSD indication: %s, session state: %s, scheme: 0x%x, data size: %u bytes",
                    mbim_ussd_response_get_string (ussd_response),
                    mbim_ussd_session_state_get_string (ussd_session_state),
                    scheme,
                    data_size);
        process_ussd_message (self, ussd_response, ussd_session_state, scheme, data_size, data);
    }
}

/*****************************************************************************/
/* Setup/Cleanup unsolicited result codes (3GPP/USSD interface) */

static gboolean
modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gppUssd  *self,
                                                         GAsyncResult          *res,
                                                         GError               **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
common_setup_flag_ussd_ready (MMBroadbandModemMbim *self,
                              GAsyncResult         *res,
                              GTask                *task)
{
    GError *error = NULL;

    if (!common_setup_cleanup_unsolicited_events_finish (self, res, &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
common_setup_cleanup_unsolicited_ussd_events (MMBroadbandModemMbim *self,
                                              gboolean              setup,
                                              GAsyncReadyCallback   callback,
                                              gpointer              user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    g_task_set_task_data (task, GINT_TO_POINTER (setup), NULL);

    if (setup)
        self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_USSD;
    else
        self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_USSD;
    common_setup_cleanup_unsolicited_events (self, setup, (GAsyncReadyCallback)common_setup_flag_ussd_ready, task);
}

static void
modem_3gpp_ussd_cleanup_unsolicited_events (MMIfaceModem3gppUssd *self,
                                            GAsyncReadyCallback   callback,
                                            gpointer              user_data)
{
    common_setup_cleanup_unsolicited_ussd_events (MM_BROADBAND_MODEM_MBIM (self), FALSE, callback, user_data);
}

static void
modem_3gpp_ussd_setup_unsolicited_events (MMIfaceModem3gppUssd *self,
                                          GAsyncReadyCallback   callback,
                                          gpointer              user_data)
{
    common_setup_cleanup_unsolicited_ussd_events (MM_BROADBAND_MODEM_MBIM (self), TRUE, callback, user_data);
}

/*****************************************************************************/
/* Enable/Disable URCs (3GPP/USSD interface) */

static gboolean
modem_3gpp_ussd_enable_disable_unsolicited_events_finish (MMIfaceModem3gppUssd  *self,
                                                          GAsyncResult          *res,
                                                          GError               **error)
{
    return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
modem_3gpp_ussd_disable_unsolicited_events (MMIfaceModem3gppUssd *_self,
                                            GAsyncReadyCallback   callback,
                                            gpointer              user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_USSD;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

static void
modem_3gpp_ussd_enable_unsolicited_events (MMIfaceModem3gppUssd *_self,
                                           GAsyncReadyCallback   callback,
                                           gpointer              user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_USSD;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

/*****************************************************************************/
/* Send command (3GPP/USSD interface) */

static gchar *
modem_3gpp_ussd_send_finish (MMIfaceModem3gppUssd  *self,
                             GAsyncResult          *res,
                             GError               **error)
{
    return g_task_propagate_pointer (G_TASK (res), error);
}

static void
ussd_send_ready (MbimDevice           *device,
                 GAsyncResult         *res,
                 MMBroadbandModemMbim *self)
{
    MbimMessage          *response;
    GError               *error = NULL;
    MbimUssdResponse      ussd_response;
    MbimUssdSessionState  ussd_session_state;
    guint32               scheme;
    guint32               data_size;
    const guint8         *data;

    /* Note: if there is a cached task, it is ALWAYS completed here */

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_ussd_response_parse (response,
                                          &ussd_response,
                                          &ussd_session_state,
                                          &scheme,
                                          &data_size,
                                          &data,
                                          &error)) {
        mm_obj_dbg (self, "received USSD response: %s, session state: %s, scheme: 0x%x, data size: %u bytes",
                    mbim_ussd_response_get_string (ussd_response),
                    mbim_ussd_session_state_get_string (ussd_session_state),
                    scheme,
                    data_size);
        process_ussd_message (self, ussd_response, ussd_session_state, scheme, data_size, data);
    } else {
        /* Report error in the cached task, if any */
        if (self->priv->pending_ussd_action) {
            GTask *task;

            task = self->priv->pending_ussd_action;
            self->priv->pending_ussd_action = NULL;
            g_task_return_error (task, error);
            g_object_unref (task);
        } else {
            mm_obj_dbg (self, "failed to parse USSD response: %s", error->message);
            g_clear_error (&error);
        }
    }

    if (response)
        mbim_message_unref (response);

    /* Balance out received reference */
    g_object_unref (self);
}

static void
modem_3gpp_ussd_send (MMIfaceModem3gppUssd *_self,
                      const gchar          *command,
                      GAsyncReadyCallback   callback,
                      gpointer              user_data)
{
    MMBroadbandModemMbim *self;
    MbimDevice           *device;
    GTask                *task;
    MbimUssdAction        action;
    MbimMessage          *message;
    GByteArray           *encoded;
    guint32               scheme = 0;
    GError               *error = NULL;

    self = MM_BROADBAND_MODEM_MBIM (_self);
    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    /* Fail if there is an ongoing operation already */
    if (self->priv->pending_ussd_action) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
                                 "there is already an ongoing USSD operation");
        g_object_unref (task);
        return;
    }

    switch (mm_iface_modem_3gpp_ussd_get_state (MM_IFACE_MODEM_3GPP_USSD (self))) {
        case MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE:
            action = MBIM_USSD_ACTION_INITIATE;
            break;
        case MM_MODEM_3GPP_USSD_SESSION_STATE_USER_RESPONSE:
            action = MBIM_USSD_ACTION_CONTINUE;
            break;
        case MM_MODEM_3GPP_USSD_SESSION_STATE_UNKNOWN:
        case MM_MODEM_3GPP_USSD_SESSION_STATE_ACTIVE:
        default:
            g_assert_not_reached ();
            return;
    }

    encoded = ussd_encode (command, &scheme, &error);
    if (!encoded) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    message = mbim_message_ussd_set_new (action, scheme, encoded->len, encoded->data, &error);
    if (!message) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* Cache the action, as it may be completed via URCs */
    self->priv->pending_ussd_action = task;
    mm_iface_modem_3gpp_ussd_update_state (_self, MM_MODEM_3GPP_USSD_SESSION_STATE_ACTIVE);

    mbim_device_command (device,
                         message,
                         100,
                         NULL,
                         (GAsyncReadyCallback)ussd_send_ready,
                         g_object_ref (self)); /* Full reference! */
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Cancel USSD (3GPP/USSD interface) */

static gboolean
modem_3gpp_ussd_cancel_finish (MMIfaceModem3gppUssd  *self,
                               GAsyncResult          *res,
                               GError               **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
ussd_cancel_ready (MbimDevice   *device,
                   GAsyncResult *res,
                   GTask        *task)
{
    MMBroadbandModemMbim *self;
    MbimMessage          *response;
    GError               *error = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (response)
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);

    /* Complete the pending action, regardless of the operation result */
    if (self->priv->pending_ussd_action) {
        GTask *pending_task;

        pending_task = self->priv->pending_ussd_action;
        self->priv->pending_ussd_action = NULL;

        g_task_return_new_error (pending_task, MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
                                 "USSD session was cancelled");
        g_object_unref (pending_task);
    }

    mm_iface_modem_3gpp_ussd_update_state (MM_IFACE_MODEM_3GPP_USSD (self),
                                           MM_MODEM_3GPP_USSD_SESSION_STATE_IDLE);

    if (error)
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);

    if (response)
        mbim_message_unref (response);
}

static void
modem_3gpp_ussd_cancel (MMIfaceModem3gppUssd *_self,
                        GAsyncReadyCallback   callback,
                        gpointer              user_data)
{
    MMBroadbandModemMbim *self;
    MbimDevice           *device;
    GTask                *task;
    MbimMessage          *message;
    GError               *error = NULL;

    self = MM_BROADBAND_MODEM_MBIM (_self);
    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_ussd_set_new (MBIM_USSD_ACTION_CANCEL, 0, 0, NULL, &error);
    if (!message) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)ussd_cancel_ready,
                         task);
    mbim_message_unref (message);
}

/*****************************************************************************/
/* Check support (Messaging interface) */

static gboolean
messaging_check_support_finish (MMIfaceModemMessaging *self,
                                GAsyncResult *res,
                                GError **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
messaging_check_support (MMIfaceModemMessaging *_self,
                         GAsyncReadyCallback callback,
                         gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);

    /* We only handle 3GPP messaging (PDU based) currently */
    if (self->priv->caps_sms & MBIM_SMS_CAPS_PDU_RECEIVE &&
        self->priv->caps_sms & MBIM_SMS_CAPS_PDU_SEND) {
        mm_obj_dbg (self, "messaging capabilities supported");
        g_task_return_boolean (task, TRUE);
    } else {
        mm_obj_dbg (self, "messaging capabilities not supported by this modem");
        g_task_return_boolean (task, FALSE);
    }
    g_object_unref (task);
}

/*****************************************************************************/
/* Load supported storages (Messaging interface) */

static gboolean
messaging_load_supported_storages_finish (MMIfaceModemMessaging *self,
                                          GAsyncResult *res,
                                          GArray **mem1,
                                          GArray **mem2,
                                          GArray **mem3,
                                          GError **error)
{
    MMSmsStorage supported;

    *mem1 = g_array_sized_new (FALSE, FALSE, sizeof (MMSmsStorage), 2);
    supported = MM_SMS_STORAGE_MT;
    g_array_append_val (*mem1, supported);
    *mem2 = g_array_ref (*mem1);
    *mem3 = g_array_ref (*mem1);
    return TRUE;
}

static void
messaging_load_supported_storages (MMIfaceModemMessaging *self,
                                   GAsyncReadyCallback callback,
                                   gpointer user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);
    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

/*****************************************************************************/
/* Load initial SMS parts */

static gboolean
load_initial_sms_parts_finish (MMIfaceModemMessaging  *self,
                               GAsyncResult           *res,
                               GError                **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
add_sms_part (MMBroadbandModemMbim       *self,
              const MbimSmsPduReadRecord *pdu,
              guint                       part_index,
              guint                       expected_index)
{
    MMSmsPart         *part;
    g_autoptr(GError)  error = NULL;

    part = mm_sms_part_3gpp_new_from_binary_pdu (part_index,
                                                 pdu->pdu_data,
                                                 pdu->pdu_data_size,
                                                 self,
                                                 FALSE,
                                                 &error);
    if (part) {
        mm_obj_dbg (self, "correctly parsed PDU (%d)", pdu->message_index);
        if (expected_index != SMS_PART_INVALID_INDEX && (expected_index != mm_sms_part_get_index (part))) {
            /* Some Fibocom L850 modems report an invalid part index (always 1) in the response
             * message. Because we know which message part was requested, we can use that value
             * instead to workaround this bug. */
            mm_obj_dbg (self, "Expected SMS part index '%u' but device reports index '%u': using the expected one",
                        expected_index, mm_sms_part_get_index (part));
            mm_sms_part_set_index (part, expected_index);
        }
        mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
                                            part,
                                            mm_sms_state_from_mbim_message_status (pdu->message_status),
                                            MM_SMS_STORAGE_MT);
    } else {
        /* Don't treat the error as critical */
        mm_obj_dbg (self, "error parsing PDU (%d): %s",
                    pdu->message_index, error->message);
    }
}

static gboolean
process_pdu_messages (MMBroadbandModemMbim       *self,
                      MbimSmsFormat               format,
                      guint32                     messages_count,
                      MbimSmsPduReadRecordArray  *pdu_messages,
                      guint                       expected_index,
                      gboolean                    unstored,
                      GError                    **error)
{
    guint i;

    if (format != MBIM_SMS_FORMAT_PDU) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                     "Ignoring SMS, unsupported format: '%s'",
                     mbim_sms_format_get_string (format));
        return FALSE;
    }

    if (!pdu_messages || !messages_count) {
        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                     "No SMS PDUs read");
        return FALSE;
    }

    if (messages_count > 1 && expected_index != SMS_PART_INVALID_INDEX) {
        mm_obj_warn (self, "can read only single message when expected index is set ('%u')",
                     expected_index);
        messages_count = 1;
    }

    mm_obj_dbg (self, "%u SMS PDUs reported", messages_count);
    for (i = 0; i < messages_count; i++) {
        guint message_index;

        if (!pdu_messages[i]) {
            mm_obj_dbg (self, "ignoring invalid SMS PDU %u/%u...", i+1, messages_count);
            continue;
        }

        mm_obj_dbg (self, "processing SMS PDU %u/%u...", i+1, messages_count);

        message_index = pdu_messages[i]->message_index;
        if (unstored && message_index != 0) {
            mm_obj_warn (self,
                         "processed unstored PDU with non-zero message index (%u)",
                         message_index);
        }

        add_sms_part (self,
                      pdu_messages[i],
                      unstored ? SMS_PART_INVALID_INDEX : message_index,
                      expected_index);
    }

    return TRUE;
}

static void
sms_read_query_ready (MbimDevice   *device,
                      GAsyncResult *res,
                      GTask        *task)
{
    MMBroadbandModemMbim                *self;
    GError                              *error = NULL;
    MbimSmsFormat                        format;
    guint32                              messages_count;
    g_autoptr(MbimMessage)               response = NULL;
    g_autoptr(MbimSmsPduReadRecordArray) pdu_messages = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_sms_read_response_parse (
            response,
            &format,
            &messages_count,
            &pdu_messages,
            NULL, /* cdma_messages */
            &error) ||
        !process_pdu_messages (self,
                               format,
                               messages_count,
                               pdu_messages,
                               SMS_PART_INVALID_INDEX,
                               FALSE,
                               &error))
        g_task_return_error (task, error);
    else
        g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
load_initial_sms_parts (MMIfaceModemMessaging *self,
                        MMSmsStorage           storage,
                        GAsyncReadyCallback    callback,
                        gpointer               user_data)
{
    GTask                  *task;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    g_assert (storage == MM_SMS_STORAGE_MT);

    task = g_task_new (self, NULL, callback, user_data);
    message = mbim_message_sms_read_query_new (MBIM_SMS_FORMAT_PDU,
                                               MBIM_SMS_FLAG_ALL,
                                               0, /* message index, unused */
                                               NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)sms_read_query_ready,
                         task);
}

/*****************************************************************************/
/* Setup/Cleanup unsolicited event handlers (Messaging interface) */

static gboolean
common_setup_cleanup_unsolicited_events_messaging_finish (MMIfaceModemMessaging *self,
                                                          GAsyncResult *res,
                                                          GError **error)
{
    return common_setup_cleanup_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
cleanup_unsolicited_events_messaging (MMIfaceModemMessaging *_self,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags &= ~PROCESS_NOTIFICATION_FLAG_SMS_READ;
    common_setup_cleanup_unsolicited_events (self, FALSE, callback, user_data);
}

static void
setup_unsolicited_events_messaging (MMIfaceModemMessaging *_self,
                                    GAsyncReadyCallback callback,
                                    gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->setup_flags |= PROCESS_NOTIFICATION_FLAG_SMS_READ;
    common_setup_cleanup_unsolicited_events (self, TRUE, callback, user_data);
}

/*****************************************************************************/
/* Enable/Disable unsolicited event handlers (Messaging interface) */

static gboolean
common_enable_disable_unsolicited_events_messaging_finish (MMIfaceModemMessaging *self,
                                                           GAsyncResult *res,
                                                           GError **error)
{
    return common_enable_disable_unsolicited_events_finish (MM_BROADBAND_MODEM_MBIM (self), res, error);
}

static void
disable_unsolicited_events_messaging (MMIfaceModemMessaging *_self,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags &= ~PROCESS_NOTIFICATION_FLAG_SMS_READ;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

static void
enable_unsolicited_events_messaging (MMIfaceModemMessaging *_self,
                                     GAsyncReadyCallback callback,
                                     gpointer user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);

    self->priv->enable_flags |= PROCESS_NOTIFICATION_FLAG_SMS_READ;
    common_enable_disable_unsolicited_events (self, callback, user_data);
}

/*****************************************************************************/
/* Create SMS (Messaging interface) */

static MMBaseSms *
messaging_create_sms (MMIfaceModemMessaging *self)
{
    return mm_sms_mbim_new (MM_BASE_MODEM (self));
}

/*****************************************************************************/
/* Check support (SAR interface) */

static gboolean
sar_check_support_finish (MMIfaceModemSar *self,
                          GAsyncResult    *res,
                          GError         **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
sar_check_support (MMIfaceModemSar    *_self,
                   GAsyncReadyCallback callback,
                   gpointer            user_data)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
    GTask                *task;

    task = g_task_new (self, NULL, callback, user_data);

    mm_obj_dbg (self, "SAR capabilities %s", self->priv->is_ms_sar_supported ? "supported" : "not supported");
    g_task_return_boolean (task, self->priv->is_ms_sar_supported);
    g_object_unref (task);
}

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

static gboolean
sar_load_state_finish (MMIfaceModemSar *self,
                       GAsyncResult    *res,
                       gboolean        *out_state,
                       GError         **error)
{
    GError   *inner_error = NULL;
    gboolean  result;

    result = g_task_propagate_boolean (G_TASK (res), &inner_error);
    if (inner_error) {
        g_propagate_error (error, inner_error);
        return FALSE;
    }

    if (out_state)
        *out_state = result;
    return TRUE;
}

static void
sar_config_query_state_ready (MbimDevice   *device,
                              GAsyncResult *res,
                              GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;
    MbimSarBackoffState     state;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_ms_sar_config_response_parse (
            response,
            NULL,
            &state,
            NULL,
            NULL,
            NULL,
            &error))
        g_task_return_boolean (task, state);
    else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
sar_load_state (MMIfaceModemSar    *_self,
                GAsyncReadyCallback callback,
                gpointer            user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_ms_sar_config_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)sar_config_query_state_ready,
                         task);
}

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

static gboolean
sar_load_power_level_finish (MMIfaceModemSar *self,
                             GAsyncResult    *res,
                             guint           *out_power_level,
                             GError         **error)
{
    gssize result;

    result = g_task_propagate_int (G_TASK (res), error);
    if (result < 0)
        return FALSE;

    *out_power_level = (guint) result;
    return TRUE;
}

static void
sar_config_query_power_level_ready (MbimDevice   *device,
                                    GAsyncResult *res,
                                    GTask        *task)
{
    MMBroadbandModemMbim               *self;
    GError                             *error = NULL;
    guint32                             states_count;
    g_autoptr(MbimSarConfigStateArray)  config_states = NULL;
    g_autoptr(MbimMessage)              response = NULL;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) &&
        mbim_message_ms_sar_config_response_parse (
            response,
            NULL,
            NULL,
            NULL,
            &states_count,
            &config_states,
            &error)) {
        if (states_count == 0) {
            g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_WRONG_STATE, "Couldn't load config states");
        } else {
            if (states_count > 1)
                mm_obj_dbg (self, "Device reports SAR config states for %u antennas separately, but only considering the first one", states_count);
            g_task_return_int (task, config_states[0]->backoff_index);
        }
    } else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
sar_load_power_level (MMIfaceModemSar    *_self,
                      GAsyncReadyCallback callback,
                      gpointer            user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice             *device;
    GTask                  *task;
    g_autoptr(MbimMessage)  message = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_ms_sar_config_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)sar_config_query_power_level_ready,
                         task);
}

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

static gboolean
sar_enable_finish (MMIfaceModemSar *self,
                   GAsyncResult    *res,
                   guint           *out_sar_power_level,
                   GError         **error)
{
    guint level;

    if (!g_task_propagate_boolean (G_TASK (res), error))
        return FALSE;

    level = GPOINTER_TO_UINT (g_task_get_task_data (G_TASK (res)));
    if (out_sar_power_level)
        *out_sar_power_level = level;
    return TRUE;
}

static void
sar_config_set_enable_ready (MbimDevice   *device,
                             GAsyncResult *res,
                             GTask        *task)
{
    g_autoptr(MbimMessage) response = NULL;
    GError *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_boolean (task, TRUE);
    } else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
sar_enable (MMIfaceModemSar    *_self,
            gboolean            enable,
            GAsyncReadyCallback callback,
            gpointer            user_data)
{
    MMBroadbandModemMbim          *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice                    *device;
    GTask                         *task;
    g_autoptr(MbimMessage)         message = NULL;
    g_autofree MbimSarConfigState *config_state = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    /*
     * the value 0xFFFFFFFF means all antennas
     * the backoff index set to the current index of modem
     */
    config_state = g_new (MbimSarConfigState, 1);
    config_state->antenna_index = 0xFFFFFFFF;
    config_state->backoff_index = mm_iface_modem_sar_get_power_level (_self);

    g_task_set_task_data (task, GUINT_TO_POINTER (config_state->backoff_index), NULL);

    message = mbim_message_ms_sar_config_set_new (MBIM_SAR_CONTROL_MODE_OS,
                                                  enable ? MBIM_SAR_BACKOFF_STATE_ENABLED : MBIM_SAR_BACKOFF_STATE_DISABLED,
                                                  1, (const MbimSarConfigState **)&config_state, NULL);

    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)sar_config_set_enable_ready,
                         task);
}

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

static gboolean
sar_set_power_level_finish (MMIfaceModemSar *self,
                            GAsyncResult    *res,
                            GError         **error)
{
     return g_task_propagate_boolean (G_TASK (res), error);
}

static void
sar_config_set_power_level_ready (MbimDevice   *device,
                                  GAsyncResult *res,
                                  GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_boolean (task, TRUE);
    } else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
sar_set_power_level (MMIfaceModemSar    *_self,
                     guint               power_level,
                     GAsyncReadyCallback callback,
                     gpointer            user_data)
{
    MMBroadbandModemMbim          *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice                    *device;
    GTask                         *task;
    g_autoptr(MbimMessage)         message = NULL;
    g_autofree MbimSarConfigState *config_state = NULL;

    if (!peek_device (self, &device, callback, user_data))
        return;

    /*
     * the value 0xFFFFFFFF means all antennas
     * the backoff index set to the input power level
     */
    config_state = g_new (MbimSarConfigState, 1);
    config_state->antenna_index = 0xFFFFFFFF;
    config_state->backoff_index = power_level;

    task = g_task_new (self, NULL, callback, user_data);

    message = mbim_message_ms_sar_config_set_new (MBIM_SAR_CONTROL_MODE_OS,
                                                  MBIM_SAR_BACKOFF_STATE_ENABLED,
                                                  1, (const MbimSarConfigState **)&config_state, NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)sar_config_set_power_level_ready,
                         task);
}

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

static void
set_property (GObject *object,
              guint prop_id,
              const GValue *value,
              GParamSpec *pspec)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (object);

    switch (prop_id) {
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
   case PROP_QMI_UNSUPPORTED:
        self->priv->qmi_unsupported = g_value_get_boolean (value);
        break;
#endif
   case PROP_INTEL_FIRMWARE_UPDATE_UNSUPPORTED:
        self->priv->intel_firmware_update_unsupported = g_value_get_boolean (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
get_property (GObject *object,
              guint prop_id,
              GValue *value,
              GParamSpec *pspec)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (object);

    switch (prop_id) {
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    case PROP_QMI_UNSUPPORTED:
        g_value_set_boolean (value, self->priv->qmi_unsupported);
        break;
#endif
    case PROP_INTEL_FIRMWARE_UPDATE_UNSUPPORTED:
        g_value_set_boolean (value, self->priv->intel_firmware_update_unsupported);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

/*****************************************************************************/
/* Create SIMs in all SIM slots */

typedef struct {
    GPtrArray *sim_slots;
    guint number_slots;
    guint query_slot_index;  /* range [0,number_slots-1] */
    guint active_slot_index; /* range [1,number_slots]   */
} LoadSimSlotsContext;

static void
load_sim_slots_context_free (LoadSimSlotsContext *ctx)
{
    g_clear_pointer (&ctx->sim_slots, g_ptr_array_unref);
    g_slice_free (LoadSimSlotsContext, ctx);
}

static void
sim_slot_free (MMBaseSim *sim)
{
    if (sim)
        g_object_unref (sim);
}

static gboolean
load_sim_slots_finish (MMIfaceModem *self,
                       GAsyncResult *res,
                       GPtrArray   **sim_slots,
                       guint        *primary_sim_slot,
                       GError      **error)
{
    LoadSimSlotsContext *ctx;

    if (!g_task_propagate_boolean (G_TASK(res), error))
        return FALSE;

    ctx = g_task_get_task_data (G_TASK (res));

    if (sim_slots)
        *sim_slots = g_steal_pointer (&ctx->sim_slots);
    if (primary_sim_slot)
        *primary_sim_slot = ctx->active_slot_index;
    return TRUE;
}

static void
query_slot_information_status (MbimDevice *device,
                               guint       slot_index,
                               GTask      *task);

static void
query_slot_information_status_ready (MbimDevice   *device,
                                     GAsyncResult *res,
                                     GTask        *task)
{
    MMBroadbandModemMbim  *self;
    g_autoptr(MbimMessage) response = NULL;
    GError                *error = NULL;
    guint32                slot_index;
    MbimUiccSlotState      slot_state;
    LoadSimSlotsContext   *ctx;
    MMBaseSim             *sim;
    gboolean               sim_active = FALSE;

    self = g_task_get_source_object (task);
    ctx = g_task_get_task_data (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_slot_info_status_response_parse (
                response,
                &slot_index,
                &slot_state,
                &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    /* the slot index in MM starts at 1 */
    if ((slot_index + 1) == ctx->active_slot_index)
        sim_active = TRUE;

    sim = create_sim_from_slot_state (self, sim_active, slot_index, slot_state);
    g_ptr_array_add (ctx->sim_slots, sim);

    ctx->query_slot_index++;
    if (ctx->query_slot_index < ctx->number_slots) {
        query_slot_information_status (device, ctx->query_slot_index, task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}

static void
query_slot_information_status (MbimDevice *device,
                               guint       slot_index,
                               GTask      *task)
{
    g_autoptr(MbimMessage) message = NULL;

    message = mbim_message_ms_basic_connect_extensions_slot_info_status_query_new (slot_index, NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)query_slot_information_status_ready,
                         task);
}

static void
query_device_slot_mappings_ready (MbimDevice   *device,
                                  GAsyncResult *res,
                                  GTask        *task)
{
    MMBroadbandModemMbim    *self;
    g_autoptr(MbimMessage)   response = NULL;
    GError                  *error = NULL;
    guint32                  map_count = 0;
    g_autoptr(MbimSlotArray) slot_mappings = NULL;
    LoadSimSlotsContext     *ctx;

    ctx = g_task_get_task_data (task);
    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse (
            response,
            &map_count,
            &slot_mappings,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (self->priv->executor_index >= map_count) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                                 "The executor index doesn't have an entry in the map count");
        g_object_unref (task);
        return;
    }

    /* the slot index in MM starts at 1 */
    ctx->active_slot_index = slot_mappings[self->priv->executor_index]->slot + 1;
    self->priv->active_slot_index = ctx->active_slot_index;

    query_slot_information_status (device, ctx->query_slot_index, task);
}

static void
query_device_slot_mappings (MbimDevice *device,
                            GTask      *task)
{
    g_autoptr(MbimMessage) message = NULL;

    message = mbim_message_ms_basic_connect_extensions_device_slot_mappings_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)query_device_slot_mappings_ready,
                         task);
}

static void
query_device_caps_ready (MbimDevice   *device,
                         GAsyncResult *res,
                         GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;
    guint32                 executor_index;

    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_device_caps_response_parse (
            response,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            &executor_index,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    self->priv->executor_index = executor_index;

    query_device_slot_mappings (device, task);
}

static void
query_sys_caps_ready (MbimDevice   *device,
                      GAsyncResult *res,
                      GTask        *task)
{
    MMBroadbandModemMbim  *self;
    g_autoptr(MbimMessage) response = NULL;
    g_autoptr(MbimMessage) message = NULL;
    GError                *error = NULL;
    guint32                number_executors;
    guint32                number_slots;
    guint32                concurrency;
    guint64                modem_id;
    LoadSimSlotsContext   *ctx;

    ctx = g_task_get_task_data (task);
    self = g_task_get_source_object (task);

    response = mbim_device_command_finish (device, res, &error);
    if (!response ||
        !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_sys_caps_response_parse (
            response,
            &number_executors,
            &number_slots,
            &concurrency,
            &modem_id,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    if (number_slots == 1) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                                 "Only one SIM slot is supported");
        g_object_unref (task);
        return;
    }
    ctx->number_slots = number_slots;
    ctx->sim_slots = g_ptr_array_new_full (number_slots, (GDestroyNotify) sim_slot_free);

    if (number_executors == 0) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                                 "There is no executor");
        g_object_unref (task);
        return;
    }
    /* Given that there is one single executor supported,we assume the executor index to be always 0 */
    if (number_executors == 1) {
        self->priv->executor_index = 0;
        query_device_slot_mappings (device, task);
        return;
    }
    /* Given that more than one executors supported,we first query the current device caps to know which is the current executor index */
    message = mbim_message_ms_basic_connect_extensions_device_caps_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)query_device_caps_ready,
                         task);
}

static void
load_sim_slots_mbim (GTask *task)
{
    MMBroadbandModemMbim   *self;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;

    self = g_task_get_source_object (task);

    if (!peek_device_in_task (self, &device, task))
        return;

    message = mbim_message_ms_basic_connect_extensions_sys_caps_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)query_sys_caps_ready,
                         task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
static void
shared_qmi_load_sim_slots_ready (MMIfaceModem *self,
                                 GAsyncResult *res,
                                 GTask        *task)
{
    g_autoptr(GPtrArray)   sim_slots = NULL;
    guint                  primary_sim_slot = 0;
    LoadSimSlotsContext   *ctx;

    if (!mm_shared_qmi_load_sim_slots_finish (self, res, &sim_slots, &primary_sim_slot, NULL)) {
        load_sim_slots_mbim (task);
        return;
    }

    ctx = g_task_get_task_data (task);

    ctx->sim_slots = g_steal_pointer (&sim_slots);
    ctx->active_slot_index = primary_sim_slot;

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}
#endif

static void
load_sim_slots (MMIfaceModem       *self,
                GAsyncReadyCallback callback,
                gpointer            user_data)
{
    GTask               *task;
    LoadSimSlotsContext *ctx;

    task = g_task_new (self, NULL, callback, user_data);

    ctx = g_slice_new0 (LoadSimSlotsContext);
    g_task_set_task_data (task, ctx, (GDestroyNotify)load_sim_slots_context_free);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    mm_shared_qmi_load_sim_slots (self, (GAsyncReadyCallback)shared_qmi_load_sim_slots_ready, task);
#else
    load_sim_slots_mbim (task);
#endif
}

/*****************************************************************************/
/* Set Primary SIM slot (modem interface) */

static gboolean
set_primary_sim_slot_finish (MMIfaceModem *self,
                             GAsyncResult *res,
                             GError      **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
set_device_slot_mappings_ready (MbimDevice   *device,
                                GAsyncResult *res,
                                GTask        *task)
{
    MMBroadbandModemMbim    *self;
    g_autoptr(MbimMessage)   response = NULL;
    GError                  *error = NULL;
    guint32                  map_count = 0;
    g_autoptr(MbimSlotArray) slot_mappings = NULL;
    guint                    i;
    guint                    slot_number;

    self = g_task_get_source_object (task);

    g_assert (self->priv->pending_sim_slot_switch_action);

    /* the slot index in MM starts at 1 */
    slot_number = GPOINTER_TO_UINT (g_task_get_task_data (task)) - 1;

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse (
            response,
            &map_count,
            &slot_mappings,
            &error)) {
        self->priv->pending_sim_slot_switch_action = FALSE;
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    for (i = 0; i < map_count; i++) {
        if (i == self->priv->executor_index) {
            if (slot_number != slot_mappings[i]->slot) {
                self->priv->pending_sim_slot_switch_action = FALSE;
                g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                                         "SIM slot switch to '%u' failed", slot_number);
            } else {
                /* Keep pending_sim_slot_switch_action flag TRUE to cleanly ignore SIM related indications
                 * during slot switching, We don't want SIM related indications received trigger the update
                 * of SimSlots property, which may not be what we want as the modem object is being shutdown */
                self->priv->active_slot_index = slot_number + 1;
                g_task_return_boolean (task, TRUE);
            }

            g_object_unref (task);
            return;
        }
    }

    self->priv->pending_sim_slot_switch_action = FALSE;
    g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                             "Can't find executor index '%u'", self->priv->executor_index);
    g_object_unref (task);
}

static void
before_set_query_device_slot_mappings_ready (MbimDevice   *device,
                                             GAsyncResult *res,
                                             GTask        *task)
{
    MMBroadbandModemMbim    *self;
    g_autoptr(MbimMessage)   response = NULL;
    GError                  *error = NULL;
    g_autoptr(MbimMessage)   message = NULL;
    guint32                  map_count = 0;
    g_autoptr(MbimSlotArray) slot_mappings = NULL;
    guint                    i;
    guint                    slot_number;

    self = g_task_get_source_object (task);

    /* the slot index in MM starts at 1 */
    slot_number = GPOINTER_TO_UINT (g_task_get_task_data (task)) - 1;

    response = mbim_device_command_finish (device, res, &error);
    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
        !mbim_message_ms_basic_connect_extensions_device_slot_mappings_response_parse (
            response,
            &map_count,
            &slot_mappings,
            &error)) {
        g_task_return_error (task, error);
        g_object_unref (task);
        return;
    }

    for (i = 0; i < map_count; i++) {
        if (slot_number == slot_mappings[i]->slot) {
            if (i != self->priv->executor_index) {
                g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND,
                                         "The sim slot '%u' is already used by executor '%u'", slot_number, i);
            } else {
                mm_obj_dbg (self, "The slot is already the requested one");
                g_task_return_boolean (task, TRUE);
            }

            g_object_unref (task);
            return;
        }
    }

    /* Flag a pending SIM slot switch operation, so that we can ignore slot state updates
     * during the process. */
    if (self->priv->pending_sim_slot_switch_action) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS,
                                 "there is already an ongoing SIM slot switch operation");
        g_object_unref (task);
        return;
    }
    self->priv->pending_sim_slot_switch_action = TRUE;

    for (i = 0; i < map_count; i++) {
        if (i == self->priv->executor_index)
            slot_mappings[i]->slot = slot_number;
    }

    message = mbim_message_ms_basic_connect_extensions_device_slot_mappings_set_new (map_count,
                                                                                     (const MbimSlot **)slot_mappings,
                                                                                     NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)set_device_slot_mappings_ready,
                         task);
}

static void
set_primary_sim_slot_mbim (GTask *task)
{
    MMBroadbandModemMbim   *self;
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;

    self = g_task_get_source_object (task);

    if (!peek_device_in_task (self, &device, task))
        return;

    message = mbim_message_ms_basic_connect_extensions_device_slot_mappings_query_new (NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)before_set_query_device_slot_mappings_ready,
                         task);
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
static void
shared_qmi_set_primary_sim_slot_ready (MMIfaceModem *self,
                                       GAsyncResult *res,
                                       GTask        *task)
{
    g_autoptr(GError) error = NULL;

    if (!mm_shared_qmi_set_primary_sim_slot_finish (self, res, &error)) {
        /* Fallback to MBIM */
        if (g_error_matches (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
            set_primary_sim_slot_mbim (task);
            return;
        }
        g_task_return_error (task, g_steal_pointer (&error));
        g_object_unref (task);
        return;
    }

    g_task_return_boolean (task, TRUE);
    g_object_unref (task);
}
#endif

static void
set_primary_sim_slot (MMIfaceModem       *self,
                      guint               sim_slot,
                      GAsyncReadyCallback callback,
                      gpointer            user_data)
{
    GTask *task;

    task = g_task_new (self, NULL, callback, user_data);

    g_task_set_task_data (task, GUINT_TO_POINTER (sim_slot), NULL);

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    mm_shared_qmi_set_primary_sim_slot (self, sim_slot, (GAsyncReadyCallback)shared_qmi_set_primary_sim_slot_ready, task);
#else
    set_primary_sim_slot_mbim (task);
#endif
}

/*****************************************************************************/
/* Set packet service state (3GPP interface)  */

static gboolean
set_packet_service_state_finish (MMIfaceModem3gpp  *self,
                                 GAsyncResult      *res,
                                 GError           **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
packet_service_set_ready (MbimDevice   *device,
                          GAsyncResult *res,
                          GTask        *task)
{
    MMBroadbandModemMbim   *self;
    g_autoptr(MbimMessage)  response = NULL;
    g_autoptr(GError)       error = NULL;
    MbimPacketServiceState  requested_packet_service_state;
    MbimPacketServiceState  packet_service_state = MBIM_PACKET_SERVICE_STATE_UNKNOWN;

    self = g_task_get_source_object (task);

    /* The NwError field is valid if MBIM_SET_PACKET_SERVICE response status code
     * equals MBIM_STATUS_FAILURE, so we parse the message both on success and on that
     * specific failure */
    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        (mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error) ||
         g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_FAILURE))) {
        g_autoptr(GError) inner_error = NULL;
        guint32           normalized_nw_error = 0;

        if (!common_process_packet_service (self,
                                            device,
                                            response,
                                            &normalized_nw_error,
                                            &packet_service_state,
                                            &inner_error)) {
            mm_obj_warn (self, "%s", inner_error->message);
            /* Prefer the error from the result to the parsing error */
            if (!error)
                error = g_steal_pointer (&inner_error);
        } else {
            /* Prefer the NW error if available */
            if (normalized_nw_error) {
                g_clear_error (&error);
                error = mm_error_from_mbim_nw_error (normalized_nw_error, self);
            }
        }
    }

    if (error) {
        if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_NO_DEVICE_SUPPORT))
            g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "%s", error->message);
        else
            g_task_return_error (task, g_steal_pointer (&error));
        g_object_unref (task);
        return;
    }

    requested_packet_service_state = GPOINTER_TO_UINT (g_task_get_task_data (task));

    if (((requested_packet_service_state == MBIM_PACKET_SERVICE_STATE_ATTACHED) &&
         (packet_service_state == MBIM_PACKET_SERVICE_STATE_ATTACHED || packet_service_state == MBIM_PACKET_SERVICE_STATE_ATTACHING)) ||
        ((requested_packet_service_state == MBIM_PACKET_SERVICE_STATE_DETACHED) &&
         (packet_service_state == MBIM_PACKET_SERVICE_STATE_DETACHED || packet_service_state == MBIM_PACKET_SERVICE_STATE_DETACHING)))
        g_task_return_boolean (task, TRUE);
    else
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                                 "Failed to request state %s, current state: %s",
                                 mbim_packet_service_state_get_string (requested_packet_service_state),
                                 mbim_packet_service_state_get_string (packet_service_state));
    g_object_unref (task);
}

static void
set_packet_service_state (MMIfaceModem3gpp              *self,
                          MMModem3gppPacketServiceState  packet_service_state,
                          GAsyncReadyCallback            callback,
                          gpointer                       user_data)
{
    g_autoptr(MbimMessage)   message = NULL;
    MbimDevice              *device;
    GTask                   *task;
    MbimPacketServiceAction  packet_service_action;
    MbimPacketServiceState   requested_packet_service_state;

    g_assert ((packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED) ||
              (packet_service_state == MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED));

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    switch (packet_service_state) {
    case MM_MODEM_3GPP_PACKET_SERVICE_STATE_ATTACHED:
        packet_service_action = MBIM_PACKET_SERVICE_ACTION_ATTACH;
        requested_packet_service_state = MBIM_PACKET_SERVICE_STATE_ATTACHED;
        break;
    case MM_MODEM_3GPP_PACKET_SERVICE_STATE_DETACHED:
        packet_service_action = MBIM_PACKET_SERVICE_ACTION_DETACH;
        requested_packet_service_state = MBIM_PACKET_SERVICE_STATE_DETACHED;
        break;
    case MM_MODEM_3GPP_PACKET_SERVICE_STATE_UNKNOWN:
    default:
        g_assert_not_reached ();
    }

    g_task_set_task_data (task, GUINT_TO_POINTER (requested_packet_service_state), NULL);

    message = mbim_message_packet_service_set_new (packet_service_action, NULL);
    mbim_device_command (device,
                         message,
                         30,
                         NULL,
                         (GAsyncReadyCallback)packet_service_set_ready,
                         task);
}

/*****************************************************************************/
/* Set carrier lock */

static gboolean
modem_set_carrier_lock_finish (MMIfaceModem3gpp  *self,
                               GAsyncResult      *res,
                               GError           **error)
{
    return g_task_propagate_boolean (G_TASK (res), error);
}

static void
set_carrier_lock_ready (MbimDevice   *device,
                        GAsyncResult *res,
                        GTask        *task)
{
    g_autoptr(MbimMessage)  response = NULL;
    GError                 *error = NULL;

    response = mbim_device_command_finish (device, res, &error);
    if (response &&
        mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
        g_task_return_boolean (task, TRUE);
    } else if (g_error_matches (error, MBIM_STATUS_ERROR, MBIM_STATUS_ERROR_OPERATION_NOT_ALLOWED)) {
        g_clear_error (&error);
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, "operation not allowed");
    } else
        g_task_return_error (task, error);

    g_object_unref (task);
}

static void
modem_set_carrier_lock (MMIfaceModem3gpp    *_self,
                        const guint8        *data,
                        gsize                data_size,
                        GAsyncReadyCallback  callback,
                        gpointer             user_data)
{
    MMBroadbandModemMbim   *self = MM_BROADBAND_MODEM_MBIM (_self);
    MbimDevice             *device;
    g_autoptr(MbimMessage)  message = NULL;
    GTask                  *task;

    if (!peek_device (self, &device, callback, user_data))
        return;

    task = g_task_new (self, NULL, callback, user_data);

    if (!self->priv->is_google_carrier_lock_supported) {
        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                 "Google carrier lock is not supported");
        g_object_unref (task);
        return;
    }

    mm_obj_dbg (self, "Sending carrier lock request...");
    message = mbim_message_google_carrier_lock_set_new (data_size, data, NULL);
    mbim_device_command (device,
                         message,
                         10,
                         NULL,
                         (GAsyncReadyCallback)set_carrier_lock_ready,
                         task);
}

/*****************************************************************************/
/* Load update settings (Firmware interface) */

static MMFirmwareUpdateSettings *
modem_firmware_load_update_settings_finish (MMIfaceModemFirmware  *self,
                                            GAsyncResult          *res,
                                            GError               **error)
{
    return mm_iface_modem_firmware_load_update_settings_in_port_finish (self, res, error);
}

static void
modem_firmware_load_update_settings (MMIfaceModemFirmware *self,
                                     GAsyncReadyCallback   callback,
                                     gpointer              user_data)
{
    mm_iface_modem_firmware_load_update_settings_in_port (
        self,
        MM_PORT (mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self))),
        callback,
        user_data);
}

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

MMBroadbandModemMbim *
mm_broadband_modem_mbim_new (const gchar *device,
                             const gchar *physdev,
                             const gchar **drivers,
                             const gchar *plugin,
                             guint16 vendor_id,
                             guint16 product_id)
{
    return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM,
                         MM_BASE_MODEM_DEVICE, device,
                         MM_BASE_MODEM_PHYSDEV, physdev,
                         MM_BASE_MODEM_DRIVERS, drivers,
                         MM_BASE_MODEM_PLUGIN, plugin,
                         MM_BASE_MODEM_VENDOR_ID, vendor_id,
                         MM_BASE_MODEM_PRODUCT_ID, product_id,
                         /* MBIM bearer supports NET only */
                         MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
                         MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
                         MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
                         MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
                         NULL);
}

static void
mm_broadband_modem_mbim_init (MMBroadbandModemMbim *self)
{
    /* Initialize private data */
    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
                                              MM_TYPE_BROADBAND_MODEM_MBIM,
                                              MMBroadbandModemMbimPrivate);
    self->priv->enabled_cache.available_data_classes = MBIM_DATA_CLASS_NONE;
    self->priv->enabled_cache.highest_available_data_class = MBIM_DATA_CLASS_NONE;
    self->priv->enabled_cache.reg_state = MBIM_REGISTER_STATE_UNKNOWN;
    self->priv->enabled_cache.packet_service_state = MBIM_PACKET_SERVICE_STATE_UNKNOWN;
    self->priv->enabled_cache.last_ready_state = MBIM_SUBSCRIBER_READY_STATE_NOT_INITIALIZED;
    self->priv->enabled_cache.last_pin_type = MBIM_PIN_TYPE_UNKNOWN;
}

static void
dispose (GObject *object)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (object);
    MMPortMbim *mbim;

    if (self->priv->enabling_signal_id && g_signal_handler_is_connected (self, self->priv->enabling_signal_id)) {
        g_signal_handler_disconnect (self, self->priv->enabling_signal_id);
        self->priv->enabling_signal_id = 0;
    }

    /* If any port cleanup is needed, it must be done during dispose(), as
     * the modem object will be affected by an explciit g_object_run_dispose()
     * that will remove all port references right away */
    mbim = mm_broadband_modem_mbim_peek_port_mbim (self);
    if (mbim) {
        /* Explicitly remove notification handler */
        self->priv->setup_flags = PROCESS_NOTIFICATION_FLAG_NONE;
        common_setup_cleanup_unsolicited_events_sync (self, mbim, FALSE);
    }

    g_clear_object (&self->priv->unlock_retries);

    G_OBJECT_CLASS (mm_broadband_modem_mbim_parent_class)->dispose (object);
}

static void
finalize (GObject *object)
{
    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (object);

    g_free (self->priv->caps_device_id);
    g_free (self->priv->caps_firmware_info);
    g_free (self->priv->caps_hardware_info);
    g_free (self->priv->enabled_cache.current_operator_id);
    g_free (self->priv->enabled_cache.current_operator_name);
    g_free (self->priv->requested_operator_id);
    g_list_free_full (self->priv->enabled_cache.pco_list, g_object_unref);

    G_OBJECT_CLASS (mm_broadband_modem_mbim_parent_class)->finalize (object);
}

static void
iface_modem_init (MMIfaceModemInterface *iface)
{
    iface_modem_parent = g_type_interface_peek_parent (iface);
    /* Initialization steps */
    iface->load_supported_capabilities = modem_load_supported_capabilities;
    iface->load_supported_capabilities_finish = modem_load_supported_capabilities_finish;
    iface->load_current_capabilities = modem_load_current_capabilities;
    iface->load_current_capabilities_finish = modem_load_current_capabilities_finish;
    iface->set_current_capabilities = modem_set_current_capabilities;
    iface->set_current_capabilities_finish = modem_set_current_capabilities_finish;
    iface->load_manufacturer = modem_load_manufacturer;
    iface->load_manufacturer_finish = modem_load_manufacturer_finish;
    iface->load_model = modem_load_model;
    iface->load_model_finish = modem_load_model_finish;
    iface->load_revision = modem_load_revision;
    iface->load_revision_finish = modem_load_revision_finish;
    iface->load_hardware_revision = modem_load_hardware_revision;
    iface->load_hardware_revision_finish = modem_load_hardware_revision_finish;
    iface->load_equipment_identifier = modem_load_equipment_identifier;
    iface->load_equipment_identifier_finish = modem_load_equipment_identifier_finish;
    iface->load_device_identifier = modem_load_device_identifier;
    iface->load_device_identifier_finish = modem_load_device_identifier_finish;
    iface->load_supported_modes = modem_load_supported_modes;
    iface->load_supported_modes_finish = modem_load_supported_modes_finish;
    iface->load_current_modes = modem_load_current_modes;
    iface->load_current_modes_finish = modem_load_current_modes_finish;
    iface->set_current_modes = modem_set_current_modes;
    iface->set_current_modes_finish = modem_set_current_modes_finish;
    iface->load_unlock_required = modem_load_unlock_required;
    iface->load_unlock_required_finish = modem_load_unlock_required_finish;
    iface->load_unlock_retries = modem_load_unlock_retries;
    iface->load_unlock_retries_finish = modem_load_unlock_retries_finish;
    iface->load_own_numbers = modem_load_own_numbers;
    iface->load_own_numbers_finish = modem_load_own_numbers_finish;
    iface->load_power_state = modem_load_power_state;
    iface->load_power_state_finish = modem_load_power_state_finish;
    iface->modem_power_up = modem_power_up;
    iface->modem_power_up_finish = power_up_finish;
    iface->modem_power_down = modem_power_down;
    iface->modem_power_down_finish = power_down_finish;
    iface->reset = modem_reset;
    iface->reset_finish = modem_reset_finish;
    iface->load_supported_ip_families = modem_load_supported_ip_families;
    iface->load_supported_ip_families_finish = modem_load_supported_ip_families_finish;

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    iface->load_carrier_config = mm_shared_qmi_load_carrier_config;
    iface->load_carrier_config_finish = mm_shared_qmi_load_carrier_config_finish;
    iface->setup_carrier_config = mm_shared_qmi_setup_carrier_config;
    iface->setup_carrier_config_finish = mm_shared_qmi_setup_carrier_config_finish;
    iface->load_supported_bands = mm_shared_qmi_load_supported_bands;
    iface->load_supported_bands_finish = mm_shared_qmi_load_supported_bands_finish;
    iface->load_current_bands = mm_shared_qmi_load_current_bands;
    iface->load_current_bands_finish = mm_shared_qmi_load_current_bands_finish;
    iface->set_current_bands = mm_shared_qmi_set_current_bands;
    iface->set_current_bands_finish = mm_shared_qmi_set_current_bands_finish;
#endif

    /* Additional actions */
    iface->load_signal_quality = modem_load_signal_quality;
    iface->load_signal_quality_finish = modem_load_signal_quality_finish;
    iface->get_cell_info = modem_get_cell_info;
    iface->get_cell_info_finish = modem_get_cell_info_finish;

    /* Unneeded things */
    iface->modem_after_power_up = NULL;
    iface->modem_after_power_up_finish = NULL;
    iface->load_supported_charsets = NULL;
    iface->load_supported_charsets_finish = NULL;
    iface->setup_flow_control = NULL;
    iface->setup_flow_control_finish = NULL;
    iface->setup_charset = NULL;
    iface->setup_charset_finish = NULL;
    iface->load_access_technologies = NULL;
    iface->load_access_technologies_finish = NULL;

    /* Create MBIM-specific SIM */
    iface->create_sim = create_sim;
    iface->create_sim_finish = create_sim_finish;
    iface->load_sim_slots = load_sim_slots;
    iface->load_sim_slots_finish = load_sim_slots_finish;
    iface->set_primary_sim_slot = set_primary_sim_slot;
    iface->set_primary_sim_slot_finish = set_primary_sim_slot_finish;

    /* Create MBIM-specific bearer and bearer list */
    iface->create_bearer = modem_create_bearer;
    iface->create_bearer_finish = modem_create_bearer_finish;
    iface->create_bearer_list = modem_create_bearer_list;

    /* SIM hot swapping */
    iface->setup_sim_hot_swap = modem_setup_sim_hot_swap;
    iface->setup_sim_hot_swap_finish = modem_setup_sim_hot_swap_finish;
    iface->check_basic_sim_details = modem_check_basic_sim_details;
    iface->check_basic_sim_details_finish = modem_check_basic_sim_details_finish;

    /* Other actions */
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    iface->factory_reset = mm_shared_qmi_factory_reset;
    iface->factory_reset_finish = mm_shared_qmi_factory_reset_finish;
#endif
}

static void
iface_modem_3gpp_init (MMIfaceModem3gppInterface *iface)
{
    /* Initialization steps */
    iface->load_imei = modem_3gpp_load_imei;
    iface->load_imei_finish = modem_3gpp_load_imei_finish;
    iface->load_enabled_facility_locks = modem_3gpp_load_enabled_facility_locks;
    iface->load_enabled_facility_locks_finish = modem_3gpp_load_enabled_facility_locks_finish;

    iface->setup_unsolicited_events = setup_unsolicited_events_3gpp;
    iface->setup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_3gpp_finish;
    iface->cleanup_unsolicited_events = cleanup_unsolicited_events_3gpp;
    iface->cleanup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_3gpp_finish;
    iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
    iface->enable_unsolicited_events_finish = modem_3gpp_common_enable_disable_unsolicited_events_finish;
    iface->disable_unsolicited_events = modem_3gpp_disable_unsolicited_events;
    iface->disable_unsolicited_events_finish = modem_3gpp_common_enable_disable_unsolicited_events_finish;
    iface->setup_unsolicited_registration_events = setup_unsolicited_registration_events;
    iface->setup_unsolicited_registration_events_finish = common_setup_cleanup_unsolicited_events_3gpp_finish;
    iface->cleanup_unsolicited_registration_events = cleanup_unsolicited_registration_events;
    iface->cleanup_unsolicited_registration_events_finish = common_setup_cleanup_unsolicited_events_3gpp_finish;
    iface->enable_unsolicited_registration_events = modem_3gpp_enable_unsolicited_registration_events;
    iface->enable_unsolicited_registration_events_finish = modem_3gpp_common_enable_disable_unsolicited_registration_events_finish;
    iface->disable_unsolicited_registration_events = modem_3gpp_disable_unsolicited_registration_events;
    iface->disable_unsolicited_registration_events_finish = modem_3gpp_common_enable_disable_unsolicited_registration_events_finish;
    iface->load_operator_code = modem_3gpp_load_operator_code;
    iface->load_operator_code_finish = modem_3gpp_load_operator_code_finish;
    iface->load_operator_name = modem_3gpp_load_operator_name;
    iface->load_operator_name_finish = modem_3gpp_load_operator_name_finish;
    iface->load_initial_eps_bearer = modem_3gpp_load_initial_eps_bearer;
    iface->load_initial_eps_bearer_finish = modem_3gpp_load_initial_eps_bearer_finish;
    iface->load_initial_eps_bearer_settings = modem_3gpp_load_initial_eps_bearer_settings;
    iface->load_initial_eps_bearer_settings_finish = modem_3gpp_load_initial_eps_bearer_settings_finish;
    iface->load_nr5g_registration_settings = modem_3gpp_load_nr5g_registration_settings;
    iface->load_nr5g_registration_settings_finish = modem_3gpp_load_nr5g_registration_settings_finish;
    iface->set_initial_eps_bearer_settings = modem_3gpp_set_initial_eps_bearer_settings;
    iface->set_initial_eps_bearer_settings_finish = modem_3gpp_set_initial_eps_bearer_settings_finish;
    iface->set_nr5g_registration_settings = modem_3gpp_set_nr5g_registration_settings;
    iface->set_nr5g_registration_settings_finish = modem_3gpp_set_nr5g_registration_settings_finish;
    iface->run_registration_checks = modem_3gpp_run_registration_checks;
    iface->run_registration_checks_finish = modem_3gpp_run_registration_checks_finish;
    iface->register_in_network = modem_3gpp_register_in_network;
    iface->register_in_network_finish = modem_3gpp_register_in_network_finish;
    iface->scan_networks = modem_3gpp_scan_networks;
    iface->scan_networks_finish = modem_3gpp_scan_networks_finish;
    iface->disable_facility_lock = modem_3gpp_disable_facility_lock;
    iface->disable_facility_lock_finish = modem_3gpp_disable_facility_lock_finish;
    iface->set_packet_service_state = set_packet_service_state;
    iface->set_packet_service_state_finish = set_packet_service_state_finish;
    /* carrier lock */
    iface->set_carrier_lock = modem_set_carrier_lock;
    iface->set_carrier_lock_finish = modem_set_carrier_lock_finish;
}

static void
iface_modem_3gpp_profile_manager_init (MMIfaceModem3gppProfileManagerInterface *iface)
{
    /* Initialization steps */
    iface->check_support = modem_3gpp_profile_manager_check_support;
    iface->check_support_finish = modem_3gpp_profile_manager_check_support_finish;

    /* Enabling steps */
    iface->setup_unsolicited_events = setup_unsolicited_events_3gpp_profile_manager;
    iface->setup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_3gpp_profile_manager_finish;
    iface->enable_unsolicited_events = enable_unsolicited_events_3gpp_profile_manager;
    iface->enable_unsolicited_events_finish = common_enable_disable_unsolicited_events_3gpp_profile_manager_finish;

    /* Disabling steps */
    iface->cleanup_unsolicited_events = cleanup_unsolicited_events_3gpp_profile_manager;
    iface->cleanup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_3gpp_profile_manager_finish;
    iface->disable_unsolicited_events = disable_unsolicited_events_3gpp_profile_manager;
    iface->disable_unsolicited_events_finish = common_enable_disable_unsolicited_events_3gpp_profile_manager_finish;

    /* Additional actions */
    iface->list_profiles = modem_3gpp_profile_manager_list_profiles;
    iface->list_profiles_finish = modem_3gpp_profile_manager_list_profiles_finish;
    iface->check_format = modem_3gpp_profile_manager_check_format;
    iface->check_format_finish = modem_3gpp_profile_manager_check_format_finish;
    iface->check_activated_profile = NULL;
    iface->check_activated_profile_finish = NULL;
    iface->deactivate_profile = NULL;
    iface->deactivate_profile_finish = NULL;
    iface->store_profile = modem_3gpp_profile_manager_store_profile;
    iface->store_profile_finish = modem_3gpp_profile_manager_store_profile_finish;
    iface->delete_profile = modem_3gpp_profile_manager_delete_profile;
    iface->delete_profile_finish = modem_3gpp_profile_manager_delete_profile_finish;
}

static void
iface_modem_3gpp_ussd_init (MMIfaceModem3gppUssdInterface *iface)
{
    /* Initialization steps */
    iface->check_support = modem_3gpp_ussd_check_support;
    iface->check_support_finish = modem_3gpp_ussd_check_support_finish;

    /* Enabling steps */
    iface->setup_unsolicited_events = modem_3gpp_ussd_setup_unsolicited_events;
    iface->setup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
    iface->enable_unsolicited_events = modem_3gpp_ussd_enable_unsolicited_events;
    iface->enable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_finish;

    /* Disabling steps */
    iface->cleanup_unsolicited_events_finish = modem_3gpp_ussd_setup_cleanup_unsolicited_events_finish;
    iface->cleanup_unsolicited_events = modem_3gpp_ussd_cleanup_unsolicited_events;
    iface->disable_unsolicited_events = modem_3gpp_ussd_disable_unsolicited_events;
    iface->disable_unsolicited_events_finish = modem_3gpp_ussd_enable_disable_unsolicited_events_finish;

    /* Additional actions */
    iface->send = modem_3gpp_ussd_send;
    iface->send_finish = modem_3gpp_ussd_send_finish;
    iface->cancel = modem_3gpp_ussd_cancel;
    iface->cancel_finish = modem_3gpp_ussd_cancel_finish;
}

static void
iface_modem_location_init (MMIfaceModemLocationInterface *iface)
{
#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    iface_modem_location_parent = g_type_interface_peek_parent (iface);

    iface->load_capabilities = mm_shared_qmi_location_load_capabilities;
    iface->load_capabilities_finish = mm_shared_qmi_location_load_capabilities_finish;
    iface->enable_location_gathering = mm_shared_qmi_enable_location_gathering;
    iface->enable_location_gathering_finish = mm_shared_qmi_enable_location_gathering_finish;
    iface->disable_location_gathering = mm_shared_qmi_disable_location_gathering;
    iface->disable_location_gathering_finish = mm_shared_qmi_disable_location_gathering_finish;
    iface->load_supl_server = mm_shared_qmi_location_load_supl_server;
    iface->load_supl_server_finish = mm_shared_qmi_location_load_supl_server_finish;
    iface->set_supl_server = mm_shared_qmi_location_set_supl_server;
    iface->set_supl_server_finish = mm_shared_qmi_location_set_supl_server_finish;
    iface->load_supported_assistance_data = mm_shared_qmi_location_load_supported_assistance_data;
    iface->load_supported_assistance_data_finish = mm_shared_qmi_location_load_supported_assistance_data_finish;
    iface->inject_assistance_data = mm_shared_qmi_location_inject_assistance_data;
    iface->inject_assistance_data_finish = mm_shared_qmi_location_inject_assistance_data_finish;
    iface->load_assistance_data_servers = mm_shared_qmi_location_load_assistance_data_servers;
    iface->load_assistance_data_servers_finish = mm_shared_qmi_location_load_assistance_data_servers_finish;
#else
    iface->load_capabilities = NULL;
    iface->load_capabilities_finish = NULL;
    iface->enable_location_gathering = NULL;
    iface->enable_location_gathering_finish = NULL;
#endif
}

static void
iface_modem_firmware_init (MMIfaceModemFirmwareInterface *iface)
{
    iface->load_update_settings = modem_firmware_load_update_settings;
    iface->load_update_settings_finish = modem_firmware_load_update_settings_finish;
}

static void
iface_modem_messaging_init (MMIfaceModemMessagingInterface *iface)
{
    iface->check_support = messaging_check_support;
    iface->check_support_finish = messaging_check_support_finish;
    iface->load_supported_storages = messaging_load_supported_storages;
    iface->load_supported_storages_finish = messaging_load_supported_storages_finish;
    iface->setup_sms_format = NULL;
    iface->setup_sms_format_finish = NULL;
    iface->set_default_storage = NULL;
    iface->set_default_storage_finish = NULL;
    iface->init_current_storages = NULL;
    iface->init_current_storages_finish = NULL;
    iface->load_initial_sms_parts = load_initial_sms_parts;
    iface->load_initial_sms_parts_finish = load_initial_sms_parts_finish;
    iface->setup_unsolicited_events = setup_unsolicited_events_messaging;
    iface->setup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_messaging_finish;
    iface->cleanup_unsolicited_events = cleanup_unsolicited_events_messaging;
    iface->cleanup_unsolicited_events_finish = common_setup_cleanup_unsolicited_events_messaging_finish;
    iface->enable_unsolicited_events = enable_unsolicited_events_messaging;
    iface->enable_unsolicited_events_finish = common_enable_disable_unsolicited_events_messaging_finish;
    iface->disable_unsolicited_events = disable_unsolicited_events_messaging;
    iface->disable_unsolicited_events_finish = common_enable_disable_unsolicited_events_messaging_finish;
    iface->create_sms = messaging_create_sms;
}

static void
iface_modem_signal_init (MMIfaceModemSignalInterface *iface)
{
    iface_modem_signal_parent = g_type_interface_peek_parent (iface);

    iface->check_support = modem_signal_check_support;
    iface->check_support_finish = modem_signal_check_support_finish;
    iface->load_values = modem_signal_load_values;
    iface->load_values_finish = modem_signal_load_values_finish;
    iface->setup_thresholds = modem_signal_setup_thresholds;
    iface->setup_thresholds_finish = modem_signal_setup_thresholds_finish;
}

static void
iface_modem_sar_init (MMIfaceModemSarInterface *iface)
{
    iface->check_support = sar_check_support;
    iface->check_support_finish  = sar_check_support_finish;
    iface->load_state = sar_load_state;
    iface->load_state_finish = sar_load_state_finish;
    iface->load_power_level = sar_load_power_level;
    iface->load_power_level_finish = sar_load_power_level_finish;
    iface->enable = sar_enable;
    iface->enable_finish = sar_enable_finish;
    iface->set_power_level = sar_set_power_level;
    iface->set_power_level_finish = sar_set_power_level_finish;
}

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED

static MMIfaceModemLocationInterface *
peek_parent_location_interface (MMSharedQmi *self)
{
    return iface_modem_location_parent;
}

static void
shared_qmi_init (MMSharedQmi *iface)
{
    iface->peek_client = shared_qmi_peek_client;
    iface->peek_parent_location_interface = peek_parent_location_interface;
}

#endif

static void
mm_broadband_modem_mbim_class_init (MMBroadbandModemMbimClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);

    g_type_class_add_private (object_class, sizeof (MMBroadbandModemMbimPrivate));

    klass->peek_port_mbim_for_data = peek_port_mbim_for_data;

    object_class->set_property = set_property;
    object_class->get_property = get_property;
    object_class->dispose = dispose;
    object_class->finalize = finalize;

    broadband_modem_class->initialization_started = initialization_started;
    broadband_modem_class->initialization_started_finish = initialization_started_finish;
    broadband_modem_class->enabling_started = enabling_started;
    broadband_modem_class->enabling_started_finish = enabling_started_finish;
    /* Do not initialize the MBIM modem through AT commands */
    broadband_modem_class->enabling_modem_init = NULL;
    broadband_modem_class->enabling_modem_init_finish = NULL;

#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
    g_object_class_install_property (object_class, PROP_QMI_UNSUPPORTED,
        g_param_spec_boolean (MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED,
                              "QMI over MBIM unsupported",
                              "TRUE when QMI over MBIM should not be considered.",
                              FALSE,
                              G_PARAM_READWRITE));
#endif

    g_object_class_install_property (object_class, PROP_INTEL_FIRMWARE_UPDATE_UNSUPPORTED,
        g_param_spec_boolean (MM_BROADBAND_MODEM_MBIM_INTEL_FIRMWARE_UPDATE_UNSUPPORTED,
                              "Intel Firmware Update service unsupported",
                              "TRUE when the Intel Firmware Update service should not be considered.",
                              FALSE,
                              G_PARAM_READWRITE));
}
