blob: 70de4f54fd65972791330af1296f3ec581b5530f [file] [log] [blame]
/* -*- 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;
}