From fe0d1020bfc3f7b931b250eaf89b02e200d2c8f3 Mon Sep 17 00:00:00 2001 From: Aleksander Morgado Date: Sat, 10 Oct 2015 13:51:53 +0200 Subject: qmicli,wds: setup new key-value format based Start Network command This would be equivalent to the new format used in mbimcli's --connect operation, and will allow us to add new parameters easily. Note that The old legacy format will still be supported, for backwards compatibility. --- src/qmicli/qmicli-helpers.c | 168 ++++++++++++++++++++++++++++++++++++++++++++ src/qmicli/qmicli-helpers.h | 12 ++++ src/qmicli/qmicli-wds.c | 161 ++++++++++++++++++++++++++++++++---------- 3 files changed, 305 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/qmicli/qmicli-helpers.c b/src/qmicli/qmicli-helpers.c index d5e9eee..3b62358 100644 --- a/src/qmicli/qmicli-helpers.c +++ b/src/qmicli/qmicli-helpers.c @@ -410,6 +410,24 @@ qmicli_read_autoconnect_setting_roaming_from_string (const gchar *str, return !!enum_value; } +gboolean +qmicli_read_authentication_from_string (const gchar *str, + QmiWdsAuthentication *out) +{ + if (g_ascii_strcasecmp (str, "PAP") == 0) + *out = QMI_WDS_AUTHENTICATION_PAP; + else if (g_ascii_strcasecmp (str, "CHAP") == 0) + *out = QMI_WDS_AUTHENTICATION_CHAP; + else if (g_ascii_strcasecmp (str, "BOTH") == 0) + *out = (QMI_WDS_AUTHENTICATION_PAP | QMI_WDS_AUTHENTICATION_CHAP); + else if (!str[0] || g_ascii_strcasecmp (str, "NONE") == 0) + *out = QMI_WDS_AUTHENTICATION_NONE; + else + return FALSE; + + return TRUE; +} + gboolean qmicli_read_uint_from_string (const gchar *str, guint *out) @@ -523,3 +541,153 @@ qmicli_earfcn_to_eutra_band_string (guint16 earfcn) } return "unknown"; } + +/* 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' + * + * Based on libmbim's mbimcli_parse_key_value_string(). + */ +gboolean +qmicli_parse_key_value_string (const gchar *str, + GError **error, + QmiParseKeyValueForeachFn callback, + gpointer user_data) +{ + GError *inner_error = NULL; + gchar *dupstr, *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; + + dupstr = g_strdup (str); + p = dupstr; + + 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 (QMI_CORE_ERROR, + QMI_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 (QMI_CORE_ERROR, + QMI_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 (QMI_CORE_ERROR, + QMI_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 (QMI_CORE_ERROR, + QMI_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 (QMI_CORE_ERROR, + QMI_CORE_ERROR_FAILED, + "Unexpected content (%s) after value", + p); + break; + } + + g_free (dupstr); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + return TRUE; +} diff --git a/src/qmicli/qmicli-helpers.h b/src/qmicli/qmicli-helpers.h index 0e6d567..6b19827 100644 --- a/src/qmicli/qmicli-helpers.h +++ b/src/qmicli/qmicli-helpers.h @@ -53,6 +53,8 @@ gboolean qmicli_read_autoconnect_setting_from_string (const gchar *str, QmiWdsAutoconnectSetting *out); gboolean qmicli_read_autoconnect_setting_roaming_from_string (const gchar *str, QmiWdsAutoconnectSettingRoaming *out); +gboolean qmicli_read_authentication_from_string (const gchar *str, + QmiWdsAuthentication *out); gboolean qmicli_read_non_empty_string (const gchar *str, const gchar *description, @@ -65,4 +67,14 @@ gchar *qmicli_get_supported_messages_list (const guint8 *data, const char *qmicli_earfcn_to_eutra_band_string (guint16 earfcn); +typedef gboolean (*QmiParseKeyValueForeachFn) (const gchar *key, + const gchar *value, + GError **error, + gpointer user_data); + +gboolean qmicli_parse_key_value_string (const gchar *str, + GError **error, + QmiParseKeyValueForeachFn callback, + gpointer user_data); + #endif /* __QMICLI_H__ */ diff --git a/src/qmicli/qmicli-wds.c b/src/qmicli/qmicli-wds.c index bba1c9b..77b01cb 100644 --- a/src/qmicli/qmicli-wds.c +++ b/src/qmicli/qmicli-wds.c @@ -67,8 +67,8 @@ static gboolean noop_flag; static GOptionEntry entries[] = { { "wds-start-network", 0, 0, G_OPTION_ARG_STRING, &start_network_str, - "Start network (Authentication, Username and Password are optional)", - "[(APN),(PAP|CHAP|BOTH),(Username),(Password)]" + "Start network (allowed keys: apn, auth (PAP|CHAP|BOTH), username, password)", + "[\"key=value,...\"]" }, { "wds-follow-network", 0, 0, G_OPTION_ARG_NONE, &follow_network_flag, "Follow the network status until disconnected. Use with `--wds-start-network'", @@ -327,63 +327,152 @@ packet_status_timeout (void) return TRUE; } +typedef struct { + gchar *apn; + QmiWdsAuthentication auth; + gboolean auth_set; + gchar *username; + gchar *password; +} StartNetworkProperties; + +static gboolean +start_network_properties_handle (const gchar *key, + const gchar *value, + GError **error, + gpointer user_data) +{ + StartNetworkProperties *props = user_data; + + 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, "apn") == 0 && !props->apn) { + props->apn = g_strdup (value); + 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, "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; + } + + g_set_error (error, + QMI_CORE_ERROR, + QMI_CORE_ERROR_FAILED, + "unrecognized or duplicate option '%s'", + key); + return FALSE; +} + static QmiMessageWdsStartNetworkInput * start_network_input_create (const gchar *str) { - QmiMessageWdsStartNetworkInput *input = NULL; - const gchar *apn = NULL; - const gchar *auth = NULL; - const gchar *username = NULL; - const gchar *password = NULL; + gchar *aux_auth_str = NULL; gchar **split = NULL; + QmiMessageWdsStartNetworkInput *input = NULL; + StartNetworkProperties props = { + .apn = NULL, + .auth = QMI_WDS_AUTHENTICATION_NONE, + .auth_set = FALSE, + .username = NULL, + .password = NULL, + }; /* An empty string is totally valid (i.e. no TLVs) */ if (!str[0]) return NULL; + /* New key=value format */ + if (strchr (str, '=')) { + GError *error = NULL; + + if (!qmicli_parse_key_value_string (str, + &error, + start_network_properties_handle, + &props)) { + g_printerr ("error: couldn't parse input string: %s\n", error->message); + g_error_free (error); + goto out; + } + } + /* Old non key=value format, like this: + * "[(APN),(PAP|CHAP|BOTH),(Username),(Password)]" + */ + else { + /* Parse input string into the expected fields */ + split = g_strsplit (str, ",", 0); + + props.apn = g_strdup (split[0]); + + if (props.apn && split[1]) { + if (!qmicli_read_authentication_from_string (split[1], &(props.auth))) { + g_printerr ("error: unknown auth protocol '%s'\n", split[1]); + goto out; + } + props.auth_set = TRUE; + } + + props.username = (props.auth_set ? g_strdup (split[2]) : NULL); + props.password = (props.username ? g_strdup (split[3]) : NULL); + } + /* Create input bundle */ input = qmi_message_wds_start_network_input_new (); - /* Parse input string into the expected fields */ - split = g_strsplit (str, ",", 0); - apn = split[0]; - auth = (apn ? split[1] : NULL); - username = (auth ? split[2] : NULL); - password = (username ? split[3] : NULL); - /* Set APN */ - qmi_message_wds_start_network_input_set_apn (input, apn, NULL); + if (props.apn) + qmi_message_wds_start_network_input_set_apn (input, props.apn, NULL); /* Set authentication method */ - if (auth) { - QmiWdsAuthentication qmiwdsauth; - - if (g_ascii_strcasecmp (auth, "PAP") == 0) - qmiwdsauth = QMI_WDS_AUTHENTICATION_PAP; - else if (g_ascii_strcasecmp (auth, "CHAP") == 0) - qmiwdsauth = QMI_WDS_AUTHENTICATION_CHAP; - else if (g_ascii_strcasecmp (auth, "BOTH") == 0) - qmiwdsauth = (QMI_WDS_AUTHENTICATION_PAP | QMI_WDS_AUTHENTICATION_CHAP); - else - qmiwdsauth = QMI_WDS_AUTHENTICATION_NONE; - qmi_message_wds_start_network_input_set_authentication_preference (input, qmiwdsauth, NULL); + if (props.auth_set) { + aux_auth_str = qmi_wds_authentication_build_string_from_mask (props.auth); + qmi_message_wds_start_network_input_set_authentication_preference (input, props.auth, NULL); } /* Set username, avoid empty strings */ - if (username && username[0]) - qmi_message_wds_start_network_input_set_username (input, username, NULL); + if (props.username && props.username[0]) + qmi_message_wds_start_network_input_set_username (input, props.username, NULL); /* Set password, avoid empty strings */ - if (password && password[0]) - qmi_message_wds_start_network_input_set_password (input, password, NULL); + if (props.password && props.password[0]) + qmi_message_wds_start_network_input_set_password (input, props.password, NULL); - g_debug ("Network start parameters set (apn: '%s', auth: '%s', user: '%s', pass: '%s')", - apn ? apn : "unspecified", - auth ? auth : "unspecified", - (username && username[0]) ? username : "unspecified", - (password && password[0]) ? password : "unspecified"); + g_debug ("Network start parameters set (apn: '%s', auth: '%s', username: '%s', password: '%s')", + props.apn ? props.apn : "unspecified", + aux_auth_str ? aux_auth_str : "unspecified", + (props.username && props.username[0]) ? props.username : "unspecified", + (props.password && props.password[0]) ? props.password : "unspecified"); +out: g_strfreev (split); + g_free (props.apn); + g_free (props.username); + g_free (props.password); + g_free (aux_auth_str); return input; } -- cgit v1.2.3