/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * qmicli -- Command line interface to control QMI devices
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright (C) 2012-2017 Aleksander Morgado <aleksander@aleksander.es>
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string.h>

#include <glib.h>
#include <gio/gio.h>

#include <libqmi-glib.h>

#include "qmicli.h"
#include "qmicli-helpers.h"

#undef VALIDATE_UNKNOWN
#define VALIDATE_UNKNOWN(str) (str ? str : "unknown")

/* Context */
typedef struct {
    QmiDevice *device;
    QmiClientDms *client;
    GCancellable *cancellable;
} Context;
static Context *ctx;

/* Options */
static gboolean get_ids_flag;
static gboolean get_capabilities_flag;
static gboolean get_manufacturer_flag;
static gboolean get_model_flag;
static gboolean get_revision_flag;
static gboolean get_msisdn_flag;
static gboolean get_power_state_flag;
static gchar *uim_set_pin_protection_str;
static gchar *uim_verify_pin_str;
static gchar *uim_unblock_pin_str;
static gchar *uim_change_pin_str;
static gboolean uim_get_pin_status_flag;
static gboolean uim_get_iccid_flag;
static gboolean uim_get_imsi_flag;
static gboolean uim_get_state_flag;
static gchar *uim_get_ck_status_str;
static gchar *uim_set_ck_protection_str;
static gchar *uim_unblock_ck_str;
static gboolean get_hardware_revision_flag;
static gboolean get_operating_mode_flag;
static gchar *set_operating_mode_str;
static gboolean get_time_flag;
static gboolean get_prl_version_flag;
static gboolean get_activation_state_flag;
static gchar *activate_automatic_str;
static gchar *activate_manual_str;
static gboolean get_user_lock_state_flag;
static gchar *set_user_lock_state_str;
static gchar *set_user_lock_code_str;
static gboolean read_user_data_flag;
static gchar *write_user_data_str;
static gboolean read_eri_file_flag;
static gchar *restore_factory_defaults_str;
static gchar *validate_service_programming_code_str;
static gboolean set_firmware_id_flag;
static gboolean get_band_capabilities_flag;
static gboolean get_factory_sku_flag;
static gboolean list_stored_images_flag;
static gchar *select_stored_image_str;
static gchar *delete_stored_image_str;
static gboolean get_firmware_preference_flag;
static gchar *set_firmware_preference_str;
static gboolean get_boot_image_download_mode_flag;
static gchar *set_boot_image_download_mode_str;
static gboolean get_software_version_flag;
static gboolean set_fcc_authentication_flag;
static gboolean get_supported_messages_flag;
static gchar *hp_change_device_mode_str;
static gboolean swi_get_current_firmware_flag;
static gboolean reset_flag;
static gboolean noop_flag;

static GOptionEntry entries[] = {
    { "dms-get-ids", 0, 0, G_OPTION_ARG_NONE, &get_ids_flag,
      "Get IDs",
      NULL
    },
    { "dms-get-capabilities", 0, 0, G_OPTION_ARG_NONE, &get_capabilities_flag,
      "Get capabilities",
      NULL
    },
    { "dms-get-manufacturer", 0, 0, G_OPTION_ARG_NONE, &get_manufacturer_flag,
      "Get manufacturer",
      NULL
    },
    { "dms-get-model", 0, 0, G_OPTION_ARG_NONE, &get_model_flag,
      "Get model",
      NULL
    },
    { "dms-get-revision", 0, 0, G_OPTION_ARG_NONE, &get_revision_flag,
      "Get revision",
      NULL
    },
    { "dms-get-msisdn", 0, 0, G_OPTION_ARG_NONE, &get_msisdn_flag,
      "Get MSISDN",
      NULL
    },
    { "dms-get-power-state", 0, 0, G_OPTION_ARG_NONE, &get_power_state_flag,
      "Get power state",
      NULL
    },
    { "dms-uim-set-pin-protection", 0, 0, G_OPTION_ARG_STRING, &uim_set_pin_protection_str,
      "Set PIN protection in the UIM",
      "[(PIN|PIN2),(disable|enable),(current PIN)]",
    },
    { "dms-uim-verify-pin", 0, 0, G_OPTION_ARG_STRING, &uim_verify_pin_str,
      "Verify PIN",
      "[(PIN|PIN2),(current PIN)]",
    },
    { "dms-uim-unblock-pin", 0, 0, G_OPTION_ARG_STRING, &uim_unblock_pin_str,
      "Unblock PIN",
      "[(PIN|PIN2),(PUK),(new PIN)]",
    },
    { "dms-uim-change-pin", 0, 0, G_OPTION_ARG_STRING, &uim_change_pin_str,
      "Change PIN",
      "[(PIN|PIN2),(old PIN),(new PIN)]",
    },
    { "dms-uim-get-pin-status", 0, 0, G_OPTION_ARG_NONE, &uim_get_pin_status_flag,
      "Get PIN status",
      NULL
    },
    { "dms-uim-get-iccid", 0, 0, G_OPTION_ARG_NONE, &uim_get_iccid_flag,
      "Get ICCID",
      NULL
    },
    { "dms-uim-get-imsi", 0, 0, G_OPTION_ARG_NONE, &uim_get_imsi_flag,
      "Get IMSI",
      NULL
    },
    { "dms-uim-get-state", 0, 0, G_OPTION_ARG_NONE, &uim_get_state_flag,
      "Get UIM State",
      NULL
    },
    { "dms-uim-get-ck-status", 0, 0, G_OPTION_ARG_STRING, &uim_get_ck_status_str,
      "Get CK Status",
      "[(pn|pu|pp|pc|pf)]"
    },
    { "dms-uim-set-ck-protection", 0, 0, G_OPTION_ARG_STRING, &uim_set_ck_protection_str,
      "Disable CK protection",
      "[(pn|pu|pp|pc|pf),(disable),(key)]"
    },
    { "dms-uim-unblock-ck", 0, 0, G_OPTION_ARG_STRING, &uim_unblock_ck_str,
      "Unblock CK",
      "[(pn|pu|pp|pc|pf),(key)]"
    },
    { "dms-get-hardware-revision", 0, 0, G_OPTION_ARG_NONE, &get_hardware_revision_flag,
      "Get the HW revision",
      NULL
    },
    { "dms-get-operating-mode", 0, 0, G_OPTION_ARG_NONE, &get_operating_mode_flag,
      "Get the device operating mode",
      NULL
    },
    { "dms-set-operating-mode", 0, 0, G_OPTION_ARG_STRING, &set_operating_mode_str,
      "Set the device operating mode",
      "[(Operating mode)]"
    },
    { "dms-get-time", 0, 0, G_OPTION_ARG_NONE, &get_time_flag,
      "Get the device time",
      NULL,
    },
    { "dms-get-prl-version", 0, 0, G_OPTION_ARG_NONE, &get_prl_version_flag,
      "Get the PRL version",
      NULL,
    },
    { "dms-get-activation-state", 0, 0, G_OPTION_ARG_NONE, &get_activation_state_flag,
      "Get the state of the service activation",
      NULL,
    },
    { "dms-activate-automatic", 0, 0, G_OPTION_ARG_STRING, &activate_automatic_str,
      "Request automatic service activation",
      "[Activation Code]"
    },
    { "dms-activate-manual", 0, 0, G_OPTION_ARG_STRING, &activate_manual_str,
      "Request manual service activation",
      "[SPC,SID,MDN,MIN]"
    },
    { "dms-get-user-lock-state", 0, 0, G_OPTION_ARG_NONE, &get_user_lock_state_flag,
      "Get the state of the user lock",
      NULL,
    },
    { "dms-set-user-lock-state", 0, 0, G_OPTION_ARG_STRING, &set_user_lock_state_str,
      "Set the state of the user lock",
      "[(disable|enable),(current lock code)]",
    },
    { "dms-set-user-lock-code", 0, 0, G_OPTION_ARG_STRING, &set_user_lock_code_str,
      "Change the user lock code",
      "[(old lock code),(new lock code)]",
    },
    { "dms-read-user-data", 0, 0, G_OPTION_ARG_NONE, &read_user_data_flag,
      "Read user data",
      NULL,
    },
    { "dms-write-user-data", 0, 0, G_OPTION_ARG_STRING, &write_user_data_str,
      "Write user data",
      "[(User data)]",
    },
    { "dms-read-eri-file", 0, 0, G_OPTION_ARG_NONE, &read_eri_file_flag,
      "Read ERI file",
      NULL,
    },
    { "dms-restore-factory-defaults", 0, 0, G_OPTION_ARG_STRING, &restore_factory_defaults_str,
      "Restore factory defaults",
      "[(Service Programming Code)]",
    },
    { "dms-validate-service-programming-code", 0, 0, G_OPTION_ARG_STRING, &validate_service_programming_code_str,
      "Validate the Service Programming Code",
      "[(Service Programming Code)]",
    },
    { "dms-set-firmware-id", 0, 0, G_OPTION_ARG_NONE, &set_firmware_id_flag,
      "Set firmware id",
      NULL
    },
    { "dms-get-band-capabilities", 0, 0, G_OPTION_ARG_NONE, &get_band_capabilities_flag,
      "Get band capabilities",
      NULL
    },
    { "dms-get-factory-sku", 0, 0, G_OPTION_ARG_NONE, &get_factory_sku_flag,
      "Get factory stock keeping unit",
      NULL
    },
    { "dms-list-stored-images", 0, 0, G_OPTION_ARG_NONE, &list_stored_images_flag,
      "List stored images",
      NULL
    },
    { "dms-select-stored-image", 0, 0, G_OPTION_ARG_STRING, &select_stored_image_str,
      "Select stored image",
      "[modem#,pri#] where # is the index"
    },
    { "dms-delete-stored-image", 0, 0, G_OPTION_ARG_STRING, &delete_stored_image_str,
      "Delete stored image",
      "[modem#|pri#] where # is the index"
    },
    { "dms-get-firmware-preference", 0, 0, G_OPTION_ARG_NONE, &get_firmware_preference_flag,
      "Get firmware preference",
      NULL
    },
    { "dms-set-firmware-preference", 0, 0, G_OPTION_ARG_STRING, &set_firmware_preference_str,
      "Set firmware preference",
      "[(fwver),(config),(carrier)]"
    },
    { "dms-get-boot-image-download-mode", 0, 0, G_OPTION_ARG_NONE, &get_boot_image_download_mode_flag,
      "Get boot image download mode",
      NULL
    },
    { "dms-set-boot-image-download-mode", 0, 0, G_OPTION_ARG_STRING, &set_boot_image_download_mode_str,
      "Set boot image download mode",
      "[normal|boot-and-recovery]"
    },
    { "dms-get-software-version", 0, 0, G_OPTION_ARG_NONE, &get_software_version_flag,
      "Get software version",
      NULL
    },
    { "dms-set-fcc-authentication", 0, 0, G_OPTION_ARG_NONE, &set_fcc_authentication_flag,
      "Set FCC authentication",
      NULL
    },
    { "dms-get-supported-messages", 0, 0, G_OPTION_ARG_NONE, &get_supported_messages_flag,
      "Get supported messages",
      NULL
    },
    { "dms-hp-change-device-mode", 0, 0, G_OPTION_ARG_STRING, &hp_change_device_mode_str,
      "Change device mode (HP specific)",
      "[fastboot]"
    },
    { "dms-swi-get-current-firmware", 0, 0, G_OPTION_ARG_NONE, &swi_get_current_firmware_flag,
      "Get Current Firmware (Sierra Wireless specific)",
      NULL
    },
    { "dms-reset", 0, 0, G_OPTION_ARG_NONE, &reset_flag,
      "Reset the service state",
      NULL
    },
    { "dms-noop", 0, 0, G_OPTION_ARG_NONE, &noop_flag,
      "Just allocate or release a DMS client. Use with `--client-no-release-cid' and/or `--client-cid'",
      NULL
    },
    { NULL }
};

GOptionGroup *
qmicli_dms_get_option_group (void)
{
    GOptionGroup *group;

    group = g_option_group_new ("dms",
                                "DMS options",
                                "Show Device Management Service options",
                                NULL,
                                NULL);
    g_option_group_add_entries (group, entries);

    return group;
}

gboolean
qmicli_dms_options_enabled (void)
{
    static guint n_actions = 0;
    static gboolean checked = FALSE;

    if (checked)
        return !!n_actions;

    n_actions = (get_ids_flag +
                 get_capabilities_flag +
                 get_manufacturer_flag +
                 get_model_flag +
                 get_revision_flag +
                 get_msisdn_flag +
                 get_power_state_flag +
                 !!uim_set_pin_protection_str +
                 !!uim_verify_pin_str +
                 !!uim_unblock_pin_str +
                 !!uim_change_pin_str +
                 uim_get_pin_status_flag +
                 uim_get_iccid_flag +
                 uim_get_imsi_flag +
                 uim_get_state_flag +
                 !!uim_get_ck_status_str +
                 !!uim_set_ck_protection_str +
                 !!uim_unblock_ck_str +
                 get_hardware_revision_flag +
                 get_operating_mode_flag +
                 !!set_operating_mode_str +
                 get_time_flag +
                 get_prl_version_flag +
                 get_activation_state_flag +
                 !!activate_automatic_str +
                 !!activate_manual_str +
                 get_user_lock_state_flag +
                 !!set_user_lock_state_str +
                 !!set_user_lock_code_str +
                 read_user_data_flag +
                 !!write_user_data_str +
                 read_eri_file_flag +
                 !!restore_factory_defaults_str +
                 !!validate_service_programming_code_str +
                 set_firmware_id_flag +
                 get_band_capabilities_flag +
                 get_factory_sku_flag +
                 list_stored_images_flag +
                 !!select_stored_image_str +
                 !!delete_stored_image_str +
                 get_firmware_preference_flag +
                 !!set_firmware_preference_str +
                 get_boot_image_download_mode_flag +
                 !!set_boot_image_download_mode_str +
                 get_software_version_flag +
                 set_fcc_authentication_flag +
                 get_supported_messages_flag +
                 !!hp_change_device_mode_str +
                 swi_get_current_firmware_flag +
                 reset_flag +
                 noop_flag);

    if (n_actions > 1) {
        g_printerr ("error: too many DMS actions requested\n");
        exit (EXIT_FAILURE);
    }

    checked = TRUE;
    return !!n_actions;
}

static void
context_free (Context *context)
{
    if (!context)
        return;

    if (context->cancellable)
        g_object_unref (context->cancellable);
    if (context->device)
        g_object_unref (context->device);
    if (context->client)
        g_object_unref (context->client);
    g_slice_free (Context, context);
}

static void
operation_shutdown (gboolean operation_status)
{
    /* Cleanup context and finish async operation */
    context_free (ctx);
    qmicli_async_operation_done (operation_status, FALSE);
}

static void
operation_shutdown_skip_cid_release (gboolean operation_status)
{
    /* Cleanup context and finish async operation. Explicitly ask not to release
     * the client CID. This is so that the qmicli operation doesn't fail after
     * this step, e.g. if the device just reboots after the action. */
    context_free (ctx);
    qmicli_async_operation_done (operation_status, TRUE);
}

static void
get_ids_ready (QmiClientDms *client,
               GAsyncResult *res)
{
    const gchar *esn = NULL;
    const gchar *imei = NULL;
    const gchar *meid = NULL;
    QmiMessageDmsGetIdsOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_ids_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_ids_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get IDs: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_ids_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_ids_output_get_esn  (output, &esn, NULL);
    qmi_message_dms_get_ids_output_get_imei (output, &imei, NULL);
    qmi_message_dms_get_ids_output_get_meid (output, &meid, NULL);

    g_print ("[%s] Device IDs retrieved:\n"
             "\t ESN: '%s'\n"
             "\tIMEI: '%s'\n"
             "\tMEID: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (esn),
             VALIDATE_UNKNOWN (imei),
             VALIDATE_UNKNOWN (meid));

    qmi_message_dms_get_ids_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_capabilities_ready (QmiClientDms *client,
                        GAsyncResult *res)
{
    QmiMessageDmsGetCapabilitiesOutput *output;
    guint32 max_tx_channel_rate;
    guint32 max_rx_channel_rate;
    QmiDmsDataServiceCapability data_service_capability;
    QmiDmsSimCapability sim_capability;
    GArray *radio_interface_list;
    GError *error = NULL;
    GString *networks;
    guint i;

    output = qmi_client_dms_get_capabilities_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_capabilities_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get capabilities: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_capabilities_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_capabilities_output_get_info (output,
                                                      &max_tx_channel_rate,
                                                      &max_rx_channel_rate,
                                                      &data_service_capability,
                                                      &sim_capability,
                                                      &radio_interface_list,
                                                      NULL);

    networks = g_string_new ("");
    for (i = 0; i < radio_interface_list->len; i++) {
        g_string_append (networks,
                         qmi_dms_radio_interface_get_string (
                             g_array_index (radio_interface_list,
                                            QmiDmsRadioInterface,
                                            i)));
        if (i != radio_interface_list->len - 1)
            g_string_append (networks, ", ");
    }

    g_print ("[%s] Device capabilities retrieved:\n"
             "\tMax TX channel rate: '%u'\n"
             "\tMax RX channel rate: '%u'\n"
             "\t       Data Service: '%s'\n"
             "\t                SIM: '%s'\n"
             "\t           Networks: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             max_tx_channel_rate,
             max_rx_channel_rate,
             qmi_dms_data_service_capability_get_string (data_service_capability),
             qmi_dms_sim_capability_get_string (sim_capability),
             networks->str);

    g_string_free (networks, TRUE);
    qmi_message_dms_get_capabilities_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_manufacturer_ready (QmiClientDms *client,
                        GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetManufacturerOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_manufacturer_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_manufacturer_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get manufacturer: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_manufacturer_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_manufacturer_output_get_manufacturer (output, &str, NULL);

    g_print ("[%s] Device manufacturer retrieved:\n"
             "\tManufacturer: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_manufacturer_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_model_ready (QmiClientDms *client,
                 GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetModelOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_model_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_model_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get model: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_model_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_model_output_get_model (output, &str, NULL);

    g_print ("[%s] Device model retrieved:\n"
             "\tModel: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_model_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_revision_ready (QmiClientDms *client,
                    GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetRevisionOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_revision_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_revision_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get revision: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_revision_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_revision_output_get_revision (output, &str, NULL);

    g_print ("[%s] Device revision retrieved:\n"
             "\tRevision: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_revision_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_msisdn_ready (QmiClientDms *client,
                  GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetMsisdnOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_msisdn_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_msisdn_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get MSISDN: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_msisdn_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_msisdn_output_get_msisdn (output, &str, NULL);

    g_print ("[%s] Device MSISDN retrieved:\n"
             "\tMSISDN: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_msisdn_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_power_state_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    gchar *power_state_str;
    guint8 power_state_flags;
    guint8 battery_level;
    QmiMessageDmsGetPowerStateOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_power_state_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_power_state_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get power state: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_power_state_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_power_state_output_get_info (output,
                                                     &power_state_flags,
                                                     &battery_level,
                                                     NULL);
    power_state_str = qmi_dms_power_state_build_string_from_mask ((QmiDmsPowerState)power_state_flags);

    g_print ("[%s] Device power state retrieved:\n"
             "\tPower state: '%s'\n"
             "\tBattery level: '%u %%'\n",
             qmi_device_get_path_display (ctx->device),
             power_state_str,
             (guint)battery_level);

    g_free (power_state_str);
    qmi_message_dms_get_power_state_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimSetPinProtectionInput *
uim_set_pin_protection_input_create (const gchar *str)
{
    QmiMessageDmsUimSetPinProtectionInput *input = NULL;
    gchar **split;
    QmiDmsUimPinId pin_id;
    gboolean enable_disable;
    gchar *current_pin;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(PIN|PIN2),(disable|enable),(current PIN)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_dms_uim_pin_id_from_string (split[0], &pin_id) &&
        qmicli_read_enable_disable_from_string (split[1], &enable_disable) &&
        qmicli_read_non_empty_string (split[2], "current PIN", &current_pin)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_set_pin_protection_input_new ();
        if (!qmi_message_dms_uim_set_pin_protection_input_set_info (
                input,
                pin_id,
                enable_disable,
                current_pin,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_set_pin_protection_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_set_pin_protection_ready (QmiClientDms *client,
                              GAsyncResult *res)
{
    QmiMessageDmsUimSetPinProtectionOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_set_pin_protection_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_set_pin_protection_output_get_result (output, &error)) {
        guint8 verify_retries_left;
        guint8 unblock_retries_left;

        g_printerr ("error: couldn't set PIN protection: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_set_pin_protection_output_get_pin_retries_status (
                output,
                &verify_retries_left,
                &unblock_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tVerify: %u\n"
                        "\tUnblock: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        verify_retries_left,
                        unblock_retries_left);
        }

        qmi_message_dms_uim_set_pin_protection_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] PIN protection updated\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_set_pin_protection_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimVerifyPinInput *
uim_verify_pin_input_create (const gchar *str)
{
    QmiMessageDmsUimVerifyPinInput *input = NULL;
    gchar **split;
    QmiDmsUimPinId pin_id;
    gchar *current_pin;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(PIN|PIN2),(current PIN)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_dms_uim_pin_id_from_string (split[0], &pin_id) &&
        qmicli_read_non_empty_string (split[1], "current PIN", &current_pin)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_verify_pin_input_new ();
        if (!qmi_message_dms_uim_verify_pin_input_set_info (
                input,
                pin_id,
                current_pin,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_verify_pin_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_verify_pin_ready (QmiClientDms *client,
                      GAsyncResult *res)
{
    QmiMessageDmsUimVerifyPinOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_verify_pin_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_verify_pin_output_get_result (output, &error)) {
        guint8 verify_retries_left;
        guint8 unblock_retries_left;

        g_printerr ("error: couldn't verify PIN: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_verify_pin_output_get_pin_retries_status (
                output,
                &verify_retries_left,
                &unblock_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tVerify: %u\n"
                        "\tUnblock: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        verify_retries_left,
                        unblock_retries_left);
        }

        qmi_message_dms_uim_verify_pin_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] PIN verified successfully\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_verify_pin_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimUnblockPinInput *
uim_unblock_pin_input_create (const gchar *str)
{
    QmiMessageDmsUimUnblockPinInput *input = NULL;
    gchar **split;
    QmiDmsUimPinId pin_id;
    gchar *puk;
    gchar *new_pin;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(PIN|PIN2),(PUK),(new PIN)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_dms_uim_pin_id_from_string (split[0], &pin_id) &&
        qmicli_read_non_empty_string (split[1], "PUK", &puk) &&
        qmicli_read_non_empty_string (split[2], "new PIN", &new_pin)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_unblock_pin_input_new ();
        if (!qmi_message_dms_uim_unblock_pin_input_set_info (
                input,
                pin_id,
                puk,
                new_pin,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_unblock_pin_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_unblock_pin_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    QmiMessageDmsUimUnblockPinOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_unblock_pin_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_unblock_pin_output_get_result (output, &error)) {
        guint8 verify_retries_left;
        guint8 unblock_retries_left;

        g_printerr ("error: couldn't unblock PIN: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_unblock_pin_output_get_pin_retries_status (
                output,
                &verify_retries_left,
                &unblock_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tVerify: %u\n"
                        "\tUnblock: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        verify_retries_left,
                        unblock_retries_left);
        }

        qmi_message_dms_uim_unblock_pin_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] PIN unblocked successfully\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_unblock_pin_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimChangePinInput *
uim_change_pin_input_create (const gchar *str)
{
    QmiMessageDmsUimChangePinInput *input = NULL;
    gchar **split;
    QmiDmsUimPinId pin_id;
    gchar *old_pin;
    gchar *new_pin;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(PIN|PIN2),(old PIN),(new PIN)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_dms_uim_pin_id_from_string (split[0], &pin_id) &&
        qmicli_read_non_empty_string (split[1], "old PIN", &old_pin) &&
        qmicli_read_non_empty_string (split[2], "new PIN", &new_pin)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_change_pin_input_new ();
        if (!qmi_message_dms_uim_change_pin_input_set_info (
                input,
                pin_id,
                old_pin,
                new_pin,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_change_pin_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_change_pin_ready (QmiClientDms *client,
                      GAsyncResult *res)
{
    QmiMessageDmsUimChangePinOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_change_pin_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_change_pin_output_get_result (output, &error)) {
        guint8 verify_retries_left;
        guint8 unblock_retries_left;

        g_printerr ("error: couldn't change PIN: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_change_pin_output_get_pin_retries_status (
                output,
                &verify_retries_left,
                &unblock_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tVerify: %u\n"
                        "\tUnblock: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        verify_retries_left,
                        unblock_retries_left);
        }

        qmi_message_dms_uim_change_pin_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] PIN changed successfully\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_change_pin_output_unref (output);
    operation_shutdown (TRUE);
}

static void
uim_get_pin_status_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    guint8 verify_retries_left;
    guint8 unblock_retries_left;
    QmiDmsUimPinStatus current_status;
    QmiMessageDmsUimGetPinStatusOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_get_pin_status_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_get_pin_status_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get PIN status: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_uim_get_pin_status_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] PIN status retrieved successfully\n",
             qmi_device_get_path_display (ctx->device));

    if (qmi_message_dms_uim_get_pin_status_output_get_pin1_status (
            output,
            &current_status,
            &verify_retries_left,
            &unblock_retries_left,
            NULL)) {
        g_print ("[%s] PIN1:\n"
                 "\tStatus: %s\n"
                 "\tVerify: %u\n"
                 "\tUnblock: %u\n",
                 qmi_device_get_path_display (ctx->device),
                 qmi_dms_uim_pin_status_get_string (current_status),
                 verify_retries_left,
                 unblock_retries_left);
    }

    if (qmi_message_dms_uim_get_pin_status_output_get_pin2_status (
            output,
            &current_status,
            &verify_retries_left,
            &unblock_retries_left,
            NULL)) {
        g_print ("[%s] PIN2:\n"
                 "\tStatus: %s\n"
                 "\tVerify: %u\n"
                 "\tUnblock: %u\n",
                 qmi_device_get_path_display (ctx->device),
                 qmi_dms_uim_pin_status_get_string (current_status),
                 verify_retries_left,
                 unblock_retries_left);
    }

    qmi_message_dms_uim_get_pin_status_output_unref (output);
    operation_shutdown (TRUE);
}

static void
uim_get_iccid_ready (QmiClientDms *client,
                     GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsUimGetIccidOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_get_iccid_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_get_iccid_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get ICCID: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_uim_get_iccid_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_uim_get_iccid_output_get_iccid (output, &str, NULL);

    g_print ("[%s] UIM ICCID retrieved:\n"
             "\tICCID: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_uim_get_iccid_output_unref (output);
    operation_shutdown (TRUE);
}

static void
uim_get_imsi_ready (QmiClientDms *client,
                    GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsUimGetImsiOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_get_imsi_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_get_imsi_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get IMSI: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_uim_get_imsi_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_uim_get_imsi_output_get_imsi (output, &str, NULL);

    g_print ("[%s] UIM IMSI retrieved:\n"
             "\tIMSI: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_uim_get_imsi_output_unref (output);
    operation_shutdown (TRUE);
}

static void
uim_get_state_ready (QmiClientDms *client,
                    GAsyncResult *res)
{
    QmiDmsUimState state;
    QmiMessageDmsUimGetStateOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_get_state_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_get_state_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get UIM state: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_uim_get_state_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_uim_get_state_output_get_state (output, &state, NULL);

    g_print ("[%s] UIM state retrieved:\n"
             "\tState: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             qmi_dms_uim_state_get_string (state));

    qmi_message_dms_uim_get_state_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimGetCkStatusInput *
uim_get_ck_status_input_create (const gchar *str)
{
    QmiMessageDmsUimGetCkStatusInput *input = NULL;
    QmiDmsUimFacility facility;

    if (qmicli_read_facility_from_string (str, &facility)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_get_ck_status_input_new ();
        if (!qmi_message_dms_uim_get_ck_status_input_set_facility (
                input,
                facility,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_get_ck_status_input_unref (input);
            input = NULL;
        }
    }

    return input;
}

static void
uim_get_ck_status_ready (QmiClientDms *client,
                         GAsyncResult *res)
{
    QmiMessageDmsUimGetCkStatusOutput *output;
    GError *error = NULL;
    QmiDmsUimFacilityState state;
    guint8 verify_retries_left;
    guint8 unblock_retries_left;
    gboolean blocking;

    output = qmi_client_dms_uim_get_ck_status_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_get_ck_status_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get UIM CK status: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_uim_get_ck_status_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_uim_get_ck_status_output_get_ck_status (
        output,
        &state,
        &verify_retries_left,
        &unblock_retries_left,
        NULL);

    g_print ("[%s] UIM facility state retrieved:\n"
             "\tState: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             qmi_dms_uim_facility_state_get_string (state));
    g_print ("[%s] Retries left:\n"
             "\tVerify: %u\n"
             "\tUnblock: %u\n",
             qmi_device_get_path_display (ctx->device),
             verify_retries_left,
             unblock_retries_left);

    if (qmi_message_dms_uim_get_ck_status_output_get_operation_blocking_facility (
            output,
            &blocking,
            NULL) &&
        blocking) {
        g_print ("[%s] Facility is blocking operation\n",
                 qmi_device_get_path_display (ctx->device));
    }

    qmi_message_dms_uim_get_ck_status_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimSetCkProtectionInput *
uim_set_ck_protection_input_create (const gchar *str)
{
    QmiMessageDmsUimSetCkProtectionInput *input = NULL;
    gchar **split;
    QmiDmsUimFacility facility;
    gboolean enable_disable;
    gchar *key;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(facility),disable,(key)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_facility_from_string (split[0], &facility) &&
        qmicli_read_enable_disable_from_string (split[1], &enable_disable) &&
        qmicli_read_non_empty_string (split[2], "control key", &key)) {

        /* We should only allow 'disable' here */
        if (enable_disable) {
            g_printerr ("error: only 'disable' action is allowed\n");
        } else {
            GError *error = NULL;

            input = qmi_message_dms_uim_set_ck_protection_input_new ();
            if (!qmi_message_dms_uim_set_ck_protection_input_set_facility (
                    input,
                    facility,
                    (QmiDmsUimFacilityState)enable_disable, /* 0 == DISABLE */
                    key,
                    &error)) {
                g_printerr ("error: couldn't create input data bundle: '%s'\n",
                            error->message);
                g_error_free (error);
                qmi_message_dms_uim_set_ck_protection_input_unref (input);
                input = NULL;
            }
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_set_ck_protection_ready (QmiClientDms *client,
                             GAsyncResult *res)
{
    QmiMessageDmsUimSetCkProtectionOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_set_ck_protection_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_set_ck_protection_output_get_result (output, &error)) {
        guint8 verify_retries_left;

        g_printerr ("error: couldn't set UIM CK protection: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_set_ck_protection_output_get_verify_retries_left (
                output,
                &verify_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tVerify: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        verify_retries_left);
        }

        qmi_message_dms_uim_set_ck_protection_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] UIM CK protection set\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_set_ck_protection_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsUimUnblockCkInput *
uim_unblock_ck_input_create (const gchar *str)
{
    QmiMessageDmsUimUnblockCkInput *input = NULL;
    gchar **split;
    QmiDmsUimFacility facility;
    gchar *key;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(facility),(key)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_facility_from_string (split[0], &facility) &&
        qmicli_read_non_empty_string (split[1], "control key", &key)) {
        GError *error = NULL;

        input = qmi_message_dms_uim_unblock_ck_input_new ();
        if (!qmi_message_dms_uim_unblock_ck_input_set_facility (
                input,
                facility,
                key,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_uim_unblock_ck_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
uim_unblock_ck_ready (QmiClientDms *client,
                      GAsyncResult *res)
{
    QmiMessageDmsUimUnblockCkOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_uim_unblock_ck_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_uim_unblock_ck_output_get_result (output, &error)) {
        guint8 unblock_retries_left;

        g_printerr ("error: couldn't unblock CK: %s\n", error->message);
        g_error_free (error);

        if (qmi_message_dms_uim_unblock_ck_output_get_unblock_retries_left (
                output,
                &unblock_retries_left,
                NULL)) {
            g_printerr ("[%s] Retries left:\n"
                        "\tUnblock: %u\n",
                        qmi_device_get_path_display (ctx->device),
                        unblock_retries_left);
        }

        qmi_message_dms_uim_unblock_ck_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] UIM CK unblocked\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_uim_unblock_ck_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_hardware_revision_ready (QmiClientDms *client,
                             GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetHardwareRevisionOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_hardware_revision_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_hardware_revision_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the HW revision: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_hardware_revision_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_hardware_revision_output_get_revision (output, &str, NULL);

    g_print ("[%s] Hardware revision retrieved:\n"
             "\tRevision: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_hardware_revision_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_operating_mode_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    QmiMessageDmsGetOperatingModeOutput *output;
    QmiDmsOperatingMode mode;
    gboolean hw_restricted = FALSE;
    GError *error = NULL;

    output = qmi_client_dms_get_operating_mode_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_operating_mode_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the HW revision: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_operating_mode_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_operating_mode_output_get_mode (output, &mode, NULL);

    g_print ("[%s] Operating mode retrieved:\n"
             "\tMode: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             qmi_dms_operating_mode_get_string (mode));

    if (mode == QMI_DMS_OPERATING_MODE_OFFLINE || mode == QMI_DMS_OPERATING_MODE_LOW_POWER) {
        QmiDmsOfflineReason reason;
        gchar *reason_str = NULL;

        if (qmi_message_dms_get_operating_mode_output_get_offline_reason (output, &reason, NULL)) {
            reason_str = qmi_dms_offline_reason_build_string_from_mask (reason);
        }

        g_print ("\tReason: '%s'\n", VALIDATE_UNKNOWN (reason_str));
        g_free (reason_str);
    }

    qmi_message_dms_get_operating_mode_output_get_hardware_restricted_mode (output, &hw_restricted, NULL);
    g_print ("\tHW restricted: '%s'\n", hw_restricted ? "yes" : "no");

    qmi_message_dms_get_operating_mode_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsSetOperatingModeInput *
set_operating_mode_input_create (const gchar *str)
{
    QmiMessageDmsSetOperatingModeInput *input = NULL;
    QmiDmsOperatingMode mode;

    if (qmicli_read_operating_mode_from_string (str, &mode)) {
        GError *error = NULL;

        input = qmi_message_dms_set_operating_mode_input_new ();
        if (!qmi_message_dms_set_operating_mode_input_set_mode (
                input,
                mode,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_set_operating_mode_input_unref (input);
            input = NULL;
        }
    }

    return input;
}

static void
set_operating_mode_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    QmiMessageDmsSetOperatingModeOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
        g_printerr ("error: couldn't set operating mode: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_operating_mode_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Operating mode set successfully\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_operating_mode_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_time_ready (QmiClientDms *client,
                GAsyncResult *res)
{
    QmiMessageDmsGetTimeOutput *output;
    guint64 time_count;
    QmiDmsTimeSource time_source;
    GError *error = NULL;
    gchar *str;
    GTimeZone *time_zone;
    GDateTime *gpstime_epoch;
    GDateTime *computed_epoch;

    output = qmi_client_dms_get_time_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_time_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the device time: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_time_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_time_output_get_device_time (
        output,
        &time_count,
        &time_source,
        NULL);

    /* January 6th 1980 */
    time_zone = g_time_zone_new_utc ();
    gpstime_epoch = g_date_time_new (time_zone, 1980, 1, 6, 0, 0, 0.0);

    computed_epoch = g_date_time_add_seconds (gpstime_epoch, ((gdouble) time_count / 1000.0));
    str = g_date_time_format (computed_epoch, "%F %T");
    g_print ("[%s] Time retrieved:\n"
             "\tTime count: '%" G_GUINT64_FORMAT " (x 1.25ms): %s'\n"
             "\tTime source: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             time_count, str,
             qmi_dms_time_source_get_string (time_source));
    g_free (str);
    g_date_time_unref (computed_epoch);

    if (qmi_message_dms_get_time_output_get_system_time (
        output,
        &time_count,
        NULL)){
        computed_epoch = g_date_time_add_seconds (gpstime_epoch, ((gdouble) time_count / 1000.0));
        str = g_date_time_format (computed_epoch, "%F %T");
        g_print ("\tSystem time: '%" G_GUINT64_FORMAT " (ms): %s'\n",
                 time_count, str);
        g_free (str);
        g_date_time_unref (computed_epoch);
    }

    if (qmi_message_dms_get_time_output_get_user_time (
        output,
        &time_count,
        NULL)){
        computed_epoch = g_date_time_add_seconds (gpstime_epoch, ((gdouble) time_count / 1000.0));
        str = g_date_time_format (computed_epoch, "%F %T");
        g_print ("\tUser time: '%" G_GUINT64_FORMAT " (ms): %s'\n",
                 time_count, str);
        g_free (str);
        g_date_time_unref (computed_epoch);
    }

    g_date_time_unref (gpstime_epoch);
    g_time_zone_unref (time_zone);

    qmi_message_dms_get_time_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_prl_version_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    QmiMessageDmsGetPrlVersionOutput *output;
    guint16 prl_version;
    gboolean prl_only;
    GError *error = NULL;

    output = qmi_client_dms_get_prl_version_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_prl_version_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the PRL version: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_prl_version_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_prl_version_output_get_version (
        output,
        &prl_version,
        NULL);

    g_print ("[%s] PRL version retrieved:\n"
             "\tPRL version: '%" G_GUINT16_FORMAT "'\n",
             qmi_device_get_path_display (ctx->device),
             prl_version);

    if (qmi_message_dms_get_prl_version_output_get_prl_only_preference (
        output,
        &prl_only,
        NULL)){
        g_print ("\tPRL only preference: '%s'\n",
                 prl_only ? "yes" : "no");
    }

    qmi_message_dms_get_prl_version_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_activation_state_ready (QmiClientDms *client,
                            GAsyncResult *res)
{
    QmiMessageDmsGetActivationStateOutput *output;
    QmiDmsActivationState activation_state;
    GError *error = NULL;

    output = qmi_client_dms_get_activation_state_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_activation_state_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the state of the service activation: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_activation_state_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_activation_state_output_get_info (
        output,
        &activation_state,
        NULL);

    g_print ("[%s] Activation state retrieved:\n"
             "\tState: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             qmi_dms_activation_state_get_string (activation_state));

    qmi_message_dms_get_activation_state_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsActivateManualInput *
activate_manual_input_create (const gchar *str)
{
    QmiMessageDmsActivateManualInput *input;
    gchar **split;
    GError *error = NULL;
    gulong split_1_int;

    split = g_strsplit (str, ",", -1);
    if (g_strv_length (split) != 4) {
        g_printerr ("error: incorrect number of arguments given\n");
        g_strfreev (split);
        return NULL;
    }

    split_1_int = strtoul (split[1], NULL, 10);
    if (split_1_int > G_MAXUINT16) {
        g_printerr ("error: invalid SID given '%s'\n",
                    split[1]);
        return NULL;
    }

    input = qmi_message_dms_activate_manual_input_new ();
    if (!qmi_message_dms_activate_manual_input_set_info (
            input,
            split[0],
            (guint16)split_1_int,
            split[2],
            split[3],
            &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_activate_manual_input_unref (input);
        input = NULL;
    }

    g_strfreev(split);
    return input;
}

static void
activate_manual_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    QmiMessageDmsActivateManualOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_activate_manual_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_activate_manual_output_get_result (output, &error)) {
        g_printerr ("error: couldn't request manual service activation: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_activate_manual_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_activate_manual_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsActivateAutomaticInput *
activate_automatic_input_create (const gchar *str)
{
    QmiMessageDmsActivateAutomaticInput *input;
    GError *error = NULL;

    input = qmi_message_dms_activate_automatic_input_new ();
    if (!qmi_message_dms_activate_automatic_input_set_activation_code (
            input,
            str,
            &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_activate_automatic_input_unref (input);
        input = NULL;
    }

    return input;
}

static void
activate_automatic_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    QmiMessageDmsActivateAutomaticOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_activate_automatic_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_activate_automatic_output_get_result (output, &error)) {
        g_printerr ("error: couldn't request automatic service activation: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_activate_automatic_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_activate_automatic_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_user_lock_state_ready (QmiClientDms *client,
                           GAsyncResult *res)
{
    QmiMessageDmsGetUserLockStateOutput *output;
    gboolean enabled;
    GError *error = NULL;

    output = qmi_client_dms_get_user_lock_state_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_user_lock_state_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get the state of the user lock: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_user_lock_state_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_user_lock_state_output_get_enabled (
        output,
        &enabled,
        NULL);

    g_print ("[%s] User lock state retrieved:\n"
             "\tEnabled: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             enabled ? "yes" : "no");

    qmi_message_dms_get_user_lock_state_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsSetUserLockStateInput *
set_user_lock_state_input_create (const gchar *str)
{
    QmiMessageDmsSetUserLockStateInput *input = NULL;
    gchar **split;
    gboolean enable_disable;
    gchar *code;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(disable|enable),(current lock code)]"
     */
    split = g_strsplit (str, ",", -1);

    if (qmicli_read_enable_disable_from_string (split[0], &enable_disable) &&
        qmicli_read_non_empty_string (split[1], "current lock code", &code)) {
        GError *error = NULL;

        input = qmi_message_dms_set_user_lock_state_input_new ();
        if (!qmi_message_dms_set_user_lock_state_input_set_info (
                input,
                enable_disable,
                code,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_set_user_lock_state_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
set_user_lock_state_ready (QmiClientDms *client,
                           GAsyncResult *res)
{
    QmiMessageDmsSetUserLockStateOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_user_lock_state_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_user_lock_state_output_get_result (output, &error)) {
        g_printerr ("error: couldn't set state of the user lock: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_user_lock_state_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] User lock state updated\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_user_lock_state_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsSetUserLockCodeInput *
set_user_lock_code_input_create (const gchar *str)
{
    QmiMessageDmsSetUserLockCodeInput *input = NULL;
    gchar **split;
    gchar *old_code;
    gchar *new_code;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(old lock code),(new lock code)]"
     */
    split = g_strsplit (str, ",", -1);
    if (qmicli_read_non_empty_string (split[0], "old lock code", &old_code) &&
        qmicli_read_non_empty_string (split[1], "new lock code", &new_code)) {
        GError *error = NULL;

        input = qmi_message_dms_set_user_lock_code_input_new ();
        if (!qmi_message_dms_set_user_lock_code_input_set_info (
                input,
                old_code,
                new_code,
                &error)) {
            g_printerr ("error: couldn't create input data bundle: '%s'\n",
                        error->message);
            g_error_free (error);
            qmi_message_dms_set_user_lock_code_input_unref (input);
            input = NULL;
        }
    }
    g_strfreev (split);

    return input;
}

static void
set_user_lock_code_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    QmiMessageDmsSetUserLockCodeOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_user_lock_code_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_user_lock_code_output_get_result (output, &error)) {
        g_printerr ("error: couldn't change user lock code: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_user_lock_code_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] User lock code changed\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_user_lock_code_output_unref (output);
    operation_shutdown (TRUE);
}

static void
read_user_data_ready (QmiClientDms *client,
                      GAsyncResult *res)
{
    QmiMessageDmsReadUserDataOutput *output;
    GArray *user_data = NULL;
    gchar *user_data_printable;
    GError *error = NULL;

    output = qmi_client_dms_read_user_data_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_read_user_data_output_get_result (output, &error)) {
        g_printerr ("error: couldn't read user data: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_read_user_data_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_read_user_data_output_get_user_data (
        output,
        &user_data,
        NULL);
    user_data_printable = qmicli_get_raw_data_printable (user_data, 80, "\t\t");

    g_print ("[%s] User data read:\n"
             "\tSize: '%u' bytes\n"
             "\tContents:\n"
             "%s",
             qmi_device_get_path_display (ctx->device),
             user_data->len,
             user_data_printable);
    g_free (user_data_printable);

    qmi_message_dms_read_user_data_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsWriteUserDataInput *
write_user_data_input_create (const gchar *str)
{
    QmiMessageDmsWriteUserDataInput *input;
    GArray *array;
    GError *error = NULL;

    /* Prepare inputs. Just assume we'll get some text string here, although
     * nobody said this had to be text. Read User Data actually treats the
     * contents of the user data as raw binary data. */
    array = g_array_sized_new (FALSE, FALSE, 1, strlen (str));
    g_array_insert_vals (array, 0, str, strlen (str));
    input = qmi_message_dms_write_user_data_input_new ();
    if (!qmi_message_dms_write_user_data_input_set_user_data (
            input,
            array,
            &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_write_user_data_input_unref (input);
        input = NULL;
    }
    g_array_unref (array);

    return input;
}

static void
write_user_data_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    QmiMessageDmsWriteUserDataOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_write_user_data_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_write_user_data_output_get_result (output, &error)) {
        g_printerr ("error: couldn't write user data: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_write_user_data_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] User data written",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_write_user_data_output_unref (output);
    operation_shutdown (TRUE);
}

static void
read_eri_file_ready (QmiClientDms *client,
                     GAsyncResult *res)
{
    QmiMessageDmsReadEriFileOutput *output;
    GArray *eri_file = NULL;
    gchar *eri_file_printable;
    GError *error = NULL;

    output = qmi_client_dms_read_eri_file_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_read_eri_file_output_get_result (output, &error)) {
        g_printerr ("error: couldn't read eri file: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_read_eri_file_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_read_eri_file_output_get_eri_file (
        output,
        &eri_file,
        NULL);
    eri_file_printable = qmicli_get_raw_data_printable (eri_file, 80, "\t\t");

    g_print ("[%s] ERI file read:\n"
             "\tSize: '%u' bytes\n"
             "\tContents:\n"
             "%s",
             qmi_device_get_path_display (ctx->device),
             eri_file->len,
             eri_file_printable);
    g_free (eri_file_printable);

    qmi_message_dms_read_eri_file_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsRestoreFactoryDefaultsInput *
restore_factory_defaults_input_create (const gchar *str)
{
    QmiMessageDmsRestoreFactoryDefaultsInput *input;
    GError *error = NULL;

    input = qmi_message_dms_restore_factory_defaults_input_new ();
    if (!qmi_message_dms_restore_factory_defaults_input_set_service_programming_code (
            input,
            str,
            &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_restore_factory_defaults_input_unref (input);
        input = NULL;
    }

    return input;
}

static void
restore_factory_defaults_ready (QmiClientDms *client,
                                GAsyncResult *res)
{
    QmiMessageDmsRestoreFactoryDefaultsOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_restore_factory_defaults_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_restore_factory_defaults_output_get_result (output, &error)) {
        g_printerr ("error: couldn't restores factory defaults: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_restore_factory_defaults_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Factory defaults restored\n"
             "Device needs to get power-cycled for reset to take effect.\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_restore_factory_defaults_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsValidateServiceProgrammingCodeInput *
validate_service_programming_code_input_create (const gchar *str)
{
    QmiMessageDmsValidateServiceProgrammingCodeInput *input;
    GError *error = NULL;

    input = qmi_message_dms_validate_service_programming_code_input_new ();
    if (!qmi_message_dms_validate_service_programming_code_input_set_service_programming_code (
            input,
            str,
            &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_validate_service_programming_code_input_unref (input);
        input = NULL;
    }

    return input;
}

static void
validate_service_programming_code_ready (QmiClientDms *client,
                                         GAsyncResult *res)
{
    QmiMessageDmsValidateServiceProgrammingCodeOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_validate_service_programming_code_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_validate_service_programming_code_output_get_result (output, &error)) {
        g_printerr ("error: couldn't validate Service Programming Code: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_validate_service_programming_code_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Service Programming Code validated\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_validate_service_programming_code_output_unref (output);
    operation_shutdown (TRUE);
}

static void
set_firmware_id_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    QmiMessageDmsSetFirmwareIdOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_firmware_id_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_firmware_id_output_get_result (output, &error)) {
        g_printerr ("error: couldn't set firmware id: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_firmware_id_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Firmware id set\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_firmware_id_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_band_capabilities_ready (QmiClientDms *client,
                             GAsyncResult *res)
{
    QmiMessageDmsGetBandCapabilitiesOutput *output;
    QmiDmsBandCapability band_capability;
    QmiDmsLteBandCapability lte_band_capability;
    GError *error = NULL;
    gchar *str;

    output = qmi_client_dms_get_band_capabilities_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_band_capabilities_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get band capabilities: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_band_capabilities_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_band_capabilities_output_get_band_capability (
        output,
        &band_capability,
        NULL);

    str = qmi_dms_band_capability_build_string_from_mask (band_capability);
    g_print ("[%s] Device band capabilities retrieved:\n"
             "\tBands: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             str);
    g_free (str);

    if (qmi_message_dms_get_band_capabilities_output_get_lte_band_capability (
            output,
            &lte_band_capability,
            NULL)) {
        str = qmi_dms_lte_band_capability_build_string_from_mask (lte_band_capability);
        g_print ("\tLTE bands: '%s'\n", str);
        g_free (str);
    }

    qmi_message_dms_get_band_capabilities_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_factory_sku_ready (QmiClientDms *client,
                       GAsyncResult *res)
{
    const gchar *str = NULL;
    QmiMessageDmsGetFactorySkuOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_get_factory_sku_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_factory_sku_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get factory SKU: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_factory_sku_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_factory_sku_output_get_sku (output, &str, NULL);

    g_print ("[%s] Device factory SKU retrieved:\n"
             "\tSKU: '%s'\n",
             qmi_device_get_path_display (ctx->device),
             VALIDATE_UNKNOWN (str));

    qmi_message_dms_get_factory_sku_output_unref (output);
    operation_shutdown (TRUE);
}

typedef struct {
    QmiMessageDmsListStoredImagesOutput *list_images_output;
    guint i;
    guint j;
} ListImagesContext;

static void
list_images_context_free (ListImagesContext *operation_ctx)
{
    qmi_message_dms_list_stored_images_output_unref (operation_ctx->list_images_output);
    g_slice_free (ListImagesContext, operation_ctx);
}

static void get_image_info (ListImagesContext *operation_ctx);

static void
get_stored_image_info_ready (QmiClientDms *client,
                             GAsyncResult *res,
                             ListImagesContext *operation_ctx)
{
    GArray *array;
    QmiMessageDmsGetStoredImageInfoOutput *output;
    GError *error = NULL;
    QmiMessageDmsListStoredImagesOutputListImage *image;
    QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage;
    gchar *unique_id_str;

    output = qmi_client_dms_get_stored_image_info_finish (client, res, &error);
    if (!output) {
        /* Fully ignore errors */
        g_error_free (error);
    } else if (!qmi_message_dms_get_stored_image_info_output_get_result (output, &error)) {
        /* Fully ignore errors */
        g_error_free (error);
    }

    qmi_message_dms_list_stored_images_output_get_list (
        operation_ctx->list_images_output,
        &array,
        NULL);
    image = &g_array_index (array, QmiMessageDmsListStoredImagesOutputListImage, operation_ctx->i);
    subimage = &g_array_index (image->sublist,
                               QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement,
                               operation_ctx->j);

    unique_id_str = qmicli_get_firmware_image_unique_id_printable (subimage->unique_id);

    g_print ("%s"
             "\t\t[%s%u]\n"
             "\t\tUnique ID:     '%s'\n"
             "\t\tBuild ID:      '%s'\n",
             operation_ctx->j == image->index_of_running_image ? "\t\t>>>>>>>>>> [CURRENT] <<<<<<<<<<\n" : "",
             qmi_dms_firmware_image_type_get_string (image->type),
             operation_ctx->j,
             unique_id_str,
             subimage->build_id);

    if (subimage->storage_index != 255)
        g_print ("\t\tStorage index: '%u'\n", subimage->storage_index);

    if (subimage->failure_count != 255)
        g_print ("\t\tFailure count: '%u'\n", subimage->failure_count);

    if (output) {
        /* Boot version (optional) */
        {
            guint16 boot_major_version;
            guint16 boot_minor_version;

            if (qmi_message_dms_get_stored_image_info_output_get_boot_version (
                    output,
                    &boot_major_version,
                    &boot_minor_version,
                    NULL)) {
                g_print ("\t\tBoot version:  '%u.%u'\n",
                         boot_major_version,
                         boot_minor_version);
            }
        }

        /* PRI version (optional) */
        {
            guint32 pri_version;
            const gchar *pri_info;

            if (qmi_message_dms_get_stored_image_info_output_get_pri_version (
                    output,
                    &pri_version,
                    &pri_info,
                    NULL)) {
                g_print ("\t\tPRI version:   '%u'\n"
                         "\t\tPRI info:      '%s'\n",
                         pri_version,
                         pri_info);
            }
        }

        /* OEM lock ID (optional) */
        {
            guint32 lock_id;

            if (qmi_message_dms_get_stored_image_info_output_get_oem_lock_id (
                    output,
                    &lock_id,
                    NULL)) {
                g_print ("\t\tOEM lock ID:   '%u'\n",
                         lock_id);
            }
        }

        qmi_message_dms_get_stored_image_info_output_unref (output);
    }

    g_print ("\n");
    g_free (unique_id_str);

    /* Go on to the next one */
    operation_ctx->j++;
    get_image_info (operation_ctx);
}

static void
get_image_info (ListImagesContext *operation_ctx)
{
    GArray *array;
    QmiMessageDmsListStoredImagesOutputListImage *image;
    QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage;
    QmiMessageDmsGetStoredImageInfoInputImage image_id;
    QmiMessageDmsGetStoredImageInfoInput *input;

    qmi_message_dms_list_stored_images_output_get_list (
        operation_ctx->list_images_output,
        &array,
        NULL);

    if (operation_ctx->i >= array->len) {
        /* We're done */
        list_images_context_free (operation_ctx);
        operation_shutdown (TRUE);
        return;
    }

    image = &g_array_index (array,
                            QmiMessageDmsListStoredImagesOutputListImage,
                            operation_ctx->i);

    if (operation_ctx->j >= image->sublist->len) {
        /* No more images in the sublist, go to next image type */
        operation_ctx->j = 0;
        operation_ctx->i++;
        get_image_info (operation_ctx);
        return;
    }

    /* Print info of the image type */
    if (operation_ctx->j == 0) {
        g_print ("\t[%u] Type:    '%s'\n"
                 "\t    Maximum: '%u'\n"
                 "\n",
                 operation_ctx->i,
                 qmi_dms_firmware_image_type_get_string (image->type),
                 image->maximum_images);
    }

    subimage = &g_array_index (image->sublist,
                               QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement,
                               operation_ctx->j);

    /* Query image info */
    image_id.type = image->type;
    image_id.unique_id = subimage->unique_id;
    image_id.build_id = subimage->build_id;
    input = qmi_message_dms_get_stored_image_info_input_new ();
    qmi_message_dms_get_stored_image_info_input_set_image (input, &image_id, NULL);

    qmi_client_dms_get_stored_image_info (ctx->client,
                                          input,
                                          10,
                                          ctx->cancellable,
                                          (GAsyncReadyCallback)get_stored_image_info_ready,
                                          operation_ctx);
    qmi_message_dms_get_stored_image_info_input_unref (input);
}

static void
list_stored_images_ready (QmiClientDms *client,
                          GAsyncResult *res)
{
    QmiMessageDmsListStoredImagesOutput *output;
    GError *error = NULL;
    ListImagesContext *operation_ctx;

    output = qmi_client_dms_list_stored_images_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_list_stored_images_output_get_result (output, &error)) {
        g_printerr ("error: couldn't list stored images: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_list_stored_images_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Device list of stored images retrieved:\n\n",
             qmi_device_get_path_display (ctx->device));

    operation_ctx = g_slice_new0 (ListImagesContext);
    operation_ctx->list_images_output = output;
    operation_ctx->i = 0;
    operation_ctx->j = 0;

    get_image_info (operation_ctx);
}

typedef struct {
    QmiClientDms *client;
    GSimpleAsyncResult *result;
    gint modem_index;
    gint pri_index;
} GetStoredImageContext;

typedef struct {
    GArray *modem_unique_id;
    gchar *modem_build_id;
    GArray *pri_unique_id;
    gchar *pri_build_id;
} GetStoredImageResult;

static void
get_stored_image_context_complete_and_free (GetStoredImageContext *operation_ctx)
{
    g_simple_async_result_complete (operation_ctx->result);
    g_object_unref (operation_ctx->result);
    g_object_unref (operation_ctx->client);
    g_slice_free (GetStoredImageContext, operation_ctx);
}

static void
get_stored_image_finish (QmiClientDms *client,
                         GAsyncResult *res,
                         GArray **modem_unique_id,
                         gchar **modem_build_id,
                         GArray **pri_unique_id,
                         gchar **pri_build_id)
{
    GetStoredImageResult *result;

    result = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res));

    *modem_unique_id = result->modem_unique_id ? g_array_ref (result->modem_unique_id) : NULL;
    *modem_build_id = g_strdup (result->modem_build_id);
    *pri_unique_id = result->pri_unique_id ? g_array_ref (result->pri_unique_id) : NULL;
    *pri_build_id = g_strdup (result->pri_build_id);
}

static void
get_stored_image_list_stored_images_ready (QmiClientDms *client,
                                           GAsyncResult *res,
                                           GetStoredImageContext *operation_ctx)
{
    GetStoredImageResult result = { NULL, NULL, NULL, NULL };
    GArray *array;
    QmiMessageDmsListStoredImagesOutput *output;
    GError *error = NULL;
    guint i;

    output = qmi_client_dms_list_stored_images_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_list_stored_images_output_get_result (output, &error)) {
        g_printerr ("error: couldn't list stored images: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_list_stored_images_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_list_stored_images_output_get_list (
        output,
        &array,
        NULL);

    for (i = 0; i < array->len; i++) {
        QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement *subimage;
        QmiMessageDmsListStoredImagesOutputListImage *image;
        gchar *unique_id_str;
        gint image_index;

        image = &g_array_index (array,
                                QmiMessageDmsListStoredImagesOutputListImage,
                                i);

        if (image->type == QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM)
            image_index = operation_ctx->modem_index;
        else if (image->type == QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI)
            image_index = operation_ctx->pri_index;
        else
            g_assert_not_reached ();

        /* If not looking for the specific image type, go on */
        if (image_index < 0)
            continue;

        if (image_index >= image->sublist->len) {
            g_printerr ("error: couldn't find '%s' image at index '%d'\n",
                        qmi_dms_firmware_image_type_get_string (image->type),
                        image_index);
            qmi_message_dms_list_stored_images_output_unref (output);
            operation_shutdown (FALSE);
            return;
        }

        subimage = &g_array_index (image->sublist,
                                   QmiMessageDmsListStoredImagesOutputListImageSublistSublistElement,
                                   image_index);

        unique_id_str = qmicli_get_firmware_image_unique_id_printable (subimage->unique_id);
        g_debug ("Found [%s%d]: Unique ID: '%s', Build ID: '%s'",
                 qmi_dms_firmware_image_type_get_string (image->type),
                 image_index,
                 unique_id_str,
                 subimage->build_id);
        g_free (unique_id_str);

        /* Build result */
        if (image->type == QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM) {
            result.modem_unique_id = subimage->unique_id;
            result.modem_build_id = subimage->build_id;
        } else if (image->type == QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI) {
            result.pri_unique_id = subimage->unique_id;
            result.pri_build_id = subimage->build_id;
        } else
            g_assert_not_reached ();
    }

    /* Complete */
    g_simple_async_result_set_op_res_gpointer (operation_ctx->result, &result, NULL);
    get_stored_image_context_complete_and_free (operation_ctx);
    qmi_message_dms_list_stored_images_output_unref (output);
}

static void
get_stored_image (QmiClientDms *client,
                  const gchar *str,
                  GAsyncReadyCallback callback,
                  gpointer user_data)
{
    GetStoredImageContext *operation_ctx;
    gchar **split;
    guint i = 0;
    gint modem_index = -1;
    gint pri_index = -1;

    split = g_strsplit (str, ",", -1);
    while (split[i]) {
        QmiDmsFirmwareImageType type;
        guint image_index;

        if (i >= 3) {
            g_printerr ("A maximum of 2 images should be given: '%s'\n", str);
            operation_shutdown (FALSE);
            return;
        }

        if (!qmicli_read_firmware_id_from_string (split[i], &type, &image_index)) {
            g_printerr ("Couldn't parse input string as firmware index info: '%s'\n", str);
            operation_shutdown (FALSE);
            return;
        }

        if (type == QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM) {
            if (modem_index >= 0) {
                g_printerr ("Cannot handle two 'modem' type firmware indices: '%s'\n", str);
                operation_shutdown (FALSE);
                return;
            }
            modem_index = (gint)image_index;
        } else if (type == QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI) {
            if (pri_index >= 0) {
                g_printerr ("Cannot handle two 'pri' type firmware indices: '%s'\n", str);
                operation_shutdown (FALSE);
                return;
            }
            pri_index = (gint)image_index;
        }

        i++;
    }

    operation_ctx = g_slice_new (GetStoredImageContext);
    operation_ctx->client = g_object_ref (client);
    operation_ctx->result = g_simple_async_result_new (G_OBJECT (client),
                                                       callback,
                                                       user_data,
                                                       get_stored_image);
    operation_ctx->modem_index = modem_index;
    operation_ctx->pri_index = pri_index;

    qmi_client_dms_list_stored_images (
        ctx->client,
        NULL,
        10,
        ctx->cancellable,
        (GAsyncReadyCallback)get_stored_image_list_stored_images_ready,
        operation_ctx);
}

/* Note:
 *  used by both --dms-set-firmware-preference and --dms-select-stored-image
 */
static void
dms_set_firmware_preference_ready (QmiClientDms *client,
                                   GAsyncResult *res)
{
    QmiMessageDmsSetFirmwarePreferenceOutput *output;
    GError                                   *error = NULL;
    GArray                                   *array;
    GString                                  *pending_images = NULL;

    output = qmi_client_dms_set_firmware_preference_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_firmware_preference_output_get_result (output, &error)) {
        g_printerr ("error: couldn't select stored image: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_firmware_preference_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Firmware preference successfully selected\n"
             "\n"
             "\tYou may want to power-cycle the modem now, or just set it offline and reset it:\n"
             "\t\t$> sudo qmicli ... --dms-set-operating-mode=offline\n"
             "\t\t$> sudo qmicli ... --dms-set-operating-mode=reset\n"
             "\n",
             qmi_device_get_path_display (ctx->device));

    /* do we need to download a new modem and/or pri image? */
    if (qmi_message_dms_set_firmware_preference_output_get_image_download_list (output, &array, NULL) && array->len) {
        guint                   i;
        QmiDmsFirmwareImageType type;

        pending_images = g_string_new ("");
        for (i = 0; i < array->len; i++) {
            type = g_array_index (array, QmiDmsFirmwareImageType, i);
            g_string_append (pending_images, qmi_dms_firmware_image_type_get_string (type));
            if (i < array->len -1)
                g_string_append (pending_images, ", ");
        }
    }

    if (pending_images) {
        g_print ("\tAfter reset, the modem will wait in QDL mode for new firmware.\n"
                 "\tImages to download: '%s'\n"
                 "\n",
                 pending_images->str);
        g_string_free (pending_images, TRUE);
    } else {
        /* If we're selecting an already stored image, or if we don't need any
         * more images to be downloaded, we're done. */
        g_print ("\tNo new images are required to be downloaded.\n"
                 "\n"
                 "\tYou should check that the modem|pri image pair is valid by checking the current operating mode:\n"
                 "\t\t$> sudo qmicli .... --dms-get-operating-mode\n"
                 "\tIf the Mode is reported as 'online', you're good to go.\n"
                 "\tIf the Mode is reported as 'offline' with a 'pri-version-incompatible' reason, you chose an incorrect pair\n"
                 "\n");
    }

    qmi_message_dms_set_firmware_preference_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_stored_image_select_ready (QmiClientDms *client,
                               GAsyncResult *res)
{
    QmiMessageDmsSetFirmwarePreferenceInput *input;
    GArray *array;
    QmiMessageDmsSetFirmwarePreferenceInputListImage modem_image_id;
    QmiMessageDmsSetFirmwarePreferenceInputListImage pri_image_id;

    modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM;
    pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;

    get_stored_image_finish (client,
                             res,
                             &modem_image_id.unique_id,
                             &modem_image_id.build_id,
                             &pri_image_id.unique_id,
                             &pri_image_id.build_id);

    if (!modem_image_id.unique_id || !modem_image_id.build_id ||
        !pri_image_id.unique_id || !pri_image_id.build_id) {
        g_printerr ("error: must specify a pair of 'modem' and 'pri' images to select\n");
        operation_shutdown (FALSE);
        return;
    }

    array = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageDmsSetFirmwarePreferenceInputListImage), 2);
    g_array_append_val (array, modem_image_id);
    g_array_append_val (array, pri_image_id);

    input = qmi_message_dms_set_firmware_preference_input_new ();
    qmi_message_dms_set_firmware_preference_input_set_list (input, array, NULL);

    qmi_client_dms_set_firmware_preference (
        client,
        input,
        10,
        NULL,
        (GAsyncReadyCallback)dms_set_firmware_preference_ready,
        NULL);
    qmi_message_dms_set_firmware_preference_input_unref (input);

    g_free        (modem_image_id.build_id);
    g_array_unref (modem_image_id.unique_id);
    g_free        (pri_image_id.build_id);
    g_array_unref (pri_image_id.unique_id);
}

static void
delete_stored_image_ready (QmiClientDms *client,
                           GAsyncResult *res)
{
    QmiMessageDmsDeleteStoredImageOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_delete_stored_image_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_delete_stored_image_output_get_result (output, &error)) {
        g_printerr ("error: couldn't delete stored image: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_delete_stored_image_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Stored image successfully deleted\n",
             qmi_device_get_path_display (ctx->device));
    qmi_message_dms_delete_stored_image_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_stored_image_delete_ready (QmiClientDms *client,
                               GAsyncResult *res)
{
    QmiMessageDmsDeleteStoredImageInput *input;
    QmiMessageDmsDeleteStoredImageInputImage modem_image_id;
    QmiMessageDmsDeleteStoredImageInputImage pri_image_id;

    modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM;
    pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;

    get_stored_image_finish (client,
                             res,
                             &modem_image_id.unique_id,
                             &modem_image_id.build_id,
                             &pri_image_id.unique_id,
                             &pri_image_id.build_id);

    if (modem_image_id.unique_id && modem_image_id.build_id &&
        pri_image_id.unique_id && pri_image_id.build_id) {
        g_printerr ("error: cannot specify multiple images to delete\n");
        operation_shutdown (FALSE);
        return;
    }

    input = qmi_message_dms_delete_stored_image_input_new ();
    if (modem_image_id.unique_id && modem_image_id.build_id)
        qmi_message_dms_delete_stored_image_input_set_image (input, &modem_image_id, NULL);
    else if (pri_image_id.unique_id && pri_image_id.build_id)
        qmi_message_dms_delete_stored_image_input_set_image (input, &pri_image_id, NULL);
    else {
        g_printerr ("error: didn't specify correctly an image to delete\n");
        operation_shutdown (FALSE);
        return;
    }

    qmi_client_dms_delete_stored_image (
        client,
        input,
        10,
        NULL,
        (GAsyncReadyCallback)delete_stored_image_ready,
        NULL);
    qmi_message_dms_delete_stored_image_input_unref (input);

    g_free (modem_image_id.build_id);
    if (modem_image_id.unique_id)
        g_array_unref (modem_image_id.unique_id);
    g_free (pri_image_id.build_id);
    if (pri_image_id.unique_id)
        g_array_unref (pri_image_id.unique_id);
}

static void
dms_get_firmware_preference_ready (QmiClientDms *client,
                                   GAsyncResult *res)
{
    GError                                   *error = NULL;
    QmiMessageDmsGetFirmwarePreferenceOutput *output;
    GArray                                   *array;
    guint                                     i;

    output = qmi_client_dms_get_firmware_preference_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_firmware_preference_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get firmware preference: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_firmware_preference_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_firmware_preference_output_get_list (output, &array, NULL);

    g_print ("firmware preference successfully retrieved:\n");

    if (array->len > 0) {
        for (i = 0; i < array->len; i++) {
            QmiMessageDmsGetFirmwarePreferenceOutputListImage *image;
            gchar                                             *unique_id_str;

            image = &g_array_index (array, QmiMessageDmsGetFirmwarePreferenceOutputListImage, i);

            unique_id_str = qmicli_get_firmware_image_unique_id_printable (image->unique_id);

            g_print ("[image %u]\n"
                     "\tImage type: '%s'\n"
                     "\tUnique ID:  '%s'\n"
                     "\tBuild ID:   '%s'\n",
                     i,
                     qmi_dms_firmware_image_type_get_string (image->type),
                     unique_id_str,
                     image->build_id);

            g_free (unique_id_str);
        }
    } else
        g_print ("no images specified\n");

    qmi_message_dms_get_firmware_preference_output_unref (output);
    operation_shutdown (TRUE);
}

typedef struct {
    QmiMessageDmsSetFirmwarePreferenceInputListImage modem_image_id;
    QmiMessageDmsSetFirmwarePreferenceInputListImage pri_image_id;
} SetFirmwarePreferenceContext;

static void
set_firmware_preference_context_clear (SetFirmwarePreferenceContext *firmware_preference_ctx)
{
    g_clear_pointer (&firmware_preference_ctx->modem_image_id.unique_id, (GDestroyNotify) g_array_unref);
    g_free (firmware_preference_ctx->modem_image_id.build_id);

    g_clear_pointer (&firmware_preference_ctx->pri_image_id.unique_id, (GDestroyNotify) g_array_unref);
    g_free (firmware_preference_ctx->pri_image_id.build_id);
}

static QmiMessageDmsSetFirmwarePreferenceInput *
set_firmware_preference_input_create (const gchar                  *str,
                                      SetFirmwarePreferenceContext *firmware_preference_ctx)
{
    QmiMessageDmsSetFirmwarePreferenceInput *input;
    GArray *array;
    gchar **split;

    /* Prepare inputs.
     * Format of the string is:
     *    "[(fwver),(config),(carrier)]"
     */
    split = g_strsplit (str, ",", -1);
    if (g_strv_length (split) != 3) {
        g_printerr ("error: invalid format string, expected 3 elements: [(fwver),(config),(carrier)]\n");
        g_strfreev (split);
        return NULL;
    }

    /* modem unique id is the fixed wildcard string '?_?' matching any pri.
     * modem build id format is "(fwver)_?", matching any carrier */
    firmware_preference_ctx->modem_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_MODEM;
    firmware_preference_ctx->modem_image_id.unique_id = g_array_sized_new (FALSE, TRUE, 1, 16);
    g_array_insert_vals (firmware_preference_ctx->modem_image_id.unique_id, 0, "?_?", 3);
    g_array_set_size (firmware_preference_ctx->modem_image_id.unique_id, 16);
    firmware_preference_ctx->modem_image_id.build_id = g_strdup_printf ("%s_?", split[0]);

    /* pri unique id is the "(config)" input */
    firmware_preference_ctx->pri_image_id.type = QMI_DMS_FIRMWARE_IMAGE_TYPE_PRI;
    firmware_preference_ctx->pri_image_id.unique_id = g_array_sized_new (FALSE, TRUE, 1, 16);
    g_array_insert_vals (firmware_preference_ctx->pri_image_id.unique_id, 0, split[1], strlen (split[1]));
    g_array_set_size (firmware_preference_ctx->pri_image_id.unique_id, 16);
    firmware_preference_ctx->pri_image_id.build_id = g_strdup_printf ("%s_%s", split[0], split[2]);

    /* Create an array with both images, the contents of each image struct,
     * though, aren't owned by the array (i.e. need to be disposed afterwards
     * when no longer used). */
    array = g_array_sized_new (FALSE, FALSE, sizeof (QmiMessageDmsSetFirmwarePreferenceInputListImage), 2);
    g_array_append_val (array, firmware_preference_ctx->modem_image_id);
    g_array_append_val (array, firmware_preference_ctx->pri_image_id);

    /* The input bundle takes a reference to the array itself */
    input = qmi_message_dms_set_firmware_preference_input_new ();
    qmi_message_dms_set_firmware_preference_input_set_list (input, array, NULL);
    g_array_unref (array);

    g_strfreev (split);

    return input;
}

static void
get_boot_image_download_mode_ready (QmiClientDms *client,
                                    GAsyncResult *res)
{
    QmiMessageDmsGetBootImageDownloadModeOutput *output;
    GError *error = NULL;
    QmiDmsBootImageDownloadMode mode;

    output = qmi_client_dms_get_boot_image_download_mode_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_boot_image_download_mode_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get boot image download mode: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_boot_image_download_mode_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_boot_image_download_mode_output_get_mode (output, &mode, NULL);

    g_print ("[%s] Boot image download mode: %s\n",
             qmi_device_get_path_display (ctx->device),
             qmi_dms_boot_image_download_mode_get_string (mode));

    qmi_message_dms_get_boot_image_download_mode_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsSetBootImageDownloadModeInput *
set_boot_image_download_mode_input_create (const gchar *str)
{
    QmiMessageDmsSetBootImageDownloadModeInput *input = NULL;
    QmiDmsBootImageDownloadMode mode;
    GError *error = NULL;

    /* Prepare inputs.
     * Format of the string is:
     *    [normal|boot-and-recovery]
     */
    if (!qmicli_read_boot_image_download_mode_from_string (str, &mode))
        return NULL;

    input = qmi_message_dms_set_boot_image_download_mode_input_new ();
    if (!qmi_message_dms_set_boot_image_download_mode_input_set_mode (input, mode, &error)) {
        g_printerr ("error: couldn't create input bundle: '%s'\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_boot_image_download_mode_input_unref (input);
        return NULL;
    }

    return input;
}

static void
set_boot_image_download_mode_ready (QmiClientDms *client,
                                    GAsyncResult *res)
{
    QmiMessageDmsSetBootImageDownloadModeOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_boot_image_download_mode_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_boot_image_download_mode_output_get_result (output, &error)) {
        g_printerr ("error: couldn't set boot image download mode: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_boot_image_download_mode_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Boot image download mode successfully set\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_boot_image_download_mode_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_software_version_ready (QmiClientDms *client,
                            GAsyncResult *res)
{
    QmiMessageDmsGetSoftwareVersionOutput *output;
    GError *error = NULL;
    const gchar *version;

    output = qmi_client_dms_get_software_version_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_software_version_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get boot image download mode: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_software_version_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_get_software_version_output_get_version (output, &version, NULL);

    g_print ("[%s] Software version: %s\n",
             qmi_device_get_path_display (ctx->device),
             version);

    qmi_message_dms_get_software_version_output_unref (output);
    operation_shutdown (TRUE);
}

static void
set_fcc_authentication_ready (QmiClientDms *client,
                              GAsyncResult *res)
{
    QmiMessageDmsSetFccAuthenticationOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_set_fcc_authentication_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_set_fcc_authentication_output_get_result (output, &error)) {
        g_printerr ("error: couldn't set FCC authentication: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_set_fcc_authentication_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Successfully set FCC authentication\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_set_fcc_authentication_output_unref (output);
    operation_shutdown (TRUE);
}

static void
get_supported_messages_ready (QmiClientDms *client,
                              GAsyncResult *res)
{
    QmiMessageDmsGetSupportedMessagesOutput *output;
    GError *error = NULL;
    GArray *bytearray = NULL;
    gchar *str;

    output = qmi_client_dms_get_supported_messages_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_get_supported_messages_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get supported DMS messages: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_get_supported_messages_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Successfully got supported DMS messages:\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_get_supported_messages_output_get_list (output, &bytearray, NULL);
    str = qmicli_get_supported_messages_list (bytearray ? (const guint8 *)bytearray->data : NULL,
                                              bytearray ? bytearray->len : 0);
    g_print ("%s", str);
    g_free (str);

    qmi_message_dms_get_supported_messages_output_unref (output);
    operation_shutdown (TRUE);
}

static QmiMessageDmsHpChangeDeviceModeInput *
hp_change_device_mode_input_create (const gchar *str)
{
    QmiMessageDmsHpChangeDeviceModeInput *input = NULL;
    QmiDmsHpDeviceMode mode;
    GError *error = NULL;

    if (!qmicli_read_hp_device_mode_from_string (str, &mode)) {
        g_printerr ("error: couldn't parse input HP device mode : '%s'\n", str);
        return NULL;
    }

    input = qmi_message_dms_hp_change_device_mode_input_new ();
    if (!qmi_message_dms_hp_change_device_mode_input_set_mode (input, mode, &error)) {
        g_printerr ("error: couldn't create input data bundle: '%s'\n",
                    error->message);
        g_error_free (error);
        qmi_message_dms_hp_change_device_mode_input_unref (input);
        return NULL;
    }

    return input;
}

static void
hp_change_device_mode_ready (QmiClientDms *client,
                             GAsyncResult *res)
{
    QmiMessageDmsHpChangeDeviceModeOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_hp_change_device_mode_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_hp_change_device_mode_output_get_result (output, &error)) {
        g_printerr ("error: couldn't change HP device mode: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_hp_change_device_mode_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Successfully changed HP device mode\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_hp_change_device_mode_output_unref (output);

    /* Changing the mode will end up power cycling the device right away, so
     * just ignore any error from now on and don't even try to release the
     * client CID */
    operation_shutdown_skip_cid_release (TRUE);
}

static void
swi_get_current_firmware_ready (QmiClientDms *client,
                                GAsyncResult *res)
{
    QmiMessageDmsSwiGetCurrentFirmwareOutput *output;
    GError *error = NULL;
    const gchar *model = NULL;
    const gchar *boot_version = NULL;
    const gchar *amss_version = NULL;
    const gchar *sku_id = NULL;
    const gchar *package_id = NULL;
    const gchar *carrier_id = NULL;
    const gchar *pri_version = NULL;
    const gchar *carrier = NULL;
    const gchar *config_version = NULL;

    output = qmi_client_dms_swi_get_current_firmware_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_swi_get_current_firmware_output_get_result (output, &error)) {
        g_printerr ("error: couldn't get current firmware: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_swi_get_current_firmware_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    qmi_message_dms_swi_get_current_firmware_output_get_model          (output, &model,          NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_boot_version   (output, &boot_version,   NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_amss_version   (output, &amss_version,   NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_sku_id         (output, &sku_id,         NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_package_id     (output, &package_id,     NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_carrier_id     (output, &carrier_id,     NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_pri_version    (output, &pri_version,    NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_carrier        (output, &carrier,        NULL);
    qmi_message_dms_swi_get_current_firmware_output_get_config_version (output, &config_version, NULL);

    /* We'll consider it a success if we got at least one of the expected strings */
    if (!model          &&
        !boot_version   &&
        !amss_version   &&
        !sku_id         &&
        !package_id     &&
        !carrier_id     &&
        !pri_version    &&
        !carrier        &&
        !config_version) {
        g_printerr ("error: couldn't get any of the current firmware fields\n");
        qmi_message_dms_swi_get_current_firmware_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Successfully retrieved current firmware:\n",
             qmi_device_get_path_display (ctx->device));
    g_print ("\tModel: %s\n",          VALIDATE_UNKNOWN (model));
    g_print ("\tBoot version: %s\n",   VALIDATE_UNKNOWN (boot_version));
    g_print ("\tAMSS version: %s\n",   VALIDATE_UNKNOWN (amss_version));
    g_print ("\tSKU ID: %s\n",         VALIDATE_UNKNOWN (sku_id));
    g_print ("\tPackage ID: %s\n",     VALIDATE_UNKNOWN (package_id));
    g_print ("\tCarrier ID: %s\n",     VALIDATE_UNKNOWN (carrier_id));
    g_print ("\tConfig version: %s\n", VALIDATE_UNKNOWN (config_version));

    qmi_message_dms_swi_get_current_firmware_output_unref (output);
    operation_shutdown (TRUE);
}

static void
reset_ready (QmiClientDms *client,
             GAsyncResult *res)
{
    QmiMessageDmsResetOutput *output;
    GError *error = NULL;

    output = qmi_client_dms_reset_finish (client, res, &error);
    if (!output) {
        g_printerr ("error: operation failed: %s\n", error->message);
        g_error_free (error);
        operation_shutdown (FALSE);
        return;
    }

    if (!qmi_message_dms_reset_output_get_result (output, &error)) {
        g_printerr ("error: couldn't reset the DMS service: %s\n", error->message);
        g_error_free (error);
        qmi_message_dms_reset_output_unref (output);
        operation_shutdown (FALSE);
        return;
    }

    g_print ("[%s] Successfully performed DMS service reset\n",
             qmi_device_get_path_display (ctx->device));

    qmi_message_dms_reset_output_unref (output);
    operation_shutdown (TRUE);
}

static gboolean
noop_cb (gpointer unused)
{
    operation_shutdown (TRUE);
    return FALSE;
}

void
qmicli_dms_run (QmiDevice *device,
                QmiClientDms *client,
                GCancellable *cancellable)
{
    /* Initialize context */
    ctx = g_slice_new (Context);
    ctx->device = g_object_ref (device);
    ctx->client = g_object_ref (client);
    if (cancellable)
        ctx->cancellable = g_object_ref (cancellable);

    /* Request to get IDs? */
    if (get_ids_flag) {
        g_debug ("Asynchronously getting IDs...");
        qmi_client_dms_get_ids (ctx->client,
                                NULL,
                                10,
                                ctx->cancellable,
                                (GAsyncReadyCallback)get_ids_ready,
                                NULL);
        return;
    }

    /* Request to get capabilities? */
    if (get_capabilities_flag) {
        g_debug ("Asynchronously getting capabilities...");
        qmi_client_dms_get_capabilities (ctx->client,
                                         NULL,
                                         10,
                                         ctx->cancellable,
                                         (GAsyncReadyCallback)get_capabilities_ready,
                                         NULL);
        return;
    }

    /* Request to get manufacturer? */
    if (get_manufacturer_flag) {
        g_debug ("Asynchronously getting manufacturer...");
        qmi_client_dms_get_manufacturer (ctx->client,
                                         NULL,
                                         10,
                                         ctx->cancellable,
                                         (GAsyncReadyCallback)get_manufacturer_ready,
                                         NULL);
        return;
    }

    /* Request to get model? */
    if (get_model_flag) {
        g_debug ("Asynchronously getting model...");
        qmi_client_dms_get_model (ctx->client,
                                  NULL,
                                  10,
                                  ctx->cancellable,
                                  (GAsyncReadyCallback)get_model_ready,
                                  NULL);
        return;
    }

    /* Request to get revision? */
    if (get_revision_flag) {
        g_debug ("Asynchronously getting revision...");
        qmi_client_dms_get_revision (ctx->client,
                                     NULL,
                                     10,
                                     ctx->cancellable,
                                     (GAsyncReadyCallback)get_revision_ready,
                                     NULL);
        return;
    }

    /* Request to get msisdn? */
    if (get_msisdn_flag) {
        g_debug ("Asynchronously getting msisdn...");
        qmi_client_dms_get_msisdn (ctx->client,
                                   NULL,
                                   10,
                                   ctx->cancellable,
                                   (GAsyncReadyCallback)get_msisdn_ready,
                                   NULL);
        return;
    }

    /* Request to get power status? */
    if (get_power_state_flag) {
        g_debug ("Asynchronously getting power status...");
        qmi_client_dms_get_power_state (ctx->client,
                                        NULL,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)get_power_state_ready,
                                        NULL);
        return;
    }

    /* Request to set PIN protection? */
    if (uim_set_pin_protection_str) {
        QmiMessageDmsUimSetPinProtectionInput *input;

        g_debug ("Asynchronously setting PIN protection...");
        input = uim_set_pin_protection_input_create (uim_set_pin_protection_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_set_pin_protection (ctx->client,
                                               input,
                                               10,
                                               ctx->cancellable,
                                               (GAsyncReadyCallback)uim_set_pin_protection_ready,
                                               NULL);
        qmi_message_dms_uim_set_pin_protection_input_unref (input);
        return;
    }

    /* Request to verify PIN? */
    if (uim_verify_pin_str) {
        QmiMessageDmsUimVerifyPinInput *input;

        g_debug ("Asynchronously verifying PIN...");
        input = uim_verify_pin_input_create (uim_verify_pin_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_verify_pin (ctx->client,
                                       input,
                                       10,
                                       ctx->cancellable,
                                       (GAsyncReadyCallback)uim_verify_pin_ready,
                                       NULL);
        qmi_message_dms_uim_verify_pin_input_unref (input);
        return;
    }

    /* Request to unblock PIN? */
    if (uim_unblock_pin_str) {
        QmiMessageDmsUimUnblockPinInput *input;

        g_debug ("Asynchronously unblocking PIN...");
        input = uim_unblock_pin_input_create (uim_unblock_pin_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_unblock_pin (ctx->client,
                                        input,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)uim_unblock_pin_ready,
                                        NULL);
        qmi_message_dms_uim_unblock_pin_input_unref (input);
        return;
    }

    /* Request to change the PIN? */
    if (uim_change_pin_str) {
        QmiMessageDmsUimChangePinInput *input;

        g_debug ("Asynchronously changing PIN...");
        input = uim_change_pin_input_create (uim_change_pin_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_change_pin (ctx->client,
                                       input,
                                       10,
                                       ctx->cancellable,
                                       (GAsyncReadyCallback)uim_change_pin_ready,
                                       NULL);
        qmi_message_dms_uim_change_pin_input_unref (input);
        return;
    }

    /* Request to get PIN status? */
    if (uim_get_pin_status_flag) {
        g_debug ("Asynchronously getting PIN status...");
        qmi_client_dms_uim_get_pin_status (ctx->client,
                                           NULL,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)uim_get_pin_status_ready,
                                           NULL);
        return;
    }

    /* Request to get UIM ICCID? */
    if (uim_get_iccid_flag) {
        g_debug ("Asynchronously getting UIM ICCID...");
        qmi_client_dms_uim_get_iccid (ctx->client,
                                      NULL,
                                      10,
                                      ctx->cancellable,
                                      (GAsyncReadyCallback)uim_get_iccid_ready,
                                      NULL);
        return;
    }

    /* Request to get UIM IMSI? */
    if (uim_get_imsi_flag) {
        g_debug ("Asynchronously getting UIM IMSI...");
        qmi_client_dms_uim_get_imsi (ctx->client,
                                     NULL,
                                     10,
                                     ctx->cancellable,
                                     (GAsyncReadyCallback)uim_get_imsi_ready,
                                     NULL);
        return;
    }

    /* Request to get UIM state? */
    if (uim_get_state_flag) {
        g_debug ("Asynchronously getting UIM state...");
        qmi_client_dms_uim_get_state (ctx->client,
                                      NULL,
                                      10,
                                      ctx->cancellable,
                                      (GAsyncReadyCallback)uim_get_state_ready,
                                      NULL);
        return;
    }

    /* Request to get hardware revision? */
    if (get_hardware_revision_flag) {
        g_debug ("Asynchronously getting hardware revision...");
        qmi_client_dms_get_hardware_revision (ctx->client,
                                              NULL,
                                              10,
                                              ctx->cancellable,
                                              (GAsyncReadyCallback)get_hardware_revision_ready,
                                              NULL);
        return;
    }

    /* Request to get operating mode? */
    if (get_operating_mode_flag) {
        g_debug ("Asynchronously getting operating mode...");
        qmi_client_dms_get_operating_mode (ctx->client,
                                           NULL,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)get_operating_mode_ready,
                                           NULL);
        return;
    }

    /* Request to set operating mode? */
    if (set_operating_mode_str) {
        QmiMessageDmsSetOperatingModeInput *input;

        g_debug ("Asynchronously setting operating mode...");
        input = set_operating_mode_input_create (set_operating_mode_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_set_operating_mode (ctx->client,
                                           input,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)set_operating_mode_ready,
                                           NULL);
        qmi_message_dms_set_operating_mode_input_unref (input);
        return;
    }

    /* Request to get time? */
    if (get_time_flag) {
        g_debug ("Asynchronously getting time...");
        qmi_client_dms_get_time (ctx->client,
                                 NULL,
                                 10,
                                 ctx->cancellable,
                                 (GAsyncReadyCallback)get_time_ready,
                                 NULL);
        return;
    }

    /* Request to get the PRL version? */
    if (get_prl_version_flag) {
        g_debug ("Asynchronously getting PRL version...");
        qmi_client_dms_get_prl_version (ctx->client,
                                        NULL,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)get_prl_version_ready,
                                        NULL);
        return;
    }

    /* Request to get the activation state? */
    if (get_activation_state_flag) {
        g_debug ("Asynchronously getting activation state...");
        qmi_client_dms_get_activation_state (ctx->client,
                                             NULL,
                                             10,
                                             ctx->cancellable,
                                             (GAsyncReadyCallback)get_activation_state_ready,
                                             NULL);
        return;
    }

    /* Request to activate automatically? */
    if (activate_automatic_str) {
        QmiMessageDmsActivateAutomaticInput *input;

        g_debug ("Asynchronously requesting automatic activation...");
        input = activate_automatic_input_create (activate_automatic_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_activate_automatic (ctx->client,
                                           input,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)activate_automatic_ready,
                                           NULL);
        qmi_message_dms_activate_automatic_input_unref (input);
        return;
    }

    /* Request to activate manually? */
    if (activate_manual_str) {
        QmiMessageDmsActivateManualInput *input;

        g_debug ("Asynchronously requesting manual activation...");
        input = activate_manual_input_create (activate_manual_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_activate_manual (ctx->client,
                                        input,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)activate_manual_ready,
                                        NULL);
        qmi_message_dms_activate_manual_input_unref (input);
        return;
    }

    /* Request to get the activation state? */
    if (get_user_lock_state_flag) {
        g_debug ("Asynchronously getting user lock state...");
        qmi_client_dms_get_user_lock_state (ctx->client,
                                            NULL,
                                            10,
                                            ctx->cancellable,
                                            (GAsyncReadyCallback)get_user_lock_state_ready,
                                            NULL);
        return;
    }

    /* Request to set user lock state? */
    if (set_user_lock_state_str) {
        QmiMessageDmsSetUserLockStateInput *input;

        g_debug ("Asynchronously setting user lock state...");
        input = set_user_lock_state_input_create (set_user_lock_state_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_set_user_lock_state (ctx->client,
                                            input,
                                            10,
                                            ctx->cancellable,
                                            (GAsyncReadyCallback)set_user_lock_state_ready,
                                            NULL);
        qmi_message_dms_set_user_lock_state_input_unref (input);
        return;
    }

    /* Request to set user lock code? */
    if (set_user_lock_code_str) {
        QmiMessageDmsSetUserLockCodeInput *input;

        g_debug ("Asynchronously changing user lock code...");
        input = set_user_lock_code_input_create (set_user_lock_code_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_set_user_lock_code (ctx->client,
                                           input,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)set_user_lock_code_ready,
                                            NULL);
        qmi_message_dms_set_user_lock_code_input_unref (input);
        return;
    }

    /* Request to read user data? */
    if (read_user_data_flag) {
        g_debug ("Asynchronously reading user data...");
        qmi_client_dms_read_user_data (ctx->client,
                                       NULL,
                                       10,
                                       ctx->cancellable,
                                       (GAsyncReadyCallback)read_user_data_ready,
                                       NULL);
        return;
    }

    /* Request to write user data? */
    if (write_user_data_str) {
        QmiMessageDmsWriteUserDataInput *input;

        g_debug ("Asynchronously writing user data...");
        input = write_user_data_input_create (write_user_data_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_write_user_data (ctx->client,
                                        input,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)write_user_data_ready,
                                        NULL);
        qmi_message_dms_write_user_data_input_unref (input);
        return;
    }

    /* Request to read ERI file? */
    if (read_eri_file_flag) {
        g_debug ("Asynchronously reading ERI file...");
        qmi_client_dms_read_eri_file (ctx->client,
                                      NULL,
                                      10,
                                      ctx->cancellable,
                                      (GAsyncReadyCallback)read_eri_file_ready,
                                      NULL);
        return;
    }

    /* Request to restore factory defaults? */
    if (restore_factory_defaults_str) {
        QmiMessageDmsRestoreFactoryDefaultsInput *input;

        g_debug ("Asynchronously restoring factory defaults...");
        input = restore_factory_defaults_input_create (restore_factory_defaults_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_restore_factory_defaults (ctx->client,
                                                 input,
                                                 10,
                                                 ctx->cancellable,
                                                 (GAsyncReadyCallback)restore_factory_defaults_ready,
                                                 NULL);
        qmi_message_dms_restore_factory_defaults_input_unref (input);
        return;
    }

    /* Request to validate SPC? */
    if (validate_service_programming_code_str) {
        QmiMessageDmsValidateServiceProgrammingCodeInput *input;

        g_debug ("Asynchronously validating SPC...");
        input = validate_service_programming_code_input_create (validate_service_programming_code_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_validate_service_programming_code (ctx->client,
                                                          input,
                                                          10,
                                                          ctx->cancellable,
                                                          (GAsyncReadyCallback)validate_service_programming_code_ready,
                                                          NULL);
        qmi_message_dms_validate_service_programming_code_input_unref (input);
        return;
    }

    /* Set firmware id? */
    if (set_firmware_id_flag) {
        g_debug ("Asynchronously setting firmware id...");
        qmi_client_dms_set_firmware_id (ctx->client,
                                        NULL,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)set_firmware_id_ready,
                                        NULL);
        return;
    }

    /* Request to get CK status? */
    if (uim_get_ck_status_str) {
        QmiMessageDmsUimGetCkStatusInput *input;

        g_debug ("Asynchronously getting CK status...");
        input = uim_get_ck_status_input_create (uim_get_ck_status_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_get_ck_status (ctx->client,
                                          input,
                                          10,
                                          ctx->cancellable,
                                          (GAsyncReadyCallback)uim_get_ck_status_ready,
                                          NULL);
        qmi_message_dms_uim_get_ck_status_input_unref (input);
        return;
    }

    /* Request to set CK protection? */
    if (uim_set_ck_protection_str) {
        QmiMessageDmsUimSetCkProtectionInput *input;

        g_debug ("Asynchronously setting CK protection...");
        input = uim_set_ck_protection_input_create (uim_set_ck_protection_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_set_ck_protection (ctx->client,
                                              input,
                                              10,
                                              ctx->cancellable,
                                              (GAsyncReadyCallback)uim_set_ck_protection_ready,
                                              NULL);
        qmi_message_dms_uim_set_ck_protection_input_unref (input);
        return;
    }

    /* Request to set CK protection? */
    if (uim_unblock_ck_str) {
        QmiMessageDmsUimUnblockCkInput *input;

        g_debug ("Asynchronously unblocking CK...");
        input = uim_unblock_ck_input_create (uim_unblock_ck_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_uim_unblock_ck (ctx->client,
                                       input,
                                       10,
                                       ctx->cancellable,
                                       (GAsyncReadyCallback)uim_unblock_ck_ready,
                                       NULL);
        qmi_message_dms_uim_unblock_ck_input_unref (input);
        return;
    }

    /* Request to get band capabilities? */
    if (get_band_capabilities_flag) {
        g_debug ("Asynchronously getting band capabilities...");
        qmi_client_dms_get_band_capabilities (ctx->client,
                                              NULL,
                                              10,
                                              ctx->cancellable,
                                              (GAsyncReadyCallback)get_band_capabilities_ready,
                                              NULL);
        return;
    }

    /* Request to get factory SKU? */
    if (get_factory_sku_flag) {
        g_debug ("Asynchronously getting factory SKU...");
        qmi_client_dms_get_factory_sku (ctx->client,
                                        NULL,
                                        10,
                                        ctx->cancellable,
                                        (GAsyncReadyCallback)get_factory_sku_ready,
                                        NULL);
        return;
    }

    /* Request to list stored images? */
    if (list_stored_images_flag) {
        g_debug ("Asynchronously listing stored images...");
        qmi_client_dms_list_stored_images (ctx->client,
                                           NULL,
                                           10,
                                           ctx->cancellable,
                                           (GAsyncReadyCallback)list_stored_images_ready,
                                           NULL);
        return;
    }

    /* Request to select stored image? */
    if (select_stored_image_str) {
        g_debug ("Asynchronously selecting stored image...");
        get_stored_image (ctx->client,
                          select_stored_image_str,
                          (GAsyncReadyCallback)get_stored_image_select_ready,
                          NULL);
        return;
    }

    /* Request to delete stored image? */
    if (delete_stored_image_str) {
        g_debug ("Asynchronously deleting stored image...");
        get_stored_image (ctx->client,
                          delete_stored_image_str,
                          (GAsyncReadyCallback)get_stored_image_delete_ready,
                          NULL);
        return;
    }

    /* Get firmware preference? */
    if (get_firmware_preference_flag) {
        qmi_client_dms_get_firmware_preference (
            client,
            NULL,
            10,
            NULL,
            (GAsyncReadyCallback)dms_get_firmware_preference_ready,
            NULL);
        return;
    }

    /* Set firmware preference? */
    if (set_firmware_preference_str) {
        QmiMessageDmsSetFirmwarePreferenceInput *input;
        SetFirmwarePreferenceContext             firmware_preference_ctx;

        memset (&firmware_preference_ctx, 0, sizeof (firmware_preference_ctx));

        g_debug ("Asynchronously setting firmware preference...");
        input = set_firmware_preference_input_create (set_firmware_preference_str, &firmware_preference_ctx);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }

        qmi_client_dms_set_firmware_preference (
            client,
            input,
            10,
            NULL,
            (GAsyncReadyCallback)dms_set_firmware_preference_ready,
            NULL);
        set_firmware_preference_context_clear (&firmware_preference_ctx);
        qmi_message_dms_set_firmware_preference_input_unref (input);
        return;
    }

    /* Get boot image download mode */
    if (get_boot_image_download_mode_flag) {
        g_debug ("Asynchronously getting boot image download mode...");
        qmi_client_dms_get_boot_image_download_mode (ctx->client,
                                                     NULL,
                                                     10,
                                                     ctx->cancellable,
                                                     (GAsyncReadyCallback)get_boot_image_download_mode_ready,
                                                     NULL);
        return;
    }

    /* Set boot image download mode */
    if (set_boot_image_download_mode_str) {
        QmiMessageDmsSetBootImageDownloadModeInput *input;

        g_debug ("Asynchronously setting boot image download mode...");
        input = set_boot_image_download_mode_input_create (set_boot_image_download_mode_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }
        qmi_client_dms_set_boot_image_download_mode (ctx->client,
                                                     input,
                                                     10,
                                                     ctx->cancellable,
                                                     (GAsyncReadyCallback)set_boot_image_download_mode_ready,
                                                     NULL);
        qmi_message_dms_set_boot_image_download_mode_input_unref (input);
        return;
    }

    /* Get software version */
    if (get_software_version_flag) {
        g_debug ("Asynchronously getting software version...");
        qmi_client_dms_get_software_version (ctx->client,
                                             NULL,
                                             10,
                                             ctx->cancellable,
                                             (GAsyncReadyCallback)get_software_version_ready,
                                             NULL);
        return;
    }

    /* Set FCC authentication */
    if (set_fcc_authentication_flag) {
        g_debug ("Asynchronously setting FCC auth...");
        qmi_client_dms_set_fcc_authentication (ctx->client,
                                               NULL,
                                               10,
                                               ctx->cancellable,
                                               (GAsyncReadyCallback)set_fcc_authentication_ready,
                                               NULL);
        return;
    }

    /* Request to list supported messages? */
    if (get_supported_messages_flag) {
        g_debug ("Asynchronously getting supported DMS messages...");
        qmi_client_dms_get_supported_messages (ctx->client,
                                               NULL,
                                               10,
                                               ctx->cancellable,
                                               (GAsyncReadyCallback)get_supported_messages_ready,
                                               NULL);
        return;
    }

    /* Request to change device download mode */
    if (hp_change_device_mode_str) {
        QmiMessageDmsHpChangeDeviceModeInput *input;

        g_debug ("Asynchronously changing device mode (HP specific)...");

        input = hp_change_device_mode_input_create (hp_change_device_mode_str);
        if (!input) {
            operation_shutdown (FALSE);
            return;
        }

        qmi_client_dms_hp_change_device_mode (ctx->client,
                                              input,
                                              10,
                                              ctx->cancellable,
                                              (GAsyncReadyCallback)hp_change_device_mode_ready,
                                              NULL);
        qmi_message_dms_hp_change_device_mode_input_unref (input);
        return;
    }

    /* Request to get current firmware */
    if (swi_get_current_firmware_flag) {
        g_debug ("Asynchronously getting current firmware (Sierra Wireless specific)...");
        qmi_client_dms_swi_get_current_firmware (ctx->client,
                                                 NULL,
                                                 10,
                                                 ctx->cancellable,
                                                 (GAsyncReadyCallback)swi_get_current_firmware_ready,
                                                 NULL);
        return;
    }

    /* Request to reset DMS service? */
    if (reset_flag) {
        g_debug ("Asynchronously resetting DMS service...");
        qmi_client_dms_reset (ctx->client,
                              NULL,
                              10,
                              ctx->cancellable,
                              (GAsyncReadyCallback)reset_ready,
                              NULL);
        return;
    }

    /* Just client allocate/release? */
    if (noop_flag) {
        g_idle_add (noop_cb, NULL);
        return;
    }

    g_warn_if_reached ();
}
