Files
ModemManager/plugins/xmm/mm-modem-helpers-xmm.c
Aleksander Morgado 74fc5baca2 core: port GRegex/GMatchInfo to use autoptr()
The behavior of GRegex changed in 2.73.2 once it was ported from pcre1
to pcre2. In some cases it was made more strict, which is fine, in
other cases it exposed some change in how it behaves on certain
matches that is not extremely clear whether it's ok or not.

See https://gitlab.gnome.org/GNOME/glib/-/issues/2729
See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/601
See https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/issues/621

Either way, one thing that was assumed was that initializing all
GRegex/GMatchInfo variables to NULL and making sure they're NULL
before they're initialized by glib (especially the GMatchInfo) was a
good and safer approach.

So, whenever possible, g_autoptr() is used to cleanup the allocated
GMatchInfo/GRegex variables, and otherwise, g_clear_pointer() is used
to ensure that no free/unref is attempted unless the given variable is
not NULL, and also so that the variable is reseted to NULL after being
disposed.
2022-09-05 17:33:11 +00:00

1004 lines
35 KiB
C

/* -*- 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) 2018 Aleksander Morgado <aleksander@aleksander.es>
*/
#include <string.h>
#include "mm-log.h"
#include "mm-modem-helpers.h"
#include "mm-modem-helpers-xmm.h"
#include "mm-signal.h"
/*****************************************************************************/
/* XACT common config */
typedef struct {
guint num;
MMModemBand band;
} XactBandConfig;
static const XactBandConfig xact_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 },
{ .num = 149, .band = MM_MODEM_BAND_EUTRAN_49 },
{ .num = 150, .band = MM_MODEM_BAND_EUTRAN_50 },
{ .num = 151, .band = MM_MODEM_BAND_EUTRAN_51 },
{ .num = 152, .band = MM_MODEM_BAND_EUTRAN_52 },
{ .num = 153, .band = MM_MODEM_BAND_EUTRAN_53 },
{ .num = 154, .band = MM_MODEM_BAND_EUTRAN_54 },
{ .num = 155, .band = MM_MODEM_BAND_EUTRAN_55 },
{ .num = 156, .band = MM_MODEM_BAND_EUTRAN_56 },
{ .num = 157, .band = MM_MODEM_BAND_EUTRAN_57 },
{ .num = 158, .band = MM_MODEM_BAND_EUTRAN_58 },
{ .num = 159, .band = MM_MODEM_BAND_EUTRAN_59 },
{ .num = 160, .band = MM_MODEM_BAND_EUTRAN_60 },
{ .num = 161, .band = MM_MODEM_BAND_EUTRAN_61 },
{ .num = 162, .band = MM_MODEM_BAND_EUTRAN_62 },
{ .num = 163, .band = MM_MODEM_BAND_EUTRAN_63 },
{ .num = 164, .band = MM_MODEM_BAND_EUTRAN_64 },
{ .num = 165, .band = MM_MODEM_BAND_EUTRAN_65 },
{ .num = 166, .band = MM_MODEM_BAND_EUTRAN_66 },
};
#define XACT_NUM_IS_BAND_2G(num) (num > 300)
#define XACT_NUM_IS_BAND_3G(num) (num < 100)
#define XACT_NUM_IS_BAND_4G(num) (num > 100 && num < 300)
static MMModemBand
xact_num_to_band (guint num)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
if (num == xact_band_config[i].num)
return xact_band_config[i].band;
}
return MM_MODEM_BAND_UNKNOWN;
}
static guint
xact_band_to_num (MMModemBand band)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (xact_band_config); i++) {
if (band == xact_band_config[i].band)
return xact_band_config[i].num;
}
return 0;
}
/*****************************************************************************/
/* XACT=? response parser */
/* Index of the array is the XMM-specific value */
static const MMModemMode xmm_modes[] = {
( MM_MODEM_MODE_2G ),
( MM_MODEM_MODE_3G ),
( MM_MODEM_MODE_4G ),
( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ),
( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ),
( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ),
};
gboolean
mm_xmm_parse_xact_test_response (const gchar *response,
gpointer log_object,
GArray **modes_out,
GArray **bands_out,
GError **error)
{
GError *inner_error = NULL;
GArray *modes = NULL;
GArray *all_modes = NULL;
GArray *filtered = NULL;
GArray *supported = NULL;
GArray *preferred = NULL;
GArray *bands = NULL;
gchar **split = NULL;
guint i;
MMModemModeCombination all = {
.allowed = MM_MODEM_MODE_NONE,
.preferred = MM_MODEM_MODE_NONE
};
g_assert (modes_out && bands_out);
/*
* AT+XACT=?
* +XACT: (0-6),(0-2),0,1,2,4,5,8,101,102,103,104,105,107,108,111,...
*/
response = mm_strip_tag (response, "+XACT:");
split = mm_split_string_groups (response);
if (g_strv_length (split) < 3) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing fields");
goto out;
}
/* First group is list of supported modes */
supported = mm_parse_uint_list (split[0], &inner_error);
if (inner_error)
goto out;
if (!supported) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing modes");
goto out;
}
/* Second group is list of possible preferred modes.
* 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 modes */
modes = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
for (i = 0; i < supported->len; i++) {
guint supported_value;
MMModemModeCombination combination;
guint j;
supported_value = g_array_index (supported, guint, i);
if (supported_value >= G_N_ELEMENTS (xmm_modes)) {
mm_obj_warn (log_object, "unexpected AcT supported value: %u", supported_value);
continue;
}
/* Combination without any preferred */
combination.allowed = xmm_modes[supported_value];
combination.preferred = MM_MODEM_MODE_NONE;
g_array_append_val (modes, 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 (xmm_modes)) {
mm_obj_warn (log_object, "unexpected AcT preferred value: %u", preferred_value);
continue;
}
combination.preferred = xmm_modes[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 (modes, combination);
}
}
if (modes->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No modes list built from +XACT=? response");
goto out;
}
/* Build array of bands */
bands = g_array_new (FALSE, FALSE, sizeof (MMModemBand));
/*
* The next element at index 2 may be '0'. We will just treat that field as
* any other band field as '0' isn't a supported band, we'll just ignore it.
*/
for (i = 2; split[i]; i++) {
MMModemBand band;
guint num;
if (!mm_get_uint_from_str (split[i], &num)) {
mm_obj_warn (log_object, "unexpected band value: %s", split[i]);
continue;
}
if (num == 0)
continue;
band = xact_num_to_band (num);
if (band == MM_MODEM_BAND_UNKNOWN) {
mm_obj_warn (log_object, "unsupported band value: %s", split[i]);
continue;
}
g_array_append_val (bands, band);
if (XACT_NUM_IS_BAND_2G (num))
all.allowed |= MM_MODEM_MODE_2G;
if (XACT_NUM_IS_BAND_3G (num))
all.allowed |= MM_MODEM_MODE_3G;
if (XACT_NUM_IS_BAND_4G (num))
all.allowed |= MM_MODEM_MODE_4G;
}
if (bands->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No bands list built from +XACT=? response");
goto out;
}
/* AT+XACT lies about the supported modes, e.g. it may report 2G supported
* for 3G+4G only devices. So, filter out unsupported modes based on the
* supported bands */
all_modes = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
g_array_append_val (all_modes, all);
filtered = mm_filter_supported_modes (all_modes, modes, log_object);
if (!filtered || filtered->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Empty supported mode list after frequency band filtering");
goto out;
}
/* success */
out:
if (modes)
g_array_unref (modes);
if (all_modes)
g_array_unref (all_modes);
if (supported)
g_array_unref (supported);
if (preferred)
g_array_unref (preferred);
g_strfreev (split);
if (inner_error) {
if (filtered)
g_array_unref (filtered);
if (bands)
g_array_unref (bands);
g_propagate_error (error, inner_error);
return FALSE;
}
g_assert (filtered);
*modes_out = filtered;
g_assert (bands);
*bands_out = bands;
return TRUE;
}
/*****************************************************************************/
/* AT+XACT? response parser */
gboolean
mm_xmm_parse_xact_query_response (const gchar *response,
MMModemModeCombination *mode_out,
GArray **bands_out,
GError **error)
{
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
GError *inner_error = NULL;
GArray *bands = NULL;
guint i;
MMModemModeCombination mode = {
.allowed = MM_MODEM_MODE_NONE,
.preferred = MM_MODEM_MODE_NONE,
};
/* At least one */
g_assert (mode_out || bands_out);
/*
* AT+XACT?
* +XACT: 4,1,2,1,2,4,5,8,101,102,103,104,105,107,108,111,...
*
* Note: the first 3 fields corresponde to allowed and preferred modes. Only the
* first one of those 3 first fields is mandatory, the other two may be empty.
*/
r = g_regex_new ("\\+XACT: (\\d+),([^,]*),([^,]*),(.*)(?:\\r\\n)?",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (!inner_error && g_match_info_matches (match_info)) {
if (mode_out) {
guint xmm_mode;
/* Number at index 1 */
mm_get_uint_from_match_info (match_info, 1, &xmm_mode);
if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT AcT value: %u", xmm_mode);
goto out;
}
mode.allowed = xmm_modes[xmm_mode];
/* Number at index 2 */
if (mm_count_bits_set (mode.allowed) > 1 && mm_get_uint_from_match_info (match_info, 2, &xmm_mode)) {
if (xmm_mode >= G_N_ELEMENTS (xmm_modes)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unsupported XACT preferred AcT value: %u", xmm_mode);
goto out;
}
mode.preferred = xmm_modes[xmm_mode];
}
/* Number at index 3: ignored */
}
if (bands_out) {
gchar *bandstr;
GArray *nums;
/* Bands start at index 4 */
bandstr = mm_get_string_unquoted_from_match_info (match_info, 4);
nums = mm_parse_uint_list (bandstr, &inner_error);
g_free (bandstr);
if (inner_error)
goto out;
if (!nums) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid XACT? response");
goto out;
}
bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), nums->len);
for (i = 0; i < nums->len; i++) {
MMModemBand band;
band = xact_num_to_band (g_array_index (nums, guint, i));
if (band != MM_MODEM_BAND_UNKNOWN)
g_array_append_val (bands, band);
}
g_array_unref (nums);
if (bands->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing current band list");
goto out;
}
}
}
/* success */
out:
if (inner_error) {
if (bands)
g_array_unref (bands);
g_propagate_error (error, inner_error);
return FALSE;
}
if (mode_out) {
g_assert (mode.allowed != MM_MODEM_MODE_NONE);
mode_out->allowed = mode.allowed;
mode_out->preferred = mode.preferred;
}
if (bands_out) {
g_assert (bands);
*bands_out = bands;
}
return TRUE;
}
/*****************************************************************************/
/* AT+XACT=X command builder */
static gboolean
append_rat_value (GString *str,
MMModemMode mode,
GError **error)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (xmm_modes); i++) {
if (xmm_modes[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_xmm_build_xact_set_command (const MMModemModeCombination *mode,
const GArray *bands,
GError **error)
{
GString *command;
/* At least one required */
g_assert (mode || bands);
/* Build command */
command = g_string_new ("+XACT=");
/* Mode is optional. If not given, we set all fields as empty */
if (mode) {
/* Allowed mask */
if (!append_rat_value (command, mode->allowed, error)) {
g_string_free (command, TRUE);
return NULL;
}
/* Preferred */
if (mode->preferred != MM_MODEM_MODE_NONE) {
g_string_append (command, ",");
if (!append_rat_value (command, mode->preferred, error)) {
g_string_free (command, TRUE);
return NULL;
}
/* We never set <PreferredAct2> because that is anyway not part of
* ModemManager's API. In modems with triple GSM/UMTS/LTE mode, the
* <PreferredAct2> is always the highest of the remaining ones. E.g.
* if "2G+3G+4G allowed with 2G preferred", the second preferred one
* would be 4G, not 3G. */
g_string_append (command, ",");
} else
g_string_append (command, ",,");
} else
g_string_append (command, ",,");
if (bands) {
g_string_append (command, ",");
/* Automatic band selection */
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 = xact_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);
}
/*****************************************************************************/
/* Get mode to apply when ANY */
MMModemMode
mm_xmm_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_xmm_parse_uact_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;
}
/*****************************************************************************/
/* +XCESQ? response parser */
gboolean
mm_xmm_parse_xcesq_query_response (const gchar *response,
guint *out_rxlev,
guint *out_ber,
guint *out_rscp,
guint *out_ecn0,
guint *out_rsrq,
guint *out_rsrp,
gint *out_rssnr,
GError **error)
{
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
GError *inner_error = NULL;
guint rxlev = 99;
guint ber = 99;
guint rscp = 255;
guint ecn0 = 255;
guint rsrq = 255;
guint rsrp = 255;
gint rssnr = 255;
gboolean success = FALSE;
g_assert (out_rxlev);
g_assert (out_ber);
g_assert (out_rscp);
g_assert (out_ecn0);
g_assert (out_rsrq);
g_assert (out_rsrp);
g_assert (out_rssnr);
/* Response may be e.g.:
* +XCESQ: 0,99,99,255,255,24,51,18
* +XCESQ: 0,99,99,46,31,255,255,255
* +XCESQ: 0,99,99,255,255,17,45,-2
*/
r = g_regex_new ("\\+XCESQ: (\\d+),(\\d+),(\\d+),(\\d+),(\\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)) {
/* Ignore "n" value */
if (!mm_get_uint_from_match_info (match_info, 2, &rxlev)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 3, &ber)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 4, &rscp)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 5, &ecn0)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 6, &rsrq)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 7, &rsrp)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
goto out;
}
if (!mm_get_int_from_match_info (match_info, 8, &rssnr)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSSNR");
goto out;
}
success = TRUE;
}
out:
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 +XCESQ response: %s", response);
return FALSE;
}
*out_rxlev = rxlev;
*out_ber = ber;
*out_rscp = rscp;
*out_ecn0 = ecn0;
*out_rsrq = rsrq;
*out_rsrp = rsrp;
*out_rssnr = rssnr;
return TRUE;
}
static gboolean
rssnr_level_to_rssnr (gint rssnr_level,
gpointer log_object,
gdouble *out_rssnr)
{
if (rssnr_level <= 100 &&
rssnr_level >= -100) {
*out_rssnr = rssnr_level / 2.0;
return TRUE;
}
if (rssnr_level != 255)
mm_obj_warn (log_object, "unexpected RSSNR level: %u", rssnr_level);
return FALSE;
}
/*****************************************************************************/
/* Get extended signal information */
gboolean
mm_xmm_xcesq_response_to_signal_info (const gchar *response,
gpointer log_object,
MMSignal **out_gsm,
MMSignal **out_umts,
MMSignal **out_lte,
GError **error)
{
guint rxlev = 0;
guint ber = 0;
guint rscp_level = 0;
guint ecn0_level = 0;
guint rsrq_level = 0;
guint rsrp_level = 0;
gint rssnr_level = 0;
gdouble rssi = MM_SIGNAL_UNKNOWN;
gdouble rscp = MM_SIGNAL_UNKNOWN;
gdouble ecio = MM_SIGNAL_UNKNOWN;
gdouble rsrq = MM_SIGNAL_UNKNOWN;
gdouble rsrp = MM_SIGNAL_UNKNOWN;
gdouble rssnr = MM_SIGNAL_UNKNOWN;
MMSignal *gsm = NULL;
MMSignal *umts = NULL;
MMSignal *lte = NULL;
if (!mm_xmm_parse_xcesq_query_response (response,
&rxlev, &ber,
&rscp_level, &ecn0_level,
&rsrq_level, &rsrp_level,
&rssnr_level, error))
return FALSE;
/* GERAN RSSI */
if (mm_3gpp_rxlev_to_rssi (rxlev, log_object, &rssi)) {
gsm = mm_signal_new ();
mm_signal_set_rssi (gsm, rssi);
}
/* ignore BER */
/* UMTS RSCP */
if (mm_3gpp_rscp_level_to_rscp (rscp_level, log_object, &rscp)) {
umts = mm_signal_new ();
mm_signal_set_rscp (umts, rscp);
}
/* UMTS EcIo (assumed EcN0) */
if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, log_object, &ecio)) {
if (!umts)
umts = mm_signal_new ();
mm_signal_set_ecio (umts, ecio);
}
/* Calculate RSSI if we have ecio and rscp */
if (umts && ecio != -G_MAXDOUBLE && rscp != -G_MAXDOUBLE) {
mm_signal_set_rssi (umts, rscp - ecio);
}
/* LTE RSRQ */
if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, log_object, &rsrq)) {
lte = mm_signal_new ();
mm_signal_set_rsrq (lte, rsrq);
}
/* LTE RSRP */
if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, log_object, &rsrp)) {
if (!lte)
lte = mm_signal_new ();
mm_signal_set_rsrp (lte, rsrp);
}
/* LTE RSSNR */
if (rssnr_level_to_rssnr (rssnr_level, log_object, &rssnr)) {
if (!lte)
lte = mm_signal_new ();
mm_signal_set_snr (lte, rssnr);
}
if (!gsm && !umts && !lte) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't build detailed signal info");
return FALSE;
}
if (out_gsm)
*out_gsm = gsm;
if (out_umts)
*out_umts = umts;
if (out_lte)
*out_lte = lte;
return TRUE;
}
/*****************************************************************************/
/* AT+XLCSLSR=? response parser */
static gboolean
number_group_contains_value (const gchar *group,
const gchar *group_name,
guint value,
GError **error)
{
GArray *aux;
guint i;
gboolean found;
aux = mm_parse_uint_list (group, NULL);
if (!aux) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Unsupported +XLCSLSR format: invalid %s field format", group_name);
return FALSE;
}
found = FALSE;
for (i = 0; i < aux->len; i++) {
guint value_i;
value_i = g_array_index (aux, guint, i);
if (value == value_i) {
found = TRUE;
break;
}
}
g_array_unref (aux);
return found;
}
gboolean
mm_xmm_parse_xlcslsr_test_response (const gchar *response,
gboolean *transport_protocol_invalid_supported,
gboolean *transport_protocol_supl_supported,
gboolean *standalone_position_mode_supported,
gboolean *ms_assisted_based_position_mode_supported,
gboolean *loc_response_type_nmea_supported,
gboolean *gnss_type_gps_glonass_supported,
GError **error)
{
gboolean ret = FALSE;
gchar **groups = NULL;
GError *inner_error = NULL;
/*
* AT+XLCSLSR=?
* +XLCSLSR:(0-2),(0-3), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0-2),(1-256),(0,1)
* transport_protocol: 2 (invalid) or 1 (supl)
* pos_mode: 3 (standalone) or 2 (ms assisted/based)
* client_id: <empty>
* client_id_type: <empty>
* mlc_number: <empty>
* mlc_number_type: <empty>
* interval: 1 (seconds)
* service_type_id: <empty>
* pseudonym_indicator: <empty>
* loc_response_type: 1 (NMEA strings)
* nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
* gnss_type: 0 (GPS or GLONASS)
*/
response = mm_strip_tag (response, "+XLCSLSR:");
groups = mm_split_string_groups (response);
/* We expect 12 groups */
if (g_strv_length (groups) < 12) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Unsupported +XLCSLSR format: expected 12 fields");
goto out;
}
if (transport_protocol_invalid_supported) {
*transport_protocol_invalid_supported = number_group_contains_value (groups[0],
"transport protocol",
2, /* invalid */
&inner_error);
if (inner_error)
goto out;
}
if (transport_protocol_supl_supported) {
*transport_protocol_supl_supported = number_group_contains_value (groups[0],
"transport protocol",
1, /* supl */
&inner_error);
if (inner_error)
goto out;
}
if (standalone_position_mode_supported) {
*standalone_position_mode_supported = number_group_contains_value (groups[1],
"position mode",
3, /* standalone */
&inner_error);
if (inner_error)
goto out;
}
if (ms_assisted_based_position_mode_supported) {
*ms_assisted_based_position_mode_supported = number_group_contains_value (groups[1],
"position mode",
2, /* ms assisted/based */
&inner_error);
if (inner_error)
goto out;
}
if (loc_response_type_nmea_supported) {
*loc_response_type_nmea_supported = number_group_contains_value (groups[9],
"location response type",
1, /* NMEA */
&inner_error);
if (inner_error)
goto out;
}
if (gnss_type_gps_glonass_supported) {
*gnss_type_gps_glonass_supported = number_group_contains_value (groups[11],
"gnss type",
0, /* GPS/GLONASS */
&inner_error);
if (inner_error)
goto out;
}
ret = TRUE;
out:
g_strfreev (groups);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
return ret;
}
/*****************************************************************************/
/* AT+XLCSSLP? response parser */
gboolean
mm_xmm_parse_xlcsslp_query_response (const gchar *response,
gchar **supl_address,
GError **error)
{
g_autoptr(GRegex) r = NULL;
g_autoptr(GMatchInfo) match_info = NULL;
GError *inner_error = NULL;
gchar *address = NULL;
guint port = 0;
/*
* E.g.:
* +XLCSSLP:1,"www.spirent-lcs.com",7275
*/
r = g_regex_new ("\\+XLCSSLP:\\s*(\\d+),([^,]*),(\\d+)(?:\\r\\n)?",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (!inner_error && g_match_info_matches (match_info)) {
guint type;
/* We only support types 0 (IPv4) and 1 (FQDN) */
mm_get_uint_from_match_info (match_info, 1, &type);
if (type != 0 && type != 1) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
"Unsupported SUPL server address type (%u) in response: %s", type, response);
goto out;
}
address = mm_get_string_unquoted_from_match_info (match_info, 2);
mm_get_uint_from_match_info (match_info, 3, &port);
if (!port) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Invalid SUPL address port number in response: %s", response);
goto out;
}
}
out:
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
if (supl_address)
*supl_address = g_strdup_printf ("%s:%u", address, port);
g_free (address);
return TRUE;
}