[PATCH] mbimcli: add support for Basic Connect session IDs
Dan Williams
dcbw at redhat.com
Tue Aug 18 08:43:06 PDT 2015
On Mon, 2015-08-17 at 12:19 -0500, Dan Williams wrote:
> --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.
Aleksander - on IRC you said it was "good", does that mean OK to push to
git master and backport to 1.12? :)
Dan
> ---
> 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__ */
More information about the libmbim-devel
mailing list