diff options
Diffstat (limited to 'src/plugins/huawei/mm-modem-helpers-huawei.c')
-rw-r--r-- | src/plugins/huawei/mm-modem-helpers-huawei.c | 1546 |
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); +} |