diff options
Diffstat (limited to 'src/plugins/cinterion/mm-modem-helpers-cinterion.c')
-rw-r--r-- | src/plugins/cinterion/mm-modem-helpers-cinterion.c | 1804 |
1 files changed, 1804 insertions, 0 deletions
diff --git a/src/plugins/cinterion/mm-modem-helpers-cinterion.c b/src/plugins/cinterion/mm-modem-helpers-cinterion.c new file mode 100644 index 00000000..f22a998c --- /dev/null +++ b/src/plugins/cinterion/mm-modem-helpers-cinterion.c @@ -0,0 +1,1804 @@ +/* -*- 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) 2014 Aleksander Morgado <aleksander@aleksander.es> + * Copyright (C) 2016 Trimble Navigation Limited + * Copyright (C) 2016 Matthew Stanger <matthew_stanger@trimble.com> + * Copyright (C) 2019 Purism SPC + */ + +#include <config.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "ModemManager.h" +#define _LIBMM_INSIDE_MM +#include <libmm-glib.h> +#include "mm-log-object.h" +#include "mm-charsets.h" +#include "mm-errors-types.h" +#include "mm-modem-helpers-cinterion.h" +#include "mm-modem-helpers.h" +#include "mm-common-helpers.h" +#include "mm-port-serial-at.h" + +/* Setup relationship between the 3G band bitmask in the modem and the bitmask + * in ModemManager. */ +typedef struct { + guint32 cinterion_band_flag; + MMModemBand mm_band; +} CinterionBand; + +typedef struct { + MMCinterionRbBlock cinterion_band_block; + guint32 cinterion_band_flag; + MMModemBand mm_band; +} CinterionBandEx; + +/* Table checked in PLS8-X/E/J/V/US, HC25 & PHS8 references. The table includes 2/3/4G + * frequencies. Depending on which one is configured, one access technology or + * the other will be used. This may conflict with the allowed mode configuration + * set, so you shouldn't for example set 3G frequency bands, and then use a + * 2G-only allowed mode. */ +static const CinterionBand cinterion_bands[] = { + { (1 << 0), MM_MODEM_BAND_EGSM }, + { (1 << 1), MM_MODEM_BAND_DCS }, + { (1 << 2), MM_MODEM_BAND_G850 }, + { (1 << 3), MM_MODEM_BAND_PCS }, + { (1 << 4), MM_MODEM_BAND_UTRAN_1 }, + { (1 << 5), MM_MODEM_BAND_UTRAN_2 }, + { (1 << 6), MM_MODEM_BAND_UTRAN_5 }, + { (1 << 7), MM_MODEM_BAND_UTRAN_8 }, + { (1 << 8), MM_MODEM_BAND_UTRAN_6 }, + { (1 << 9), MM_MODEM_BAND_UTRAN_4 }, + { (1 << 10), MM_MODEM_BAND_UTRAN_19 }, + { (1 << 12), MM_MODEM_BAND_UTRAN_3 }, + { (1 << 13), MM_MODEM_BAND_EUTRAN_1 }, + { (1 << 14), MM_MODEM_BAND_EUTRAN_2 }, + { (1 << 15), MM_MODEM_BAND_EUTRAN_3 }, + { (1 << 16), MM_MODEM_BAND_EUTRAN_4 }, + { (1 << 17), MM_MODEM_BAND_EUTRAN_5 }, + { (1 << 18), MM_MODEM_BAND_EUTRAN_7 }, + { (1 << 19), MM_MODEM_BAND_EUTRAN_8 }, + { (1 << 20), MM_MODEM_BAND_EUTRAN_17 }, + { (1 << 21), MM_MODEM_BAND_EUTRAN_20 }, + { (1 << 22), MM_MODEM_BAND_EUTRAN_13 }, + { (1 << 24), MM_MODEM_BAND_EUTRAN_19 } +}; + +static const CinterionBandEx cinterion_bands_ex[] = { + { MM_CINTERION_RB_BLOCK_GSM, 0x00000001, MM_MODEM_BAND_EGSM }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000002, MM_MODEM_BAND_DCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_G850 }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000008, MM_MODEM_BAND_PCS }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000004, MM_MODEM_BAND_UTRAN_3 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000020, MM_MODEM_BAND_UTRAN_6 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00001000, MM_MODEM_BAND_EUTRAN_13 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00010000, MM_MODEM_BAND_EUTRAN_17 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x02000000, MM_MODEM_BAND_EUTRAN_26 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x10000000, MM_MODEM_BAND_EUTRAN_29 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000020, MM_MODEM_BAND_EUTRAN_38 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000040, MM_MODEM_BAND_EUTRAN_39 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000080, MM_MODEM_BAND_EUTRAN_40 }, + { MM_CINTERION_RB_BLOCK_LTE_HIGH, 0x00000100, MM_MODEM_BAND_EUTRAN_41 } +}; + +static const CinterionBandEx cinterion_bands_imt[] = { + { MM_CINTERION_RB_BLOCK_GSM, 0x00000004, MM_MODEM_BAND_EGSM }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000010, MM_MODEM_BAND_DCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000020, MM_MODEM_BAND_PCS }, + { MM_CINTERION_RB_BLOCK_GSM, 0x00000040, MM_MODEM_BAND_G850 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000001, MM_MODEM_BAND_UTRAN_1 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000002, MM_MODEM_BAND_UTRAN_2 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000008, MM_MODEM_BAND_UTRAN_4 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000010, MM_MODEM_BAND_UTRAN_5 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000080, MM_MODEM_BAND_UTRAN_8 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00000100, MM_MODEM_BAND_UTRAN_9 }, + { MM_CINTERION_RB_BLOCK_UMTS, 0x00040000, MM_MODEM_BAND_UTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000001, MM_MODEM_BAND_EUTRAN_1 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000002, MM_MODEM_BAND_EUTRAN_2 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000004, MM_MODEM_BAND_EUTRAN_3 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000008, MM_MODEM_BAND_EUTRAN_4 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000010, MM_MODEM_BAND_EUTRAN_5 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000040, MM_MODEM_BAND_EUTRAN_7 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000080, MM_MODEM_BAND_EUTRAN_8 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00000800, MM_MODEM_BAND_EUTRAN_12 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00020000, MM_MODEM_BAND_EUTRAN_18 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00040000, MM_MODEM_BAND_EUTRAN_19 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x00080000, MM_MODEM_BAND_EUTRAN_20 }, + { MM_CINTERION_RB_BLOCK_LTE_LOW, 0x08000000, MM_MODEM_BAND_EUTRAN_28 } +}; + +/* Check valid combinations in 2G-only devices */ +#define VALIDATE_2G_BAND(cinterion_mask) \ + (cinterion_mask == 1 || \ + cinterion_mask == 2 || \ + cinterion_mask == 4 || \ + cinterion_mask == 8 || \ + cinterion_mask == 3 || \ + cinterion_mask == 5 || \ + cinterion_mask == 10 || \ + cinterion_mask == 12 || \ + cinterion_mask == 15) + +/*****************************************************************************/ +/* ^SCFG (3G+LTE) test parser + * + * Example 3G: + * AT^SCFG=? + * ... + * ^SCFG: "MEShutdown/OnIgnition",("on","off") + * ^SCFG: "Radio/Band",("1-511","0-1") + * ^SCFG: "Radio/NWSM",("0","1","2") + * ... + * ^SCFG: "Radio/Band\",("1"-"147") + * + * Example LTE1 (GSM charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("0x00000004"-"0x00000074") + * ^SCFG: "Radio/Band/3G",("0x00000001"-"0x0004019B") + * ^SCFG: "Radio/Band/4G",("0x00000001"-"0x080E08DF") + * ... + * + * Example LTE1 (UCS2 charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("0030007800300030003000300030003000300034"-"0030007800300030003000300030003000370034") + * ^SCFG: "Radio/Band/3G",("0030007800300030003000300030003000300031"-"0030007800300030003000340030003100390042") + * ^SCFG: "Radio/Band/4G",("0030007800300030003000300030003000300031"-"0030007800300038003000450030003800440046") + * ... + * + * Example LTE2 (all charsets): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G",("00000001-0000000f"),,("0","1") + * ^SCFG: "Radio/Band/3G",("00000001-000400b5"),,("0","1") + * ^SCFG: "Radio/Band/4G",("00000001-8a0e00d5"),("00000002-000001e2"),("0","1") + * ... + */ + +static void +parse_bands (guint bandlist, + GArray **bands, + MMCinterionRbBlock block, + MMCinterionModemFamily modem_family) +{ + guint i; + const CinterionBandEx *ref_bands; + guint nb_ref_bands; + + if (!bandlist) + return; + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + ref_bands = cinterion_bands_imt; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt); + } else { + ref_bands = cinterion_bands_ex; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex); + } + + for (i = 0; i < nb_ref_bands; i++) { + if (block == ref_bands[i].cinterion_band_block && (bandlist & ref_bands[i].cinterion_band_flag)) { + if (G_UNLIKELY (!*bands)) + *bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 23); + g_array_append_val (*bands, ref_bands[i].mm_band); + } + } +} + +static guint +take_and_convert_from_matched_string (gchar *str, + MMModemCharset charset, + MMCinterionModemFamily modem_family, + GError **error) +{ + guint val = 0; + g_autofree gchar *utf8 = NULL; + g_autofree gchar *taken_str = str; + + if (!taken_str) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, + "Couldn't convert to integer number: no input string"); + return 0; + } + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + utf8 = mm_modem_charset_str_to_utf8 (taken_str, -1, charset, FALSE, error); + if (!utf8) { + g_prefix_error (error, "Couldn't convert to integer number: "); + return 0; + } + } + + if (!mm_get_uint_from_hex_str (utf8 ? utf8 : taken_str, &val)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't convert to integer number: wrong hex encoding: %s", utf8 ? utf8 : taken_str); + return 0; + } + + return val; +} + +gboolean +mm_cinterion_parse_scfg_test (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **supported_bands, + MMCinterionRadioBandFormat *format, + GError **error) +{ + g_autoptr(GRegex) r1 = NULL; + g_autoptr(GMatchInfo) match_info1 = NULL; + g_autoptr(GRegex) r2 = NULL; + g_autoptr(GMatchInfo) match_info2 = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + + g_assert (format); + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r1 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\((?:\")?([0-9]*)(?:\")?-(?:\")?([0-9]*)(?:\")?.*\\)", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r1 != NULL); + + g_regex_match_full (r1, response, strlen (response), 0, 0, &match_info1, &inner_error); + if (inner_error) + goto finish; + if (g_match_info_matches (match_info1)) { + g_autofree gchar *maxbandstr = NULL; + guint maxband = 0; + + *format = MM_CINTERION_RADIO_BAND_FORMAT_SINGLE; + + maxbandstr = mm_get_string_unquoted_from_match_info (match_info1, 2); + if (maxbandstr) + mm_get_uint_from_str (maxbandstr, &maxband); + + if (maxband == 0) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG=? response"); + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + if (maxband & cinterion_bands[i].cinterion_band_flag) { + if (G_UNLIKELY (!bands)) + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + g_array_append_val (bands, cinterion_bands[i].mm_band); + } + } + } + goto finish; + } + + r2 = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\"," + "\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\)" + "(,*\\(\"?([0-9A-Fa-fx]*)\"?-\"?([0-9A-Fa-fx]*)\"?\\))?", + 0, 0, NULL); + g_assert (r2 != NULL); + + g_regex_match_full (r2, response, strlen (response), 0, 0, &match_info2, &inner_error); + if (inner_error) + goto finish; + + while (g_match_info_matches (match_info2)) { + g_autofree gchar *techstr = NULL; + guint maxband; + + *format = MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE; + + techstr = mm_get_string_unquoted_from_match_info (match_info2, 1); + if (g_strcmp0 (techstr, "2G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family); + } else if (g_strcmp0 (techstr, "3G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family); + } else if (g_strcmp0 (techstr, "4G") == 0) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family); + if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) { + maxband = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info2, 6), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (maxband, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family); + } + } else { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG=? response"); + break; + } + + g_match_info_next (match_info2, NULL); + } + +finish: + /* set error only if not already given */ + if (!bands && !inner_error) + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No valid bands found in ^SCFG=? response"); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + g_assert (bands != NULL && bands->len > 0); + *supported_bands = bands; + + return TRUE; +} + +/*****************************************************************************/ +/* ^SCFG response parser (2 types: 2G/3G and LTE) + * + * Example (3G): + * AT^SCFG="Radio/Band" + * ^SCFG: "Radio/Band",127 + * + * Example (2G, UCS-2): + * AT+SCFG="Radio/Band" + * ^SCFG: "Radio/Band","0031","0031" + * + * Example (2G): + * AT+SCFG="Radio/Band" + * ^SCFG: "Radio/Band","3","3" + * + * Example LTE1 (GSM charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0x00000074" + * ^SCFG: "Radio/Band/3G","0x0004019B" + * ^SCFG: "Radio/Band/4G","0x080E08DF" + * ... + * AT^SCFG=? + * ... + * Example LTE1 (UCS2 charset): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0030007800300030003000300030003000370034" + * ^SCFG: "Radio/Band/3G","0030007800300030003000340030003100390042" + * ^SCFG: "Radio/Band/4G","0030007800300038003000450030003800440046" + * ... + * Example LTE2 (all charsets): + * AT^SCFG=? + * ... + * ^SCFG: "Radio/Band/2G","0000000f" + * ^SCFG: "Radio/Band/3G","000400b5" + * ^SCFG: "Radio/Band/4G","8a0e00d5","000000e2" + * ... + */ + +gboolean +mm_cinterion_parse_scfg_response (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + GArray **current_bands, + MMCinterionRadioBandFormat format, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *bands = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band\",\\s*\"?([0-9a-fA-F]*)\"?", 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 finish; + + if (g_match_info_matches (match_info)) { + g_autofree gchar *currentstr = NULL; + guint current = 0; + + currentstr = mm_get_string_unquoted_from_match_info (match_info, 1); + if (currentstr) + mm_get_uint_from_str (currentstr, ¤t); + + if (current == 0) { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG? response"); + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + if (current & cinterion_bands[i].cinterion_band_flag) { + if (G_UNLIKELY (!bands)) + bands = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), 9); + g_array_append_val (bands, cinterion_bands[i].mm_band); + } + } + } + } + } else if (format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE) { + r = g_regex_new ("\\^SCFG:\\s*\"Radio/Band/([234]G)\",\"?([0-9A-Fa-fx]*)\"?,?\"?([0-9A-Fa-fx]*)?\"?", + 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 finish; + + while (g_match_info_matches (match_info)) { + g_autofree gchar *techstr = NULL; + guint current; + + techstr = mm_get_string_unquoted_from_match_info (match_info, 1); + if (g_strcmp0 (techstr, "2G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_GSM, modem_family); + + } else if (g_strcmp0 (techstr, "3G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_UMTS, modem_family); + } else if (g_strcmp0 (techstr, "4G") == 0) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 2), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_LOW, modem_family); + if (modem_family == MM_CINTERION_MODEM_FAMILY_DEFAULT) { + current = take_and_convert_from_matched_string (mm_get_string_unquoted_from_match_info (match_info, 3), + charset, modem_family, &inner_error); + if (inner_error) + break; + parse_bands (current, &bands, MM_CINTERION_RB_BLOCK_LTE_HIGH, modem_family); + } + } else { + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Couldn't parse ^SCFG? response"); + break; + } + + g_match_info_next (match_info, NULL); + } + } else + g_assert_not_reached (); + +finish: + /* set error only if not already given */ + if (!bands && !inner_error) + inner_error = g_error_new (MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "No valid bands found in ^SCFG response"); + + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + g_assert (bands != NULL && bands->len > 0); + *current_bands = bands; + + return TRUE; +} + +/*****************************************************************************/ +/* +CNMI test parser + * + * Example (PHS8): + * AT+CNMI=? + * +CNMI: (0,1,2),(0,1),(0,2),(0),(1) + */ + +gboolean +mm_cinterion_parse_cnmi_test (const gchar *response, + GArray **supported_mode, + GArray **supported_mt, + GArray **supported_bm, + GArray **supported_ds, + GArray **supported_bfr, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GArray) tmp_supported_mode = NULL; + g_autoptr(GArray) tmp_supported_mt = NULL; + g_autoptr(GArray) tmp_supported_bm = NULL; + g_autoptr(GArray) tmp_supported_ds = NULL; + g_autoptr(GArray) tmp_supported_bfr = NULL; + GError *inner_error = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\+CNMI:\\s*\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\),\\((.*)\\)", + 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 (supported_mode) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + tmp_supported_mode = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_mt) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 2); + tmp_supported_mt = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_bm) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 3); + tmp_supported_bm = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_ds) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 4); + tmp_supported_ds = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + if (supported_bfr) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 5); + tmp_supported_bfr = mm_parse_uint_list (str, &inner_error); + if (inner_error) + goto out; + } + } + +out: + if (inner_error) { + g_propagate_error (error, inner_error); + return FALSE; + } + + if (supported_mode) + *supported_mode = g_steal_pointer (&tmp_supported_mode); + if (supported_mt) + *supported_mt = g_steal_pointer (&tmp_supported_mt); + if (supported_bm) + *supported_bm = g_steal_pointer (&tmp_supported_bm); + if (supported_ds) + *supported_ds = g_steal_pointer (&tmp_supported_ds); + if (supported_bfr) + *supported_bfr = g_steal_pointer (&tmp_supported_bfr); + + return TRUE; +} + +/*****************************************************************************/ +/* ^SXRAT test parser + * + * Example (ELS61-E2): + * AT^SXRAT=? + * ^SXRAT: (0-6),(0,2,3),(0,2,3) + */ + +gboolean +mm_cinterion_parse_sxrat_test (const gchar *response, + GArray **supported_rat, + GArray **supported_pref1, + GArray **supported_pref2, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + GArray *tmp_supported_rat = NULL; + GArray *tmp_supported_pref1 = NULL; + GArray *tmp_supported_pref2 = NULL; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\^SXRAT:\\s*\\(([^\\)]*)\\),\\(([^\\)]*)\\)(,\\(([^\\)]*)\\))?(?:\\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 (supported_rat) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 1); + tmp_supported_rat = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + if (supported_pref1) { + g_autofree gchar *str = NULL; + + str = mm_get_string_unquoted_from_match_info (match_info, 2); + tmp_supported_pref1 = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + if (supported_pref2) { + g_autofree gchar *str = NULL; + + /* this match is optional */ + str = mm_get_string_unquoted_from_match_info (match_info, 4); + if (str) { + tmp_supported_pref2 = mm_parse_uint_list (str, &inner_error); + + if (inner_error) + goto out; + } + } + } + +out: + + if (inner_error) { + g_clear_pointer (&tmp_supported_rat, g_array_unref); + g_clear_pointer (&tmp_supported_pref1, g_array_unref); + g_clear_pointer (&tmp_supported_pref2, g_array_unref); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (supported_rat) + *supported_rat = tmp_supported_rat; + if (supported_pref1) + *supported_pref1 = tmp_supported_pref1; + if (supported_pref2) + *supported_pref2 = tmp_supported_pref2; + + return TRUE; +} + +/*****************************************************************************/ +/* Build Cinterion-specific band value */ + +gboolean +mm_cinterion_build_band (GArray *bands, + guint *supported, + gboolean only_2g, + MMCinterionRadioBandFormat format, + MMCinterionModemFamily modem_family, + guint *out_band, + GError **error) +{ + guint band[MM_CINTERION_RB_BLOCK_N] = { 0 }; + + if (format == MM_CINTERION_RADIO_BAND_FORMAT_SINGLE) { + /* The special case of ANY should be treated separately. */ + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + if (supported) + band[MM_CINTERION_RB_BLOCK_LEGACY] = supported[MM_CINTERION_RB_BLOCK_LEGACY]; + } else { + guint i; + + for (i = 0; i < G_N_ELEMENTS (cinterion_bands); i++) { + guint j; + + for (j = 0; j < bands->len; j++) { + if (g_array_index (bands, MMModemBand, j) == cinterion_bands[i].mm_band) { + band[MM_CINTERION_RB_BLOCK_LEGACY] |= cinterion_bands[i].cinterion_band_flag; + break; + } + } + } + + /* 2G-only modems only support a subset of the possible band + * combinations. Detect it early and error out. + */ + if (only_2g && !VALIDATE_2G_BAND (band[MM_CINTERION_RB_BLOCK_LEGACY])) + band[MM_CINTERION_RB_BLOCK_LEGACY] = 0; + } + + if (band[MM_CINTERION_RB_BLOCK_LEGACY] == 0) { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len); + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "The given band combination is not supported: '%s'", + bands_string); + return FALSE; + } + + } else { /* format == MM_CINTERION_RADIO_BAND_FORMAT_MULTIPLE */ + if (bands->len == 1 && g_array_index (bands, MMModemBand, 0) == MM_MODEM_BAND_ANY) { + if (supported) + memcpy (band, supported, sizeof (guint) * MM_CINTERION_RB_BLOCK_N); + } else { + guint i; + const CinterionBandEx *ref_bands; + guint nb_ref_bands; + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + ref_bands = cinterion_bands_imt; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_imt); + } else { + ref_bands = cinterion_bands_ex; + nb_ref_bands = G_N_ELEMENTS (cinterion_bands_ex); + } + + for (i = 0; i < nb_ref_bands; i++) { + guint j; + + for (j = 0; j < bands->len; j++) { + if (g_array_index (bands, MMModemBand, j) == ref_bands[i].mm_band) { + band[ref_bands[i].cinterion_band_block] |= ref_bands[i].cinterion_band_flag; + break; + } + } + } + } + + /* this modem family does not allow disabling all bands in a given technology through this command */ + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT && + (!band[MM_CINTERION_RB_BLOCK_GSM] || + !band[MM_CINTERION_RB_BLOCK_UMTS] || + !band[MM_CINTERION_RB_BLOCK_LTE_LOW])) { + g_autofree gchar *bands_string = NULL; + + bands_string = mm_common_build_bands_string ((MMModemBand *)(gpointer)bands->data, bands->len); + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "The given band combination is not supported: '%s'", + bands_string); + return FALSE; + } + } + + memcpy (out_band, band, sizeof (guint) * MM_CINTERION_RB_BLOCK_N); + return TRUE; +} + +/*****************************************************************************/ +/* Single ^SIND response parser */ + +gboolean +mm_cinterion_parse_sind_response (const gchar *response, + gchar **description, + guint *mode, + guint *value, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + guint errors = 0; + + if (!response) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); + return FALSE; + } + + r = g_regex_new ("\\^SIND:\\s*(.*),(\\d+),(\\d+)(\\r\\n)?", 0, 0, NULL); + g_assert (r != NULL); + + if (g_regex_match (r, response, 0, &match_info)) { + if (description) { + *description = mm_get_string_unquoted_from_match_info (match_info, 1); + if (*description == NULL) + errors++; + } + if (mode && !mm_get_uint_from_match_info (match_info, 2, mode)) + errors++; + if (value && !mm_get_uint_from_match_info (match_info, 3, value)) + errors++; + } else + errors++; + + if (errors > 0) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed parsing ^SIND response"); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ +/* ^SWWAN read parser + * + * Description: Parses <cid>, <state>[, <WWAN adapter>] or CME ERROR from SWWAN. + * + * The method returns a MMSwwanState with the connection status of a single + * PDP context, the one being queried via the cid given as input. + * + * Note that we use CID for matching because the WWAN adapter field is optional + * it seems. + * + * Read Command + * AT^SWWAN? + * Response(s) + * [^SWWAN: <cid>, <state>[, <WWAN adapter>]] + * [^SWWAN: ...] + * OK + * ERROR + * +CME ERROR: <err> + * + * Examples: + * OK - If no WWAN connection is active, then read command just returns OK + * ^SWWAN: 3,1,1 - 3rd PDP Context, Activated, First WWAN Adaptor + * +CME ERROR: ? - + */ + +enum { + MM_SWWAN_STATE_DISCONNECTED = 0, + MM_SWWAN_STATE_CONNECTED = 1, +}; + +MMBearerConnectionStatus +mm_cinterion_parse_swwan_response (const gchar *response, + guint cid, + gpointer log_object, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GError *inner_error = NULL; + MMBearerConnectionStatus status; + + g_assert (response); + + /* If no WWAN connection is active, then ^SWWAN read command just returns OK + * (which we receive as an empty string) */ + if (!response[0]) + return MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + + if (!g_str_has_prefix (response, "^SWWAN:")) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't parse ^SWWAN response: '%s'", response); + return MM_BEARER_CONNECTION_STATUS_UNKNOWN; + } + + r = g_regex_new ("\\^SWWAN:\\s*(\\d+),\\s*(\\d+)(?:,\\s*(\\d+))?(?:\\r\\n)?", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, NULL); + g_assert (r != NULL); + + status = MM_BEARER_CONNECTION_STATUS_UNKNOWN; + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error); + while (!inner_error && g_match_info_matches (match_info)) { + guint read_state; + guint read_cid; + + if (!mm_get_uint_from_match_info (match_info, 1, &read_cid)) + mm_obj_warn (log_object, "couldn't read cid in ^SWWAN response: %s", response); + else if (!mm_get_uint_from_match_info (match_info, 2, &read_state)) + mm_obj_warn (log_object, "couldn't read state in ^SWWAN response: %s", response); + else if (read_cid == cid) { + if (read_state == MM_SWWAN_STATE_CONNECTED) { + status = MM_BEARER_CONNECTION_STATUS_CONNECTED; + break; + } + if (read_state == MM_SWWAN_STATE_DISCONNECTED) { + status = MM_BEARER_CONNECTION_STATUS_DISCONNECTED; + break; + } + mm_obj_warn (log_object, "invalid state read in ^SWWAN response: %u", read_state); + break; + } + g_match_info_next (match_info, &inner_error); + } + + if (status == MM_BEARER_CONNECTION_STATUS_UNKNOWN) + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "No state returned for CID %u", cid); + + return status; +} + +/*****************************************************************************/ +/* ^SGAUTH response parser */ + +/* at^sgauth? + * ^SGAUTH: 1,2,"vf" + * ^SGAUTH: 3,0,"" + * ^SGAUTH: 4,0 + * + * OK + */ + +gboolean +mm_cinterion_parse_sgauth_response (const gchar *response, + guint cid, + MMBearerAllowedAuth *out_auth, + gchar **out_username, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + + r = g_regex_new ("\\^SGAUTH:\\s*(\\d+),(\\d+),?\"?([a-zA-Z0-9_-]+)?\"?", 0, 0, NULL); + g_assert (r != NULL); + + g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, NULL); + while (g_match_info_matches (match_info)) { + guint sgauth_cid = 0; + + if (mm_get_uint_from_match_info (match_info, 1, &sgauth_cid) && + (sgauth_cid == cid)) { + guint cinterion_auth_type = 0; + + mm_get_uint_from_match_info (match_info, 2, &cinterion_auth_type); + *out_auth = mm_auth_type_from_cinterion_auth_type (cinterion_auth_type); + *out_username = mm_get_string_unquoted_from_match_info (match_info, 3); + return TRUE; + } + g_match_info_next (match_info, NULL); + } + + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_NOT_FOUND, + "Auth settings for context %u not found", cid); + return FALSE; +} + +/*****************************************************************************/ +/* ^SMONG response parser */ + +static gboolean +get_access_technology_from_smong_gprs_status (guint gprs_status, + MMModemAccessTechnology *out, + GError **error) +{ + switch (gprs_status) { + case 0: + *out = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + return TRUE; + case 1: + case 2: + *out = MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + return TRUE; + case 3: + case 4: + *out = MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + return TRUE; + default: + break; + } + + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_INVALID_ARGS, + "Couldn't get network capabilities, " + "unsupported GPRS status value: '%u'", + gprs_status); + return FALSE; +} + +gboolean +mm_cinterion_parse_smong_response (const gchar *response, + MMModemAccessTechnology *access_tech, + GError **error) +{ + guint value = 0; + GError *inner_error = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GRegex) regex = NULL; + + /* The AT^SMONG command returns a cell info table, where the second + * column identifies the "GPRS status", which is exactly what we want. + * So we'll try to read that second number in the values row. + * + * AT^SMONG + * GPRS Monitor + * BCCH G PBCCH PAT MCC MNC NOM TA RAC # Cell # + * 0776 1 - - 214 03 2 00 01 + * OK + */ + regex = g_regex_new (".*GPRS Monitor(?:\r\n)*" + "BCCH\\s*G.*\\r\\n" + "\\s*(\\d+)\\s*(\\d+)\\s*", + G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, + 0, NULL); + g_assert (regex); + + g_regex_match_full (regex, response, strlen (response), 0, 0, &match_info, &inner_error); + + if (inner_error) { + g_prefix_error (&inner_error, "Failed to match AT^SMONG response: "); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!g_match_info_matches (match_info) || !mm_get_uint_from_match_info (match_info, 2, &value)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't read 'GPRS status' field from AT^SMONG response"); + return FALSE; + } + + return get_access_technology_from_smong_gprs_status (value, access_tech, error); +} + +/*****************************************************************************/ +/* ^SIND psinfo helper */ + +MMModemAccessTechnology +mm_cinterion_get_access_technology_from_sind_psinfo (guint val, + gpointer log_object) +{ + switch (val) { + case 0: + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + case 1: + case 2: + return MM_MODEM_ACCESS_TECHNOLOGY_GPRS; + case 3: + case 4: + return MM_MODEM_ACCESS_TECHNOLOGY_EDGE; + case 5: + case 6: + return MM_MODEM_ACCESS_TECHNOLOGY_UMTS; + case 7: + case 8: + return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA; + case 9: + case 10: + return (MM_MODEM_ACCESS_TECHNOLOGY_HSDPA | MM_MODEM_ACCESS_TECHNOLOGY_HSUPA); + case 16: + case 17: + return MM_MODEM_ACCESS_TECHNOLOGY_LTE; + default: + mm_obj_dbg (log_object, "unable to identify access technology from psinfo reported value: %u", val); + return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN; + } +} + +/*****************************************************************************/ +/* ^SLCC psinfo helper */ + +GRegex * +mm_cinterion_get_slcc_regex (void) +{ + /* The list of active calls displayed with this URC will always be terminated + * with an empty line preceded by prefix "^SLCC: ", in order to indicate the end + * of the list. + */ + return g_regex_new ("\\r\\n(\\^SLCC: .*\\r\\n)*\\^SLCC: \\r\\n", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +static void +cinterion_call_info_free (MMCallInfo *info) +{ + if (!info) + return; + g_free (info->number); + g_slice_free (MMCallInfo, info); +} + +gboolean +mm_cinterion_parse_slcc_list (const gchar *str, + gpointer log_object, + GList **out_list, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + GList *list = NULL; + GError *inner_error = NULL; + + static const MMCallDirection cinterion_call_direction[] = { + [0] = MM_CALL_DIRECTION_OUTGOING, + [1] = MM_CALL_DIRECTION_INCOMING, + }; + + static const MMCallState cinterion_call_state[] = { + [0] = MM_CALL_STATE_ACTIVE, + [1] = MM_CALL_STATE_HELD, + [2] = MM_CALL_STATE_DIALING, /* Dialing (MOC) */ + [3] = MM_CALL_STATE_RINGING_OUT, /* Alerting (MOC) */ + [4] = MM_CALL_STATE_RINGING_IN, /* Incoming (MTC) */ + [5] = MM_CALL_STATE_WAITING, /* Waiting (MTC) */ + }; + + g_assert (out_list); + + /* + * 1 2 3 4 5 6 7 8 9 + * ^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]] + * [^SLCC: <idx>, <dir>, <stat>, <mode>, <mpty>, <Reserved>[, <number>, <type>[,<alpha>]]] + * [... ] + * ^SLCC : + */ + + r = g_regex_new ("\\^SLCC:\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+),\\s*(\\d+)" /* mandatory fields */ + "(?:,\\s*([^,]*),\\s*(\\d+)" /* number and type */ + "(?:,\\s*([^,]*)" /* alpha */ + ")?)?$", + G_REGEX_RAW | G_REGEX_MULTILINE | G_REGEX_NEWLINE_CRLF, + G_REGEX_MATCH_NEWLINE_CRLF, + NULL); + g_assert (r != NULL); + + g_regex_match_full (r, str, strlen (str), 0, 0, &match_info, &inner_error); + if (inner_error) + goto out; + + /* Parse the results */ + while (g_match_info_matches (match_info)) { + MMCallInfo *call_info; + guint aux; + + call_info = g_slice_new0 (MMCallInfo); + + if (!mm_get_uint_from_match_info (match_info, 1, &call_info->index)) { + mm_obj_warn (log_object, "couldn't parse call index from ^SLCC line"); + goto next; + } + + if (!mm_get_uint_from_match_info (match_info, 2, &aux) || + (aux >= G_N_ELEMENTS (cinterion_call_direction))) { + mm_obj_warn (log_object, "couldn't parse call direction from ^SLCC line"); + goto next; + } + call_info->direction = cinterion_call_direction[aux]; + + if (!mm_get_uint_from_match_info (match_info, 3, &aux) || + (aux >= G_N_ELEMENTS (cinterion_call_state))) { + mm_obj_warn (log_object, "couldn't parse call state from ^SLCC line"); + goto next; + } + call_info->state = cinterion_call_state[aux]; + + if (g_match_info_get_match_count (match_info) >= 8) + call_info->number = mm_get_string_unquoted_from_match_info (match_info, 7); + + list = g_list_append (list, call_info); + call_info = NULL; + + next: + cinterion_call_info_free (call_info); + g_match_info_next (match_info, NULL); + } + +out: + if (inner_error) { + mm_cinterion_call_info_list_free (list); + g_propagate_error (error, inner_error); + return FALSE; + } + + *out_list = list; + + return TRUE; +} + +void +mm_cinterion_call_info_list_free (GList *call_info_list) +{ + g_list_free_full (call_info_list, (GDestroyNotify) cinterion_call_info_free); +} + +/*****************************************************************************/ +/* +CTZU URC helpers */ + +GRegex * +mm_cinterion_get_ctzu_regex (void) +{ + /* + * From PLS-8 AT command spec: + * +CTZU:<nitzUT>, <nitzTZ>[, <nitzDST>] + * E.g.: + * +CTZU: "19/07/09,10:19:15",+08,1 + */ + + return g_regex_new ("\\r\\n\\+CTZU:\\s*\"(\\d+)\\/(\\d+)\\/(\\d+),(\\d+):(\\d+):(\\d+)\",([\\-\\+\\d]+)(?:,(\\d+))?(?:\\r\\n)?", + G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); +} + +gboolean +mm_cinterion_parse_ctzu_urc (GMatchInfo *match_info, + gchar **iso8601p, + MMNetworkTimezone **tzp, + GError **error) +{ + gboolean ret = TRUE; + guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, dst = 0; + gint tz = 0; + + if (!mm_get_uint_from_match_info (match_info, 1, &year) || + !mm_get_uint_from_match_info (match_info, 2, &month) || + !mm_get_uint_from_match_info (match_info, 3, &day) || + !mm_get_uint_from_match_info (match_info, 4, &hour) || + !mm_get_uint_from_match_info (match_info, 5, &minute) || + !mm_get_uint_from_match_info (match_info, 6, &second) || + !mm_get_int_from_match_info (match_info, 7, &tz)) { + g_set_error_literal (error, + MM_CORE_ERROR, + MM_CORE_ERROR_FAILED, + "Failed to parse +CTZU URC"); + return FALSE; + } + + /* adjust year */ + if (year < 100) + year += 2000; + + /* + * tz = timezone offset in 15 minute intervals + */ + if (iso8601p) { + /* Return ISO-8601 format date/time string */ + *iso8601p = mm_new_iso8601_time (year, month, day, hour, + minute, second, + TRUE, tz * 15, + error); + ret = (*iso8601p != NULL); + } + + if (tzp) { + *tzp = mm_network_timezone_new (); + mm_network_timezone_set_offset (*tzp, tz * 15); + } + + /* dst flag is optional in the URC + * + * tz = timezone offset in 15 minute intervals + * dst = daylight adjustment, 0 = none, 1 = 1 hour, 2 = 2 hours + */ + if (tzp && mm_get_uint_from_match_info (match_info, 8, &dst)) + mm_network_timezone_set_dst_offset (*tzp, dst * 60); + + return ret; +} + +/*****************************************************************************/ +/* ^SMONI response parser */ + +gboolean +mm_cinterion_parse_smoni_query_response (const gchar *response, + MMCinterionRadioGen *out_tech, + gdouble *out_rssi, + gdouble *out_ecn0, + gdouble *out_rscp, + gdouble *out_rsrp, + gdouble *out_rsrq, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GRegex) pre = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autoptr(GMatchInfo) match_info_pre = NULL; + GError *inner_error = NULL; + MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE; + gdouble rssi = -G_MAXDOUBLE; + gdouble ecn0 = -G_MAXDOUBLE; + gdouble rscp = -G_MAXDOUBLE; + gdouble rsrq = -G_MAXDOUBLE; + gdouble rsrp = -G_MAXDOUBLE; + gboolean success = FALSE; + + g_assert (out_tech); + g_assert (out_rssi); + g_assert (out_ecn0); + g_assert (out_rscp); + g_assert (out_rsrp); + g_assert (out_rsrq); + g_assert (out_rssi); + + /* Possible Responses: + * 2G + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,Conn_state // registered + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // searching + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,PWR,RXLev,ARFCN,TS,timAdv,dBm,Q,ChMod // limsrv + * ^SMONI: 2G,ARFCN,BCCH,MCC,MNC,LAC,cell,C1,C2,NCC,BCC,GPRS,ARFCN,TS,timAdv,dBm,Q,ChMod // dedicated channel + * + * ^SMONI: 2G,71,-61,262,02,0143,83BA,33,33,3,6,G,NOCONN + * ^^^ + * ^SMONI: 2G,SEARCH,SEARCH + * ^SMONI: 2G,673,-89,262,07,4EED,A500,16,16,7,4,G,5,-107,LIMSRV + * ^^^ ^^^^ RXLev dBm + * ^SMONI: 2G,673,-80,262,07,4EED,A500,35,35,7,4,G,643,4,0,-80,0,S_FR + * ^^^ ^^^ dBm: Receiving level of the traffic channel carrier in dBm + * BCCH: Receiving level of the BCCH carrier in dBm (level is limited from -110dBm to -47dBm) + * -> rssi for 2G, directly without mm_3gpp_rxlev_to_rssi + * + * + * 3G + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,,Conn_state", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * ^SMONI: 3G,UARFCN,PSC,EC/n0,RSCP,MCC,MNC,LAC,cell,SQual,SRxLev,PhysCh, SF,Slot,EC/n0,RSCP,ComMod,HSUPA,HSDPA", + * + * ^SMONI: 3G,10564,296,-7.5,-79,262,02,0143,00228FF,-92,-78,NOCONN + * ^^^^ ^^^ + * ^SMONI: 3G,SEARCH,SEARCH + * ^SMONI: 3G,10564,96,-7.5,-79,262,02,0143,00228FF,-92,-78,LIMSRV + * ^^^^ ^^^ + * ^SMONI: 3G,10737,131,-5,-93,260,01,7D3D,C80BC9A,--,--,----,---,-,-5,-93,0,01,06 + * ^^ ^^^ + * RSCP: Received Signal Code Power in dBm -> no need for mm_3gpp_rscp_level_to_rscp + * EC/n0: EC/n0 Carrier to noise ratio in dB = measured Ec/Io value in dB. Please refer to 3GPP 25.133, section 9.1.2.3, Table 9.9 for details on the mapping from EC/n0 to EC/Io. + * -> direct value, without need for mm_3gpp_ecn0_level_to_ecio + * + * + * 4G + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,Srxlev,RSRP,RSRQ,Conn_state + * ^SMONI: 4G,EARFCN,Band,DL bandwidth,UL bandwidth,Mode,MCC,MNC,TAC,Global Cell ID,Physical Cell ID,TX_power,RSRP,RSRQ,Conn_state + * + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,NOCONN + * ^^^ ^^ + * ^SMONI: 4G,SEARCH + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,33,-94,-7,LIMSRV + * ^^^ ^^ + * ^SMONI: 4G,6300,20,10,10,FDD,262,02,BF75,0345103,350,90,-94,-7,CONN + * ^^^ ^^ + * RSRP Reference Signal Received Power (see 3GPP 36.214 Section 5.1.1.) -> directly the value without mm_3gpp_rsrq_level_to_rsrp + * RSRQ Reference Signal Received Quality (see 3GPP 36.214 Section 5.1.2.) -> directly the value without mm_3gpp_rsrq_level_to_rsrq + */ + if (g_regex_match_simple ("\\^SMONI:\\s*[234]G,SEARCH", response, 0, 0)) { + success = TRUE; + goto out; + } + pre = g_regex_new ("\\^SMONI:\\s*([234])", 0, 0, NULL); + g_assert (pre != NULL); + g_regex_match_full (pre, response, strlen (response), 0, 0, &match_info_pre, &inner_error); + if (!inner_error && g_match_info_matches (match_info_pre)) { + if (!mm_get_uint_from_match_info (match_info_pre, 1, &tech)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read tech"); + goto out; + } + #define FLOAT "([-+]?[0-9]+\\.?[0-9]*)" + switch (tech) { + case MM_CINTERION_RADIO_GEN_2G: + r = g_regex_new ("\\^SMONI:\\s*2G,(\\d+),"FLOAT, 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)) { + /* skip ARFCN */ + if (!mm_get_double_from_match_info (match_info, 2, &rssi)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BCCH=rssi"); + goto out; + } + } + break; + case MM_CINTERION_RADIO_GEN_3G: + r = g_regex_new ("\\^SMONI:\\s*3G,(\\d+),(\\d+),"FLOAT","FLOAT, 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)) { + /* skip UARFCN */ + /* skip PSC (Primary scrambling code) */ + if (!mm_get_double_from_match_info (match_info, 3, &ecn0)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read EcN0"); + goto out; + } + if (!mm_get_double_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; + } + } + break; + case MM_CINTERION_RADIO_GEN_4G: + r = g_regex_new ("\\^SMONI:\\s*4G,(\\d+),(\\d+),(\\d+),(\\d+),(\\w+),(\\d+),(\\d+),(\\w+),(\\w+),(\\d+),([^,]*),"FLOAT","FLOAT, 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)) { + /* skip EARFCN */ + /* skip Band */ + /* skip DL bandwidth */ + /* skip UL bandwidth */ + /* skip Mode */ + /* skip MCC */ + /* skip MNC */ + /* skip TAC */ + /* skip Global Cell ID */ + /* skip Physical Cell ID */ + /* skip Srxlev/TX_power */ + if (!mm_get_double_from_match_info (match_info, 12, &rsrp)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ"); + goto out; + } + if (!mm_get_double_from_match_info (match_info, 13, &rsrq)) { + inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP"); + goto out; + } + } + break; + case MM_CINTERION_RADIO_GEN_NONE: + default: + goto out; + } + #undef FLOAT + 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 ^SMONI response: %s", response); + return FALSE; + } + + *out_tech = tech; + *out_rssi = rssi; + *out_rscp = rscp; + *out_ecn0 = ecn0; + *out_rsrq = rsrq; + *out_rsrp = rsrp; + return TRUE; +} + +/*****************************************************************************/ +/* Get extended signal information */ + +gboolean +mm_cinterion_smoni_response_to_signal_info (const gchar *response, + MMSignal **out_gsm, + MMSignal **out_umts, + MMSignal **out_lte, + GError **error) +{ + MMCinterionRadioGen tech = MM_CINTERION_RADIO_GEN_NONE; + gdouble rssi = MM_SIGNAL_UNKNOWN; + gdouble ecn0 = MM_SIGNAL_UNKNOWN; + gdouble rscp = MM_SIGNAL_UNKNOWN; + gdouble rsrq = MM_SIGNAL_UNKNOWN; + gdouble rsrp = MM_SIGNAL_UNKNOWN; + MMSignal *gsm = NULL; + MMSignal *umts = NULL; + MMSignal *lte = NULL; + + if (!mm_cinterion_parse_smoni_query_response (response, + &tech, &rssi, + &ecn0, &rscp, + &rsrp, &rsrq, + error)) + return FALSE; + + switch (tech) { + case MM_CINTERION_RADIO_GEN_2G: + gsm = mm_signal_new (); + mm_signal_set_rssi (gsm, rssi); + break; + case MM_CINTERION_RADIO_GEN_3G: + umts = mm_signal_new (); + mm_signal_set_rscp (umts, rscp); + mm_signal_set_ecio (umts, ecn0); /* UMTS EcIo (assumed EcN0) */ + break; + case MM_CINTERION_RADIO_GEN_4G: + lte = mm_signal_new (); + mm_signal_set_rsrp (lte, rsrp); + mm_signal_set_rsrq (lte, rsrq); + break; + case MM_CINTERION_RADIO_GEN_NONE: /* not registered, searching */ + break; /* no error case */ + default: /* should not happen, so if it does, error */ + 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; +} + +/*****************************************************************************/ +/* provider cfg information to CID number for EPS initial settings */ + +/* + * at^scfg="MEopMode/Prov/Cfg" + * ^SCFG: "MEopMode/Prov/Cfg","vdfde" + * ^SCFG: "MEopMode/Prov/Cfg","attus" + * ^SCFG: "MEopMode/Prov/Cfg","2" -> PLS8-X vzw + * ^SCFG: "MEopMode/Prov/Cfg","vzwdcus" -> PLAS9-x vzw + * ^SCFG: "MEopMode/Prov/Cfg","tmode" -> t-mob germany + * OK + */ +gboolean +mm_cinterion_provcfg_response_to_cid (const gchar *response, + MMCinterionModemFamily modem_family, + MMModemCharset charset, + gpointer log_object, + gint *cid, + GError **error) +{ + g_autoptr(GRegex) r = NULL; + g_autoptr(GMatchInfo) match_info = NULL; + g_autofree gchar *mno = NULL; + GError *inner_error = NULL; + + r = g_regex_new ("\\^SCFG:\\s*\"MEopMode/Prov/Cfg\",\\s*\"([0-9a-zA-Z*]*)\"", 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_prefix_error (&inner_error, "Failed to match Prov/Cfg response: "); + g_propagate_error (error, inner_error); + return FALSE; + } + + if (!g_match_info_matches (match_info)) { + g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "Couldn't match Prov/Cfg response"); + return FALSE; + } + + mno = mm_get_string_unquoted_from_match_info (match_info, 1); + if (mno && modem_family == MM_CINTERION_MODEM_FAMILY_IMT) { + gchar *mno_utf8; + + mno_utf8 = mm_modem_charset_str_to_utf8 (mno, -1, charset, FALSE, error); + if (!mno_utf8) + return FALSE; + g_free (mno); + mno = mno_utf8; + } + mm_obj_dbg (log_object, "current mno: %s", mno ? mno : "none"); + + /* for Cinterion LTE modules, some CID numbers have special meaning. + * This is dictated by the chipset and by the MNO: + * - the chipset uses a special one, CID 1, as a LTE combined attach chipset + * - the MNOs can define the sequence and number of APN to be used for their network. + * This takes priority over the chipset preferences, and therefore for some of them + * the CID for the initial EPS context must be changed. + */ + if (g_strcmp0 (mno, "2") == 0 || g_strcmp0 (mno, "vzwdcus") == 0) + *cid = 3; + else if (g_strcmp0 (mno, "tmode") == 0) + *cid = 2; + else + *cid = 1; + return TRUE; +} + +/*****************************************************************************/ +/* Auth related helpers */ + +typedef enum { + BEARER_CINTERION_AUTH_UNKNOWN = -1, + BEARER_CINTERION_AUTH_NONE = 0, + BEARER_CINTERION_AUTH_PAP = 1, + BEARER_CINTERION_AUTH_CHAP = 2, + BEARER_CINTERION_AUTH_MSCHAPV2 = 3, +} BearerCinterionAuthType; + +static BearerCinterionAuthType +parse_auth_type (MMBearerAllowedAuth mm_auth) +{ + switch (mm_auth) { + case MM_BEARER_ALLOWED_AUTH_NONE: + return BEARER_CINTERION_AUTH_NONE; + case MM_BEARER_ALLOWED_AUTH_PAP: + return BEARER_CINTERION_AUTH_PAP; + case MM_BEARER_ALLOWED_AUTH_CHAP: + return BEARER_CINTERION_AUTH_CHAP; + case MM_BEARER_ALLOWED_AUTH_MSCHAPV2: + return BEARER_CINTERION_AUTH_MSCHAPV2; + case MM_BEARER_ALLOWED_AUTH_UNKNOWN: + case MM_BEARER_ALLOWED_AUTH_MSCHAP: + case MM_BEARER_ALLOWED_AUTH_EAP: + default: + return BEARER_CINTERION_AUTH_UNKNOWN; + } +} + +MMBearerAllowedAuth +mm_auth_type_from_cinterion_auth_type (guint cinterion_auth) +{ + switch (cinterion_auth) { + case BEARER_CINTERION_AUTH_NONE: + return MM_BEARER_ALLOWED_AUTH_NONE; + case BEARER_CINTERION_AUTH_PAP: + return MM_BEARER_ALLOWED_AUTH_PAP; + case BEARER_CINTERION_AUTH_CHAP: + return MM_BEARER_ALLOWED_AUTH_CHAP; + default: + return MM_BEARER_ALLOWED_AUTH_UNKNOWN; + } +} + +/* Cinterion authentication is done with the command AT^SGAUTH, + whose syntax depends on the modem family, as follow: + - AT^SGAUTH=<cid>[, <auth_type>[, <user>, <passwd>]] for the IMT family + - AT^SGAUTH=<cid>[, <auth_type>[, <passwd>, <user>]] for the rest */ +gchar * +mm_cinterion_build_auth_string (gpointer log_object, + MMCinterionModemFamily modem_family, + MMBearerProperties *config, + guint cid) +{ + MMBearerAllowedAuth auth; + BearerCinterionAuthType encoded_auth = BEARER_CINTERION_AUTH_UNKNOWN; + gboolean has_user; + gboolean has_passwd; + const gchar *user; + const gchar *passwd; + g_autofree gchar *quoted_user = NULL; + g_autofree gchar *quoted_passwd = NULL; + + user = mm_bearer_properties_get_user (config); + passwd = mm_bearer_properties_get_password (config); + auth = mm_bearer_properties_get_allowed_auth (config); + + has_user = (user && user[0]); + has_passwd = (passwd && passwd[0]); + encoded_auth = parse_auth_type (auth); + + /* When 'none' requested, we won't require user/password */ + if (encoded_auth == BEARER_CINTERION_AUTH_NONE) { + if (has_user || has_passwd) + mm_obj_warn (log_object, "APN user/password given but 'none' authentication requested"); + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) + return g_strdup_printf ("^SGAUTH=%u,%d,\"\",\"\"", cid, encoded_auth); + return g_strdup_printf ("^SGAUTH=%u,%d", cid, encoded_auth); + } + + /* No explicit auth type requested? */ + if (encoded_auth == BEARER_CINTERION_AUTH_UNKNOWN) { + /* If no user/passwd given, do nothing */ + if (!has_user && !has_passwd) + return NULL; + + /* If user/passwd given, default to CHAP (more common than PAP) */ + mm_obj_dbg (log_object, "APN user/password given but no authentication type explicitly requested: defaulting to 'CHAP'"); + encoded_auth = BEARER_CINTERION_AUTH_CHAP; + } + + quoted_user = mm_port_serial_at_quote_string (user ? user : ""); + quoted_passwd = mm_port_serial_at_quote_string (passwd ? passwd : ""); + + if (modem_family == MM_CINTERION_MODEM_FAMILY_IMT) + return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s", + cid, + encoded_auth, + quoted_user, + quoted_passwd); + + return g_strdup_printf ("^SGAUTH=%u,%d,%s,%s", + cid, + encoded_auth, + quoted_passwd, + quoted_user); +} + +/*****************************************************************************/ +/* ^SXRAT set command builder */ + +/* Index of the array is the centerion-specific sxrat value */ +static const MMModemMode sxrat_combinations[] = { + [0] = ( MM_MODEM_MODE_2G ), + [1] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G ), + [2] = ( MM_MODEM_MODE_3G ), + [3] = ( MM_MODEM_MODE_4G ), + [4] = ( MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), + [5] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_4G ), + [6] = ( MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G ), +}; + +static gboolean +append_sxrat_rat_value (GString *str, + MMModemMode mode, + GError **error) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (sxrat_combinations); i++) { + if (sxrat_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_cinterion_build_sxrat_set_command (MMModemMode allowed, + MMModemMode preferred, + GError **error) +{ + GString *command; + + command = g_string_new ("^SXRAT="); + if (!append_sxrat_rat_value (command, allowed, error)) { + g_string_free (command, TRUE); + return NULL; + } + + if (preferred != MM_MODEM_MODE_NONE) { + if (mm_count_bits_set (preferred) != 1) { + *error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, + "AcT preferred value should be a single AcT"); + g_string_free (command, TRUE); + return NULL; + } + g_string_append (command, ","); + if (!append_sxrat_rat_value (command, preferred, error)) { + g_string_free (command, TRUE); + return NULL; + } + } + + return g_string_free (command, FALSE); +} |