Merge remote-tracking branch 'cros/upstream' into 'cros/master'

Change-Id: Ic23b12bf9950a6937439d9943df48a17046fb3e2
diff --git a/configure.ac b/configure.ac
index d26ca3e..d560db9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,7 +4,7 @@
 dnl The QMI version number
 m4_define([qmi_major_version], [1])
 m4_define([qmi_minor_version], [21])
-m4_define([qmi_micro_version], [3])
+m4_define([qmi_micro_version], [4])
 m4_define([qmi_version],
           [qmi_major_version.qmi_minor_version.qmi_micro_version])
 
diff --git a/data/qmi-service-wds.json b/data/qmi-service-wds.json
index d30a591..2993f32 100644
--- a/data/qmi-service-wds.json
+++ b/data/qmi-service-wds.json
@@ -1740,6 +1740,10 @@
                  { "common-ref"    : "WDS IPv4 Address Preference",
                    "since"         : "1.22" },
                  { "common-ref"    : "WDS PDP Context Number",
+                   "since"         : "1.22" },
+                 { "common-ref"    : "WDS APN Disabled Flag",
+                   "since"         : "1.22" },
+                 { "common-ref"    : "WDS Roaming Disallowed Flag",
                    "since"         : "1.22" } ],
    "output"  : [ { "common-ref"    : "Operation Result" },
                  { "common-ref"    : "WDS Profile Identifier",
diff --git a/src/libqmi-glib/qmi-errors.h b/src/libqmi-glib/qmi-errors.h
index 4ff446c..b70ae6f 100644
--- a/src/libqmi-glib/qmi-errors.h
+++ b/src/libqmi-glib/qmi-errors.h
@@ -71,7 +71,7 @@
  * @QMI_CORE_ERROR_TLV_NOT_FOUND: TLV not found.
  * @QMI_CORE_ERROR_TLV_TOO_LONG: TLV is too long.
  * @QMI_CORE_ERROR_UNSUPPORTED: Not supported.
- * @QMI_CORE_ERROR_TLV_EMPTY: TLV has no value. Since: 1.12.
+ * @QMI_CORE_ERROR_TLV_EMPTY: TLV has no value. Empty TLVs are not a real error, so this error type is never generated. Since: 1.12. Deprecated: 1.22.
  * @QMI_CORE_ERROR_UNEXPECTED_MESSAGE: QMI message is unexpected. Since: 1.16.
  *
  * Common errors that may be reported by libqmi-glib.
diff --git a/src/libqmi-glib/qmi-message.c b/src/libqmi-glib/qmi-message.c
index f610020..0831057 100644
--- a/src/libqmi-glib/qmi-message.c
+++ b/src/libqmi-glib/qmi-message.c
@@ -585,15 +585,9 @@
     g_return_val_if_fail (self != NULL, FALSE);
     g_return_val_if_fail (self->len >= (tlv_offset + sizeof (struct tlv)), FALSE);
 
+    /* A TLV without content is actually not an error, e.g. TLV strings with no
+     * data are totally valid. */
     tlv_length = self->len - tlv_offset;
-    if (tlv_length == sizeof (struct tlv)) {
-        g_set_error (error,
-                     QMI_CORE_ERROR,
-                     QMI_CORE_ERROR_TLV_EMPTY,
-                     "Empty TLV, no value set");
-        g_byte_array_set_size (self, tlv_offset);
-        return FALSE;
-    }
 
     /* Update length fields. */
     tlv = tlv_get_header (self, tlv_offset);
@@ -873,11 +867,6 @@
     }
 
     tlv_length = GUINT16_FROM_LE (tlv->length);
-    if (!tlv_length) {
-        g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_EMPTY,
-                     "TLV 0x%02X is empty", type);
-        return 0;
-    }
 
     if (((guint8 *) tlv_next (tlv)) > ((guint8 *) qmi_end (self))) {
         g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_TOO_LONG,
diff --git a/src/libqmi-glib/test/test-message.c b/src/libqmi-glib/test/test-message.c
index 973b383..4e7c703 100644
--- a/src/libqmi-glib/test/test-message.c
+++ b/src/libqmi-glib/test/test-message.c
@@ -373,8 +373,8 @@
     g_assert (init_offset > 0);
 
     ret = qmi_message_tlv_write_complete (self, init_offset, &error);
-    g_assert_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_EMPTY);
-    g_assert (!ret);
+    g_assert_no_error (error);
+    g_assert (ret);
 
     qmi_message_unref (self);
 }
diff --git a/src/qmicli/qmicli-helpers.c b/src/qmicli/qmicli-helpers.c
index 7d996d2..3481b36 100644
--- a/src/qmicli/qmicli-helpers.c
+++ b/src/qmicli/qmicli-helpers.c
@@ -676,6 +676,24 @@
 }
 
 gboolean
+qmicli_read_pdp_type_from_string (const gchar *str,
+                                  QmiWdsPdpType *out)
+{
+    if (g_ascii_strcasecmp (str, "IP") == 0 || g_ascii_strcasecmp (str, "IPV4") == 0)
+        *out = QMI_WDS_PDP_TYPE_IPV4;
+    else if (g_ascii_strcasecmp (str, "PPP") == 0)
+        *out = QMI_WDS_PDP_TYPE_PPP;
+    else if (g_ascii_strcasecmp (str, "IPV6") == 0)
+        *out = (QMI_WDS_PDP_TYPE_IPV6);
+    else if (g_ascii_strcasecmp (str, "IPV4V6") == 0)
+        *out = QMI_WDS_PDP_TYPE_IPV4_OR_IPV6;
+    else
+        return FALSE;
+
+    return TRUE;
+}
+
+gboolean
 qmicli_read_boot_image_download_mode_from_string (const gchar *str,
                                                   QmiDmsBootImageDownloadMode *out)
 {
diff --git a/src/qmicli/qmicli-helpers.h b/src/qmicli/qmicli-helpers.h
index 144151e..ed8e61a 100644
--- a/src/qmicli/qmicli-helpers.h
+++ b/src/qmicli/qmicli-helpers.h
@@ -70,6 +70,8 @@
                                                               QmiWdsAutoconnectSettingRoaming *out);
 gboolean qmicli_read_authentication_from_string              (const gchar *str,
                                                               QmiWdsAuthentication *out);
+gboolean qmicli_read_pdp_type_from_string                    (const gchar *str,
+                                                              QmiWdsPdpType *out);
 gboolean qmicli_read_boot_image_download_mode_from_string    (const gchar *str,
                                                               QmiDmsBootImageDownloadMode *out);
 gboolean qmicli_read_hp_device_mode_from_string              (const gchar *str,
diff --git a/src/qmicli/qmicli-loc.c b/src/qmicli/qmicli-loc.c
index 8c9c169..6fc2845 100644
--- a/src/qmicli/qmicli-loc.c
+++ b/src/qmicli/qmicli-loc.c
@@ -185,7 +185,7 @@
     }
 
     if (timeout > 0 && !(get_position_report_flag || get_gnss_sv_info_flag)) {
-        g_printerr ("error: `--loc-timeout' is only applicable with `--loc-get-position' or `--loc-get-satellite-info'\n");
+        g_printerr ("error: `--loc-timeout' is only applicable with `--loc-get-position-report' or `--loc-get-gnss-sv-info'\n");
         exit (EXIT_FAILURE);
     }
 
diff --git a/src/qmicli/qmicli-wds.c b/src/qmicli/qmicli-wds.c
index 17ce609..02b2471 100644
--- a/src/qmicli/qmicli-wds.c
+++ b/src/qmicli/qmicli-wds.c
@@ -64,6 +64,10 @@
 static gboolean go_dormant_flag;
 static gboolean go_active_flag;
 static gboolean get_dormancy_status_flag;
+static gchar *create_profile_str;
+static gchar *swi_create_profile_indexed_str;
+static gchar *modify_profile_str;
+static gchar *delete_profile_str;
 static gchar *get_profile_list_str;
 static gchar *get_default_profile_num_str;
 static gchar *set_default_profile_num_str;
@@ -122,6 +126,22 @@
       "Get the dormancy status of the active data connection",
       NULL
     },
+    { "wds-create-profile", 0, 0, G_OPTION_ARG_STRING, &create_profile_str,
+      "Create new profile using first available profile index (optional keys: name, apn, pdp-type (IP|PPP|IPV6|IPV4V6), auth (NONE|PAP|CHAP|BOTH), username, password, context-num, no-roaming=yes, disabled=yes)",
+      "[\"(3gpp|3gpp2)[,key=value,...]\"]"
+    },
+    { "wds-swi-create-profile-indexed", 0, 0, G_OPTION_ARG_STRING, &swi_create_profile_indexed_str,
+      "Create new profile at specified profile index [Sierra Wireless specific] (optional keys: name, apn, pdp-type (IP|PPP|IPV6|IPV4V6), auth (NONE|PAP|CHAP|BOTH), username, password, context-num, no-roaming=yes, disabled=yes)",
+      "[\"(3gpp|3gpp2),#[,key=value,...]\"]"
+    },
+    { "wds-modify-profile", 0, 0, G_OPTION_ARG_STRING, &modify_profile_str,
+      "Modify existing profile (optional keys: name, apn, pdp-type (IP|PPP|IPV6|IPV4V6), auth (NONE|PAP|CHAP|BOTH), username, password, context-num, no-roaming=yes, disabled=yes)",
+      "[\"(3gpp|3gpp2),#,key=value,...\"]"
+    },
+    { "wds-delete-profile", 0, 0, G_OPTION_ARG_STRING, &delete_profile_str,
+      "Delete existing profile",
+      "[(3gpp|3gpp2),#]"
+    },
     { "wds-get-profile-list", 0, 0, G_OPTION_ARG_STRING, &get_profile_list_str,
       "Get profile list",
       "[3gpp|3gpp2]"
@@ -209,6 +229,10 @@
                  go_dormant_flag +
                  go_active_flag +
                  get_dormancy_status_flag +
+                 !!create_profile_str +
+                 !!swi_create_profile_indexed_str +
+                 !!modify_profile_str +
+                 !!delete_profile_str +
                  !!get_profile_list_str +
                  !!get_default_profile_num_str +
                  !!set_default_profile_num_str +
@@ -1180,6 +1204,620 @@
     operation_shutdown (TRUE);
 }
 
+
+typedef struct {
+    QmiWdsProfileType     profile_type;
+    guint                 profile_index;
+    guint                 context_number;
+    gchar                *name;
+    QmiWdsPdpType         pdp_type;
+    gboolean              pdp_type_set;
+    gchar                *apn;
+    gchar                *username;
+    gchar                *password;
+    QmiWdsAuthentication  auth;
+    gboolean              auth_set;
+    gboolean              no_roaming;
+    gboolean              no_roaming_set;
+    gboolean              disabled;
+    gboolean              disabled_set;
+} CreateModifyProfileProperties;
+
+static gboolean
+create_modify_profile_properties_handle (const gchar  *key,
+                                         const gchar  *value,
+                                         GError      **error,
+                                         gpointer      user_data)
+{
+    CreateModifyProfileProperties *props = user_data;
+
+    /* Allow empty values for string parameters */
+
+    if (g_ascii_strcasecmp (key, "name") == 0 && !props->name) {
+        props->name = g_strdup (value);
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "apn") == 0 && !props->apn) {
+        props->apn = g_strdup (value);
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "username") == 0 && !props->username) {
+        props->username = g_strdup (value);
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "password") == 0 && !props->password) {
+        props->password = g_strdup (value);
+        return TRUE;
+    }
+
+    /* all other TLVs do require a value... */
+    if (!value || !value[0]) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "key '%s' required a value",
+                     key);
+        return FALSE;
+    }
+
+    if (g_ascii_strcasecmp (key, "context-num") == 0 && !props->context_number) {
+        props->context_number = atoi (value);
+        if (props->context_number <= 0 || props->context_number > G_MAXUINT8) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "invalid or out of range context number [1,%u]: '%s'",
+                         G_MAXUINT8,
+                         value);
+            return FALSE;
+        }
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "auth") == 0 && !props->auth_set) {
+        if (!qmicli_read_authentication_from_string (value, &(props->auth))) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "unknown auth protocol '%s'",
+                         value);
+            return FALSE;
+        }
+        props->auth_set = TRUE;
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "pdp-type") == 0 && !props->pdp_type_set) {
+        if (!qmicli_read_pdp_type_from_string (value, &(props->pdp_type))) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "unknown pdp type '%s'",
+                         value);
+            return FALSE;
+        }
+        props->pdp_type_set = TRUE;
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "no-roaming") == 0 && !props->no_roaming_set) {
+        if (!qmicli_read_yes_no_from_string (value, &(props->no_roaming))) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "unknown 'no-roaming' value '%s'",
+                         value);
+            return FALSE;
+        }
+        props->no_roaming_set = TRUE;
+        return TRUE;
+    }
+
+    if (g_ascii_strcasecmp (key, "disabled") == 0 && !props->disabled_set) {
+        if (!qmicli_read_yes_no_from_string (value, &(props->disabled))) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "unknown 'disabled' value '%s'",
+                         value);
+            return FALSE;
+        }
+        props->disabled_set = TRUE;
+        return TRUE;
+    }
+
+    g_set_error (error,
+                 QMI_CORE_ERROR,
+                 QMI_CORE_ERROR_FAILED,
+                 "unrecognized or duplicate option '%s'",
+                 key);
+    return FALSE;
+}
+
+static gboolean
+create_profile_input_create (const gchar                      *str,
+                             QmiMessageWdsCreateProfileInput **input,
+                             GError                          **error)
+{
+    GError *parse_error = NULL;
+    CreateModifyProfileProperties props = {};
+    gboolean success = FALSE;
+    gchar **split;
+
+    g_assert (input && !*input);
+
+    split = g_strsplit (str, ",", 2);
+    if (g_strv_length (split) < 1) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "expected at least 1 arguments for 'wds create profile' command");
+        goto out;
+    }
+
+    g_strstrip (split[0]);
+    if (g_str_equal (split[0], "3gpp"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP;
+    else if (g_str_equal (split[0], "3gpp2"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP2;
+    else {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "invalid profile type. Expected '3gpp' or '3gpp2'.'");
+        goto out;
+    }
+
+    if (split[1]) {
+        if (!qmicli_parse_key_value_string (split[1],
+                                            &parse_error,
+                                            create_modify_profile_properties_handle,
+                                            &props)) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "couldn't parse input string: %s",
+                         parse_error->message);
+            g_error_free (parse_error);
+            goto out;
+        }
+    }
+
+    /* Create input bundle */
+    *input = qmi_message_wds_create_profile_input_new ();
+
+    /* Profile type is required */
+    qmi_message_wds_create_profile_input_set_profile_type (*input, props.profile_type, NULL);
+
+    if (props.context_number)
+        qmi_message_wds_create_profile_input_set_pdp_context_number (*input, props.context_number, NULL);
+
+    if (props.pdp_type_set)
+        qmi_message_wds_create_profile_input_set_pdp_type (*input, props.pdp_type, NULL);
+
+    if (props.name)
+        qmi_message_wds_create_profile_input_set_profile_name (*input, props.name, NULL);
+
+    if (props.apn)
+        qmi_message_wds_create_profile_input_set_apn_name (*input, props.apn, NULL);
+
+    if (props.auth_set)
+        qmi_message_wds_create_profile_input_set_authentication (*input, props.auth, NULL);
+
+    if (props.username)
+        qmi_message_wds_create_profile_input_set_username (*input, props.username, NULL);
+
+    if (props.password)
+        qmi_message_wds_create_profile_input_set_password (*input, props.password, NULL);
+
+    if (props.no_roaming_set)
+        qmi_message_wds_create_profile_input_set_roaming_disallowed_flag (*input, props.no_roaming, NULL);
+
+    if (props.disabled_set)
+        qmi_message_wds_create_profile_input_set_apn_disabled_flag (*input, props.disabled, NULL);
+
+    success = TRUE;
+
+out:
+    g_strfreev (split);
+    g_free     (props.name);
+    g_free     (props.apn);
+    g_free     (props.username);
+    g_free     (props.password);
+
+    return success;
+}
+
+static void
+create_profile_ready (QmiClientWds *client,
+                      GAsyncResult *res)
+{
+    QmiMessageWdsCreateProfileOutput *output;
+    GError *error = NULL;
+    QmiWdsProfileType profile_type;
+    guint8 profile_index;
+
+    output = qmi_client_wds_create_profile_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_wds_create_profile_output_get_result (output, &error)) {
+        QmiWdsDsProfileError ds_profile_error;
+
+        if (g_error_matches (error,
+                             QMI_PROTOCOL_ERROR,
+                             QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
+            qmi_message_wds_create_profile_output_get_extended_error_code (
+                output,
+                &ds_profile_error,
+                NULL)) {
+            g_printerr ("error: couldn't create profile: ds profile error: %s\n",
+                        qmi_wds_ds_profile_error_get_string (ds_profile_error));
+        } else {
+            g_printerr ("error: couldn't create profile: %s\n",
+                        error->message);
+        }
+        g_error_free (error);
+        qmi_message_wds_create_profile_output_unref (output);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    g_print ("New profile created:\n");
+    if (qmi_message_wds_create_profile_output_get_profile_identifier (output,
+                                                                      &profile_type,
+                                                                      &profile_index,
+                                                                      NULL)) {
+        g_print ("\tProfile type: '%s'\n", qmi_wds_profile_type_get_string(profile_type));
+        g_print ("\tProfile index: '%d'\n", profile_index);
+    }
+    qmi_message_wds_create_profile_output_unref (output);
+    operation_shutdown (TRUE);
+}
+
+static gboolean
+swi_create_profile_indexed_input_create (const gchar                                *str,
+                                         QmiMessageWdsSwiCreateProfileIndexedInput **input,
+                                         GError                                    **error)
+{
+    GError *parse_error = NULL;
+    CreateModifyProfileProperties props = {};
+    gchar **split;
+    gboolean success = FALSE;
+
+    g_assert (input && !*input);
+
+    /* Expect max 3 tokens: the first two give us the mandatory parameters of the command,
+     * the 3rd one will contain the key/value pair list */
+    split = g_strsplit (str, ",", 3);
+    if (g_strv_length (split) < 2) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "expected at least 2 arguments for 'wds swi create profile indexed' command");
+        goto out;
+    }
+
+    g_strstrip (split[0]);
+    if (g_str_equal (split[0], "3gpp"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP;
+    else if (g_str_equal (split[0], "3gpp2"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP2;
+    else {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "invalid profile type '%s'. Expected '3gpp' or '3gpp2'.'",
+                     split[0]);
+        goto out;
+    }
+
+    g_strstrip (split[1]);
+    props.profile_index = atoi (split[1]);
+    if (props.profile_index <= 0 || props.profile_index > G_MAXUINT8) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "invalid or out of range profile index [1,%u]: '%s'\n",
+                     G_MAXUINT8,
+                     split[1]);
+        goto out;
+    }
+
+    if (split[2]) {
+        if (!qmicli_parse_key_value_string (split[2],
+                                            &parse_error,
+                                            create_modify_profile_properties_handle,
+                                            &props)) {
+            g_set_error (error,
+                         QMI_CORE_ERROR,
+                         QMI_CORE_ERROR_FAILED,
+                         "couldn't parse input string: %s",
+                         parse_error->message);
+            g_error_free (parse_error);
+            goto out;
+        }
+    }
+
+    /* Create input bundle */
+    *input = qmi_message_wds_swi_create_profile_indexed_input_new ();
+
+    /* Profile identifier is required */
+    qmi_message_wds_swi_create_profile_indexed_input_set_profile_identifier (*input, props.profile_type, props.profile_index, NULL);
+
+    if (props.context_number)
+        qmi_message_wds_swi_create_profile_indexed_input_set_pdp_context_number (*input, props.context_number, NULL);
+
+    if (props.pdp_type_set)
+        qmi_message_wds_swi_create_profile_indexed_input_set_pdp_type (*input, props.pdp_type, NULL);
+
+    if (props.name)
+        qmi_message_wds_swi_create_profile_indexed_input_set_profile_name (*input, props.name, NULL);
+
+    if (props.apn)
+        qmi_message_wds_swi_create_profile_indexed_input_set_apn_name (*input, props.apn, NULL);
+
+    if (props.auth_set)
+        qmi_message_wds_swi_create_profile_indexed_input_set_authentication (*input, props.auth, NULL);
+
+    if (props.username)
+        qmi_message_wds_swi_create_profile_indexed_input_set_username (*input, props.username, NULL);
+
+    if (props.password)
+        qmi_message_wds_swi_create_profile_indexed_input_set_password (*input, props.password, NULL);
+
+    if (props.no_roaming_set)
+        qmi_message_wds_swi_create_profile_indexed_input_set_roaming_disallowed_flag (*input, props.no_roaming, NULL);
+
+    if (props.disabled_set)
+        qmi_message_wds_swi_create_profile_indexed_input_set_apn_disabled_flag (*input, props.disabled, NULL);
+
+    success = TRUE;
+
+out:
+    g_strfreev (split);
+    g_free     (props.name);
+    g_free     (props.apn);
+    g_free     (props.username);
+    g_free     (props.password);
+
+    return success;
+}
+
+static void
+swi_create_profile_indexed_ready (QmiClientWds *client,
+                                  GAsyncResult *res)
+{
+    QmiMessageWdsSwiCreateProfileIndexedOutput *output;
+    GError *error = NULL;
+    QmiWdsProfileType profile_type;
+    guint8 profile_index;
+
+    output = qmi_client_wds_swi_create_profile_indexed_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_wds_swi_create_profile_indexed_output_get_result (output, &error)) {
+        g_printerr ("error: couldn't create indexed profile: %s\n", error->message);
+        g_error_free (error);
+        qmi_message_wds_swi_create_profile_indexed_output_unref (output);
+        operation_shutdown (FALSE);
+        return;
+    }
+
+    g_print ("New profile created:\n");
+    if (qmi_message_wds_swi_create_profile_indexed_output_get_profile_identifier (output,
+                                                                                  &profile_type,
+                                                                                  &profile_index,
+                                                                                  NULL)) {
+        g_print ("\tProfile type: '%s'\n", qmi_wds_profile_type_get_string(profile_type));
+        g_print ("\tProfile index: '%d'\n", profile_index);
+    }
+    qmi_message_wds_swi_create_profile_indexed_output_unref (output);
+    operation_shutdown (TRUE);
+}
+
+static gboolean
+modify_profile_input_create (const gchar                      *str,
+                             QmiMessageWdsModifyProfileInput **input,
+                             GError                          **error)
+{
+    GError *parse_error = NULL;
+    CreateModifyProfileProperties props = {};
+    gchar **split;
+    gboolean success = FALSE;
+
+    g_assert (input && !*input);
+
+    /* Expect max 3 tokens: the first two give us the mandatory parameters of the command,
+     * the 3rd one will contain the key/value pair list */
+    split = g_strsplit (str, ",", 3);
+    if (g_strv_length (split) < 3) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "expected at least 3 arguments for 'wds modify profile' command");
+        goto out;
+    }
+
+    g_strstrip (split[0]);
+    if (g_str_equal (split[0], "3gpp"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP;
+    else if (g_str_equal (split[0], "3gpp2"))
+        props.profile_type = QMI_WDS_PROFILE_TYPE_3GPP2;
+    else {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "invalid profile type '%s'. Expected '3gpp' or '3gpp2'.'",
+                     split[0]);
+        goto out;
+    }
+
+    g_strstrip (split[1]);
+    props.profile_index = atoi (split[1]);
+    if (props.profile_index <= 0 || props.profile_index > G_MAXUINT8) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "invalid or out of range profile index [1,%u]: '%s'\n",
+                     G_MAXUINT8,
+                     split[1]);
+        goto out;
+    }
+
+    /* advance to third token, that's where key/value pairs start */
+    if (!qmicli_parse_key_value_string (split[2],
+                                        &parse_error,
+                                        create_modify_profile_properties_handle,
+                                        &props)) {
+        g_set_error (error,
+                     QMI_CORE_ERROR,
+                     QMI_CORE_ERROR_FAILED,
+                     "couldn't parse input string: %s",
+                    parse_error->message);
+        g_error_free (parse_error);
+        goto out;
+    }
+
+    /* Create input bundle */
+    *input = qmi_message_wds_modify_profile_input_new ();
+
+    /* Profile identifier is required */
+    qmi_message_wds_modify_profile_input_set_profile_identifier (*input, props.profile_type, props.profile_index, NULL);
+
+    if (props.context_number)
+        qmi_message_wds_modify_profile_input_set_pdp_context_number (*input, props.context_number, NULL);
+
+    if (props.pdp_type_set)
+        qmi_message_wds_modify_profile_input_set_pdp_type (*input, props.pdp_type, NULL);
+
+    if (props.name)
+        qmi_message_wds_modify_profile_input_set_profile_name (*input, props.name, NULL);
+
+    if (props.apn)
+        qmi_message_wds_modify_profile_input_set_apn_name (*input, props.apn, NULL);
+
+    if (props.auth_set)
+        qmi_message_wds_modify_profile_input_set_authentication (*input, props.auth, NULL);
+
+    if (props.username)
+        qmi_message_wds_modify_profile_input_set_username (*input, props.username, NULL);
+
+    if (props.password)
+        qmi_message_wds_modify_profile_input_set_password (*input, props.password, NULL);
+
+    if (props.no_roaming_set)
+        qmi_message_wds_modify_profile_input_set_roaming_disallowed_flag (*input, props.no_roaming, NULL);
+
+    if (props.disabled_set)
+        qmi_message_wds_modify_profile_input_set_apn_disabled_flag (*input, props.disabled, NULL);
+
+    success = TRUE;
+
+out:
+    g_strfreev (split);
+    g_free     (props.name);
+    g_free     (props.apn);
+    g_free     (props.username);
+    g_free     (props.password);
+
+    return success;
+}
+
+static void
+modify_profile_ready (QmiClientWds *client,
+                      GAsyncResult *res)
+{
+    QmiMessageWdsModifyProfileOutput *output;
+    GError *error = NULL;
+
+    output = qmi_client_wds_modify_profile_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_wds_modify_profile_output_get_result (output, &error)) {
+        QmiWdsDsProfileError ds_profile_error;
+
+        if (g_error_matches (error,
+                             QMI_PROTOCOL_ERROR,
+                             QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
+            qmi_message_wds_modify_profile_output_get_extended_error_code (
+                output,
+                &ds_profile_error,
+                NULL)) {
+            g_printerr ("error: couldn't modify profile: ds profile error: %s\n",
+                        qmi_wds_ds_profile_error_get_string (ds_profile_error));
+        } else {
+            g_printerr ("error: couldn't modify profile: %s\n",
+                        error->message);
+        }
+        g_error_free (error);
+        qmi_message_wds_modify_profile_output_unref (output);
+        operation_shutdown (FALSE);
+        return;
+    }
+    qmi_message_wds_modify_profile_output_unref (output);
+    g_print ("Profile successfully modified.\n");
+    operation_shutdown (TRUE);
+}
+
+static void
+delete_profile_ready (QmiClientWds *client,
+                      GAsyncResult *res)
+{
+    QmiMessageWdsDeleteProfileOutput *output;
+    GError *error = NULL;
+
+    output = qmi_client_wds_delete_profile_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_wds_delete_profile_output_get_result (output, &error)) {
+        QmiWdsDsProfileError ds_profile_error;
+
+        if (g_error_matches (error,
+                             QMI_PROTOCOL_ERROR,
+                             QMI_PROTOCOL_ERROR_EXTENDED_INTERNAL) &&
+            qmi_message_wds_delete_profile_output_get_extended_error_code (
+                output,
+                &ds_profile_error,
+                NULL)) {
+            g_printerr ("error: couldn't delete profile: ds profile error: %s\n",
+                        qmi_wds_ds_profile_error_get_string (ds_profile_error));
+        } else {
+            g_printerr ("error: couldn't delete profile: %s\n",
+                        error->message);
+        }
+        g_error_free (error);
+        qmi_message_wds_delete_profile_output_unref (output);
+        operation_shutdown (FALSE);
+        return;
+    }
+    qmi_message_wds_delete_profile_output_unref (output);
+    g_print ("Profile successfully deleted.\n");
+    operation_shutdown (TRUE);
+}
+
 typedef struct {
     guint i;
     GArray *profile_list;
@@ -1219,13 +1857,17 @@
         qmi_message_wds_get_profile_settings_output_unref (output);
     } else {
         const gchar *str;
+        guint8 context_number;
         QmiWdsPdpType pdp_type;
         QmiWdsAuthentication auth;
+        gboolean flag;
 
         if (qmi_message_wds_get_profile_settings_output_get_apn_name (output, &str, NULL))
             g_print ("\t\tAPN: '%s'\n", str);
         if (qmi_message_wds_get_profile_settings_output_get_pdp_type (output, &pdp_type, NULL))
             g_print ("\t\tPDP type: '%s'\n", qmi_wds_pdp_type_get_string (pdp_type));
+        if (qmi_message_wds_get_profile_settings_output_get_pdp_context_number (output, &context_number, NULL))
+            g_print ("\t\tPDP context number: '%d'\n", context_number);
         if (qmi_message_wds_get_profile_settings_output_get_username (output, &str, NULL))
             g_print ("\t\tUsername: '%s'\n", str);
         if (qmi_message_wds_get_profile_settings_output_get_password (output, &str, NULL))
@@ -1237,6 +1879,10 @@
             g_print ("\t\tAuth: '%s'\n", aux);
             g_free (aux);
         }
+        if (qmi_message_wds_get_profile_settings_output_get_roaming_disallowed_flag (output, &flag, NULL))
+            g_print ("\t\tNo roaming: '%s'\n", flag ? "yes" : "no");
+        if (qmi_message_wds_get_profile_settings_output_get_apn_disabled_flag (output, &flag, NULL))
+            g_print ("\t\tAPN disabled: '%s'\n", flag ? "yes" : "no");
         qmi_message_wds_get_profile_settings_output_unref (output);
     }
 
@@ -2184,6 +2830,128 @@
         return;
     }
 
+    /* Create a new profile using first available profile index */
+    if (create_profile_str) {
+        QmiMessageWdsCreateProfileInput *input = NULL;
+        GError *error = NULL;
+
+        if (!create_profile_input_create (create_profile_str, &input, &error)) {
+            g_printerr ("error: %s\n", error->message);
+            g_error_free (error);
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        g_debug ("Asynchronously creating new profile...");
+        qmi_client_wds_create_profile (ctx->client,
+                                       input,
+                                       10,
+                                       ctx->cancellable,
+                                       (GAsyncReadyCallback)create_profile_ready,
+                                       NULL);
+       qmi_message_wds_create_profile_input_unref (input);
+       return;
+    }
+
+    /* Create a new profile at spedified profile index (Sierra Wireless only)*/
+    if (swi_create_profile_indexed_str) {
+        QmiMessageWdsSwiCreateProfileIndexedInput *input = NULL;
+        GError *error = NULL;
+
+        if (!swi_create_profile_indexed_input_create (swi_create_profile_indexed_str, &input, &error)) {
+            g_printerr ("error: %s\n", error->message);
+            g_error_free (error);
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        g_debug ("Asynchronously creating new indexed profile...");
+        qmi_client_wds_swi_create_profile_indexed (ctx->client,
+                                                   input,
+                                                   10,
+                                                   ctx->cancellable,
+                                                   (GAsyncReadyCallback)swi_create_profile_indexed_ready,
+                                                   NULL);
+       qmi_message_wds_swi_create_profile_indexed_input_unref (input);
+       return;
+    }
+
+    /* Modify an existing profile */
+    if (modify_profile_str) {
+        QmiMessageWdsModifyProfileInput *input = NULL;
+        GError *error = NULL;
+
+        if (!modify_profile_input_create (modify_profile_str, &input, &error)) {
+            g_printerr ("error: %s\n", error->message);
+            g_error_free (error);
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        g_debug ("Asynchronously modifying profile...");
+        qmi_client_wds_modify_profile (ctx->client,
+                                       input,
+                                       10,
+                                       ctx->cancellable,
+                                       (GAsyncReadyCallback)modify_profile_ready,
+                                       NULL);
+        qmi_message_wds_modify_profile_input_unref (input);
+        return;
+    }
+
+    /* Delete an existing profile */
+    if (delete_profile_str) {
+        QmiMessageWdsDeleteProfileInput *input;
+        gchar **split;
+        QmiWdsProfileType profile_type;
+        guint profile_index;
+
+        split = g_strsplit (delete_profile_str, ",", -1);
+        input = qmi_message_wds_delete_profile_input_new ();
+
+        if (g_strv_length (split) != 2) {
+            g_printerr ("error: expected 2 arguments for delete profile command\n");
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        g_strstrip (split[0]);
+        if (g_str_equal (split[0], "3gpp"))
+            profile_type = QMI_WDS_PROFILE_TYPE_3GPP;
+        else if (g_str_equal (split[0], "3gpp2"))
+            profile_type = QMI_WDS_PROFILE_TYPE_3GPP2;
+        else {
+            g_printerr ("error: invalid profile type '%s'. Expected '3gpp' or '3gpp2'.'\n",
+                        split[0]);
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        g_strstrip (split[1]);
+        profile_index = atoi (split[1]);
+        if (profile_index <= 0 || profile_index > G_MAXUINT8) {
+            g_printerr ("error: invalid or out of range profile number [1,%u]: '%s'\n",
+                        G_MAXUINT8,
+                        split[1]);
+            operation_shutdown (FALSE);
+            return;
+        }
+
+        qmi_message_wds_delete_profile_input_set_profile_identifier (input, profile_type, (guint8)profile_index, NULL);
+
+        g_strfreev (split);
+
+        g_debug ("Asynchronously deleting new profile...");
+        qmi_client_wds_delete_profile (ctx->client,
+                                       input,
+                                       10,
+                                       ctx->cancellable,
+                                       (GAsyncReadyCallback)delete_profile_ready,
+                                       NULL);
+       qmi_message_wds_delete_profile_input_unref (input);
+       return;
+    }
+
     /* Request to list profiles? */
     if (get_profile_list_str) {
         QmiMessageWdsGetProfileListInput *input;