[PATCH] mbimcli: add support for Basic Connect session IDs

Dan Williams dcbw at redhat.com
Mon Aug 17 10:19:05 PDT 2015


--query-connection-state=[SessionID]
--disconnect=[SessionID]
--connect=["key=value,..."]

As part of enabling session IDs, we must also convert --connect
over to a key=value format for all its arguments, but still
preserve backwards compat with the old format.
---
 src/mbimcli/mbimcli-basic-connect.c | 284 ++++++++++++++++++++++++++++--------
 src/mbimcli/mbimcli-helpers.c       | 148 +++++++++++++++++++
 src/mbimcli/mbimcli-helpers.h       |  10 ++
 3 files changed, 385 insertions(+), 57 deletions(-)

diff --git a/src/mbimcli/mbimcli-basic-connect.c b/src/mbimcli/mbimcli-basic-connect.c
index 5cbd3ec..4c0b018 100644
--- a/src/mbimcli/mbimcli-basic-connect.c
+++ b/src/mbimcli/mbimcli-basic-connect.c
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <locale.h>
 #include <string.h>
+#include <errno.h>
 
 #include <glib.h>
 #include <gio/gio.h>
@@ -61,9 +62,9 @@ static gboolean  query_signal_state_flag;
 static gboolean  query_packet_service_flag;
 static gboolean  set_packet_service_attach_flag;
 static gboolean  set_packet_service_detach_flag;
-static gboolean  query_connect_flag;
+static gchar    *query_connect_str;
 static gchar    *set_connect_activate_str;
-static gboolean  set_connect_deactivate_flag;
+static gchar    *set_connect_deactivate_str;
 static gboolean  query_packet_statistics_flag;
 
 static GOptionEntry entries[] = {
@@ -147,17 +148,17 @@ static GOptionEntry entries[] = {
       "Detach from the packet service",
       NULL
     },
-    { "query-connection-state", 0, 0, G_OPTION_ARG_NONE, &query_connect_flag,
-      "Query connection state",
-      NULL
+    { "query-connection-state", 0, 0, G_OPTION_ARG_NONE, &query_connect_str,
+      "Query connection state (SessionID is optional, defaults to 0)",
+      "[SessionID]"
     },
     { "connect", 0, 0, G_OPTION_ARG_STRING, &set_connect_activate_str,
-      "Connect (Authentication, Username and Password are optional)",
-      "[(APN),(PAP|CHAP|MSCHAPV2),(Username),(Password)]"
+      "Connect (allowed keys: session-id, apn, auth (PAP|CHAP|MSCHAPV2), username, password)",
+      "[\"key=value,...\"]"
     },
-    { "disconnect", 0, 0, G_OPTION_ARG_NONE, &set_connect_deactivate_flag,
-      "Disconnect",
-      NULL
+    { "disconnect", 0, 0, G_OPTION_ARG_STRING, &set_connect_deactivate_str,
+      "Disconnect (SessionID is optional, defaults to 0)",
+      "[SessionID]"
     },
     { "query-packet-statistics", 0, 0, G_OPTION_ARG_NONE, &query_packet_statistics_flag,
       "Query packet statistics",
@@ -210,9 +211,9 @@ mbimcli_basic_connect_options_enabled (void)
                  query_packet_service_flag +
                  set_packet_service_attach_flag +
                  set_packet_service_detach_flag +
-                 query_connect_flag +
+                 !!query_connect_str +
                  !!set_connect_activate_str +
-                 set_connect_deactivate_flag +
+                 !!set_connect_deactivate_str +
                  query_packet_statistics_flag);
 
     if (n_actions > 1) {
@@ -791,66 +792,217 @@ connect_ready (MbimDevice   *device,
 }
 
 static gboolean
+mbim_auth_protocol_from_string (const gchar      *str,
+                                MbimAuthProtocol *auth_protocol)
+{
+    if (g_ascii_strcasecmp (str, "PAP") == 0) {
+        *auth_protocol = MBIM_AUTH_PROTOCOL_PAP;
+        return TRUE;
+    } else if (g_ascii_strcasecmp (str, "CHAP") == 0) {
+        *auth_protocol = MBIM_AUTH_PROTOCOL_CHAP;
+        return TRUE;
+    } else if (g_ascii_strcasecmp (str, "MSCHAPV2") == 0) {
+        *auth_protocol = MBIM_AUTH_PROTOCOL_MSCHAPV2;
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static gboolean
+connect_session_id_parse (const gchar  *str,
+                          gboolean      allow_empty,
+                          guint        *session_id,
+                          GError      **error)
+{
+    gchar *endptr = NULL;
+    gint64 n;
+
+    g_assert (str != NULL);
+    g_assert (session_id != NULL);
+
+    if (!str[0]) {
+        if (allow_empty) {
+            *session_id = 0;
+            return TRUE;
+        }
+        g_set_error_literal (error,
+                             MBIM_CORE_ERROR,
+                             MBIM_CORE_ERROR_FAILED,
+                             "missing session ID (must be 0 - 255)");
+        return FALSE;
+    }
+
+    errno = 0;
+    n = g_ascii_strtoll (str, &endptr, 10);
+    if (errno || n < 0 || n > 255 || ((endptr - str) < strlen (str))) {
+        g_set_error (error,
+                     MBIM_CORE_ERROR,
+                     MBIM_CORE_ERROR_FAILED,
+                     "couldn't parse session ID '%s' (must be 0 - 255)",
+                     str);
+        return FALSE;
+    }
+    *session_id = (guint) n;
+
+    return TRUE;
+}
+
+typedef struct {
+    guint             session_id;
+    gchar            *apn;
+    MbimAuthProtocol  auth_protocol;
+    gchar            *username;
+    gchar            *password;
+} ConnectActivateProperties;
+
+static gboolean connect_activate_properties_handle (const gchar  *key,
+                                                    const gchar  *value,
+                                                    GError      **error,
+                                                    gpointer      user_data)
+{
+    ConnectActivateProperties *props = user_data;
+
+    if (!value || !value[0]) {
+        g_set_error (error,
+                     MBIM_CORE_ERROR,
+                     MBIM_CORE_ERROR_FAILED,
+                     "key '%s' required a value",
+                     key);
+        return FALSE;
+    }
+
+    if (g_ascii_strcasecmp (key, "session-id") == 0) {
+        if (!connect_session_id_parse (value, FALSE, &props->session_id, error))
+            return FALSE;
+    } else if (g_ascii_strcasecmp (key, "apn") == 0 && !props->apn) {
+        props->apn = g_strdup (value);
+    } else if (g_ascii_strcasecmp (key, "auth") == 0) {
+        if (!mbim_auth_protocol_from_string (value, &props->auth_protocol)) {
+            g_set_error (error,
+                         MBIM_CORE_ERROR,
+                         MBIM_CORE_ERROR_FAILED,
+                         "unknown auth protocol '%s'",
+                         value);
+            return FALSE;
+        }
+    } else if (g_ascii_strcasecmp (key, "username") == 0 && !props->username) {
+        props->username = g_strdup (value);
+    } else if (g_ascii_strcasecmp (key, "password") == 0 && !props->password) {
+        props->password = g_strdup (value);
+    } else {
+            g_set_error (error,
+                         MBIM_CORE_ERROR,
+                         MBIM_CORE_ERROR_FAILED,
+                         "unrecognized or duplicate option '%s'",
+                         key);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
 set_connect_activate_parse (const gchar       *str,
+                            guint             *session_id,
                             gchar            **apn,
                             MbimAuthProtocol  *auth_protocol,
                             gchar            **username,
                             gchar            **password)
 {
-    gchar **split;
+    ConnectActivateProperties props = {
+        .session_id    = 0,
+        .apn           = NULL,
+        .auth_protocol = MBIM_AUTH_PROTOCOL_NONE,
+        .username      = NULL,
+        .password      = NULL
+    };
+    gchar **split = NULL;
 
+    g_assert (session_id != NULL);
     g_assert (apn != NULL);
     g_assert (auth_protocol != NULL);
     g_assert (username != NULL);
     g_assert (password != NULL);
 
-    /* Format of the string is:
-     *    "[(APN),(PAP|CHAP|MSCHAPV2),(Username),(Password)]"
-     */
-    split = g_strsplit (str, ",", -1);
+    if (strchr (str, '=')) {
+        GError *error = NULL;
 
-    if (g_strv_length (split) > 4) {
-        g_printerr ("error: couldn't parse input string, too many arguments\n");
-        g_strfreev (split);
-        return FALSE;
-    }
+        /* New key=value format */
+        if (!mbimcli_parse_key_value_string (str,
+                                             &error,
+                                             connect_activate_properties_handle,
+                                             &props)) {
+            g_printerr ("error: couldn't parse input string: %s\n", error->message);
+            g_error_free (error);
+            goto error;
+        }
+    } else {
+        /* Old non key=value format, like this:
+         *    "[(APN),(PAP|CHAP|MSCHAPV2),(Username),(Password)]"
+         */
+        split = g_strsplit (str, ",", -1);
 
-    if (g_strv_length (split) < 1) {
-        g_printerr ("error: couldn't parse input string, missing arguments\n");
-        g_strfreev (split);
-        return FALSE;
+        if (g_strv_length (split) > 4) {
+            g_printerr ("error: couldn't parse input string, too many arguments\n");
+            goto error;
+        }
+
+        if (g_strv_length (split) < 1) {
+            g_printerr ("error: couldn't parse input string, missing arguments\n");
+            goto error;
+        }
+
+        /* APN */
+        props.apn = g_strdup (split[0]);
+
+        /* Use authentication method */
+        if (split[1]) {
+            if (!mbim_auth_protocol_from_string (split[1], &props.auth_protocol)) {
+                g_printerr ("error: couldn't parse input string, unknown auth protocol '%s'\n", split[1]);
+                goto error;
+            }
+
+            /* Username */
+            if (split[2]) {
+                props.username = g_strdup (split[2]);
+
+                /* Password */
+                props.password = g_strdup (split[3]);
+            }
+        }
     }
 
-    /* APN */
-    *apn = g_strdup (split[0]);
-
-    /* Some defaults */
-    *auth_protocol = MBIM_AUTH_PROTOCOL_NONE;
-    *username = NULL;
-    *password = NULL;
-
-    /* Use authentication method */
-    if (split[1]) {
-        if (g_ascii_strcasecmp (split[1], "PAP") == 0)
-            *auth_protocol = MBIM_AUTH_PROTOCOL_PAP;
-        else if (g_ascii_strcasecmp (split[1], "CHAP") == 0)
-            *auth_protocol = MBIM_AUTH_PROTOCOL_CHAP;
-        else if (g_ascii_strcasecmp (split[1], "MSCHAPV2") == 0)
-            *auth_protocol = MBIM_AUTH_PROTOCOL_MSCHAPV2;
-        else
-            *auth_protocol = MBIM_AUTH_PROTOCOL_NONE;
-
-        /* Username */
-        if (split[2]) {
-            *username = g_strdup (split[2]);
-
-            /* Password */
-            *password = g_strdup (split[3]);
+    if (props.auth_protocol == MBIM_AUTH_PROTOCOL_NONE) {
+        if (username || password) {
+            g_printerr ("error: username or password requires an auth protocol\n");
+            goto error;
+        }
+    } else {
+        if (!username) {
+            g_printerr ("error: auth protocol requires a username\n");
+            goto error;
         }
     }
 
-    g_strfreev (split);
+    *session_id = props.session_id;
+    *apn = props.apn;
+    *auth_protocol = props.auth_protocol;
+    *username = props.username;
+    *password = props.password;
+
+    if (split)
+        g_strfreev (split);
+
     return TRUE;
+
+error:
+    if (split)
+        g_strfreev (split);
+    g_free (props.apn);
+    g_free (props.username);
+    g_free (props.password);
+    return FALSE;
 }
 
 static void
@@ -1679,11 +1831,19 @@ mbimcli_basic_connect_run (MbimDevice   *device,
     }
 
     /* Query connection status? */
-    if (query_connect_flag) {
+    if (query_connect_str) {
         MbimMessage *request;
         GError *error = NULL;
+        guint session_id = 0;
 
-        request = mbim_message_connect_query_new (0,
+        if (!connect_session_id_parse (query_connect_str, TRUE, &session_id, &error)) {
+            g_printerr ("error: couldn't parse session ID: %s\n", error->message);
+            g_error_free (error);
+            shutdown (FALSE);
+            return;
+        }
+
+        request = mbim_message_connect_query_new (session_id,
                                                   MBIM_ACTIVATION_STATE_UNKNOWN,
                                                   MBIM_VOICE_CALL_STATE_NONE,
                                                   MBIM_CONTEXT_IP_TYPE_DEFAULT,
@@ -1711,12 +1871,14 @@ mbimcli_basic_connect_run (MbimDevice   *device,
     if (set_connect_activate_str) {
         MbimMessage *request;
         GError *error = NULL;
+        guint session_id = 0;
         gchar *apn;
         MbimAuthProtocol auth_protocol;
         gchar *username = NULL;
         gchar *password = NULL;
 
         if (!set_connect_activate_parse (set_connect_activate_str,
+                                         &session_id,
                                          &apn,
                                          &auth_protocol,
                                          &username,
@@ -1725,7 +1887,7 @@ mbimcli_basic_connect_run (MbimDevice   *device,
             return;
         }
 
-        request = mbim_message_connect_set_new (0,
+        request = mbim_message_connect_set_new (session_id,
                                                 MBIM_ACTIVATION_COMMAND_ACTIVATE,
                                                 apn,
                                                 username,
@@ -1757,11 +1919,19 @@ mbimcli_basic_connect_run (MbimDevice   *device,
     }
 
     /* Disconnect? */
-    if (set_connect_deactivate_flag) {
+    if (set_connect_deactivate_str) {
         MbimMessage *request;
         GError *error = NULL;
+        guint session_id = 0;
 
-        request = mbim_message_connect_set_new (0,
+        if (!connect_session_id_parse (set_connect_deactivate_str, TRUE, &session_id, &error)) {
+            g_printerr ("error: couldn't parse session ID: %s\n", error->message);
+            g_error_free (error);
+            shutdown (FALSE);
+            return;
+        }
+
+        request = mbim_message_connect_set_new (session_id,
                                                 MBIM_ACTIVATION_COMMAND_DEACTIVATE,
                                                 NULL,
                                                 NULL,
diff --git a/src/mbimcli/mbimcli-helpers.c b/src/mbimcli/mbimcli-helpers.c
index 21aea9b..d4485b0 100644
--- a/src/mbimcli/mbimcli-helpers.c
+++ b/src/mbimcli/mbimcli-helpers.c
@@ -189,3 +189,151 @@ mbimcli_print_ip_config (MbimDevice *device,
     return TRUE;
 }
 
+/* Expecting input as:
+ *   key1=string,key2=true,key3=false...
+ * Strings may also be passed enclosed between double or single quotes, like:
+ *   key1="this is a string", key2='and so is this'
+ */
+gboolean
+mbimcli_parse_key_value_string (const gchar *str,
+                                GError **error,
+                                MbimParseKeyValueForeachFn callback,
+                                gpointer user_data)
+{
+    GError *inner_error = NULL;
+    gchar *dup, *p, *key, *key_end, *value, *value_end, quote;
+
+    g_return_val_if_fail (callback != NULL, FALSE);
+    g_return_val_if_fail (str != NULL, FALSE);
+
+    /* Allow empty strings, we'll just return with success */
+    while (g_ascii_isspace (*str))
+        str++;
+    if (!str[0])
+        return TRUE;
+
+    dup = g_strdup (str);
+    p = dup;
+
+    while (TRUE) {
+        gboolean keep_iteration = FALSE;
+
+        /* Skip leading spaces */
+        while (g_ascii_isspace (*p))
+            p++;
+
+        /* Key start */
+        key = p;
+        if (!g_ascii_isalnum (*key)) {
+            inner_error = g_error_new (MBIM_CORE_ERROR,
+                                       MBIM_CORE_ERROR_FAILED,
+                                       "Key must start with alpha/num, starts with '%c'",
+                                       *key);
+            break;
+        }
+
+        /* Key end */
+        while (g_ascii_isalnum (*p) || (*p == '-') || (*p == '_'))
+            p++;
+        key_end = p;
+        if (key_end == key) {
+            inner_error = g_error_new (MBIM_CORE_ERROR,
+                                       MBIM_CORE_ERROR_FAILED,
+                                       "Couldn't find a proper key");
+            break;
+        }
+
+        /* Skip whitespaces, if any */
+        while (g_ascii_isspace (*p))
+            p++;
+
+        /* Equal sign must be here */
+        if (*p != '=') {
+            inner_error = g_error_new (MBIM_CORE_ERROR,
+                                       MBIM_CORE_ERROR_FAILED,
+                                       "Couldn't find equal sign separator");
+            break;
+        }
+        /* Skip the equal */
+        p++;
+
+        /* Skip whitespaces, if any */
+        while (g_ascii_isspace (*p))
+            p++;
+
+        /* Do we have a quote-enclosed string? */
+        if (*p == '\"' || *p == '\'') {
+            quote = *p;
+            /* Skip the quote */
+            p++;
+            /* Value start */
+            value = p;
+            /* Find the closing quote */
+            p = strchr (p, quote);
+            if (!p) {
+                inner_error = g_error_new (MBIM_CORE_ERROR,
+                                           MBIM_CORE_ERROR_FAILED,
+                                           "Unmatched quotes in string value");
+                break;
+            }
+
+            /* Value end */
+            value_end = p;
+            /* Skip the quote */
+            p++;
+        } else {
+            /* Value start */
+            value = p;
+
+            /* Value end */
+            while ((*p != ',') && (*p != '\0') && !g_ascii_isspace (*p))
+                p++;
+            value_end = p;
+        }
+
+        /* Note that we allow value == value_end here */
+
+        /* Skip whitespaces, if any */
+        while (g_ascii_isspace (*p))
+            p++;
+
+        /* If a comma is found, we should keep the iteration */
+        if (*p == ',') {
+            /* skip the comma */
+            p++;
+            keep_iteration = TRUE;
+        }
+
+        /* Got key and value, prepare them and run the callback */
+        *value_end = '\0';
+        *key_end = '\0';
+        if (!callback (key, value, &inner_error, user_data)) {
+            /* We were told to abort */
+            break;
+        }
+        g_assert (!inner_error);
+
+        if (keep_iteration)
+            continue;
+
+        /* Check if no more key/value pairs expected */
+        if (*p == '\0')
+            break;
+
+        inner_error = g_error_new (MBIM_CORE_ERROR,
+                                   MBIM_CORE_ERROR_FAILED,
+                                   "Unexpected content (%s) after value",
+                                   p);
+        break;
+    }
+
+    g_free (dup);
+
+    if (inner_error) {
+        g_propagate_error (error, inner_error);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
diff --git a/src/mbimcli/mbimcli-helpers.h b/src/mbimcli/mbimcli-helpers.h
index 969f416..61d1483 100644
--- a/src/mbimcli/mbimcli-helpers.h
+++ b/src/mbimcli/mbimcli-helpers.h
@@ -32,4 +32,14 @@ gboolean mbimcli_print_ip_config (MbimDevice *device,
                                   MbimMessage *response,
                                   GError **error);
 
+typedef gboolean (*MbimParseKeyValueForeachFn) (const gchar *key,
+                                                const gchar *value,
+                                                GError **error,
+                                                gpointer user_data);
+
+gboolean mbimcli_parse_key_value_string (const gchar *str,
+                                         GError **error,
+                                         MbimParseKeyValueForeachFn callback,
+                                         gpointer user_data);
+
 #endif /* __MBIMCLI_H__ */
-- 
2.1.0




More information about the libmbim-devel mailing list