/* -*- 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 ),
};
GArray *
mm_ublox_parse_urat_test_response (const gchar *response,
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_warn ("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_warn ("Unexpected AcT preferred value: %u", preferred_value);
continue;
}
combination.preferred = ublox_combinations[preferred_value];
if (mm_count_bits_set (combination.preferred) != 1) {
mm_warn ("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;
}
/*****************************************************************************/
static MMModemMode
supported_modes_per_model (const gchar *model)
{
MMModemMode all = (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
if (model) {
/* Some TOBY-L2/MPCI-L2 devices don't support 2G */
if (g_str_equal (model, "TOBY-L201") || g_str_equal (model, "TOBY-L220") || g_str_equal (model, "MPCI-L201"))
all &= ~MM_MODEM_MODE_2G;
/* None of the LISA-U or SARA-U devices support 4G */
else if (g_str_has_prefix (model, "LISA-U") || g_str_has_prefix (model, "SARA-U")) {
all &= ~MM_MODEM_MODE_4G;
/* Some SARA devices don't support 2G */
if (g_str_equal (model, "SARA-U270-53S") || g_str_equal (model, "SARA-U280"))
all &= ~MM_MODEM_MODE_2G;
}
}
return all;
}
GArray *
mm_ublox_filter_supported_modes (const gchar *model,
GArray *combinations,
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);
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 */
typedef struct {
guint ubandsel_value;
MMModemBand bands_2g[2];
MMModemBand bands_3g[2];
MMModemBand bands_4g[2];
} BandConfiguration;
static const BandConfiguration band_configuration[] = {
{
.ubandsel_value = 700,
.bands_4g = { MM_MODEM_BAND_EUTRAN_13, MM_MODEM_BAND_EUTRAN_17 }
},
{
.ubandsel_value = 800,
.bands_3g = { MM_MODEM_BAND_UTRAN_6 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_20 }
},
{
.ubandsel_value = 850,
.bands_2g = { MM_MODEM_BAND_G850 },
.bands_3g = { MM_MODEM_BAND_UTRAN_5 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_5 }
},
{
.ubandsel_value = 900,
.bands_2g = { MM_MODEM_BAND_EGSM },
.bands_3g = { MM_MODEM_BAND_UTRAN_8 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_8 }
},
{
.ubandsel_value = 1500,
.bands_3g = { MM_MODEM_BAND_UTRAN_11 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_11 }
},
{
.ubandsel_value = 1700,
.bands_3g = { MM_MODEM_BAND_UTRAN_4 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_4 }
},
{
.ubandsel_value = 1800,
.bands_2g = { MM_MODEM_BAND_DCS },
.bands_3g = { MM_MODEM_BAND_UTRAN_3 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_3 }
},
{
.ubandsel_value = 1900,
.bands_2g = { MM_MODEM_BAND_PCS },
.bands_3g = { MM_MODEM_BAND_UTRAN_2 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_2 }
},
{
.ubandsel_value = 2100,
.bands_3g = { MM_MODEM_BAND_UTRAN_1 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_1 }
},
{
.ubandsel_value = 2600,
.bands_3g = { MM_MODEM_BAND_UTRAN_7 },
.bands_4g = { MM_MODEM_BAND_EUTRAN_7 }
},
};
GArray *
mm_ublox_get_supported_bands (const gchar *model,
GError **error)
{
MMModemMode mode;
GArray *bands;
guint i;
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 ((mode & MM_MODEM_MODE_2G) && band_configuration[i].bands_2g[0]) {
bands = g_array_append_val (bands, band_configuration[i].bands_2g[0]);
if (band_configuration[i].bands_2g[1])
bands = g_array_append_val (bands, band_configuration[i].bands_2g[1]);
}
if ((mode & MM_MODEM_MODE_3G) && band_configuration[i].bands_3g[0]) {
bands = g_array_append_val (bands, band_configuration[i].bands_3g[0]);
if (band_configuration[i].bands_3g[1])
bands = g_array_append_val (bands, band_configuration[i].bands_3g[1]);
}
if ((mode & MM_MODEM_MODE_4G) && band_configuration[i].bands_4g[0]) {
bands = g_array_append_val (bands, band_configuration[i].bands_4g[0]);
if (band_configuration[i].bands_4g[1])
bands = g_array_append_val (bands, band_configuration[i].bands_4g[1]);
}
}
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;
}
/*****************************************************************************/
/* +UBANDSEL? response parser */
static void
append_bands (GArray *bands,
guint ubandsel_value)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (band_configuration); i++)
if (ubandsel_value == band_configuration[i].ubandsel_value)
break;
if (i == G_N_ELEMENTS (band_configuration)) {
mm_warn ("Unknown band configuration value given: %u", ubandsel_value);
return;
}
/* Note: we don't care if the device doesn't support one of these modes;
* the generic logic will filter out all bands not supported before
* exposing them in the DBus property */
if (band_configuration[i].bands_2g[0]) {
g_array_append_val (bands, band_configuration[i].bands_2g[0]);
if (band_configuration[i].bands_2g[1])
g_array_append_val (bands, band_configuration[i].bands_2g[1]);
}
if (band_configuration[i].bands_3g[0]) {
g_array_append_val (bands, band_configuration[i].bands_3g[0]);
if (band_configuration[i].bands_3g[1])
g_array_append_val (bands, band_configuration[i].bands_3g[1]);
}
if (band_configuration[i].bands_4g[0]) {
g_array_append_val (bands, band_configuration[i].bands_4g[0]);
if (band_configuration[i].bands_4g[1])
g_array_append_val (bands, band_configuration[i].bands_4g[1]);
}
}
GArray *
mm_ublox_parse_ubandsel_response (const gchar *response,
GError **error)
{
GArray *array_values = NULL;
GArray *array = NULL;
gchar *dupstr = NULL;
GError *inner_error = NULL;
guint i;
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 */
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));
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,
GError **error)
{
GString *command = NULL;
GArray *ubandsel_nums;
guint i;
if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY)
return g_strdup ("+UBANDSEL=0");
ubandsel_nums = g_array_sized_new (FALSE, FALSE, sizeof (guint), G_N_ELEMENTS (band_configuration));
for (i = 0; i < G_N_ELEMENTS (band_configuration); i++) {
guint j;
for (j = 0; j < bands->len; j++) {
MMModemBand band;
band = g_array_index (bands, MMModemBand, j);
if (band == band_configuration[i].bands_2g[0] || band == band_configuration[i].bands_2g[1] ||
band == band_configuration[i].bands_3g[0] || band == band_configuration[i].bands_3g[1] ||
band == band_configuration[i].bands_4g[0] || band == band_configuration[i].bands_4g[1]) {
g_array_append_val (ubandsel_nums, band_configuration[i].ubandsel_value);
break;
}
}
}
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)
{
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 *)(bands->data), bands->len);
mm_dbg ("modem reports support for %s bands: %s", group, tmpstr);
g_free (tmpstr);
g_array_unref (nums);
} else if (inner_error) {
mm_warn ("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,
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");
bands3g = parse_bands_from_string (bands3g_str, "3G");
bands4g = parse_bands_from_string (bands4g_str, "4G");
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,
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_dbg ("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_dbg ("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,
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_warn ("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,
guint *out_session_tx_bytes,
guint *out_session_rx_bytes,
guint *out_total_tx_bytes,
guint *out_total_rx_bytes,
GError **error)
{
GRegex *r;
GMatchInfo *match_info = NULL;
GError *inner_error = NULL;
guint session_tx_bytes = 0;
guint session_rx_bytes = 0;
guint total_tx_bytes = 0;
guint 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_uint_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_uint_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_uint_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_uint_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;
}