diff options
Diffstat (limited to 'plugins/ublox/mm-modem-helpers-ublox.c')
-rw-r--r-- | plugins/ublox/mm-modem-helpers-ublox.c | 2065 |
1 files changed, 2065 insertions, 0 deletions
diff --git a/plugins/ublox/mm-modem-helpers-ublox.c b/plugins/ublox/mm-modem-helpers-ublox.c new file mode 100644 index 00000000..bb0e02ac --- /dev/null +++ b/plugins/ublox/mm-modem-helpers-ublox.c @@ -0,0 +1,2065 @@ +/* -*- 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) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <glib.h> +#include <string.h> + +#include "mm-log.h" +#include "mm-modem-helpers.h" +#include "mm-modem-helpers-ublox.h" + +/*****************************************************************************/ +/* +UPINCNT response parser */ + +gboolean +mm_ublox_parse_upincnt_response (const gchar *response, + guint *out_pin_attempts, + guint *out_pin2_attempts, + guint *out_puk_attempts, + guint *out_puk2_attempts, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + guint pin_attempts = 0; + guint pin2_attempts = 0; + guint puk_attempts = 0; + guint puk2_attempts = 0; + gboolean success = TRUE; + + g_assert (out_pin_attempts); + g_assert (out_pin2_attempts); + g_assert (out_puk_attempts); + g_assert (out_puk2_attempts); + + /* Response may be e.g.: + * +UPINCNT: 3,3,10,10 + */ + r = g_regex_new ("\\+UPINCNT: (\\d+),(\\d+),(\\d+),(\\d+)(?:\\r\\n)?", 0, 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)) { + if (!mm_get_uint_from_match_info (match_info, 1, &pin_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PIN attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 2, &pin2_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PIN2 attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 3, &puk_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PUK attempts"); + goto out; + } + if (!mm_get_uint_from_match_info (match_info, 4, &puk2_attempts)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Couldn't parse PUK2 attempts"); + goto out; + } + success = TRUE; + } + +out: + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!success) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +UPINCNT response: '%s'", response); + return FALSE; + } + + *out_pin_attempts = pin_attempts; + *out_pin2_attempts = pin2_attempts; + *out_puk_attempts = puk_attempts; + *out_puk2_attempts = puk2_attempts; + return TRUE; +} + +/*****************************************************************************/ +/* UUSBCONF? response parser */ + +gboolean +mm_ublox_parse_uusbconf_response (const gchar *response, + MMUbloxUsbProfile *out_profile, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + MMUbloxUsbProfile profile = MM_UBLOX_USB_PROFILE_UNKNOWN; + + g_assert (out_profile != NULL); + + /* Response may be e.g.: + * +UUSBCONF: 3,"RNDIS",,"0x1146" + * +UUSBCONF: 2,"ECM",,"0x1143" + * +UUSBCONF: 0,"",,"0x1141" + * + * Note: we don't rely on the PID; assuming future new modules will + * have a different PID but they may keep the profile names. + */ + r = g_regex_new ("\\+UUSBCONF: (\\d+),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 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)) { + gchar *profile_name; + + profile_name = mm_get_string_unquoted_from_match_info (match_info, 2); + if (profile_name && profile_name[0]) { + if (g_str_equal (profile_name, "RNDIS")) + profile = MM_UBLOX_USB_PROFILE_RNDIS; + else if (g_str_equal (profile_name, "ECM")) + profile = MM_UBLOX_USB_PROFILE_ECM; + else + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unknown USB profile: '%s'", profile_name); + } else + profile = MM_UBLOX_USB_PROFILE_BACK_COMPATIBLE; + g_free (profile_name); + } + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (profile == MM_UBLOX_USB_PROFILE_UNKNOWN) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse profile response"); + return FALSE; + } + + *out_profile = profile; + return TRUE; +} + +/*****************************************************************************/ +/* UBMCONF? response parser */ + +gboolean +mm_ublox_parse_ubmconf_response (const gchar *response, + MMUbloxNetworkingMode *out_mode, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + MMUbloxNetworkingMode mode = MM_UBLOX_NETWORKING_MODE_UNKNOWN; + + g_assert (out_mode != NULL); + + /* Response may be e.g.: + * +UBMCONF: 1 + * +UBMCONF: 2 + */ + r = g_regex_new ("\\+UBMCONF: (\\d+)(?:\\r\\n)?", 0, 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 mode_id = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &mode_id)) { + switch (mode_id) { + case 1: + mode = MM_UBLOX_NETWORKING_MODE_ROUTER; + break; + case 2: + mode = MM_UBLOX_NETWORKING_MODE_BRIDGE; + break; + default: + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Unknown mode id: '%u'", mode_id); + break; + } + } + } + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (mode == MM_UBLOX_NETWORKING_MODE_UNKNOWN) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse networking mode response"); + return FALSE; + } + + *out_mode = mode; + return TRUE; +} + +/*****************************************************************************/ +/* UIPADDR=N response parser */ + +gboolean +mm_ublox_parse_uipaddr_response (const gchar *response, + guint *out_cid, + gchar **out_if_name, + gchar **out_ipv4_address, + gchar **out_ipv4_subnet, + gchar **out_ipv6_global_address, + gchar **out_ipv6_link_local_address, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + guint cid = 0; + gchar *if_name = NULL; + gchar *ipv4_address = NULL; + gchar *ipv4_subnet = NULL; + gchar *ipv6_global_address = NULL; + gchar *ipv6_link_local_address = NULL; + + /* Response may be e.g.: + * +UIPADDR: 1,"ccinet0","5.168.120.13","255.255.255.0","","" + * +UIPADDR: 2,"ccinet1","","","2001::2:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64" + * +UIPADDR: 3,"ccinet2","5.10.100.2","255.255.255.0","2001::1:200:FF:FE00:0/64","FE80::200:FF:FE00:0/64" + * + * We assume only ONE line is returned; because we request +UIPADDR with a specific N CID. + */ + r = g_regex_new ("\\+UIPADDR: (\\d+),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)(?:\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + if (inner_error) + goto out; + + if (!g_match_info_matches (match_info)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +UIPADDR response"); + goto out; + } + + if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid"); + goto out; + } + + if (out_if_name && !(if_name = mm_get_string_unquoted_from_match_info (match_info, 2))) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing interface name"); + goto out; + } + + /* Remaining strings are optional */ + + if (out_ipv4_address) + ipv4_address = mm_get_string_unquoted_from_match_info (match_info, 3); + + if (out_ipv4_subnet) + ipv4_subnet = mm_get_string_unquoted_from_match_info (match_info, 4); + + if (out_ipv6_global_address) + ipv6_global_address = mm_get_string_unquoted_from_match_info (match_info, 5); + + if (out_ipv6_link_local_address) + ipv6_link_local_address = mm_get_string_unquoted_from_match_info (match_info, 6); + +out: + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_free (if_name); + g_free (ipv4_address); + g_free (ipv4_subnet); + g_free (ipv6_global_address); + g_free (ipv6_link_local_address); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (out_cid) + *out_cid = cid; + if (out_if_name) + *out_if_name = if_name; + if (out_ipv4_address) + *out_ipv4_address = ipv4_address; + if (out_ipv4_subnet) + *out_ipv4_subnet = ipv4_subnet; + if (out_ipv6_global_address) + *out_ipv6_global_address = ipv6_global_address; + if (out_ipv6_link_local_address) + *out_ipv6_link_local_address = ipv6_link_local_address; + return TRUE; +} + +/*****************************************************************************/ +/* CFUN? response parser */ + +gboolean +mm_ublox_parse_cfun_response (const gchar *response, + MMModemPowerState *out_state, + GError **error) +{ + guint state; + + if (!mm_3gpp_parse_cfun_query_response (response, &state, error)) + return FALSE; + + switch (state) { + case 1: + *out_state = MM_MODEM_POWER_STATE_ON; + return TRUE; + case 0: + /* minimum functionality */ + case 4: + /* airplane mode */ + case 19: + /* minimum functionality with SIM deactivated */ + *out_state = MM_MODEM_POWER_STATE_LOW; + return TRUE; + default: + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown +CFUN state: %u", state); + return FALSE; + } +} + +/*****************************************************************************/ +/* URAT=? response parser */ + +/* Index of the array is the ublox-specific value */ +static const MMModemMode ublox_combinations[] = { + ( MM_MODEM_MODE_2G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_3G ), + ( MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_4G ), + ( MM_MODEM_MODE_4G ), +}; + +GArray * +mm_ublox_parse_urat_test_response (const gchar *response, + gpointer log_object, + GError **error) +{ + GArray *combinations = NULL; + GArray *selected = NULL; + GArray *preferred = NULL; + gchar **split; + guint split_len; + GError *inner_error = NULL; + guint i; + + /* + * E.g.: + * AT+URAT=? + * +URAT: (0-6),(0,2,3) + */ + response = mm_strip_tag (response, "+URAT:"); + split = mm_split_string_groups (response); + split_len = g_strv_length (split); + if (split_len > 2 || split_len < 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected number of groups in +URAT=? response: %u", g_strv_length (split)); + goto out; + } + + /* The selected list must have values */ + selected = mm_parse_uint_list (split[0], &inner_error); + if (inner_error) + goto out; + + if (!selected) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No selected RAT values given in +URAT=? response"); + goto out; + } + + /* For our purposes, the preferred list may be empty */ + preferred = mm_parse_uint_list (split[1], &inner_error); + if (inner_error) + goto out; + + /* Build array of combinations */ + combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination)); + + for (i = 0; i < selected->len; i++) { + guint selected_value; + MMModemModeCombination combination; + guint j; + + selected_value = g_array_index (selected, guint, i); + if (selected_value >= G_N_ELEMENTS (ublox_combinations)) { + mm_obj_warn (log_object, "unexpected AcT value: %u", selected_value); + continue; + } + + /* Combination without any preferred */ + combination.allowed = ublox_combinations[selected_value]; + combination.preferred = MM_MODEM_MODE_NONE; + g_array_append_val (combinations, combination); + + if (mm_count_bits_set (combination.allowed) == 1) + continue; + + if (!preferred) + continue; + + for (j = 0; j < preferred->len; j++) { + guint preferred_value; + + preferred_value = g_array_index (preferred, guint, j); + if (preferred_value >= G_N_ELEMENTS (ublox_combinations)) { + mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value); + continue; + } + combination.preferred = ublox_combinations[preferred_value]; + if (mm_count_bits_set (combination.preferred) != 1) { + mm_obj_warn (log_object, "AcT preferred value should be a single AcT: %u", preferred_value); + continue; + } + if (!(combination.allowed & combination.preferred)) + continue; + g_array_append_val (combinations, combination); + } + } + + if (combinations->len == 0) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No combinations built from +URAT=? response"); + goto out; + } + +out: + g_strfreev (split); + if (selected) + g_array_unref (selected); + if (preferred) + g_array_unref (preferred); + + if (inner_error) { + if (combinations) + g_array_unref (combinations); + g_propagate_error (error, inner_error); + return NULL; + } + + return combinations; +} + +typedef struct { + const gchar *model; + SettingsUpdateMethod method; + FeatureSupport uact; + FeatureSupport ubandsel; + MMModemMode mode; + MMModemBand bands_2g[4]; + MMModemBand bands_3g[6]; + MMModemBand bands_4g[12]; +} BandConfiguration; + +static const BandConfiguration band_configuration[] = { + { + .model = "SARA-G300", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS } + }, + { + .model = "SARA-G310", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "SARA-G340", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS } + }, + { + .model = "SARA-G350", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "SARA-G450", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS } + }, + { + .model = "LISA-U200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U230", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "LISA-U260", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "LISA-U270", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_6, MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U260", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "SARA-U270", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 } + }, + { + .model = "SARA-U280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 } + }, + { + .model = "MPCI-L201", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "MPCI-L200", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "MPCI-L210", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "MPCI-L220", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 } + }, + { + .model = "MPCI-L280", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L200", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_4, + MM_MODEM_BAND_UTRAN_2, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "TOBY-L201", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } + }, + { + .model = "TOBY-L210", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "TOBY-L220", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_19 } + }, + { + .model = "TOBY-L280", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L4006", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_4, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, + MM_MODEM_BAND_EUTRAN_29 } + }, + { + .model = "TOBY-L4106", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, + MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_38 } + }, + { + .model = "TOBY-L4206", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_9, + MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "TOBY-L4906", + .method = SETTINGS_UPDATE_METHOD_CFUN, + .uact = FEATURE_SUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_39, + MM_MODEM_BAND_EUTRAN_40, MM_MODEM_BAND_EUTRAN_41 } + }, + { + .model = "TOBY-R200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_8, MM_MODEM_BAND_UTRAN_2, + MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "TOBY-R202", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R202", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_5, MM_MODEM_BAND_UTRAN_2 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R203", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_12 } + }, + { + .model = "LARA-R204", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_13 } + }, + { + .model = "LARA-R211", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "LARA-R280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, + .bands_3g = { MM_MODEM_BAND_UTRAN_1 }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "LARA-R3121", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_3, MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N200", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_8 } + }, + { + .model = "SARA-N201", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_5 } + }, + { + .model = "SARA-N210", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N211", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_8, MM_MODEM_BAND_EUTRAN_20 } + }, + { + .model = "SARA-N280", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_SUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_28 } + }, + { + .model = "SARA-R410M-52B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13 } + }, + { + .model = "SARA-R410M-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 } + }, + { + .model = "SARA-R412M-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, + .bands_2g = { MM_MODEM_BAND_G850, MM_MODEM_BAND_EGSM, MM_MODEM_BAND_DCS, MM_MODEM_BAND_PCS }, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28, MM_MODEM_BAND_EUTRAN_39 } + }, + { + .model = "SARA-N410-02B", + .method = SETTINGS_UPDATE_METHOD_COPS, + .uact = FEATURE_UNSUPPORTED, + .ubandsel = FEATURE_UNSUPPORTED, + .mode = MM_MODEM_MODE_4G, + .bands_4g = { MM_MODEM_BAND_EUTRAN_1, MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_3, + MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_8, + MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_19, + MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_28 } + }, +}; + +gboolean +mm_ublox_get_support_config (const gchar *model, + UbloxSupportConfig *config, + GError **error) +{ + guint i; + + if (!model) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Support configuration unknown for unknown model"); + return FALSE; + } + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + /* NOTE: matching by prefix! */ + if (g_str_has_prefix (model, band_configuration[i].model)) { + config->loaded = TRUE; + config->method = band_configuration[i].method; + config->uact = band_configuration[i].uact; + config->ubandsel = band_configuration[i].ubandsel; + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No support configuration found for modem: %s", model); + return FALSE; +} + +/*****************************************************************************/ +/* Supported modes loading */ + +static MMModemMode +supported_modes_per_model (const gchar *model) +{ + MMModemMode mode; + guint i; + + mode = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G; + + if (model) { + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) + if (g_str_has_prefix (model, band_configuration[i].model)) { + mode = band_configuration[i].mode; + return mode;; + } + } + + return mode; +} + +GArray * +mm_ublox_filter_supported_modes (const gchar *model, + GArray *combinations, + gpointer logger, + GError **error) +{ + MMModemModeCombination mode; + GArray *all; + GArray *filtered; + + /* Model not specified? */ + if (!model) + return combinations; + + /* AT+URAT=? lies; we need an extra per-device filtering, thanks u-blox. + * Don't know all PIDs for all devices, so model string based filtering... */ + + mode.allowed = supported_modes_per_model (model); + mode.preferred = MM_MODEM_MODE_NONE; + + /* Nothing filtered? */ + if (mode.allowed == supported_modes_per_model (NULL)) + return combinations; + + all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1); + g_array_append_val (all, mode); + filtered = mm_filter_supported_modes (all, combinations, logger); + g_array_unref (all); + g_array_unref (combinations); + + /* Error if nothing left */ + if (filtered->len == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid mode combinations built after filtering (model %s)", model); + g_array_unref (filtered); + return NULL; + } + + return filtered; +} + +/*****************************************************************************/ +/* Supported bands loading */ + +GArray * +mm_ublox_get_supported_bands (const gchar *model, + gpointer log_object, + GError **error) +{ + MMModemMode mode; + GArray *bands; + guint i, j; + + mode = supported_modes_per_model (model); + bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) { + mm_obj_dbg (log_object, "known supported bands found for model: %s", band_configuration[i].model); + break; + } + } + + if (i == G_N_ELEMENTS (band_configuration)) { + mm_obj_warn (log_object, "unknown model name given when looking for supported bands: %s", model); + return NULL; + } + + mode = band_configuration[i].mode; + + if (mode & MM_MODEM_MODE_2G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_2g[j]); + } + } + + if (mode & MM_MODEM_MODE_3G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_3g[j]); + } + } + + if (mode & MM_MODEM_MODE_4G) { + for (j = 0; j < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[j]; j++) { + bands = g_array_append_val (bands, band_configuration[i].bands_4g[j]); + } + } + + if (bands->len == 0) { + g_array_unref (bands); + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No valid supported bands loaded"); + return NULL; + } + + return bands; +} + +typedef struct { + guint num; + MMModemBand band[4]; +} NumToBand; + +/* 2G GSM Band Frequencies */ +static const NumToBand num_bands_2g [] = { + { .num = 850, .band = { MM_MODEM_BAND_G850 } }, + { .num = 900, .band = { MM_MODEM_BAND_EGSM } }, + { .num = 1900, .band = { MM_MODEM_BAND_PCS } }, + { .num = 1800, .band = { MM_MODEM_BAND_DCS } }, +}; + +/* 3G UMTS Band Frequencies */ +static const NumToBand num_bands_3g [] = { + { .num = 800, .band = { MM_MODEM_BAND_UTRAN_6 } }, + { .num = 850, .band = { MM_MODEM_BAND_UTRAN_5 } }, + { .num = 900, .band = { MM_MODEM_BAND_UTRAN_8 } }, + { .num = 1700, .band = { MM_MODEM_BAND_UTRAN_4 } }, + { .num = 1900, .band = { MM_MODEM_BAND_UTRAN_2 } }, + { .num = 2100, .band = { MM_MODEM_BAND_UTRAN_1 } }, +}; + +/* 4G LTE Band Frequencies */ +static const NumToBand num_bands_4g [] = { + { .num = 700, .band = { MM_MODEM_BAND_EUTRAN_12, MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 } }, + { .num = 800, .band = { MM_MODEM_BAND_EUTRAN_20, MM_MODEM_BAND_EUTRAN_27 } }, + { .num = 850, .band = { MM_MODEM_BAND_EUTRAN_5, MM_MODEM_BAND_EUTRAN_18, MM_MODEM_BAND_EUTRAN_19, MM_MODEM_BAND_EUTRAN_26 } }, + { .num = 900, .band = { MM_MODEM_BAND_EUTRAN_8 } }, + { .num = 1700, .band = { MM_MODEM_BAND_EUTRAN_4, MM_MODEM_BAND_EUTRAN_10 } }, + { .num = 1800, .band = { MM_MODEM_BAND_EUTRAN_3 } }, + { .num = 1900, .band = { MM_MODEM_BAND_EUTRAN_2, MM_MODEM_BAND_EUTRAN_39 } }, + { .num = 2100, .band = { MM_MODEM_BAND_EUTRAN_1 } }, + { .num = 2300, .band = { MM_MODEM_BAND_EUTRAN_40 } }, + { .num = 2500, .band = { MM_MODEM_BAND_EUTRAN_41 } }, + { .num = 2600, .band = { MM_MODEM_BAND_EUTRAN_7, MM_MODEM_BAND_EUTRAN_38 } }, +}; +/*****************************************************************************/ +/* +UBANDSEL? response parser */ + +static MMModemBand +num_to_band_2g (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) { + if (num == num_bands_2g[i].num) + return num_bands_2g[i].band[0]; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static MMModemBand +num_to_band_3g (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) { + if (num == num_bands_3g[i].num) + return num_bands_3g[i].band[0]; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static guint +band_to_num (MMModemBand band) +{ + guint i, j; + + /* Search 2G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_2g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_2g[i].band) && num_bands_2g[i].band[j]; j++) { + if (band == num_bands_2g[i].band[j]) + return num_bands_2g[i].num; + } + } + + /* Search 3G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_3g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_3g[i].band) && num_bands_3g[i].band[j]; j++) { + if (band == num_bands_3g[i].band[j]) + return num_bands_3g[i].num; + } + } + + /* Search 4G list */ + for (i = 0; i < G_N_ELEMENTS (num_bands_4g); i++) { + for (j = 0; j < G_N_ELEMENTS (num_bands_4g[i].band) && num_bands_4g[i].band[j]; j++) { + if (band == num_bands_4g[i].band[j]) + return num_bands_4g[i].num; + } + } + + /* Should never happen */ + return 0; +} + +static void +append_bands (GArray *bands, + guint ubandsel_value, + MMModemMode mode, + const gchar *model, + gpointer log_object) +{ + guint i, j, k, x; + MMModemBand band; + + /* Find Modem Model Index in band_configuration */ + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) { + mm_obj_dbg (log_object, "known bands found for model: %s", band_configuration[i].model); + break; + } + } + + if (i == G_N_ELEMENTS (band_configuration)) { + mm_obj_warn (log_object, "unknown model name given when looking for bands: %s", model); + return; + } + + if (mode & MM_MODEM_MODE_2G) { + band = num_to_band_2g (ubandsel_value); + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_2g); x++) { + if (band_configuration[i].bands_2g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + + if (mode & MM_MODEM_MODE_3G) { + band = num_to_band_3g (ubandsel_value); + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_3g); x++) { + if (band_configuration[i].bands_3g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + + /* Note: The weird code segment below is to separate out specific LTE bands since + * UBANDSEL? reports back the frequency of the band and not the band itself. + */ + + if (mode & MM_MODEM_MODE_4G) { + for (j = 0; j < G_N_ELEMENTS (num_bands_4g); j++) { + if (ubandsel_value == num_bands_4g[j].num) { + for (k = 0; k < G_N_ELEMENTS (num_bands_4g[j].band); k++) { + band = num_bands_4g[j].band[k]; + if (band != MM_MODEM_BAND_UNKNOWN) { + for (x = 0; x < G_N_ELEMENTS (band_configuration[i].bands_4g); x++) { + if (band_configuration[i].bands_4g[x] == band) { + g_array_append_val (bands, band); + break; + } + } + } + } + break; + } + } + } +} + +GArray * +mm_ublox_parse_ubandsel_response (const gchar *response, + const gchar *model, + gpointer log_object, + GError **error) +{ + GArray *array_values = NULL; + GArray *array = NULL; + gchar *dupstr = NULL; + GError *inner_error = NULL; + guint i; + MMModemMode mode; + + if (!g_str_has_prefix (response, "+UBANDSEL")) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +UBANDSEL response: '%s'", response); + goto out; + } + + /* Response may be e.g.: + * +UBANDSEL: 850,900,1800,1900 + */ + dupstr = g_strchomp (g_strdup (mm_strip_tag (response, "+UBANDSEL:"))); + + array_values = mm_parse_uint_list (dupstr, &inner_error); + if (!array_values) + goto out; + + /* Convert list of ubandsel numbers to MMModemBand values */ + mode = supported_modes_per_model (model); + array = g_array_new (FALSE, FALSE, sizeof (MMModemBand)); + for (i = 0; i < array_values->len; i++) + append_bands (array, g_array_index (array_values, guint, i), mode, model, log_object); + + if (!array->len) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No known band selection values matched in +UBANDSEL response: '%s'", response); + goto out; + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + g_clear_pointer (&array, g_array_unref); + } + g_clear_pointer (&array_values, g_array_unref); + g_free (dupstr); + return array; +} + +/*****************************************************************************/ +/* UBANDSEL=X command builder */ + +static gint +ubandsel_num_cmp (const guint *a, const guint *b) +{ + return (*a - *b); +} + +gchar * +mm_ublox_build_ubandsel_set_command (GArray *bands, + const gchar *model, + GError **error) +{ + GString *command = NULL; + GArray *ubandsel_nums; + guint num; + guint i, j, k; + + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) + return g_strdup ("+UBANDSEL=0"); + + for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) { + if (g_str_has_prefix (model, band_configuration[i].model)) + break; + } + + if (i == G_N_ELEMENTS (band_configuration)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unknown modem model %s", model); + return NULL; + } + + ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), bands->len); + + for (j = 0; j < bands->len; j++) { + MMModemBand band; + gboolean found = FALSE; + + band = g_array_index (bands, MMModemBand, j); + + /* Check to see if band is supported by the model */ + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_2g) && band_configuration[i].bands_2g[k]; k++) { + if (band == band_configuration[i].bands_2g[k]) + found = TRUE; + } + + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_3g) && band_configuration[i].bands_3g[k]; k++) { + if (band == band_configuration[i].bands_3g[k]) + found = TRUE; + } + + for (k = 0; !found && k < G_N_ELEMENTS (band_configuration[i].bands_4g) && band_configuration[i].bands_4g[k]; k++) { + if (band == band_configuration[i].bands_4g[k]) + found = TRUE; + } + + if (found) { + num = band_to_num (band); + g_assert (num != 0); + g_array_append_val (ubandsel_nums, num); + } + } + + if (ubandsel_nums->len == 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Given band combination is unsupported"); + g_array_unref (ubandsel_nums); + return NULL; + } + + if (ubandsel_nums->len > 1) + g_array_sort (ubandsel_nums, (GCompareFunc) ubandsel_num_cmp); + + /* Build command */ + command = g_string_new ("+UBANDSEL="); + for (i = 0; i < ubandsel_nums->len; i++) + g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", g_array_index (ubandsel_nums, guint, i)); + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* Get mode to apply when ANY */ + +MMModemMode +mm_ublox_get_modem_mode_any (const GArray *combinations) +{ + guint i; + MMModemMode any = MM_MODEM_MODE_NONE; + guint any_bits_set = 0; + + for (i = 0; i < combinations->len; i++) { + MMModemModeCombination *combination; + guint bits_set; + + combination = &g_array_index (combinations, MMModemModeCombination, i); + if (combination->preferred != MM_MODEM_MODE_NONE) + continue; + bits_set = mm_count_bits_set (combination->allowed); + if (bits_set > any_bits_set) { + any_bits_set = bits_set; + any = combination->allowed; + } + } + + /* If combinations were processed via mm_ublox_parse_urat_test_response(), + * we're sure that there will be at least one combination with preferred + * 'none', so there must be some valid combination as result */ + g_assert (any != MM_MODEM_MODE_NONE); + return any; +} + +/*****************************************************************************/ +/* UACT common config */ + +typedef struct { + guint num; + MMModemBand band; +} UactBandConfig; + +static const UactBandConfig uact_band_config[] = { + /* GSM bands */ + { .num = 900, .band = MM_MODEM_BAND_EGSM }, + { .num = 1800, .band = MM_MODEM_BAND_DCS }, + { .num = 1900, .band = MM_MODEM_BAND_PCS }, + { .num = 850, .band = MM_MODEM_BAND_G850 }, + { .num = 450, .band = MM_MODEM_BAND_G450 }, + { .num = 480, .band = MM_MODEM_BAND_G480 }, + { .num = 750, .band = MM_MODEM_BAND_G750 }, + { .num = 380, .band = MM_MODEM_BAND_G380 }, + { .num = 410, .band = MM_MODEM_BAND_G410 }, + { .num = 710, .band = MM_MODEM_BAND_G710 }, + { .num = 810, .band = MM_MODEM_BAND_G810 }, + /* UMTS bands */ + { .num = 1, .band = MM_MODEM_BAND_UTRAN_1 }, + { .num = 2, .band = MM_MODEM_BAND_UTRAN_2 }, + { .num = 3, .band = MM_MODEM_BAND_UTRAN_3 }, + { .num = 4, .band = MM_MODEM_BAND_UTRAN_4 }, + { .num = 5, .band = MM_MODEM_BAND_UTRAN_5 }, + { .num = 6, .band = MM_MODEM_BAND_UTRAN_6 }, + { .num = 7, .band = MM_MODEM_BAND_UTRAN_7 }, + { .num = 8, .band = MM_MODEM_BAND_UTRAN_8 }, + { .num = 9, .band = MM_MODEM_BAND_UTRAN_9 }, + { .num = 10, .band = MM_MODEM_BAND_UTRAN_10 }, + { .num = 11, .band = MM_MODEM_BAND_UTRAN_11 }, + { .num = 12, .band = MM_MODEM_BAND_UTRAN_12 }, + { .num = 13, .band = MM_MODEM_BAND_UTRAN_13 }, + { .num = 14, .band = MM_MODEM_BAND_UTRAN_14 }, + { .num = 19, .band = MM_MODEM_BAND_UTRAN_19 }, + { .num = 20, .band = MM_MODEM_BAND_UTRAN_20 }, + { .num = 21, .band = MM_MODEM_BAND_UTRAN_21 }, + { .num = 22, .band = MM_MODEM_BAND_UTRAN_22 }, + { .num = 25, .band = MM_MODEM_BAND_UTRAN_25 }, + /* LTE bands */ + { .num = 101, .band = MM_MODEM_BAND_EUTRAN_1 }, + { .num = 102, .band = MM_MODEM_BAND_EUTRAN_2 }, + { .num = 103, .band = MM_MODEM_BAND_EUTRAN_3 }, + { .num = 104, .band = MM_MODEM_BAND_EUTRAN_4 }, + { .num = 105, .band = MM_MODEM_BAND_EUTRAN_5 }, + { .num = 106, .band = MM_MODEM_BAND_EUTRAN_6 }, + { .num = 107, .band = MM_MODEM_BAND_EUTRAN_7 }, + { .num = 108, .band = MM_MODEM_BAND_EUTRAN_8 }, + { .num = 109, .band = MM_MODEM_BAND_EUTRAN_9 }, + { .num = 110, .band = MM_MODEM_BAND_EUTRAN_10 }, + { .num = 111, .band = MM_MODEM_BAND_EUTRAN_11 }, + { .num = 112, .band = MM_MODEM_BAND_EUTRAN_12 }, + { .num = 113, .band = MM_MODEM_BAND_EUTRAN_13 }, + { .num = 114, .band = MM_MODEM_BAND_EUTRAN_14 }, + { .num = 117, .band = MM_MODEM_BAND_EUTRAN_17 }, + { .num = 118, .band = MM_MODEM_BAND_EUTRAN_18 }, + { .num = 119, .band = MM_MODEM_BAND_EUTRAN_19 }, + { .num = 120, .band = MM_MODEM_BAND_EUTRAN_20 }, + { .num = 121, .band = MM_MODEM_BAND_EUTRAN_21 }, + { .num = 122, .band = MM_MODEM_BAND_EUTRAN_22 }, + { .num = 123, .band = MM_MODEM_BAND_EUTRAN_23 }, + { .num = 124, .band = MM_MODEM_BAND_EUTRAN_24 }, + { .num = 125, .band = MM_MODEM_BAND_EUTRAN_25 }, + { .num = 126, .band = MM_MODEM_BAND_EUTRAN_26 }, + { .num = 127, .band = MM_MODEM_BAND_EUTRAN_27 }, + { .num = 128, .band = MM_MODEM_BAND_EUTRAN_28 }, + { .num = 129, .band = MM_MODEM_BAND_EUTRAN_29 }, + { .num = 130, .band = MM_MODEM_BAND_EUTRAN_30 }, + { .num = 131, .band = MM_MODEM_BAND_EUTRAN_31 }, + { .num = 132, .band = MM_MODEM_BAND_EUTRAN_32 }, + { .num = 133, .band = MM_MODEM_BAND_EUTRAN_33 }, + { .num = 134, .band = MM_MODEM_BAND_EUTRAN_34 }, + { .num = 135, .band = MM_MODEM_BAND_EUTRAN_35 }, + { .num = 136, .band = MM_MODEM_BAND_EUTRAN_36 }, + { .num = 137, .band = MM_MODEM_BAND_EUTRAN_37 }, + { .num = 138, .band = MM_MODEM_BAND_EUTRAN_38 }, + { .num = 139, .band = MM_MODEM_BAND_EUTRAN_39 }, + { .num = 140, .band = MM_MODEM_BAND_EUTRAN_40 }, + { .num = 141, .band = MM_MODEM_BAND_EUTRAN_41 }, + { .num = 142, .band = MM_MODEM_BAND_EUTRAN_42 }, + { .num = 143, .band = MM_MODEM_BAND_EUTRAN_43 }, + { .num = 144, .band = MM_MODEM_BAND_EUTRAN_44 }, + { .num = 145, .band = MM_MODEM_BAND_EUTRAN_45 }, + { .num = 146, .band = MM_MODEM_BAND_EUTRAN_46 }, + { .num = 147, .band = MM_MODEM_BAND_EUTRAN_47 }, + { .num = 148, .band = MM_MODEM_BAND_EUTRAN_48 }, +}; + +static MMModemBand +uact_num_to_band (guint num) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) { + if (num == uact_band_config[i].num) + return uact_band_config[i].band; + } + return MM_MODEM_BAND_UNKNOWN; +} + +static guint +uact_band_to_num (MMModemBand band) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (uact_band_config); i++) { + if (band == uact_band_config[i].band) + return uact_band_config[i].num; + } + return 0; +} + +/*****************************************************************************/ +/* UACT? response parser */ + +static GArray * +uact_num_array_to_band_array (GArray *nums) +{ + GArray *bands = NULL; + guint i; + + if (!nums) + return NULL; + + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len); + for (i = 0; i < nums->len; i++) { + MMModemBand band; + + band = uact_num_to_band (g_array_index (nums, guint, i)); + g_array_append_val (bands, band); + } + + return bands; +} + +GArray * +mm_ublox_parse_uact_response (const gchar *response, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + GArray *nums = NULL; + GArray *bands = NULL; + + /* + * AT+UACT? + * +UACT: ,,,900,1800,1,8,101,103,107,108,120,138 + */ + r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\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)) { + gchar *bandstr; + + bandstr = mm_get_string_unquoted_from_match_info (match_info, 4); + nums = mm_parse_uint_list (bandstr, &inner_error); + g_free (bandstr); + } + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return NULL; + } + + /* Convert to MMModemBand values */ + if (nums) { + bands = uact_num_array_to_band_array (nums); + g_array_unref (nums); + } + + if (!bands) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No known band selection values matched in +UACT response: '%s'", response); + + return bands; +} + +/*****************************************************************************/ +/* UACT=? response parser */ + +static GArray * +parse_bands_from_string (const gchar *str, + const gchar *group, + gpointer log_object) +{ + GArray *bands = NULL; + GError *inner_error = NULL; + GArray *nums; + + nums = mm_parse_uint_list (str, &inner_error); + if (nums) { + gchar *tmpstr; + + bands = uact_num_array_to_band_array (nums); + tmpstr = mm_common_build_bands_string ((MMModemBand *)(gpointer)(bands->data), bands->len); + mm_obj_dbg (log_object, "modem reports support for %s bands: %s", group, tmpstr); + g_free (tmpstr); + + g_array_unref (nums); + } else if (inner_error) { + mm_obj_warn (log_object, "couldn't parse list of supported %s bands: %s", group, inner_error->message); + g_clear_error (&inner_error); + } + + return bands; +} + +gboolean +mm_ublox_parse_uact_test (const gchar *response, + gpointer log_object, + GArray **bands2g_out, + GArray **bands3g_out, + GArray **bands4g_out, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + const gchar *bands2g_str = NULL; + const gchar *bands3g_str = NULL; + const gchar *bands4g_str = NULL; + GArray *bands2g = NULL; + GArray *bands3g = NULL; + GArray *bands4g = NULL; + gchar **split = NULL; + + g_assert (bands2g_out && bands3g_out && bands4g_out); + + /* + * AT+UACT=? + * +UACT: ,,,(900,1800),(1,8),(101,103,107,108,120),(138) + */ + r = g_regex_new ("\\+UACT: ([^,]*),([^,]*),([^,]*),(.*)(?:\\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) + goto out; + + if (g_match_info_matches (match_info)) { + gchar *aux; + guint n_groups; + + aux = mm_get_string_unquoted_from_match_info (match_info, 4); + split = mm_split_string_groups (aux); + n_groups = g_strv_length (split); + if (n_groups >= 1) + bands2g_str = split[0]; + if (n_groups >= 2) + bands3g_str = split[1]; + if (n_groups >= 3) + bands4g_str = split[2]; + g_free (aux); + } + + if (!bands2g_str && !bands3g_str && !bands4g_str) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "frequency groups not found: %s", response); + goto out; + } + + bands2g = parse_bands_from_string (bands2g_str, "2G", log_object); + bands3g = parse_bands_from_string (bands3g_str, "3G", log_object); + bands4g = parse_bands_from_string (bands4g_str, "4G", log_object); + + if (!bands2g->len && !bands3g->len && !bands4g->len) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "no supported frequencies reported: %s", response); + goto out; + } + + /* success */ + +out: + g_strfreev (split); + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + if (bands2g) + g_array_unref (bands2g); + if (bands3g) + g_array_unref (bands3g); + if (bands4g) + g_array_unref (bands4g); + g_propagate_error (error, inner_error); + return FALSE; + } + + *bands2g_out = bands2g; + *bands3g_out = bands3g; + *bands4g_out = bands4g; + return TRUE; +} + +/*****************************************************************************/ +/* UACT=X command builder */ + +gchar * +mm_ublox_build_uact_set_command (GArray *bands, + GError **error) +{ + GString *command; + + /* Build command */ + command = g_string_new ("+UACT=,,,"); + + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) + g_string_append (command, "0"); + else { + guint i; + + for (i = 0; i < bands->len; i++) { + MMModemBand band; + guint num; + + band = g_array_index (bands, MMModemBand, i); + num = uact_band_to_num (band); + if (!num) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED, + "Band unsupported by this plugin: %s", mm_modem_band_get_string (band)); + g_string_free (command, TRUE); + return NULL; + } + + g_string_append_printf (command, "%s%u", i == 0 ? "" : ",", num); + } + } + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* URAT? response parser */ + +gboolean +mm_ublox_parse_urat_read_response (const gchar *response, + gpointer log_object, + MMModemMode *out_allowed, + MMModemMode *out_preferred, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info; + GError *inner_error = NULL; + MMModemMode allowed = MM_MODEM_MODE_NONE; + MMModemMode preferred = MM_MODEM_MODE_NONE; + gchar *allowed_str = NULL; + gchar *preferred_str = NULL; + + g_assert (out_allowed != NULL && out_preferred != NULL); + + /* Response may be e.g.: + * +URAT: 1,2 + * +URAT: 1 + */ + r = g_regex_new ("\\+URAT: (\\d+)(?:,(\\d+))?(?:\\r\\n)?", 0, 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 value = 0; + + /* Selected item is mandatory */ + if (!mm_get_uint_from_match_info (match_info, 1, &value)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read AcT selected value"); + goto out; + } + if (value >= G_N_ELEMENTS (ublox_combinations)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected AcT selected value: %u", value); + goto out; + } + allowed = ublox_combinations[value]; + allowed_str = mm_modem_mode_build_string_from_mask (allowed); + mm_obj_dbg (log_object, "current allowed modes retrieved: %s", allowed_str); + + /* Preferred item is optional */ + if (mm_get_uint_from_match_info (match_info, 2, &value)) { + if (value >= G_N_ELEMENTS (ublox_combinations)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected AcT preferred value: %u", value); + goto out; + } + preferred = ublox_combinations[value]; + preferred_str = mm_modem_mode_build_string_from_mask (preferred); + mm_obj_dbg (log_object, "current preferred modes retrieved: %s", preferred_str); + if (mm_count_bits_set (preferred) != 1) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value should be a single AcT: %s", preferred_str); + goto out; + } + if (!(allowed & preferred)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value (%s) not a subset of the allowed value (%s)", + preferred_str, allowed_str); + goto out; + } + } + } + +out: + + g_free (allowed_str); + g_free (preferred_str); + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (allowed == MM_MODEM_MODE_NONE) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse +URAT response: %s", response); + return FALSE; + } + + *out_allowed = allowed; + *out_preferred = preferred; + return TRUE; +} + +/*****************************************************************************/ +/* URAT=X command builder */ + +static gboolean +append_rat_value (GString *str, + MMModemMode mode, + GError **error) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (ublox_combinations); i++) { + if (ublox_combinations[i] == mode) { + g_string_append_printf (str, "%u", i); + return TRUE; + } + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No AcT value matches requested mode"); + return FALSE; +} + +gchar * +mm_ublox_build_urat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error) +{ + GString *command; + + command = g_string_new ("+URAT="); + if (!append_rat_value (command, allowed, error)) { + g_string_free (command, TRUE); + return NULL; + } + + if (preferred != MM_MODEM_MODE_NONE) { + g_string_append (command, ","); + if (!append_rat_value (command, preferred, error)) { + g_string_free (command, TRUE); + return NULL; + } + } + + return g_string_free (command, FALSE); +} + +/*****************************************************************************/ +/* +UAUTHREQ=? test parser */ + +MMUbloxBearerAllowedAuth +mm_ublox_parse_uauthreq_test (const char *response, + gpointer log_object, + GError **error) +{ + MMUbloxBearerAllowedAuth mask = MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; + GError *inner_error = NULL; + GArray *allowed_auths = NULL; + gchar **split; + guint split_len; + + /* + * Response may be like: + * AT+UAUTHREQ=? + * +UAUTHREQ: (1-4),(0-2),, + */ + response = mm_strip_tag (response, "+UAUTHREQ:"); + split = mm_split_string_groups (response); + split_len = g_strv_length (split); + if (split_len < 2) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Unexpected number of groups in +UAUTHREQ=? response: %u", g_strv_length (split)); + goto out; + } + + allowed_auths = mm_parse_uint_list (split[1], &inner_error); + if (inner_error) + goto out; + + if (allowed_auths) { + guint i; + + for (i = 0; i < allowed_auths->len; i++) { + guint val; + + val = g_array_index (allowed_auths, guint, i); + switch (val) { + case 0: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_NONE; + break; + case 1: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_PAP; + break; + case 2: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_CHAP; + break; + case 3: + mask |= MM_UBLOX_BEARER_ALLOWED_AUTH_AUTO; + break; + default: + mm_obj_warn (log_object, "unexpected +UAUTHREQ value: %u", val); + break; + } + } + } + + if (!mask) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No supported authentication methods in +UAUTHREQ=? response"); + goto out; + } + +out: + g_strfreev (split); + + if (allowed_auths) + g_array_unref (allowed_auths); + + if (inner_error) { + g_propagate_error (error, inner_error); + return MM_UBLOX_BEARER_ALLOWED_AUTH_UNKNOWN; + } + + return mask; +} + +/*****************************************************************************/ +/* +UGCNTRD response parser */ + +gboolean +mm_ublox_parse_ugcntrd_response_for_cid (const gchar *response, + guint in_cid, + guint64 *out_session_tx_bytes, + guint64 *out_session_rx_bytes, + guint64 *out_total_tx_bytes, + guint64 *out_total_rx_bytes, + GError **error) +{ + GRegex *r; + GMatchInfo *match_info = NULL; + GError *inner_error = NULL; + guint64 session_tx_bytes = 0; + guint64 session_rx_bytes = 0; + guint64 total_tx_bytes = 0; + guint64 total_rx_bytes = 0; + gboolean matched = FALSE; + + /* Response may be e.g.: + * +UGCNTRD: 31,2704,1819,2724,1839 + * We assume only ONE line is returned. + */ + r = g_regex_new ("\\+UGCNTRD:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + /* Report invalid CID given */ + if (!in_cid) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid CID given"); + goto out; + } + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + while (!inner_error && g_match_info_matches (match_info)) { + guint cid = 0; + + /* Matched CID? */ + if (!mm_get_uint_from_match_info (match_info, 1, &cid) || cid != in_cid) { + g_match_info_next (match_info, &inner_error); + continue; + } + + if (out_session_tx_bytes && !mm_get_u64_from_match_info (match_info, 2, &session_tx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session TX bytes"); + goto out; + } + + if (out_session_rx_bytes && !mm_get_u64_from_match_info (match_info, 3, &session_rx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing session RX bytes"); + goto out; + } + + if (out_total_tx_bytes && !mm_get_u64_from_match_info (match_info, 4, &total_tx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total TX bytes"); + goto out; + } + + if (out_total_rx_bytes && !mm_get_u64_from_match_info (match_info, 5, &total_rx_bytes)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing total RX bytes"); + goto out; + } + + matched = TRUE; + break; + } + + if (!matched) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No statistics found for CID %u", in_cid); + goto out; + } + +out: + + g_match_info_free (match_info); + g_regex_unref (r); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (out_session_tx_bytes) + *out_session_tx_bytes = session_tx_bytes; + if (out_session_rx_bytes) + *out_session_rx_bytes = session_rx_bytes; + if (out_total_tx_bytes) + *out_total_tx_bytes = total_tx_bytes; + if (out_total_rx_bytes) + *out_total_rx_bytes = total_rx_bytes; + return TRUE; +} |