From 4e58451a744dbc3f086e9c1dd2d7469ff70d50ef Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 17 Aug 2015 12:19:05 -0500 Subject: mbimcli: add support for Basic Connect session IDs --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..8fdff82 100644 --- a/src/mbimcli/mbimcli-basic-connect.c +++ b/src/mbimcli/mbimcli-basic-connect.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -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) { @@ -790,67 +791,218 @@ connect_ready (MbimDevice *device, shutdown (TRUE); } +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) > 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"); - g_strfreev (split); - return FALSE; - } + if (g_strv_length (split) < 1) { + g_printerr ("error: couldn't parse input string, missing arguments\n"); + goto error; + } - /* 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; + /* APN */ + props.apn = g_strdup (split[0]); - /* Username */ - if (split[2]) { - *username = g_strdup (split[2]); + /* 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 */ - *password = g_strdup (split[3]); + /* Password */ + props.password = g_strdup (split[3]); + } } } - g_strfreev (split); + 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; + } + } + + *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; + + 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 (0, + 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__ */ -- cgit v1.2.1