diff options
Diffstat (limited to 'src/plugins/xmm/mm-modem-helpers-xmm.c')
-rw-r--r-- | src/plugins/xmm/mm-modem-helpers-xmm.c | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/src/plugins/xmm/mm-modem-helpers-xmm.c b/src/plugins/xmm/mm-modem-helpers-xmm.c new file mode 100644 index 00000000..70e02a8f --- /dev/null +++ b/src/plugins/xmm/mm-modem-helpers-xmm.c @@ -0,0 +1,1003 @@ +/* -*- 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; +} |