/* -*- 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 * Copyright (C) 2016 Trimble Navigation Limited * Copyright (C) 2016 Matthew Stanger * Copyright (C) 2019 Purism SPC */ #include #include #include #include #include "ModemManager.h" #define _LIBMM_INSIDE_MM #include #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; } /*****************************************************************************/ /* ^SCFG response sim parser * * Example: * ... * ^SCFG: "SIM/CS","SIM_1" * ... */ gboolean mm_cinterion_parse_scfg_sim_response (const gchar *response, guint *active_slot, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = 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 ("\\^SCFG:\\s*\"SIM/CS\",\".*?(\\d)\"", 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, "No valid SIM/CS entry found in ^SCFG response: "); g_propagate_error (error, inner_error); return FALSE; } if (!mm_get_uint_from_match_info (match_info, 1, active_slot)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse SIM slot index in ^SCFG response"); return FALSE; } return TRUE; } gboolean mm_cinterion_get_available_from_simlocal (const gchar *response, GArray **available, GError **error) { g_autoptr(GArray) tmp_available = NULL; g_auto(GStrv) sim_groups = NULL; GError *inner_error = NULL; guint sim_length; guint i; if (!response) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing response"); return FALSE; } sim_groups = mm_split_string_groups (response); sim_length = g_strv_length (sim_groups); tmp_available = g_array_sized_new (FALSE, FALSE, sizeof (gboolean), sim_length); for (i = 0; i < sim_length; i++) { guint index_value; gboolean is_available; if (!mm_get_uint_from_str (sim_groups[i], &index_value)) { inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Could not parse SIM index value '%s'", sim_groups[i]); g_propagate_error (error, inner_error); return FALSE; } is_available = (gboolean) index_value; g_array_append_val (tmp_available, is_available); } *available = g_steal_pointer (&tmp_available); return TRUE; } /*****************************************************************************/ /* ^SIND response simlocal parser * * Example: * ^SIND: simlocal,1,0,0 */ gboolean mm_cinterion_parse_sind_simlocal_response (const gchar *response, GArray **available, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; g_autofree gchar *str = 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 ("\\^SIND:\\s*simlocal,\\d+,((\\d,)*\\d)", 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, "No valid SIM/CS entry found in ^SCFG 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 SIM slot index"); return FALSE; } str = g_match_info_fetch (match_info, 1); if (!mm_cinterion_get_available_from_simlocal (str, available, &inner_error)) { g_propagate_error (error, inner_error); return FALSE; } 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*\\(([^\\)]*)\\),\\s*\\(([^\\)]*)\\)(,\\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 , [, ] 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: , [, ]] * [^SWWAN: ...] * OK * ERROR * +CME ERROR: * * 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: , , , , , [, , [,]] * [^SLCC: , , , , , [, , [,]]] * [... ] * ^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:, [, ] * 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=[, [, , ]] for the IMT family - AT^SGAUTH=[, [, , ]] 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); /* No explicit auth type requested? */ if (encoded_auth == BEARER_CINTERION_AUTH_UNKNOWN) { if (!has_user && !has_passwd) { /* If no user/passwd given, default to 'none' */ mm_obj_dbg (log_object, "APN user/password and authentication type not given: defaulting to 'none'"); encoded_auth = BEARER_CINTERION_AUTH_NONE; } else { /* 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; } } /* 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); } quoted_user = mm_at_quote_string (user ? user : ""); quoted_passwd = mm_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); } static gboolean modem_mode_to_cops_uint (MMModemMode mode, guint *out, GError **error) { switch (mode) { case MM_MODEM_MODE_2G: /* 2G-only force GERAN RAT (AcT=0) */ *out = 0; break; case MM_MODEM_MODE_3G: /* 3G-only force UTRAN RAT (AcT=2) */ *out = 2; break; case MM_MODEM_MODE_4G: /* 4G-only force E-UTRAN RAT (AcT=7) */ *out = 7; break; case MM_MODEM_MODE_NONE: case MM_MODEM_MODE_CS: case MM_MODEM_MODE_5G: case MM_MODEM_MODE_ANY: default: g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Cannot use mode '%s' for COPS", mm_modem_mode_build_string_from_mask (mode)); return FALSE; } return TRUE; } gboolean mm_cinterion_build_cops_set_command (MMModemMode mode, const gchar *operator_code, gchar **out, GError **error) { GString *command; guint cops_mode; command = g_string_new ("+COPS="); if (!operator_code && mode == MM_MODEM_MODE_ANY) { /* any operator, any mode */ g_string_append (command, "0"); } else { /* append ,, */ if (!operator_code) g_string_append (command, "0,,"); else g_string_append_printf (command, "1,2,\"%s\"", operator_code); if (mode != MM_MODEM_MODE_ANY) { /* append */ if (!modem_mode_to_cops_uint (mode, &cops_mode, error)) { g_string_free (command, TRUE); return FALSE; } g_string_append_printf (command, ",%u", cops_mode); } } *out = g_string_free (command, FALSE); return TRUE; } gboolean mm_cinterion_parse_ws46_response (const gchar *response, MMModemMode *result, GError **error) { g_autoptr(GRegex) r = NULL; g_autoptr(GMatchInfo) match_info = NULL; guint mode_num; r = g_regex_new ("\\+WS46:\\s*(\\d+)", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); g_assert (r != NULL); if (!g_regex_match (r, response, 0, &match_info)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to parse WS46 response '%s'", response); return FALSE; } if (!mm_get_uint_from_match_info (match_info, 1, &mode_num)) { g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Failed to get mode from WS46 response '%s'", response); return FALSE; } switch (mode_num) { case 12: /* GSM digital cellular (GERAN only) */ *result = MM_MODEM_MODE_2G; break; case 22: /* UTRAN only */ *result = MM_MODEM_MODE_3G; break; case 25: /* GERAN, UTRAN and E-UTRAN */ *result = MM_MODEM_MODE_ANY; break; case 28: /* E-UTRAN only */ *result = MM_MODEM_MODE_4G; break; default: g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Unknown WS46 mode '%u'", mode_num); return FALSE; } return TRUE; }