/* -*- 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 */ #include #include #include #include #define _LIBMM_INSIDE_MM #include #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:
,,,,,,, * * 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: ,,,,[,,] */ /* 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: ,,,,,,,, * * and 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: */ 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; } /* code shared for SYSCFG and SYSCFGEX */ static gboolean parse_syscfg_bands (const gchar *bands_str, guint64 *bands_out, GError **error) { g_auto (GStrv) groups = NULL; guint i; g_assert (bands_str); g_assert (bands_out); /* Input format examples: * * Example GSM/UMTS: * (2000000400380,"GSM900/GSM1800/WCDMA BCVIII/WCDMA BCI"),(280000,"GSM850/GSM1900"),(3fffffff,"All bands") * * Example LTE: * (800c5,"LTE2100/LTE1800/LTE2600/LTE900/LTE800"),(7fffffffffffffff,"All bands") * * The bands might be split in multiple groups. * Also, there's always a special "All bands" mask in one group. * The number and order of the groups might differ between modems. */ *bands_out = 0; groups = mm_split_string_groups (bands_str); if (!groups) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse ^SYSCFG bands string"); return FALSE; } for (i = 0; groups[i] != NULL; i++) { g_auto (GStrv) split = NULL; guint64 band_mask; split = g_strsplit (groups[i], ",", -1); g_assert (split); /* special case: empty bands string, e.g. ^SYSCFGEX LTE bands string from 3G modem */ if (split[0] == NULL) continue; if (!mm_get_u64_from_hex_str (split[0], &band_mask)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse band mask from string: '%s'", split[0]); return FALSE; } if (band_mask == MM_HUAWEI_SYSCFG_BAND_ANY || band_mask == MM_HUAWEI_SYSCFGEX_BAND_ANY_LTE) continue; /* skip "All bands" mask */ *bands_out |= band_mask; } return TRUE; } gboolean mm_huawei_parse_syscfg_test (const gchar *response, GArray **supported_modes_out, guint64 *supported_gsm_umts_bands_out, GError **error) { g_auto(GStrv) split = NULL; GError *inner_error = NULL; if (!response || !g_str_has_prefix (response, "^SYSCFG:")) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^SYSCFG prefix"); return FALSE; } /* Examples: * * ^SYSCFG:(2,13,14,16), * (0-3), * ((2000000400380,"GSM900/GSM1800/WCDMA900/WCDMA2100"),(280000,"GSM850/GSM1900"),(3fffffff,"All bands")), * (0-2), * (0-4) * * Note that "all bands" bit mask doesn't include all bands. */ split = split_groups (mm_strip_tag (response, "^SYSCFG:"), error); /* We expect 5 string chunks */ if (!split || g_strv_length (split) < 5) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected ^SYSCFG format"); return FALSE; } /* Parse supported mode combinations, if requested */ if (supported_modes_out) { *supported_modes_out = parse_syscfg_modes (split[0], split[1], NULL, &inner_error); if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } } /* Parse supported bands, if requested */ if (supported_gsm_umts_bands_out) { if (!parse_syscfg_bands (split[2], supported_gsm_umts_bands_out, &inner_error)) { g_propagate_error (error, inner_error); return FALSE; } } return TRUE; } /*****************************************************************************/ /* ^SYSCFG response parser */ gboolean mm_huawei_parse_syscfg_response (const gchar *response, MMModemModeCombination *current_mode_out, guint64 *current_gsm_umts_bands_out, GError **error) { g_auto(GStrv) split = NULL; guint mode; guint acqorder; guint64 bands; if (!response || !g_str_has_prefix (response, "^SYSCFG:")) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^SYSCFG prefix"); return FALSE; } /* Format: * * ^SYSCFG: ,,,, */ 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) || !mm_get_u64_from_hex_str (split[2], &bands)) { /* Dump error to upper layer */ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected ^SYSCFG response: '%s'", response); return FALSE; } if (current_mode_out) { /* 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; if (mode == 2) { /* auto */ current_mode_out->allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G; if (acqorder == 1) current_mode_out->preferred = MM_MODEM_MODE_2G; else if (acqorder == 2) current_mode_out->preferred = MM_MODEM_MODE_3G; else current_mode_out->preferred = MM_MODEM_MODE_NONE; } else if (mode == 13) { /* GSM only */ current_mode_out->allowed = MM_MODEM_MODE_2G; current_mode_out->preferred = MM_MODEM_MODE_NONE; } else if (mode == 14) { /* WCDMA only */ current_mode_out->allowed = MM_MODEM_MODE_3G; current_mode_out->preferred = MM_MODEM_MODE_NONE; } else { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unhandled SYSCFG combination (%d,%d)", mode, acqorder); return FALSE; } } if (current_gsm_umts_bands_out) *current_gsm_umts_bands_out = bands; return TRUE; } /*****************************************************************************/ /* ^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 syscfgex_mode_to_mm_modem_mode (const gchar *mode_str, MMModemMode *mm_mode_out) { g_assert (mode_str); g_assert (mm_mode_out); if (g_str_has_prefix (mode_str, "00")) /* auto */ *mm_mode_out = MM_MODEM_MODE_ANY; else if (g_str_has_prefix (mode_str, "01")) /* GSM */ *mm_mode_out = MM_MODEM_MODE_2G; else if (g_str_has_prefix (mode_str, "02")) /* WCDMA */ *mm_mode_out = MM_MODEM_MODE_3G; else if (g_str_has_prefix (mode_str, "03")) /* LTE */ *mm_mode_out = MM_MODEM_MODE_4G; else /* 04 -> CDMA1x, 06 -> WiMAX, 07 -> EV-DO, 99 -> no change */ return FALSE; return TRUE; } 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 (syscfgex_mode_to_mm_modem_mode (&mode_str[n], &mode)) { /* 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; } gboolean mm_huawei_parse_syscfgex_test (const gchar *response, GArray **supported_modes_out, guint64 *supported_gsm_umts_bands_out, guint64 *supported_lte_bands_out, GError **error) { g_auto(GStrv) split = NULL; GError *inner_error = NULL; GArray *modes; if (!response || !g_str_has_prefix (response, "^SYSCFGEX:")) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^SYSCFGEX prefix"); return FALSE; } /* 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); /* We expect 5 string chunks */ if (!split || g_strv_length (split) < 5) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected ^SYSCFGEX format"); return FALSE; } if (supported_modes_out != NULL) { modes = parse_mode_combination_string_list (split[0], &inner_error); if (inner_error) { g_propagate_error (error, inner_error); return FALSE; } *supported_modes_out = modes; } if (supported_gsm_umts_bands_out != NULL && !parse_syscfg_bands (split[1], supported_gsm_umts_bands_out, &inner_error)) { g_propagate_error (error, inner_error); return FALSE; } if (supported_lte_bands_out != NULL && !parse_syscfg_bands (split[4], supported_lte_bands_out, &inner_error)) { g_propagate_error (error, inner_error); return FALSE; } return TRUE; } /*****************************************************************************/ /* ^SYSCFGEX response parser */ gboolean mm_huawei_parse_syscfgex_response (const gchar *response, MMModemModeCombination *current_mode_out, guint64 *current_gsm_umts_bands_out, guint64 *current_lte_bands_out, GError **error) { g_auto(GStrv) split = NULL; 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 FALSE; } /* Format: * * ^SYSCFGEX: "00",3FFFFFFF,1,2,7FFFFFFFFFFFFFFF * ^SYSCFGEX: ,,,, */ 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"); return FALSE; } if (current_mode_out != NULL) { /* current modes is a string without delimiters, but modes have two chars each. * The preference is given by the order from left to right. * * Ex: "00" -> automatic * "030102" -> LTE > GSM > WCDMA */ MMModemMode first_mode; current_mode_out->allowed = MM_MODEM_MODE_NONE; current_mode_out->preferred = MM_MODEM_MODE_NONE; str = mm_strip_quotes (split[0]); len = strlen(str); if (!len || len % 2 != 0) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unexpected ^SYSCFGEX mode string length: %" G_GSIZE_FORMAT, len); return FALSE; } for (i = 0; i < len; i += 2) { MMModemMode mode; if (syscfgex_mode_to_mm_modem_mode (&str[i], &mode)) { if (mode == MM_MODEM_MODE_ANY) { current_mode_out->allowed = MM_MODEM_MODE_ANY; current_mode_out->preferred = MM_MODEM_MODE_NONE; break; } /* if only one mode, keep NONE as preferred mode */ if (i == 0) first_mode = mode; /* ... but if multiple modes are listed, the first is preferred */ else if (i == 2) current_mode_out->preferred = first_mode; current_mode_out->allowed |= mode; } } } if (current_gsm_umts_bands_out != NULL) { *current_gsm_umts_bands_out = 0; if (*split[1] != '\0' && !mm_get_u64_from_hex_str (split[1], current_gsm_umts_bands_out)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse band mask from string: '%s'", split[1]); return FALSE; } } if (current_lte_bands_out != NULL) { *current_lte_bands_out = 0; if (*split[4] != '\0' && !mm_get_u64_from_hex_str (split[4], current_lte_bands_out)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse band mask from string: '%s'", split[1]); return FALSE; } } return TRUE; } /*****************************************************************************/ /* ^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), 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>,,, */ 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); }