aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/huawei/mm-modem-helpers-huawei.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/huawei/mm-modem-helpers-huawei.c')
-rw-r--r--src/plugins/huawei/mm-modem-helpers-huawei.c1546
1 files changed, 1546 insertions, 0 deletions
diff --git a/src/plugins/huawei/mm-modem-helpers-huawei.c b/src/plugins/huawei/mm-modem-helpers-huawei.c
new file mode 100644
index 00000000..67bb7089
--- /dev/null
+++ b/src/plugins/huawei/mm-modem-helpers-huawei.c
@@ -0,0 +1,1546 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2013 Huawei Technologies Co., Ltd
+ * Copyright (C) 2013 Aleksander Morgado <aleksander@gnu.org>
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log-object.h"
+#include "mm-common-helpers.h"
+#include "mm-modem-helpers.h"
+#include "mm-modem-helpers-huawei.h"
+#include "mm-huawei-enums-types.h"
+
+/*****************************************************************************/
+/* ^NDISSTAT / ^NDISSTATQRY response parser */
+
+gboolean
+mm_huawei_parse_ndisstatqry_response (const gchar *response,
+ gboolean *ipv4_available,
+ gboolean *ipv4_connected,
+ gboolean *ipv6_available,
+ gboolean *ipv6_connected,
+ GError **error)
+{
+ GError *inner_error = NULL;
+
+ if (!response ||
+ !(g_ascii_strncasecmp (response, "^NDISSTAT:", strlen ("^NDISSTAT:")) == 0 ||
+ g_ascii_strncasecmp (response, "^NDISSTATQRY:", strlen ("^NDISSTATQRY:")) == 0)) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^NDISSTAT / ^NDISSTATQRY prefix");
+ return FALSE;
+ }
+
+ *ipv4_available = FALSE;
+ *ipv6_available = FALSE;
+
+ /* The response maybe as:
+ * ^NDISSTAT: 1,,,IPV4
+ * ^NDISSTAT: 0,33,,IPV6
+ * ^NDISSTATQRY: 1,,,IPV4
+ * ^NDISSTATQRY: 0,33,,IPV6
+ * OK
+ *
+ * Or, in newer firmwares:
+ * ^NDISSTATQRY:0,,,"IPV4",0,,,"IPV6"
+ * OK
+ *
+ * Or, even (handled separately):
+ * ^NDISSTATQry:1
+ * OK
+ */
+
+ /* If multiple fields available, try first parsing method */
+ if (strchr (response, ',')) {
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?"
+ "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint ip_type_field = 4;
+
+ /* IPv4 and IPv6 are fields 4 and (if available) 8 */
+
+ while (!inner_error && ip_type_field <= 8) {
+ gchar *ip_type_str;
+ guint connected;
+
+ ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field);
+ if (!ip_type_str)
+ break;
+
+ if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) ||
+ (connected != 0 && connected != 1)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
+ } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) {
+ *ipv4_available = TRUE;
+ *ipv4_connected = (gboolean)connected;
+ } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) {
+ *ipv6_available = TRUE;
+ *ipv6_connected = (gboolean)connected;
+ }
+
+ g_free (ip_type_str);
+ ip_type_field += 4;
+ }
+ }
+ }
+ /* No separate IPv4/IPv6 info given just connected/not connected */
+ else {
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d)(?:\\r\\n)?",
+ G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+ 0, NULL);
+ g_assert (r != NULL);
+
+ g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+ if (!inner_error && g_match_info_matches (match_info)) {
+ guint connected;
+
+ if (!mm_get_uint_from_match_info (match_info, 1, &connected) ||
+ (connected != 0 && connected != 1)) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
+ } else {
+ /* We'll assume IPv4 */
+ *ipv4_available = TRUE;
+ *ipv4_connected = (gboolean)connected;
+ }
+ }
+ }
+
+ if (!ipv4_available && !ipv6_available) {
+ inner_error = g_error_new (MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't find IPv4 or IPv6 info in ^NDISSTAT / ^NDISSTATQRY response");
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^DHCP response parser */
+
+static gboolean
+match_info_to_ip4_addr (GMatchInfo *match_info,
+ guint match_index,
+ guint *out_addr)
+{
+ g_autofree gchar *s = NULL;
+ g_autofree guint8 *bin = NULL;
+ gchar buf[9];
+ gsize len;
+ gsize bin_len;
+ guint32 aux;
+
+ s = g_match_info_fetch (match_info, match_index);
+ g_return_val_if_fail (s != NULL, FALSE);
+
+ len = strlen (s);
+ if (len == 1 && s[0] == '0') {
+ *out_addr = 0;
+ return TRUE;
+ }
+
+ if (len < 7 || len > 8)
+ return FALSE;
+
+ /* Handle possibly missing leading zero */
+ memset (buf, 0, sizeof (buf));
+ if (len == 7) {
+ strcpy (&buf[1], s);
+ buf[0] = '0';
+ } else if (len == 8)
+ strcpy (buf, s);
+ else
+ g_assert_not_reached ();
+
+ bin = mm_utils_hexstr2bin (buf, -1, &bin_len, NULL);
+ if (!bin || bin_len != 4)
+ return FALSE;
+
+ memcpy (&aux, bin, 4);
+ *out_addr = GUINT32_SWAP_LE_BE (aux);
+ return TRUE;
+}
+
+gboolean
+mm_huawei_parse_dhcp_response (const char *reply,
+ guint *out_address,
+ guint *out_prefix,
+ guint *out_gateway,
+ guint *out_dns1,
+ guint *out_dns2,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (reply != NULL);
+ g_assert (out_address != NULL);
+ g_assert (out_prefix != NULL);
+ g_assert (out_gateway != NULL);
+ g_assert (out_dns1 != NULL);
+ g_assert (out_dns2 != NULL);
+
+ /* Format:
+ *
+ * ^DHCP: <address>,<netmask>,<gateway>,<?>,<dns1>,<dns2>,<uplink>,<downlink>
+ *
+ * All numbers are hexadecimal representations of IPv4 addresses, with
+ * least-significant byte first. eg, 192.168.50.32 is expressed as
+ * "2032A8C0". Sometimes leading zeros are stripped, so "1010A0A" is
+ * actually 10.10.1.1.
+ */
+
+ r = g_regex_new ("\\^DHCP:\\s*(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),(?:0[xX])?([0-9a-fA-F]+),.*$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^DHCP results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^DHCP reply");
+ }
+ } else {
+ guint netmask;
+
+ if (match_info_to_ip4_addr (match_info, 1, out_address) &&
+ match_info_to_ip4_addr (match_info, 2, &netmask) &&
+ match_info_to_ip4_addr (match_info, 3, out_gateway) &&
+ match_info_to_ip4_addr (match_info, 5, out_dns1) &&
+ match_info_to_ip4_addr (match_info, 6, out_dns2)) {
+ *out_prefix = mm_count_bits_set (netmask);
+ matched = TRUE;
+ }
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^SYSINFO response parser */
+
+gboolean
+mm_huawei_parse_sysinfo_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sys_mode,
+ guint *out_sim_state,
+ gboolean *out_sys_submode_valid,
+ guint *out_sys_submode,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (out_srv_status != NULL);
+ g_assert (out_srv_domain != NULL);
+ g_assert (out_roam_status != NULL);
+ g_assert (out_sys_mode != NULL);
+ g_assert (out_sim_state != NULL);
+ g_assert (out_sys_submode_valid != NULL);
+ g_assert (out_sys_submode != NULL);
+
+ /* Format:
+ *
+ * ^SYSINFO: <srv_status>,<srv_domain>,<roam_status>,<sys_mode>,<sim_state>[,<reserved>,<sys_submode>]
+ */
+
+ /* Can't just use \d here since sometimes you get "^SYSINFO:2,1,0,3,1,,3" */
+ r = g_regex_new ("\\^SYSINFO:\\s*(\\d+),(\\d+),(\\d+),(\\d+),(\\d+),?(\\d+)?,?(\\d+)?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^SYSINFO results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^SYSINFO reply");
+ }
+ } else {
+ mm_get_uint_from_match_info (match_info, 1, out_srv_status);
+ mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
+ mm_get_uint_from_match_info (match_info, 3, out_roam_status);
+ mm_get_uint_from_match_info (match_info, 4, out_sys_mode);
+ mm_get_uint_from_match_info (match_info, 5, out_sim_state);
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ if (g_match_info_get_match_count (match_info) >= 8) {
+ *out_sys_submode_valid = TRUE;
+ mm_get_uint_from_match_info (match_info, 7, out_sys_submode);
+ }
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^SYSINFOEX response parser */
+
+gboolean
+mm_huawei_parse_sysinfoex_response (const char *reply,
+ guint *out_srv_status,
+ guint *out_srv_domain,
+ guint *out_roam_status,
+ guint *out_sim_state,
+ guint *out_sys_mode,
+ guint *out_sys_submode,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ gboolean matched;
+ GError *match_error = NULL;
+
+ g_assert (out_srv_status != NULL);
+ g_assert (out_srv_domain != NULL);
+ g_assert (out_roam_status != NULL);
+ g_assert (out_sim_state != NULL);
+ g_assert (out_sys_mode != NULL);
+ g_assert (out_sys_submode != NULL);
+
+ /* Format:
+ *
+ * ^SYSINFOEX: <srv_status>,<srv_domain>,<roam_status>,<sim_state>,<reserved>,<sysmode>,<sysmode_name>,<submode>,<submode_name>
+ *
+ * <sysmode_name> and <submode_name> may not be quoted on some Huawei modems (e.g. E303).
+ */
+
+ /* ^SYSINFOEX:2,3,0,1,,3,"WCDMA",41,"HSPA+" */
+
+ r = g_regex_new ("\\^SYSINFOEX:\\s*(\\d+),(\\d+),(\\d+),(\\d+),?(\\d*),(\\d+),\"?([^\"]*)\"?,(\\d+),\"?([^\"]*)\"?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ matched = g_regex_match_full (r, reply, -1, 0, 0, &match_info, &match_error);
+ if (!matched) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^SYSINFOEX results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^SYSINFOEX reply");
+ }
+ } else {
+ mm_get_uint_from_match_info (match_info, 1, out_srv_status);
+ mm_get_uint_from_match_info (match_info, 2, out_srv_domain);
+ mm_get_uint_from_match_info (match_info, 3, out_roam_status);
+ mm_get_uint_from_match_info (match_info, 4, out_sim_state);
+
+ /* We just ignore the sysmode and submode name strings */
+ mm_get_uint_from_match_info (match_info, 6, out_sys_mode);
+ mm_get_uint_from_match_info (match_info, 8, out_sys_submode);
+ }
+
+ return matched;
+}
+
+/*****************************************************************************/
+/* ^PREFMODE test parser
+ *
+ * AT^PREFMODE=?
+ * ^PREFMODE:(2,4,8)
+ */
+
+static gboolean
+mode_from_prefmode (guint huawei_mode,
+ MMModemMode *modem_mode,
+ GError **error)
+{
+ g_assert (modem_mode != NULL);
+
+ *modem_mode = MM_MODEM_MODE_NONE;
+ switch (huawei_mode) {
+ case 2:
+ *modem_mode = MM_MODEM_MODE_2G;
+ break;
+ case 4:
+ *modem_mode = MM_MODEM_MODE_3G;
+ break;
+ case 8:
+ *modem_mode = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+ break;
+ default:
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No translation from huawei prefmode '%u' to mode",
+ huawei_mode);
+ }
+
+ return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE;
+}
+
+GArray *
+mm_huawei_parse_prefmode_test (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ gchar **split;
+ guint i;
+ MMModemMode all = MM_MODEM_MODE_NONE;
+ GArray *out;
+
+ response = mm_strip_tag (response, "^PREFMODE:");
+ split = g_strsplit_set (response, " (,)\r\n", -1);
+ if (!split) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^PREFMODE format output");
+ return NULL;
+ }
+
+ out = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiPrefmodeCombination),
+ 3);
+ for (i = 0; split[i]; i++) {
+ guint val;
+ MMModemMode preferred = MM_MODEM_MODE_NONE;
+ GError *inner_error = NULL;
+ MMHuaweiPrefmodeCombination combination;
+
+ if (split[i][0] == '\0')
+ continue;
+
+ if (!mm_get_uint_from_str (split[i], &val)) {
+ mm_obj_dbg (log_object, "error parsing ^PREFMODE value '%s'", split[i]);
+ continue;
+ }
+
+ if (!mode_from_prefmode (val, &preferred, &inner_error)) {
+ mm_obj_dbg (log_object, "unhandled ^PREFMODE value: %s", inner_error->message);
+ g_error_free (inner_error);
+ continue;
+ }
+
+ combination.prefmode = val;
+ combination.allowed = MM_MODEM_MODE_NONE; /* reset it later */
+ combination.preferred = preferred;
+
+ all |= preferred;
+
+ g_array_append_val (out, combination);
+ }
+ g_strfreev (split);
+
+ /* No value */
+ if (out->len == 0) {
+ g_array_unref (out);
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "^PREFMODE response contains no valid values");
+ return NULL;
+ }
+
+ /* Single value listed; PREFERRED=NONE... */
+ if (out->len == 1) {
+ MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (out, MMHuaweiPrefmodeCombination, 0);
+ combination->allowed = all;
+ combination->preferred = MM_MODEM_MODE_NONE;
+ } else {
+ /* Multiple values, reset ALLOWED */
+ for (i = 0; i < out->len; i++) {
+ MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (out, MMHuaweiPrefmodeCombination, i);
+ combination->allowed = all;
+ if (combination->preferred == all)
+ combination->preferred = MM_MODEM_MODE_NONE;
+ }
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^PREFMODE response parser */
+
+const MMHuaweiPrefmodeCombination *
+mm_huawei_parse_prefmode_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ guint mode;
+ guint i;
+
+ /* Format:
+ *
+ * ^PREFMODE: <mode>
+ */
+
+ response = mm_strip_tag (response, "^PREFMODE:");
+ if (!mm_get_uint_from_str (response, &mode)) {
+ /* Dump error to upper layer */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected PREFMODE response: '%s'",
+ response);
+ return NULL;
+ }
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiPrefmodeCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiPrefmodeCombination,
+ i);
+ if (mode == combination->prefmode)
+ return combination;
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No PREFMODE combination found matching the current one (%d)",
+ mode);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^SYSCFG test parser */
+
+static gchar **
+split_groups (const gchar *str,
+ GError **error)
+{
+ const gchar *p = str;
+ GPtrArray *out;
+ guint groups = 0;
+
+ /*
+ * Split string: (a),((b1),(b2)),,(d),((e1),(e2))
+ * Into:
+ * - a
+ * - (b1),(b2)
+ * -
+ * - d
+ * - (e1),(e2)
+ */
+
+ out = g_ptr_array_new_with_free_func (g_free);
+
+ while (TRUE) {
+ const gchar *start;
+ guint inner_groups;
+
+ /* Skip whitespaces */
+ while (*p == ' ' || *p == '\r' || *p == '\n')
+ p++;
+
+ /* We're done, return */
+ if (*p == '\0') {
+ g_ptr_array_set_size (out, out->len + 1);
+ return (gchar **) g_ptr_array_free (out, FALSE);
+ }
+
+ /* Group separators */
+ if (groups > 0) {
+ if (*p != ',') {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected group separator");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+ p++;
+ }
+
+ /* Skip whitespaces */
+ while (*p == ' ' || *p == '\r' || *p == '\n')
+ p++;
+
+ /* New group */
+ groups++;
+
+ /* Empty group? */
+ if (*p == ',' || *p == '\0') {
+ g_ptr_array_add (out, g_strdup (""));
+ continue;
+ }
+
+ /* No group start? */
+ if (*p != '(') {
+ /* Error */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Expected '(' not found");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+ p++;
+
+ inner_groups = 0;
+ start = p;
+ while (TRUE) {
+ if (*p == '(') {
+ inner_groups++;
+ p++;
+ continue;
+ }
+
+ if (*p == '\0') {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Early end of string found, unfinished group");
+ g_ptr_array_unref (out);
+ return NULL;
+ }
+
+ if (*p == ')') {
+ gchar *group;
+
+ if (inner_groups > 0) {
+ inner_groups--;
+ p++;
+ continue;
+ }
+
+ group = g_strndup (start, p - start);
+ g_ptr_array_add (out, group);
+ p++;
+ break;
+ }
+
+ /* keep on */
+ p++;
+ }
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+mode_from_syscfg (guint huawei_mode,
+ MMModemMode *modem_mode,
+ GError **error)
+{
+ g_assert (modem_mode != NULL);
+
+ *modem_mode = MM_MODEM_MODE_NONE;
+ switch (huawei_mode) {
+ case 2:
+ *modem_mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+ break;
+ case 13:
+ *modem_mode = MM_MODEM_MODE_2G;
+ break;
+ case 14:
+ *modem_mode = MM_MODEM_MODE_3G;
+ break;
+ case 16:
+ /* ignore */
+ break;
+ default:
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No translation from huawei prefmode '%u' to mode",
+ huawei_mode);
+ }
+
+ return *modem_mode != MM_MODEM_MODE_NONE ? TRUE : FALSE;
+}
+
+static GArray *
+parse_syscfg_modes (const gchar *modes_str,
+ const gchar *acqorder_str,
+ gpointer log_object,
+ GError **error)
+{
+ GArray *out;
+ gchar **split;
+ guint i;
+ gint min_acqorder = 0;
+ gint max_acqorder = 0;
+
+ /* Start parsing acquisition order */
+ if (!sscanf (acqorder_str, "%d-%d", &min_acqorder, &max_acqorder))
+ mm_obj_dbg (log_object, "error parsing ^SYSCFG acquisition order range '%s'", acqorder_str);
+
+ /* Just in case, we default to supporting only auto */
+ if (max_acqorder < min_acqorder) {
+ min_acqorder = 0;
+ max_acqorder = 0;
+ }
+
+ /* Now parse modes */
+ split = g_strsplit (modes_str, ",", -1);
+ out = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiSyscfgCombination),
+ g_strv_length (split));
+ for (i = 0; split[i]; i++) {
+ guint val;
+ guint allowed = MM_MODEM_MODE_NONE;
+ GError *inner_error = NULL;
+ MMHuaweiSyscfgCombination combination;
+
+ if (!mm_get_uint_from_str (mm_strip_quotes (split[i]), &val)) {
+ mm_obj_dbg (log_object, "error parsing ^SYSCFG mode value: %s", split[i]);
+ continue;
+ }
+
+ if (!mode_from_syscfg (val, &allowed, &inner_error)) {
+ if (inner_error) {
+ mm_obj_dbg (log_object, "unhandled ^SYSCFG: %s", inner_error->message);
+ g_error_free (inner_error);
+ }
+ continue;
+ }
+
+ switch (allowed) {
+ case MM_MODEM_MODE_2G:
+ case MM_MODEM_MODE_3G:
+ /* single mode */
+ combination.allowed = allowed;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.mode = val;
+ combination.acqorder = 0;
+ g_array_append_val (out, combination);
+ break;
+ case (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G):
+ /* 2G and 3G; auto */
+ combination.allowed = allowed;
+ combination.mode = val;
+ if (min_acqorder == 0) {
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.acqorder = 0;
+ g_array_append_val (out, combination);
+ }
+ /* 2G and 3G; 2G preferred */
+ if (min_acqorder <= 1 && max_acqorder >= 1) {
+ combination.preferred = MM_MODEM_MODE_2G;
+ combination.acqorder = 1;
+ g_array_append_val (out, combination);
+ }
+ /* 2G and 3G; 3G preferred */
+ if (min_acqorder <= 2 && max_acqorder >= 2) {
+ combination.preferred = MM_MODEM_MODE_3G;
+ combination.acqorder = 2;
+ g_array_append_val (out, combination);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ g_strfreev (split);
+
+ /* If we didn't build a valid array of combinations, return an error */
+ if (out->len == 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot parse list of allowed mode combinations: '%s,%s'",
+ modes_str,
+ acqorder_str);
+ g_array_unref (out);
+ return NULL;
+ }
+
+ return out;
+}
+
+GArray *
+mm_huawei_parse_syscfg_test (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ gchar **split;
+ GError *inner_error = NULL;
+ GArray *out;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFG:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFG prefix");
+ return NULL;
+ }
+
+ /* Examples:
+ *
+ * ^SYSCFG:(2,13,14,16),
+ * (0-3),
+ * ((400000,"WCDMA2100")),
+ * (0-2),
+ * (0-4)
+ */
+ split = split_groups (mm_strip_tag (response, "^SYSCFG:"), error);
+ if (!split)
+ return NULL;
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFG format");
+ g_strfreev (split);
+ return FALSE;
+ }
+
+ /* Parse supported mode combinations */
+ out = parse_syscfg_modes (split[0], split[1], log_object, &inner_error);
+
+ g_strfreev (split);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^SYSCFG response parser */
+
+const MMHuaweiSyscfgCombination *
+mm_huawei_parse_syscfg_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ gchar **split;
+ guint mode;
+ guint acqorder;
+ guint i;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFG:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFG prefix");
+ return NULL;
+ }
+
+ /* Format:
+ *
+ * ^SYSCFG: <mode>,<acqorder>,<band>,<roam>,<srvdomain>
+ */
+
+ response = mm_strip_tag (response, "^SYSCFG:");
+ split = g_strsplit (response, ",", -1);
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5 ||
+ !mm_get_uint_from_str (split[0], &mode) ||
+ !mm_get_uint_from_str (split[1], &acqorder)) {
+ /* Dump error to upper layer */
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFG response: '%s'",
+ response);
+ g_strfreev (split);
+ return NULL;
+ }
+
+ /* Fix invalid modes with non-sensical acquisition orders */
+ if (mode == 14 && acqorder != 0) /* WCDMA only but acqorder != "Automatic" */
+ acqorder = 0;
+ else if (mode == 13 && acqorder != 0) /* GSM only but acqorder != "Automatic" */
+ acqorder = 0;
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiSyscfgCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiSyscfgCombination,
+ i);
+ if (mode == combination->mode && acqorder == combination->acqorder) {
+ g_strfreev (split);
+ return combination;
+ }
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No SYSCFG combination found matching the current one (%d,%d)",
+ mode,
+ acqorder);
+ g_strfreev (split);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^SYSCFGEX test parser */
+
+static void
+huawei_syscfgex_combination_free (MMHuaweiSyscfgexCombination *item)
+{
+ /* Just the contents, not the item itself! */
+ g_free (item->mode_str);
+}
+
+static gboolean
+parse_mode_combination_string (const gchar *mode_str,
+ MMModemMode *allowed,
+ MMModemMode *preferred)
+{
+ guint n;
+
+ if (g_str_equal (mode_str, "00")) {
+ *allowed = MM_MODEM_MODE_ANY;
+ *preferred = MM_MODEM_MODE_NONE;
+ return TRUE;
+ }
+
+ *allowed = MM_MODEM_MODE_NONE;
+ *preferred = MM_MODEM_MODE_NONE;
+
+ for (n = 0; n < strlen (mode_str); n+=2) {
+ MMModemMode mode;
+
+ if (g_ascii_strncasecmp (&mode_str[n], "01", 2) == 0)
+ /* GSM */
+ mode = MM_MODEM_MODE_2G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "02", 2) == 0)
+ /* WCDMA */
+ mode = MM_MODEM_MODE_3G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "03", 2) == 0)
+ /* LTE */
+ mode = MM_MODEM_MODE_4G;
+ else if (g_ascii_strncasecmp (&mode_str[n], "04", 2) == 0)
+ /* CDMA Note: no EV-DO, just return single value, so assume CDMA1x*/
+ mode = MM_MODEM_MODE_2G;
+ else
+ mode = MM_MODEM_MODE_NONE;
+
+ if (mode != MM_MODEM_MODE_NONE) {
+ /* The first one in the list is the preferred combination */
+ if (n == 0)
+ *preferred |= mode;
+ *allowed |= mode;
+ }
+ }
+
+ switch (mm_count_bits_set (*allowed)) {
+ case 0:
+ /* No allowed, error */
+ return FALSE;
+ case 1:
+ /* If only one mode allowed, NONE preferred */
+ *preferred = MM_MODEM_MODE_NONE;
+ /* fall through */
+ default:
+ return TRUE;
+ }
+}
+
+static GArray *
+parse_mode_combination_string_list (const gchar *modes_str,
+ GError **error)
+{
+ GArray *supported_mode_combinations;
+ gchar **mode_combinations;
+ MMModemMode all = MM_MODEM_MODE_NONE;
+ gboolean has_all = FALSE;
+ guint i;
+
+ mode_combinations = g_strsplit (modes_str, ",", -1);
+ supported_mode_combinations = g_array_sized_new (FALSE,
+ FALSE,
+ sizeof (MMHuaweiSyscfgexCombination),
+ g_strv_length (mode_combinations));
+ g_array_set_clear_func (supported_mode_combinations,
+ (GDestroyNotify)huawei_syscfgex_combination_free);
+
+ for (i = 0; mode_combinations[i]; i++) {
+ MMHuaweiSyscfgexCombination combination;
+
+ mode_combinations[i] = mm_strip_quotes (mode_combinations[i]);
+ if (!parse_mode_combination_string (mode_combinations[i],
+ &combination.allowed,
+ &combination.preferred))
+ continue;
+
+ if (combination.allowed != MM_MODEM_MODE_ANY) {
+ combination.mode_str = g_strdup (mode_combinations[i]);
+ g_array_append_val (supported_mode_combinations, combination);
+
+ all |= combination.allowed;
+ } else {
+ /* don't add the all_combination here, we may have more
+ * combinations in the loop afterwards */
+ has_all = TRUE;
+ }
+ }
+
+ g_strfreev (mode_combinations);
+
+ /* Add here the all_combination */
+ if (has_all) {
+ MMHuaweiSyscfgexCombination combination;
+
+ combination.allowed = all;
+ combination.preferred = MM_MODEM_MODE_NONE;
+ combination.mode_str = g_strdup ("00");
+ g_array_append_val (supported_mode_combinations, combination);
+ }
+
+ /* If we didn't build a valid array of combinations, return an error */
+ if (supported_mode_combinations->len == 0) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Cannot parse list of allowed mode combinations: '%s'",
+ modes_str);
+ g_array_unref (supported_mode_combinations);
+ return NULL;
+ }
+
+ return supported_mode_combinations;
+}
+
+GArray *
+mm_huawei_parse_syscfgex_test (const gchar *response,
+ GError **error)
+{
+ gchar **split;
+ GError *inner_error = NULL;
+ GArray *out;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFGEX prefix");
+ return NULL;
+ }
+
+ /* Examples:
+ *
+ * ^SYSCFGEX: ("00","03","02","01","99"),
+ * ((2000004e80380,"GSM850/GSM900/GSM1800/GSM1900/WCDMA850/WCDMA900/WCDMA1900/WCDMA2100"),
+ * (3fffffff,"All Bands")),
+ * (0-3),
+ * (0-4),
+ * ((800c5,"LTE2100/LTE1800/LTE2600/LTE900/LTE800"),
+ * (7fffffffffffffff,"All bands"))
+ */
+ split = split_groups (mm_strip_tag (response, "^SYSCFGEX:"), error);
+ if (!split)
+ return NULL;
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFGEX format");
+ g_strfreev (split);
+ return NULL;
+ }
+
+ out = parse_mode_combination_string_list (split[0], &inner_error);
+
+ g_strfreev (split);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ return out;
+}
+
+/*****************************************************************************/
+/* ^SYSCFGEX response parser */
+
+const MMHuaweiSyscfgexCombination *
+mm_huawei_parse_syscfgex_response (const gchar *response,
+ const GArray *supported_mode_combinations,
+ GError **error)
+{
+ gchar **split;
+ guint i;
+ gsize len;
+ gchar *str;
+
+ if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Missing ^SYSCFGEX prefix");
+ return NULL;
+ }
+
+ /* Format:
+ *
+ * ^SYSCFGEX: "00",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF
+ * ^SYSCFGEX: <mode>,<band>,<roam>,<srvdomain>,<lte-band>
+ */
+
+ response = mm_strip_tag (response, "^SYSCFGEX:");
+ split = g_strsplit (response, ",", -1);
+
+ /* We expect 5 string chunks */
+ if (g_strv_length (split) < 5) {
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Unexpected ^SYSCFGEX response format");
+ g_strfreev (split);
+ return NULL;
+ }
+
+ /* Unquote */
+ str = split[0];
+ len = strlen (str);
+ if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) {
+ str[0] = ' ';
+ str[len - 1] = ' ';
+ str = g_strstrip (str);
+ }
+
+ /* Look for current modes among the supported ones */
+ for (i = 0; i < supported_mode_combinations->len; i++) {
+ const MMHuaweiSyscfgexCombination *combination;
+
+ combination = &g_array_index (supported_mode_combinations,
+ MMHuaweiSyscfgexCombination,
+ i);
+ if (g_str_equal (str, combination->mode_str)) {
+ g_strfreev (split);
+ return combination;
+ }
+ }
+
+ g_set_error (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "No SYSCFGEX combination found matching the current one (%s)",
+ str);
+ g_strfreev (split);
+ return NULL;
+}
+
+/*****************************************************************************/
+/* ^NWTIME response parser */
+
+gboolean
+mm_huawei_parse_nwtime_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year = 0;
+ guint month = 0;
+ guint day = 0;
+ guint hour = 0;
+ guint minute = 0;
+ guint second = 0;
+ guint dt = 0;
+ gint tz = 0;
+
+ g_assert (iso8601p || tzp); /* at least one */
+
+ r = g_regex_new ("\\^NWTIME:\\s*(\\d+)/(\\d+)/(\\d+),(\\d+):(\\d+):(\\d*)([\\-\\+\\d]+),(\\d+)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^NWTIME results: ");
+ } else {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match ^NWTIME reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 9);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second) &&
+ mm_get_int_from_match_info (match_info, 7, &tz) &&
+ mm_get_uint_from_match_info (match_info, 8, &dt)) {
+
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+ /*
+ * tz = timezone offset in 15 minute intervals
+ * dt = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours
+ * other values are marked reserved.
+ */
+ if (tzp) {
+ *tzp = mm_network_timezone_new ();
+ mm_network_timezone_set_offset (*tzp, tz * 15);
+ mm_network_timezone_set_dst_offset (*tzp, dt * 60);
+ }
+ if (iso8601p) {
+ /* Return ISO-8601 format date/time string */
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second,
+ TRUE, (tz * 15) + (dt * 60),
+ error);
+ return (*iso8601p != NULL);
+ }
+
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^NWTIME reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^TIME response parser */
+
+gboolean
+mm_huawei_parse_time_response (const gchar *response,
+ gchar **iso8601p,
+ MMNetworkTimezone **tzp,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint year = 0;
+ guint month = 0;
+ guint day = 0;
+ guint hour = 0;
+ guint minute = 0;
+ guint second = 0;
+
+ g_assert (iso8601p || tzp); /* at least one */
+
+ /* TIME response cannot ever provide TZ info */
+ if (tzp) {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_UNSUPPORTED,
+ "^TIME does not provide timezone information");
+ return FALSE;
+ }
+
+ /* Already in ISO-8601 format, but verify just to be sure */
+ r = g_regex_new ("\\^TIME:\\s*(\\d+)/(\\d+)/(\\d+)\\s*(\\d+):(\\d+):(\\d*)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^TIME results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^TIME reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 7);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+ mm_get_uint_from_match_info (match_info, 2, &month) &&
+ mm_get_uint_from_match_info (match_info, 3, &day) &&
+ mm_get_uint_from_match_info (match_info, 4, &hour) &&
+ mm_get_uint_from_match_info (match_info, 5, &minute) &&
+ mm_get_uint_from_match_info (match_info, 6, &second)) {
+ /* adjust year */
+ if (year < 100)
+ year += 2000;
+
+ /* Return ISO-8601 format date/time string */
+ if (iso8601p) {
+ *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+ minute, second, FALSE, 0,
+ error);
+ return (*iso8601p != NULL);
+ }
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^TIME reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^HCSQ response parser */
+
+gboolean
+mm_huawei_parse_hcsq_response (const gchar *response,
+ MMModemAccessTechnology *out_act,
+ guint *out_value1,
+ guint *out_value2,
+ guint *out_value3,
+ guint *out_value4,
+ guint *out_value5,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+
+ r = g_regex_new ("\\^HCSQ:\\s*\"?([a-zA-Z]*)\"?,(\\d+),?(\\d+)?,?(\\d+)?,?(\\d+)?,?(\\d+)?$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^HCSQ results: ");
+ } else {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Couldn't match ^HCSQ reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ if (g_match_info_get_match_count (match_info) < 3) {
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Not enough elements in ^HCSQ reply");
+ return FALSE;
+ }
+
+ if (out_act) {
+ g_autofree gchar *s = NULL;
+
+ s = g_match_info_fetch (match_info, 1);
+ *out_act = mm_string_to_access_tech (s);
+ }
+
+ if (out_value1)
+ mm_get_uint_from_match_info (match_info, 2, out_value1);
+ if (out_value2)
+ mm_get_uint_from_match_info (match_info, 3, out_value2);
+ if (out_value3)
+ mm_get_uint_from_match_info (match_info, 4, out_value3);
+ if (out_value4)
+ mm_get_uint_from_match_info (match_info, 5, out_value4);
+ if (out_value5)
+ mm_get_uint_from_match_info (match_info, 6, out_value5);
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* ^CVOICE response parser */
+
+gboolean
+mm_huawei_parse_cvoice_response (const gchar *response,
+ guint *out_hz,
+ guint *out_bits,
+ GError **error)
+{
+ g_autoptr(GRegex) r = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ GError *match_error = NULL;
+ guint supported = 0;
+ guint hz = 0;
+ guint bits = 0;
+
+ /* ^CVOICE: <0=supported,1=unsupported>,<hz>,<bits>,<unknown> */
+ r = g_regex_new ("\\^CVOICE:\\s*(\\d)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)$", 0, 0, NULL);
+ g_assert (r != NULL);
+
+ if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+ if (match_error) {
+ g_propagate_error (error, match_error);
+ g_prefix_error (error, "Could not parse ^CVOICE results: ");
+ } else {
+ g_set_error_literal (error,
+ MM_CORE_ERROR,
+ MM_CORE_ERROR_FAILED,
+ "Couldn't match ^CVOICE reply");
+ }
+ return FALSE;
+ }
+
+ /* Remember that g_match_info_get_match_count() includes match #0 */
+ g_assert (g_match_info_get_match_count (match_info) >= 5);
+
+ if (mm_get_uint_from_match_info (match_info, 1, &supported) &&
+ mm_get_uint_from_match_info (match_info, 2, &hz) &&
+ mm_get_uint_from_match_info (match_info, 3, &bits)) {
+ if (supported == 0) {
+ if (out_hz)
+ *out_hz = hz;
+ if (out_bits)
+ *out_bits = bits;
+ return TRUE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "^CVOICE not supported by this device");
+ return FALSE;
+ }
+
+ g_set_error_literal (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Failed to parse ^CVOICE reply");
+ return FALSE;
+}
+
+/*****************************************************************************/
+/* ^GETPORTMODE response parser */
+
+#define GETPORTMODE_PREFIX "^GETPORTMODE:"
+
+GArray *
+mm_huawei_parse_getportmode_response (const gchar *response,
+ gpointer log_object,
+ GError **error)
+{
+ g_autoptr(GArray) modes = NULL;
+ g_auto(GStrv) split = NULL;
+ guint i;
+ gint n_items;
+
+ split = g_strsplit (response, ",", -1);
+ n_items = g_strv_length (split) - 1;
+ if (n_items < 1) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected number of items in response");
+ return NULL;
+ }
+
+ /* validate response prefix */
+ if (g_ascii_strncasecmp (split[0], GETPORTMODE_PREFIX, strlen (GETPORTMODE_PREFIX)) != 0) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "Unexpected response prefix");
+ return NULL;
+ }
+
+ mm_obj_dbg (log_object, "processing ^GETPORTMODE response...");
+
+ modes = g_array_sized_new (FALSE, FALSE, sizeof (MMHuaweiPortMode), n_items);
+
+ /* iterate all port items found */
+ for (i = 1; split[i]; i++) {
+ MMHuaweiPortMode mode = MM_HUAWEI_PORT_MODE_NONE;
+ gchar *separator;
+ guint port_number;
+
+ separator = strchr (split[i], ':');
+ if (!separator)
+ continue;
+
+ /* the reported port number may start either by 0 or by 1; the important
+ * thing is therefore no the number itself, only that it's a number */
+ g_strstrip (&separator[1]);
+ if (!mm_get_uint_from_str (&separator[1], &port_number)) {
+ mm_obj_warn (log_object, " couldn't parse port number: %s", split[i]);
+ break;
+ }
+
+ *separator = '\0';
+ g_strstrip (split[i]);
+
+ if (g_ascii_strcasecmp (split[i], "pcui") == 0)
+ mode = MM_HUAWEI_PORT_MODE_PCUI;
+ else if ((g_ascii_strcasecmp (split[i], "mdm") == 0) ||
+ (g_ascii_strcasecmp (split[i], "modem") == 0) ||
+ (g_ascii_strcasecmp (split[i], "3g_modem") == 0))
+ mode = MM_HUAWEI_PORT_MODE_MODEM;
+ else if ((g_ascii_strcasecmp (split[i], "diag") == 0) ||
+ (g_ascii_strcasecmp (split[i], "3g_diag") == 0) ||
+ (g_ascii_strcasecmp (split[i], "4g_diag") == 0))
+ mode = MM_HUAWEI_PORT_MODE_DIAG;
+ else if (g_ascii_strcasecmp (split[i], "gps") == 0)
+ mode = MM_HUAWEI_PORT_MODE_GPS;
+ else if ((g_ascii_strcasecmp (split[i], "ndis") == 0) ||
+ (g_ascii_strcasecmp (split[i], "rndis") == 0) ||
+ (g_ascii_strcasecmp (split[i], "ncm") == 0) ||
+ (g_ascii_strcasecmp (split[i], "ecm") == 0))
+ mode = MM_HUAWEI_PORT_MODE_NET;
+ else if (g_ascii_strcasecmp (split[i], "cdrom") == 0)
+ mode = MM_HUAWEI_PORT_MODE_CDROM;
+ else if ((g_ascii_strcasecmp (split[i], "sd") == 0) ||
+ (g_ascii_strncasecmp (split[i], "mass", 4) == 0))
+ mode = MM_HUAWEI_PORT_MODE_SD;
+ else if (g_ascii_strcasecmp (split[i], "bt") == 0)
+ mode = MM_HUAWEI_PORT_MODE_BT;
+ else if ((g_ascii_strcasecmp (split[i], "a_shell") == 0) ||
+ (g_ascii_strcasecmp (split[i], "c_shell") == 0))
+ mode = MM_HUAWEI_PORT_MODE_SHELL;
+
+ mm_obj_dbg (log_object, " port mode %s reported at port number %u",
+ mm_huawei_port_mode_get_string (mode), port_number);
+ g_array_append_val (modes, mode);
+ }
+
+ if (!modes->len) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No port modes loaded");
+ return NULL;
+ }
+
+ return g_steal_pointer (&modes);
+}