| /* -*- 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.h" |
| #include "mm-modem-helpers.h" |
| #include "mm-modem-helpers-huawei.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) |
| { |
| GRegex *r; |
| GMatchInfo *match_info; |
| 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, ',')) { |
| 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; |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| } |
| /* No separate IPv4/IPv6 info given just connected/not connected */ |
| else { |
| 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; |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| } |
| |
| 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) |
| { |
| gchar *s, *bin = NULL; |
| gchar buf[9]; |
| gsize len, bin_len; |
| gboolean success = FALSE; |
| |
| 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; |
| success = TRUE; |
| goto done; |
| } |
| if (len < 7 || len > 8) |
| goto done; |
| |
| /* 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, &bin_len); |
| if (!bin || bin_len != 4) |
| goto done; |
| |
| *out_addr = GUINT32_SWAP_LE_BE (*((guint32 *) bin)); |
| success = TRUE; |
| |
| done: |
| g_free (s); |
| g_free (bin); |
| return success; |
| } |
| |
| 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) |
| { |
| gboolean matched; |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| 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; |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| 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) |
| { |
| gboolean matched; |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| 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); |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| 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) |
| { |
| gboolean matched; |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| 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); |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| 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, |
| 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_dbg ("Error parsing ^PREFMODE value: %s", split[i]); |
| continue; |
| } |
| |
| if (!mode_from_prefmode (val, &preferred, &inner_error)) { |
| mm_dbg ("Unhandled ^PREFMODE: %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) |
| { |
| gint mode; |
| guint i; |
| |
| /* Format: |
| * |
| * ^PREFMODE: <mode> |
| */ |
| |
| response = mm_strip_tag (response, "^PREFMODE:"); |
| if (!sscanf (response, "%d", &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, |
| 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_dbg ("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_dbg ("Error parsing ^SYSCFG mode value: %s", split[i]); |
| continue; |
| } |
| |
| if (!mode_from_syscfg (val, &allowed, &inner_error)) { |
| if (inner_error) { |
| mm_dbg ("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, |
| 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], &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 down */ |
| 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) |
| { |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| GError *match_error = NULL; |
| guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dt = 0; |
| gint tz = 0; |
| gboolean ret = FALSE; |
| |
| 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"); |
| } |
| } else { |
| /* 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 (iso8601p) { |
| /* Return ISO-8601 format date/time string */ |
| *iso8601p = mm_new_iso8601_time (year, month, day, hour, |
| minute, second, |
| TRUE, (tz * 15) + (dt * 60)); |
| } |
| if (tzp) { |
| *tzp = mm_network_timezone_new (); |
| mm_network_timezone_set_offset (*tzp, tz * 15); |
| mm_network_timezone_set_dst_offset (*tzp, dt * 60); |
| } |
| |
| ret = TRUE; |
| } else { |
| g_set_error_literal (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Failed to parse ^NWTIME reply"); |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| /* ^TIME response parser */ |
| |
| gboolean mm_huawei_parse_time_response (const gchar *response, |
| gchar **iso8601p, |
| MMNetworkTimezone **tzp, |
| GError **error) |
| { |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| GError *match_error = NULL; |
| guint year, month, day, hour, minute, second; |
| gboolean ret = FALSE; |
| |
| 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"); |
| } |
| } else { |
| /* 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); |
| ret = TRUE; |
| } else { |
| g_set_error_literal (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Failed to parse ^TIME reply"); |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| /* ^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) |
| { |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| GError *match_error = NULL; |
| gboolean ret = FALSE; |
| char *s; |
| |
| 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"); |
| } |
| goto done; |
| } |
| |
| /* 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"); |
| goto done; |
| } |
| |
| if (out_act) { |
| s = g_match_info_fetch (match_info, 1); |
| *out_act = mm_string_to_access_tech (s); |
| g_free (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); |
| |
| ret = TRUE; |
| |
| done: |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| /* ^CVOICE response parser */ |
| |
| gboolean |
| mm_huawei_parse_cvoice_response (const gchar *response, |
| guint *out_hz, |
| guint *out_bits, |
| GError **error) |
| { |
| GRegex *r; |
| GMatchInfo *match_info = NULL; |
| GError *match_error = NULL; |
| guint supported = 0, hz = 0, bits = 0; |
| gboolean ret = FALSE; |
| |
| /* ^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"); |
| } |
| } else { |
| /* 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; |
| ret = TRUE; |
| } else { |
| g_set_error_literal (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_UNSUPPORTED, |
| "^CVOICE not supported by this device"); |
| } |
| } else { |
| g_set_error_literal (error, |
| MM_CORE_ERROR, |
| MM_CORE_ERROR_FAILED, |
| "Failed to parse ^CVOICE reply"); |
| } |
| } |
| |
| g_match_info_free (match_info); |
| g_regex_unref (r); |
| |
| return ret; |
| } |