blob: cf008d73694d215d26a4852c15b3af2542c47341 [file] [log] [blame]
/* -*- 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) 2008 - 2009 Novell, Inc.
* Copyright (C) 2009 - 2012 Red Hat, Inc.
* Copyright (C) 2012 Google, Inc.
*/
#include <config.h>
#include <stdio.h>
#include <ctype.h>
#include <glib.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ModemManager.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-sms-part.h"
#include "mm-modem-helpers.h"
#include "mm-helper-enums-types.h"
#include "mm-log.h"
/*****************************************************************************/
gchar *
mm_strip_quotes (gchar *str)
{
gsize len;
if (!str)
return NULL;
len = strlen (str);
if ((len >= 2) && (str[0] == '"') && (str[len - 1] == '"')) {
str[0] = ' ';
str[len - 1] = ' ';
}
return g_strstrip (str);
}
const gchar *
mm_strip_tag (const gchar *str, const gchar *cmd)
{
const gchar *p = str;
if (p) {
if (!strncmp (p, cmd, strlen (cmd)))
p += strlen (cmd);
while (isspace (*p))
p++;
}
return p;
}
/*****************************************************************************/
gchar **
mm_split_string_groups (const gchar *str)
{
GPtrArray *array;
const gchar *start;
const gchar *next;
array = g_ptr_array_new ();
/*
* Manually parse splitting groups. Groups may be single elements, or otherwise
* lists given between parenthesis, e.g.:
*
* ("SM","ME"),("SM","ME"),("SM","ME")
* "SM","SM","SM"
* "SM",("SM","ME"),("SM","ME")
*/
/* Iterate string splitting groups */
for (start = str; start; start = next) {
gchar *item;
gssize len = -1;
/* skip leading whitespaces */
while (*start == ' ')
start++;
if (*start == '(') {
start++;
next = strchr (start, ')');
if (next) {
len = next - start;
next = strchr (next, ',');
if (next)
next++;
}
} else {
next = strchr (start, ',');
if (next) {
len = next - start;
next++;
}
}
if (len < 0)
item = g_strdup (start);
else
item = g_strndup (start, len);
g_ptr_array_add (array, item);
}
if (array->len > 0) {
g_ptr_array_add (array, NULL);
return (gchar **) g_ptr_array_free (array, FALSE);
}
g_ptr_array_unref (array);
return NULL;
}
/*****************************************************************************/
static int uint_compare_func (gconstpointer a, gconstpointer b)
{
return (*(guint *)a - *(guint *)b);
}
GArray *
mm_parse_uint_list (const gchar *str,
GError **error)
{
GArray *array;
gchar *dupstr;
gchar *aux;
GError *inner_error = NULL;
if (!str || !str[0])
return NULL;
/* Parses into a GArray of guints, the list of numbers given in the string,
* also supporting number intervals.
* E.g.:
* 1-6 --> 1,2,3,4,5,6
* 1,2,4-6 --> 1,2,4,5,6
*/
array = g_array_new (FALSE, FALSE, sizeof (guint));
aux = dupstr = g_strdup (str);
while (aux) {
gchar *next;
gchar *interval;
next = strchr (aux, ',');
if (next) {
*next = '\0';
next++;
}
interval = strchr (aux, '-');
if (interval) {
guint start = 0;
guint stop = 0;
*interval = '\0';
interval++;
if (!mm_get_uint_from_str (aux, &start)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"couldn't parse interval start integer: '%s'", aux);
goto out;
}
if (!mm_get_uint_from_str (interval, &stop)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"couldn't parse interval stop integer: '%s'", interval);
goto out;
}
if (start > stop) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"interval start (%u) cannot be bigger than interval stop (%u)", start, stop);
goto out;
}
for (; start <= stop; start++)
g_array_append_val (array, start);
} else {
guint num;
if (!mm_get_uint_from_str (aux, &num)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"couldn't parse integer: '%s'", aux);
goto out;
}
g_array_append_val (array, num);
}
aux = next;
}
if (!array->len)
inner_error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"couldn't parse list of integers: '%s'", str);
else
g_array_sort (array, uint_compare_func);
out:
g_free (dupstr);
if (inner_error) {
g_propagate_error (error, inner_error);
g_array_unref (array);
return NULL;
}
return array;
}
/*****************************************************************************/
guint
mm_count_bits_set (gulong number)
{
guint c;
for (c = 0; number; c++)
number &= number - 1;
return c;
}
guint
mm_find_bit_set (gulong number)
{
guint c = 0;
for (c = 0; !(number & 0x1); c++)
number >>= 1;
return c;
}
/*****************************************************************************/
gchar *
mm_create_device_identifier (guint vid,
guint pid,
const gchar *ati,
const gchar *ati1,
const gchar *gsn,
const gchar *revision,
const gchar *model,
const gchar *manf)
{
GString *devid, *msg = NULL;
GChecksum *sum;
gchar *p, *ret = NULL;
gchar str_vid[10], str_pid[10];
/* Build up the device identifier */
devid = g_string_sized_new (50);
if (ati)
g_string_append (devid, ati);
if (ati1) {
/* Only append "ATI1" if it's differnet than "ATI" */
if (!ati || (strcmp (ati, ati1) != 0))
g_string_append (devid, ati1);
}
if (gsn)
g_string_append (devid, gsn);
if (revision)
g_string_append (devid, revision);
if (model)
g_string_append (devid, model);
if (manf)
g_string_append (devid, manf);
if (!strlen (devid->str)) {
g_string_free (devid, TRUE);
return NULL;
}
p = devid->str;
msg = g_string_sized_new (strlen (devid->str) + 17);
sum = g_checksum_new (G_CHECKSUM_SHA1);
if (vid) {
snprintf (str_vid, sizeof (str_vid) - 1, "%08x", vid);
g_checksum_update (sum, (const guchar *) &str_vid[0], strlen (str_vid));
g_string_append_printf (msg, "%08x", vid);
}
if (pid) {
snprintf (str_pid, sizeof (str_pid) - 1, "%08x", pid);
g_checksum_update (sum, (const guchar *) &str_pid[0], strlen (str_pid));
g_string_append_printf (msg, "%08x", pid);
}
while (*p) {
/* Strip spaces and linebreaks */
if (!isblank (*p) && !isspace (*p) && isascii (*p)) {
g_checksum_update (sum, (const guchar *) p, 1);
g_string_append_c (msg, *p);
}
p++;
}
ret = g_strdup (g_checksum_get_string (sum));
g_checksum_free (sum);
mm_dbg ("Device ID source '%s'", msg->str);
mm_dbg ("Device ID '%s'", ret);
g_string_free (msg, TRUE);
g_string_free (devid, TRUE);
return ret;
}
/*****************************************************************************/
guint
mm_netmask_to_cidr (const gchar *netmask)
{
guint32 num = 0;
inet_pton (AF_INET, netmask, &num);
return mm_count_bits_set (num);
}
/*****************************************************************************/
GArray *
mm_filter_current_bands (const GArray *supported_bands,
const GArray *current_bands)
{
/* We will assure that the list given in 'current' bands maps the list
* given in 'supported' bands, unless 'UNKNOWN' or 'ANY' is given, of
* course */
guint i;
GArray *filtered;
if (!supported_bands ||
supported_bands->len == 0 ||
!current_bands ||
current_bands->len == 0)
return NULL;
if (supported_bands->len == 1 &&
(g_array_index (supported_bands, MMModemBand, 0) == MM_MODEM_BAND_UNKNOWN ||
g_array_index (supported_bands, MMModemBand, 0) == MM_MODEM_BAND_ANY))
return NULL;
if (current_bands->len == 1 &&
(g_array_index (current_bands, MMModemBand, 0) == MM_MODEM_BAND_UNKNOWN ||
g_array_index (current_bands, MMModemBand, 0) == MM_MODEM_BAND_ANY))
return NULL;
filtered = g_array_sized_new (FALSE, FALSE, sizeof (MMModemBand), current_bands->len);
for (i = 0; i < current_bands->len; i++) {
guint j;
for (j = 0; j < supported_bands->len; j++) {
if (g_array_index (supported_bands, MMModemBand, j) == g_array_index (current_bands, MMModemBand, i)) {
g_array_append_val (filtered, g_array_index (current_bands, MMModemBand, i));
/* Found */
break;
}
}
}
if (filtered->len == 0) {
g_array_unref (filtered);
return NULL;
}
return filtered;
}
/*****************************************************************************/
gchar *
mm_new_iso8601_time (guint year,
guint month,
guint day,
guint hour,
guint minute,
guint second,
gboolean have_offset,
gint offset_minutes)
{
GString *str;
str = g_string_sized_new (30);
g_string_append_printf (str, "%04d-%02d-%02dT%02d:%02d:%02d",
year, month, day, hour, minute, second);
if (have_offset) {
if (offset_minutes >=0 ) {
g_string_append_printf (str, "+%02d:%02d",
offset_minutes / 60,
offset_minutes % 60);
} else {
offset_minutes *= -1;
g_string_append_printf (str, "-%02d:%02d",
offset_minutes / 60,
offset_minutes % 60);
}
}
return g_string_free (str, FALSE);
}
/*****************************************************************************/
GArray *
mm_filter_supported_modes (const GArray *all,
const GArray *supported_combinations)
{
MMModemModeCombination all_item;
guint i;
GArray *filtered_combinations;
gboolean all_item_added = FALSE;
g_return_val_if_fail (all != NULL, NULL);
g_return_val_if_fail (all->len == 1, NULL);
g_return_val_if_fail (supported_combinations != NULL, NULL);
all_item = g_array_index (all, MMModemModeCombination, 0);
g_return_val_if_fail (all_item.allowed != MM_MODEM_MODE_NONE, NULL);
/* We will filter out all combinations which have modes not listed in 'all' */
filtered_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), supported_combinations->len);
for (i = 0; i < supported_combinations->len; i++) {
MMModemModeCombination *mode;
mode = &g_array_index (supported_combinations, MMModemModeCombination, i);
if (!(mode->allowed & ~all_item.allowed)) {
/* Compare only 'allowed', *not* preferred. If there is at least one item with allowed
* containing all supported modes, we're already good to go. This allows us to have a
* default with preferred != NONE (e.g. Wavecom 2G modem with allowed=CS+2G and
* preferred=2G */
if (all_item.allowed == mode->allowed)
all_item_added = TRUE;
g_array_append_val (filtered_combinations, *mode);
}
}
if (filtered_combinations->len == 0)
mm_warn ("All supported mode combinations were filtered out.");
/* Add default entry with the generic mask including all items */
if (!all_item_added) {
mm_dbg ("Adding an explicit item with all supported modes allowed");
g_array_append_val (filtered_combinations, all_item);
}
return filtered_combinations;
}
/*****************************************************************************/
GArray *
mm_filter_supported_capabilities (MMModemCapability all,
const GArray *supported_combinations)
{
guint i;
GArray *filtered_combinations;
g_return_val_if_fail (all != MM_MODEM_CAPABILITY_NONE, NULL);
g_return_val_if_fail (supported_combinations != NULL, NULL);
/* We will filter out all combinations which have modes not listed in 'all' */
filtered_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), supported_combinations->len);
for (i = 0; i < supported_combinations->len; i++) {
MMModemCapability capability;
capability = g_array_index (supported_combinations, MMModemCapability, i);
if (!(capability & ~all))
g_array_append_val (filtered_combinations, capability);
}
if (filtered_combinations->len == 0)
mm_warn ("All supported capability combinations were filtered out.");
return filtered_combinations;
}
/*****************************************************************************/
static const gchar bcd_chars[] = "0123456789\0\0\0\0\0\0";
gchar *
mm_bcd_to_string (const guint8 *bcd, gsize bcd_len)
{
GString *str;
gsize i;
g_return_val_if_fail (bcd != NULL, NULL);
str = g_string_sized_new (bcd_len * 2 + 1);
for (i = 0 ; i < bcd_len; i++) {
str = g_string_append_c (str, bcd_chars[bcd[i] & 0xF]);
str = g_string_append_c (str, bcd_chars[(bcd[i] >> 4) & 0xF]);
}
return g_string_free (str, FALSE);
}
/*****************************************************************************/
GRegex *
mm_voice_ring_regex_get (void)
{
/* Example:
* <CR><LF>RING<CR><LF>
*/
return g_regex_new ("\\r\\nRING\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
GRegex *
mm_voice_cring_regex_get (void)
{
/* Example:
* <CR><LF>+CRING: VOICE<CR><LF>
* <CR><LF>+CRING: DATA<CR><LF>
*/
return g_regex_new ("\\r\\n\\+CRING:\\s*(\\S+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
GRegex *
mm_voice_clip_regex_get (void)
{
/* Example:
* <CR><LF>+CLIP: "+393351391306",145,,,,0<CR><LF>
* \_ Number \_ Type \_ Validity
*/
return g_regex_new ("\\r\\n\\+CLIP:\\s*(\\S+),\\s*(\\d+),\\s*,\\s*,\\s*,\\s*(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
static MMFlowControl
flow_control_array_to_mask (GArray *array,
const gchar *item)
{
MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN;
guint i;
for (i = 0; i < array->len; i++) {
guint mode;
mode = g_array_index (array, guint, i);
switch (mode) {
case 0:
mm_dbg ("%s supports no flow control", item);
mask |= MM_FLOW_CONTROL_NONE;
break;
case 1:
mm_dbg ("%s supports XON/XOFF flow control", item);
mask |= MM_FLOW_CONTROL_XON_XOFF;
break;
case 2:
mm_dbg ("%s supports RTS/CTS flow control", item);
mask |= MM_FLOW_CONTROL_RTS_CTS;
break;
default:
break;
}
}
return mask;
}
static MMFlowControl
flow_control_match_info_to_mask (GMatchInfo *match_info,
guint index,
const gchar *item,
GError **error)
{
MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN;
gchar *aux = NULL;
GArray *array = NULL;
if (!(aux = mm_get_string_unquoted_from_match_info (match_info, index))) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Error retrieving list of supported %s flow control methods", item);
goto out;
}
if (!(array = mm_parse_uint_list (aux, error))) {
g_prefix_error (error, "Error parsing list of supported %s flow control methods: ", item);
goto out;
}
if ((mask = flow_control_array_to_mask (array, item)) == MM_FLOW_CONTROL_UNKNOWN) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"No known %s flow control method given", item);
goto out;
}
out:
g_clear_pointer (&aux, g_free);
g_clear_pointer (&array, g_array_unref);
return mask;
}
MMFlowControl
mm_parse_ifc_test_response (const gchar *response,
GError **error)
{
GRegex *r;
GError *inner_error = NULL;
GMatchInfo *match_info = NULL;
MMFlowControl te_mask = MM_FLOW_CONTROL_UNKNOWN;
MMFlowControl ta_mask = MM_FLOW_CONTROL_UNKNOWN;
MMFlowControl mask = MM_FLOW_CONTROL_UNKNOWN;
r = g_regex_new ("(?:\\+IFC:)?\\s*\\((.*)\\),\\((.*)\\)(?:\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
if (!g_match_info_matches (match_info)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match response");
goto out;
}
/* Parse TE flow control methods */
if ((te_mask = flow_control_match_info_to_mask (match_info, 1, "TE", &inner_error)) == MM_FLOW_CONTROL_UNKNOWN)
goto out;
/* Parse TA flow control methods */
if ((ta_mask = flow_control_match_info_to_mask (match_info, 2, "TA", &inner_error)) == MM_FLOW_CONTROL_UNKNOWN)
goto out;
/* Only those methods in both TA and TE will be the ones we report */
mask = te_mask & ta_mask;
out:
g_clear_pointer (&match_info, g_match_info_free);
g_regex_unref (r);
if (inner_error)
g_propagate_error (error, inner_error);
return mask;
}
MMFlowControl
mm_flow_control_from_string (const gchar *str,
GError **error)
{
GFlagsClass *flags_class;
guint value;
guint i;
flags_class = G_FLAGS_CLASS (g_type_class_ref (MM_TYPE_FLOW_CONTROL));
for (i = 0; flags_class->values[i].value_nick; i++) {
if (!g_ascii_strcasecmp (str, flags_class->values[i].value_nick)) {
value = flags_class->values[i].value;
g_type_class_unref (flags_class);
return value;
}
}
g_type_class_unref (flags_class);
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_INVALID_ARGS,
"Couldn't match '%s' with a valid MMFlowControl value",
str);
return MM_FLOW_CONTROL_UNKNOWN;
}
/*************************************************************************/
/* +CREG: <stat> (GSM 07.07 CREG=1 unsolicited) */
#define CREG1 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9])"
/* +CREG: <n>,<stat> (GSM 07.07 CREG=1 solicited) */
#define CREG2 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*0*([0-9])"
/* +CREG: <stat>,<lac>,<ci> (GSM 07.07 CREG=2 unsolicited) */
#define CREG3 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)"
#define CREG11 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*(\"[^\"\\s]*\")\\s*,\\s*(\"[^\"\\s]*\")"
/* +CREG: <n>,<stat>,<lac>,<ci> (GSM 07.07 solicited and some CREG=2 unsolicited) */
#define CREG4 "\\+(CREG|CGREG|CEREG):\\s*([0-9]),\\s*([0-9])\\s*,\\s*([^,]*)\\s*,\\s*([^,\\s]*)"
#define CREG5 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*(\"[^,]*\")\\s*,\\s*(\"[^,\\s]*\")"
/* +CREG: <stat>,<lac>,<ci>,<AcT> (ETSI 27.007 CREG=2 unsolicited) */
#define CREG6 "\\+(CREG|CGREG|CEREG):\\s*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([0-9])"
#define CREG7 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9])\\s*,\\s*(\"[^,\\s]*\")\\s*,\\s*(\"[^,\\s]*\")\\s*,\\s*0*([0-9])"
/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT> (ETSI 27.007 solicited and some CREG=2 unsolicited) */
#define CREG8 "\\+(CREG|CGREG|CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])"
/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT?>,<something> (Samsung Wave S8500) */
/* '<CR><LF>+CREG: 2,1,000B,2816, B, C2816<CR><LF><CR><LF>OK<CR><LF>' */
#define CREG9 "\\+(CREG|CGREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*[^,\\s]*"
/* +CREG: <stat>,<lac>,<ci>,<AcT>,<RAC> (ETSI 27.007 v9.20 CREG=2 unsolicited with RAC) */
#define CREG10 "\\+(CREG|CGREG):\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])\\s*,\\s*([^,\\s]*)"
/* +CEREG: <stat>,<lac>,<rac>,<ci>,<AcT> (ETSI 27.007 v8.6 CREG=2 unsolicited with RAC) */
#define CEREG1 "\\+(CEREG):\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])"
/* +CEREG: <n>,<stat>,<lac>,<rac>,<ci>,<AcT> (ETSI 27.007 v8.6 CREG=2 solicited with RAC) */
#define CEREG2 "\\+(CEREG):\\s*0*([0-9]),\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])"
GPtrArray *
mm_3gpp_creg_regex_get (gboolean solicited)
{
GPtrArray *array = g_ptr_array_sized_new (13);
GRegex *regex;
/* #1 */
if (solicited)
regex = g_regex_new (CREG1 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG1 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #2 */
if (solicited)
regex = g_regex_new (CREG2 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG2 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #3 */
if (solicited)
regex = g_regex_new (CREG3 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG3 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #4 */
if (solicited)
regex = g_regex_new (CREG4 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG4 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #5 */
if (solicited)
regex = g_regex_new (CREG5 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG5 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #6 */
if (solicited)
regex = g_regex_new (CREG6 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG6 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #7 */
if (solicited)
regex = g_regex_new (CREG7 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG7 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #8 */
if (solicited)
regex = g_regex_new (CREG8 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG8 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #9 */
if (solicited)
regex = g_regex_new (CREG9 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG9 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #10 */
if (solicited)
regex = g_regex_new (CREG10 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG10 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* #11 */
if (solicited)
regex = g_regex_new (CREG11 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CREG11 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* CEREG #1 */
if (solicited)
regex = g_regex_new (CEREG1 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CEREG1 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
/* CEREG #2 */
if (solicited)
regex = g_regex_new (CEREG2 "$", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
else
regex = g_regex_new ("\\r\\n" CEREG2 "\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
g_assert (regex);
g_ptr_array_add (array, regex);
return array;
}
void
mm_3gpp_creg_regex_destroy (GPtrArray *array)
{
g_ptr_array_foreach (array, (GFunc) g_regex_unref, NULL);
g_ptr_array_free (array, TRUE);
}
/*************************************************************************/
GRegex *
mm_3gpp_ciev_regex_get (void)
{
return g_regex_new ("\\r\\n\\+CIEV: (.*),(\\d)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
GRegex *
mm_3gpp_cgev_regex_get (void)
{
return g_regex_new ("\\r\\n\\+CGEV:\\s*(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
GRegex *
mm_3gpp_cusd_regex_get (void)
{
return g_regex_new ("\\r\\n\\+CUSD:\\s*(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
GRegex *
mm_3gpp_cmti_regex_get (void)
{
return g_regex_new ("\\r\\n\\+CMTI:\\s*\"(\\S+)\",\\s*(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
GRegex *
mm_3gpp_cds_regex_get (void)
{
/* Example:
* <CR><LF>+CDS: 24<CR><LF>07914356060013F10659098136395339F6219011707193802190117071938030<CR><LF>
*/
return g_regex_new ("\\r\\n\\+CDS:\\s*(\\d+)\\r\\n(.*)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
/* AT+WS46=? response parser
*
* More than one numeric ID may appear in the list, that's why
* they are checked separately.
*
* NOTE: ignore WS46 prefix or it will break Cinterion handling.
*
* For the specific case of '25', we will check if any other mode supports
* 4G, and if there is none, we'll remove 4G caps from it.
*/
typedef struct {
guint ws46;
MMModemMode mode;
} Ws46Mode;
/* 3GPP TS 27.007 r14, section 5.9: select wireless network +WS46 */
static const Ws46Mode ws46_modes[] = {
/* GSM Digital Cellular Systems (GERAN only) */
{ 12, MM_MODEM_MODE_2G },
/* UTRAN only */
{ 22, MM_MODEM_MODE_3G },
/* 3GPP Systems (GERAN, UTRAN and E-UTRAN) */
{ 25, MM_MODEM_MODE_ANY },
/* E-UTRAN only */
{ 28, MM_MODEM_MODE_4G },
/* GERAN and UTRAN */
{ 29, MM_MODEM_MODE_2G | MM_MODEM_MODE_3G },
/* GERAN and E-UTRAN */
{ 30, MM_MODEM_MODE_2G | MM_MODEM_MODE_4G },
/* UERAN and E-UTRAN */
{ 31, MM_MODEM_MODE_3G | MM_MODEM_MODE_4G },
};
GArray *
mm_3gpp_parse_ws46_test_response (const gchar *response,
GError **error)
{
GArray *modes = NULL;
GArray *tech_values = NULL;
GRegex *r;
GError *inner_error = NULL;
GMatchInfo *match_info = NULL;
gchar *full_list = NULL;
guint val;
guint i;
guint j;
gboolean supported_4g = FALSE;
gboolean supported_3g = FALSE;
gboolean supported_2g = FALSE;
r = g_regex_new ("(?:\\+WS46:)?\\s*\\((.*)\\)(?:\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
if (!g_match_info_matches (match_info)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match response");
goto out;
}
if (!(full_list = mm_get_string_unquoted_from_match_info (match_info, 1))) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing full list string");
goto out;
}
if (!(tech_values = mm_parse_uint_list (full_list, &inner_error)))
goto out;
modes = g_array_new (FALSE, FALSE, sizeof (MMModemMode));
for (i = 0; i < tech_values->len; i++) {
val = g_array_index (tech_values, guint, i);
for (j = 0; j < G_N_ELEMENTS (ws46_modes); j++) {
if (ws46_modes[j].ws46 == val) {
if (val != 25) {
if (ws46_modes[j].mode & MM_MODEM_MODE_4G)
supported_4g = TRUE;
if (ws46_modes[j].mode & MM_MODEM_MODE_3G)
supported_3g = TRUE;
if (ws46_modes[j].mode & MM_MODEM_MODE_2G)
supported_2g = TRUE;
}
g_array_append_val (modes, ws46_modes[j].mode);
break;
}
}
if (j == G_N_ELEMENTS (ws46_modes))
g_warning ("Unknown +WS46 mode reported: %u", val);
}
if (modes->len == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No valid modes reported");
g_clear_pointer (&modes, g_array_unref);
goto out;
}
/* Fixup the ANY value, based on which are the supported modes */
for (i = 0; i < modes->len; i++) {
MMModemMode *mode;
mode = &g_array_index (modes, MMModemMode, i);
if (*mode == MM_MODEM_MODE_ANY) {
*mode = 0;
if (supported_2g)
*mode |= MM_MODEM_MODE_2G;
if (supported_3g)
*mode |= MM_MODEM_MODE_3G;
if (supported_4g)
*mode |= MM_MODEM_MODE_4G;
if (*mode == 0) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "No way to fixup the ANY value");
g_clear_pointer (&modes, g_array_unref);
goto out;
}
}
}
out:
if (tech_values)
g_array_unref (tech_values);
g_free (full_list);
g_clear_pointer (&match_info, g_match_info_free);
g_regex_unref (r);
if (inner_error) {
g_propagate_error (error, inner_error);
return NULL;
}
g_assert (modes && modes->len);
return modes;
}
/*************************************************************************/
static void
mm_3gpp_network_info_free (MM3gppNetworkInfo *info)
{
g_free (info->operator_long);
g_free (info->operator_short);
g_free (info->operator_code);
g_free (info);
}
void
mm_3gpp_network_info_list_free (GList *info_list)
{
g_list_free_full (info_list, (GDestroyNotify) mm_3gpp_network_info_free);
}
static MMModemAccessTechnology
get_mm_access_tech_from_etsi_access_tech (guint act)
{
/* See ETSI TS 27.007 */
switch (act) {
case 0:
return MM_MODEM_ACCESS_TECHNOLOGY_GSM;
case 1:
return MM_MODEM_ACCESS_TECHNOLOGY_GSM_COMPACT;
case 2:
return MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
case 3:
return MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
case 4:
return MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
case 5:
return MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
case 6:
return MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
case 7:
return MM_MODEM_ACCESS_TECHNOLOGY_LTE;
default:
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
}
}
static MMModem3gppNetworkAvailability
parse_network_status (const gchar *str)
{
/* Expecting a value between '0' and '3' inclusive */
if (!str ||
strlen (str) != 1 ||
str[0] < '0' ||
str[0] > '3') {
mm_warn ("Cannot parse network status: '%s'", str);
return MM_MODEM_3GPP_NETWORK_AVAILABILITY_UNKNOWN;
}
return (MMModem3gppNetworkAvailability) (str[0] - '0');
}
static MMModemAccessTechnology
parse_access_tech (const gchar *str)
{
/* Recognized access technologies are between '0' and '7' inclusive... */
if (!str ||
strlen (str) != 1 ||
str[0] < '0' ||
str[0] > '7') {
mm_warn ("Cannot parse access tech: '%s'", str);
return MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
}
return get_mm_access_tech_from_etsi_access_tech (str[0] - '0');
}
GList *
mm_3gpp_parse_cops_test_response (const gchar *reply,
GError **error)
{
GRegex *r;
GList *info_list = NULL;
GMatchInfo *match_info;
gboolean umts_format = TRUE;
GError *inner_error = NULL;
g_return_val_if_fail (reply != NULL, NULL);
if (error)
g_return_val_if_fail (*error == NULL, NULL);
if (!strstr (reply, "+COPS: ")) {
g_set_error_literal (error,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse scan results.");
return NULL;
}
reply = strstr (reply, "+COPS: ") + 7;
/* Cell access technology (GSM, UTRAN, etc) got added later and not all
* modems implement it. Some modesm have quirks that make it hard to
* use one regular experession for matching both pre-UMTS and UMTS
* responses. So try UMTS-format first and fall back to pre-UMTS if
* we get no UMTS-formst matches.
*/
/* Quirk: Sony-Ericsson TM-506 sometimes includes a stray ')' like so,
* which is what makes it hard to match both pre-UMTS and UMTS in
* the same regex:
*
* +COPS: (2,"","T-Mobile","31026",0),(1,"AT&T","AT&T","310410"),0)
*/
r = g_regex_new ("\\((\\d),\"([^\"\\)]*)\",([^,\\)]*),([^,\\)]*)[\\)]?,(\\d)\\)", G_REGEX_UNGREEDY, 0, &inner_error);
if (inner_error) {
mm_err ("Invalid regular expression: %s", inner_error->message);
g_error_free (inner_error);
g_set_error_literal (error,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse scan results");
return NULL;
}
/* If we didn't get any hits, try the pre-UMTS format match */
if (!g_regex_match (r, reply, 0, &match_info)) {
g_regex_unref (r);
g_match_info_free (match_info);
match_info = NULL;
/* Pre-UMTS format doesn't include the cell access technology after
* the numeric operator element.
*
* Ex: Motorola C-series (BUSlink SCWi275u) like so:
*
* +COPS: (2,"T-Mobile","","310260"),(0,"Cingular Wireless","","310410")
*/
/* Quirk: Some Nokia phones (N80) don't send the quotes for empty values:
*
* +COPS: (2,"T - Mobile",,"31026"),(1,"Einstein PCS",,"31064"),(1,"Cingular",,"31041"),,(0,1,3),(0,2)
*/
r = g_regex_new ("\\((\\d),([^,\\)]*),([^,\\)]*),([^\\)]*)\\)", G_REGEX_UNGREEDY, 0, &inner_error);
if (inner_error) {
mm_err ("Invalid regular expression: %s", inner_error->message);
g_error_free (inner_error);
g_set_error_literal (error,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse scan results");
return NULL;
}
g_regex_match (r, reply, 0, &match_info);
umts_format = FALSE;
}
/* Parse the results */
while (g_match_info_matches (match_info)) {
MM3gppNetworkInfo *info;
gchar *tmp;
gboolean valid = FALSE;
info = g_new0 (MM3gppNetworkInfo, 1);
tmp = mm_get_string_unquoted_from_match_info (match_info, 1);
info->status = parse_network_status (tmp);
g_free (tmp);
info->operator_long = mm_get_string_unquoted_from_match_info (match_info, 2);
info->operator_short = mm_get_string_unquoted_from_match_info (match_info, 3);
info->operator_code = mm_get_string_unquoted_from_match_info (match_info, 4);
/* Only try for access technology with UMTS-format matches.
* If none give, assume GSM */
tmp = (umts_format ?
mm_get_string_unquoted_from_match_info (match_info, 5) :
NULL);
info->access_tech = (tmp ?
parse_access_tech (tmp) :
MM_MODEM_ACCESS_TECHNOLOGY_GSM);
g_free (tmp);
/* If the operator number isn't valid (ie, at least 5 digits),
* ignore the scan result; it's probably the parameter stuff at the
* end of the +COPS response. The regex will sometimes catch this
* but there's no good way to ignore it.
*/
if (info->operator_code && (strlen (info->operator_code) >= 5)) {
valid = TRUE;
tmp = info->operator_code;
while (*tmp) {
if (!isdigit (*tmp) && (*tmp != '-')) {
valid = FALSE;
break;
}
tmp++;
}
}
if (valid) {
gchar *access_tech_str;
access_tech_str = mm_modem_access_technology_build_string_from_mask (info->access_tech);
mm_dbg ("Found network '%s' ('%s','%s'); availability: %s, access tech: %s",
info->operator_code,
info->operator_short ? info->operator_short : "no short name",
info->operator_long ? info->operator_long : "no long name",
mm_modem_3gpp_network_availability_get_string (info->status),
access_tech_str);
g_free (access_tech_str);
info_list = g_list_prepend (info_list, info);
}
else
mm_3gpp_network_info_free (info);
g_match_info_next (match_info, NULL);
}
g_match_info_free (match_info);
g_regex_unref (r);
return info_list;
}
/*************************************************************************/
gboolean
mm_3gpp_parse_cops_read_response (const gchar *response,
guint *out_mode,
guint *out_format,
gchar **out_operator,
MMModemAccessTechnology *out_act,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
GError *inner_error = NULL;
guint mode = 0;
guint format = 0;
gchar *operator = NULL;
guint actval = 0;
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
g_assert (out_mode || out_format || out_operator || out_act);
/* We assume the response to be either:
* +COPS: <mode>,<format>,<oper>
* or:
* +COPS: <mode>,<format>,<oper>,<AcT>
*/
r = g_regex_new ("\\+COPS:\\s*(\\d+),(\\d+),([^,]*)(?:,(\\d+))?(?:\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
if (!g_match_info_matches (match_info)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't match response");
goto out;
}
if (out_mode && !mm_get_uint_from_match_info (match_info, 1, &mode)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing mode");
goto out;
}
if (out_format && !mm_get_uint_from_match_info (match_info, 2, &format)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing format");
goto out;
}
if (out_operator && !(operator = mm_get_string_unquoted_from_match_info (match_info, 3))) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing operator");
goto out;
}
/* AcT is optional */
if (out_act && g_match_info_get_match_count (match_info) >= 5) {
if (!mm_get_uint_from_match_info (match_info, 4, &actval)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing AcT");
goto out;
}
act = get_mm_access_tech_from_etsi_access_tech (actval);
}
out:
g_match_info_free (match_info);
g_regex_unref (r);
if (inner_error) {
g_free (operator);
g_propagate_error (error, inner_error);
return FALSE;
}
if (out_mode)
*out_mode = mode;
if (out_format)
*out_format = format;
if (out_operator)
*out_operator = operator;
if (out_act)
*out_act = act;
return TRUE;
}
/*************************************************************************/
/* Logic to compare two APN names */
gboolean
mm_3gpp_cmp_apn_name (const gchar *requested,
const gchar *existing)
{
size_t requested_len;
size_t existing_len;
/* If both empty, that's a good match */
if ((!existing || !existing[0]) && (!requested || !requested[0]))
return TRUE;
/* Both must be given to compare properly */
if (!existing || !existing[0] || !requested || !requested[0])
return FALSE;
requested_len = strlen (requested);
/*
* 1) The requested APN should be at least the prefix of the existing one.
*/
if (g_ascii_strncasecmp (existing, requested, requested_len) != 0)
return FALSE;
/*
* 2) If the existing one is actually the same as the requested one (i.e.
* there are no more different chars in the existing one), we're done.
*/
if (existing[requested_len] == '\0')
return TRUE;
existing_len = strlen (existing);
/* 3) Special handling for PDP contexts reported by u-blox modems once the
* contexts have been activated at least once:
* "ac.vodafone.es.MNC001.MCC214.GPRS" should match "ac.vodafone.es"
*/
if ((existing_len > (requested_len + 14)) &&
g_ascii_strncasecmp (&existing[requested_len], ".mnc", 4) == 0 &&
g_ascii_strncasecmp (&existing[requested_len + 7], ".mcc", 4) == 0)
return TRUE;
/* No match */
return FALSE;
}
/*************************************************************************/
static void
mm_3gpp_pdp_context_format_free (MM3gppPdpContextFormat *format)
{
g_slice_free (MM3gppPdpContextFormat, format);
}
void
mm_3gpp_pdp_context_format_list_free (GList *pdp_format_list)
{
g_list_free_full (pdp_format_list, (GDestroyNotify) mm_3gpp_pdp_context_format_free);
}
GList *
mm_3gpp_parse_cgdcont_test_response (const gchar *response,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
GError *inner_error = NULL;
GList *list = NULL;
if (!response || !g_str_has_prefix (response, "+CGDCONT:")) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing +CGDCONT prefix");
return NULL;
}
r = g_regex_new ("\\+CGDCONT:\\s*\\(\\s*(\\d+)\\s*-?\\s*(\\d+)?[^\\)]*\\)\\s*,\\s*\\(?\"(\\S+)\"",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
0, &inner_error);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
while (!inner_error && g_match_info_matches (match_info)) {
gchar *pdp_type_str;
guint min_cid;
guint max_cid;
MMBearerIpFamily pdp_type;
/* Read PDP type */
pdp_type_str = mm_get_string_unquoted_from_match_info (match_info, 3);
pdp_type = mm_3gpp_get_ip_family_from_pdp_type (pdp_type_str);
if (pdp_type == MM_BEARER_IP_FAMILY_NONE)
mm_dbg ("Unhandled PDP type in CGDCONT=? reply: '%s'", pdp_type_str);
else {
/* Read min CID */
if (!mm_get_uint_from_match_info (match_info, 1, &min_cid))
mm_warn ("Invalid min CID in CGDCONT=? reply for PDP type '%s'", pdp_type_str);
else {
MM3gppPdpContextFormat *format;
/* Read max CID: Optional! If no value given, we default to min CID */
if (!mm_get_uint_from_match_info (match_info, 2, &max_cid))
max_cid = min_cid;
format = g_slice_new (MM3gppPdpContextFormat);
format->pdp_type = pdp_type;
format->min_cid = min_cid;
format->max_cid = max_cid;
list = g_list_prepend (list, format);
}
}
g_free (pdp_type_str);
g_match_info_next (match_info, &inner_error);
}
g_match_info_free (match_info);
g_regex_unref (r);
if (inner_error) {
mm_warn ("Unexpected error matching +CGDCONT response: '%s'", inner_error->message);
g_error_free (inner_error);
}
return list;
}
/*************************************************************************/
static void
mm_3gpp_pdp_context_free (MM3gppPdpContext *pdp)
{
g_free (pdp->apn);
g_slice_free (MM3gppPdpContext, pdp);
}
void
mm_3gpp_pdp_context_list_free (GList *list)
{
g_list_free_full (list, (GDestroyNotify) mm_3gpp_pdp_context_free);
}
static gint
mm_3gpp_pdp_context_cmp (MM3gppPdpContext *a,
MM3gppPdpContext *b)
{
return (a->cid - b->cid);
}
GList *
mm_3gpp_parse_cgdcont_read_response (const gchar *reply,
GError **error)
{
GError *inner_error = NULL;
GRegex *r;
GMatchInfo *match_info;
GList *list;
if (!reply || !reply[0])
/* No APNs configured, all done */
return NULL;
list = NULL;
r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\s*,([^, \\)]*)\\s*,([^, \\)]*)\\s*,([^, \\)]*)",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
0, &inner_error);
if (r) {
g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error);
while (!inner_error &&
g_match_info_matches (match_info)) {
gchar *str;
MMBearerIpFamily ip_family;
str = mm_get_string_unquoted_from_match_info (match_info, 2);
ip_family = mm_3gpp_get_ip_family_from_pdp_type (str);
if (ip_family == MM_BEARER_IP_FAMILY_NONE)
mm_dbg ("Ignoring PDP context type: '%s'", str);
else {
MM3gppPdpContext *pdp;
pdp = g_slice_new0 (MM3gppPdpContext);
if (!mm_get_uint_from_match_info (match_info, 1, &pdp->cid)) {
inner_error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse CID from reply: '%s'",
reply);
break;
}
pdp->pdp_type = ip_family;
pdp->apn = mm_get_string_unquoted_from_match_info (match_info, 3);
list = g_list_prepend (list, pdp);
}
g_free (str);
g_match_info_next (match_info, &inner_error);
}
g_match_info_free (match_info);
g_regex_unref (r);
}
if (inner_error) {
mm_3gpp_pdp_context_list_free (list);
g_propagate_error (error, inner_error);
g_prefix_error (error, "Couldn't properly parse list of PDP contexts. ");
return NULL;
}
list = g_list_sort (list, (GCompareFunc)mm_3gpp_pdp_context_cmp);
return list;
}
/*************************************************************************/
static void
mm_3gpp_pdp_context_active_free (MM3gppPdpContextActive *pdp_active)
{
g_slice_free (MM3gppPdpContextActive, pdp_active);
}
void
mm_3gpp_pdp_context_active_list_free (GList *pdp_active_list)
{
g_list_free_full (pdp_active_list, (GDestroyNotify) mm_3gpp_pdp_context_active_free);
}
gint
mm_3gpp_pdp_context_active_cmp (MM3gppPdpContextActive *a,
MM3gppPdpContextActive *b)
{
return (a->cid - b->cid);
}
GList *
mm_3gpp_parse_cgact_read_response (const gchar *reply,
GError **error)
{
GError *inner_error = NULL;
GRegex *r;
GMatchInfo *match_info;
GList *list;
if (!reply || !reply[0])
/* Nothing configured, all done */
return NULL;
list = NULL;
r = g_regex_new ("\\+CGACT:\\s*(\\d+),(\\d+)",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW, 0, &inner_error);
g_assert (r);
g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &inner_error);
while (!inner_error && g_match_info_matches (match_info)) {
MM3gppPdpContextActive *pdp_active;
guint cid = 0;
guint aux = 0;
if (!mm_get_uint_from_match_info (match_info, 1, &cid)) {
inner_error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse CID from reply: '%s'",
reply);
break;
}
if (!mm_get_uint_from_match_info (match_info, 2, &aux) || (aux != 0 && aux != 1)) {
inner_error = g_error_new (MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse context status from reply: '%s'",
reply);
break;
}
pdp_active = g_slice_new0 (MM3gppPdpContextActive);
pdp_active->cid = cid;
pdp_active->active = (gboolean) aux;
list = g_list_prepend (list, pdp_active);
g_match_info_next (match_info, &inner_error);
}
g_match_info_free (match_info);
g_regex_unref (r);
if (inner_error) {
mm_3gpp_pdp_context_active_list_free (list);
g_propagate_error (error, inner_error);
g_prefix_error (error, "Couldn't properly parse list of active/inactive PDP contexts. ");
return NULL;
}
list = g_list_sort (list, (GCompareFunc) mm_3gpp_pdp_context_active_cmp);
return list;
}
/*************************************************************************/
static gulong
parse_uint (char *str, int base, glong nmin, glong nmax, gboolean *valid)
{
gulong ret = 0;
gchar *endquote;
*valid = FALSE;
if (!str)
return 0;
/* Strip quotes */
if (str[0] == '"')
str++;
endquote = strchr (str, '"');
if (endquote)
*endquote = '\0';
if (strlen (str)) {
ret = strtol (str, NULL, base);
if ((nmin == nmax) || (ret >= nmin && ret <= nmax))
*valid = TRUE;
}
return *valid ? (guint) ret : 0;
}
static gboolean
item_is_lac_not_stat (GMatchInfo *info, guint32 item)
{
gchar *str;
gboolean is_lac = FALSE;
/* A <stat> will always be a single digit, without quotes */
str = g_match_info_fetch (info, item);
g_assert (str);
is_lac = (strchr (str, '"') || strlen (str) > 1);
g_free (str);
return is_lac;
}
gboolean
mm_3gpp_parse_creg_response (GMatchInfo *info,
MMModem3gppRegistrationState *out_reg_state,
gulong *out_lac,
gulong *out_ci,
MMModemAccessTechnology *out_act,
gboolean *out_cgreg,
gboolean *out_cereg,
GError **error)
{
gboolean success = FALSE, foo;
gint n_matches, act = -1;
gulong stat = 0, lac = 0, ci = 0;
guint istat = 0, ilac = 0, ici = 0, iact = 0;
gchar *str;
g_return_val_if_fail (info != NULL, FALSE);
g_return_val_if_fail (out_reg_state != NULL, FALSE);
g_return_val_if_fail (out_lac != NULL, FALSE);
g_return_val_if_fail (out_ci != NULL, FALSE);
g_return_val_if_fail (out_act != NULL, FALSE);
g_return_val_if_fail (out_cgreg != NULL, FALSE);
g_return_val_if_fail (out_cereg != NULL, FALSE);
str = g_match_info_fetch (info, 1);
*out_cgreg = (str && strstr (str, "CGREG")) ? TRUE : FALSE;
*out_cereg = (str && strstr (str, "CEREG")) ? TRUE : FALSE;
g_free (str);
/* Normally the number of matches could be used to determine what each
* item is, but we have overlap in one case.
*/
n_matches = g_match_info_get_match_count (info);
if (n_matches == 3) {
/* CREG=1: +CREG: <stat> */
istat = 2;
} else if (n_matches == 4) {
/* Solicited response: +CREG: <n>,<stat> */
istat = 3;
} else if (n_matches == 5) {
/* CREG=2 (GSM 07.07): +CREG: <stat>,<lac>,<ci> */
istat = 2;
ilac = 3;
ici = 4;
} else if (n_matches == 6) {
/* CREG=2 (ETSI 27.007): +CREG: <stat>,<lac>,<ci>,<AcT>
* CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci>
*/
/* Check if the third item is the LAC to distinguish the two cases */
if (item_is_lac_not_stat (info, 3)) {
istat = 2;
ilac = 3;
ici = 4;
iact = 5;
} else {
istat = 3;
ilac = 4;
ici = 5;
}
} else if (n_matches == 7) {
/* CREG=2 (solicited): +CREG: <n>,<stat>,<lac>,<ci>,<AcT>
* CREG=2 (unsolicited with RAC): +CREG: <stat>,<lac>,<ci>,<AcT>,<RAC>
* CEREG=2 (solicited): +CEREG: <n>,<stat>,<lac>,<ci>,<AcT>
* CEREG=2 (unsolicited with RAC): +CEREG: <stat>,<lac>,<rac>,<ci>,<AcT>
*/
if (*out_cereg) {
/* Check if the third item is the LAC to distinguish the two cases */
if (item_is_lac_not_stat (info, 3)) {
istat = 2;
ilac = 3;
} else {
istat = 3;
ilac = 4;
}
ici = 5;
iact = 6;
} else {
/* Check if the third item is the LAC to distinguish the two cases */
if (item_is_lac_not_stat (info, 3)) {
istat = 2;
ilac = 3;
ici = 4;
iact = 5;
} else {
istat = 3;
ilac = 4;
ici = 5;
iact = 6;
}
}
} else if (n_matches == 8) {
/* CEREG=2 (solicited with RAC): +CEREG: <n>,<stat>,<lac>,<rac>,<ci>,<AcT>
*/
if (*out_cereg) {
istat = 3;
ilac = 4;
ici = 6;
iact = 7;
}
}
/* Status */
str = g_match_info_fetch (info, istat);
stat = parse_uint (str, 10, 0, G_MAXUINT, &success);
g_free (str);
if (!success) {
g_set_error_literal (error,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse the registration status response");
return FALSE;
}
/* 'roaming (csfb not preferred)' is the last valid state */
if (stat > MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED) {
mm_warn ("Registration State '%lu' is unknown", stat);
stat = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
}
/* Location Area Code */
if (ilac) {
/* FIXME: some phones apparently swap the LAC bytes (LG, SonyEricsson,
* Sagem). Need to handle that.
*/
str = g_match_info_fetch (info, ilac);
lac = parse_uint (str, 16, 1, 0xFFFF, &foo);
g_free (str);
}
/* Cell ID */
if (ici) {
str = g_match_info_fetch (info, ici);
ci = parse_uint (str, 16, 1, 0x0FFFFFFE, &foo);
g_free (str);
}
/* Access Technology */
if (iact) {
str = g_match_info_fetch (info, iact);
act = (gint) parse_uint (str, 10, 0, 7, &foo);
g_free (str);
if (!foo)
act = -1;
}
*out_reg_state = (MMModem3gppRegistrationState) stat;
if (stat != MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN) {
/* Don't fill in lac/ci/act if the device's state is unknown */
*out_lac = lac;
*out_ci = ci;
*out_act = get_mm_access_tech_from_etsi_access_tech (act);
}
return TRUE;
}
/*************************************************************************/
#define CMGF_TAG "+CMGF:"
gboolean
mm_3gpp_parse_cmgf_test_response (const gchar *reply,
gboolean *sms_pdu_supported,
gboolean *sms_text_supported,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
gchar *s;
guint32 min = -1, max = -1;
/* Strip whitespace and response tag */
if (g_str_has_prefix (reply, CMGF_TAG))
reply += strlen (CMGF_TAG);
while (isspace (*reply))
reply++;
r = g_regex_new ("\\(?\\s*(\\d+)\\s*[-,]?\\s*(\\d+)?\\s*\\)?", 0, 0, error);
if (!r)
return FALSE;
if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse CMGF query result '%s'",
reply);
g_match_info_free (match_info);
g_regex_unref (r);
return FALSE;
}
s = g_match_info_fetch (match_info, 1);
if (s)
min = atoi (s);
g_free (s);
s = g_match_info_fetch (match_info, 2);
if (s)
max = atoi (s);
g_free (s);
/* CMGF=0 for PDU mode */
*sms_pdu_supported = (min == 0);
/* CMGF=1 for Text mode */
*sms_text_supported = (max >= 1);
g_match_info_free (match_info);
g_regex_unref (r);
return TRUE;
}
/*************************************************************************/
MM3gppPduInfo *
mm_3gpp_parse_cmgr_read_response (const gchar *reply,
guint index,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
gint count;
gint status;
gchar *pdu;
MM3gppPduInfo *info = NULL;
/* +CMGR: <stat>,<alpha>,<length>(whitespace)<pdu> */
/* The <alpha> and <length> fields are matched, but not currently used */
r = g_regex_new ("\\+CMGR:\\s*(\\d+)\\s*,([^,]*),\\s*(\\d+)\\s*([^\\r\\n]*)", 0, 0, NULL);
g_assert (r);
if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse CMGR read result: response didn't match '%s'",
reply);
goto done;
}
/* g_match_info_get_match_count includes match #0 */
if ((count = g_match_info_get_match_count (match_info)) != 5) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to match CMGR fields (matched %d) '%s'",
count,
reply);
goto done;
}
if (!mm_get_int_from_match_info (match_info, 1, &status)) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to extract CMGR status field '%s'",
reply);
goto done;
}
pdu = mm_get_string_unquoted_from_match_info (match_info, 4);
if (!pdu) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to extract CMGR pdu field '%s'",
reply);
goto done;
}
info = g_new0 (MM3gppPduInfo, 1);
info->index = index;
info->status = status;
info->pdu = pdu;
done:
g_match_info_free (match_info);
g_regex_unref (r);
return info;
}
/*****************************************************************************/
/* AT+CRSM response parser */
gboolean
mm_3gpp_parse_crsm_response (const gchar *reply,
guint *sw1,
guint *sw2,
gchar **hex,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
g_assert (sw1 != NULL);
g_assert (sw2 != NULL);
g_assert (hex != NULL);
*sw1 = 0;
*sw2 = 0;
*hex = NULL;
if (!reply || !g_str_has_prefix (reply, "+CRSM:")) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing +CRSM prefix");
return FALSE;
}
r = g_regex_new ("\\+CRSM:\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*\"?([0-9a-fA-F]+)\"?",
G_REGEX_RAW, 0, NULL);
g_assert (r != NULL);
if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL) &&
mm_get_uint_from_match_info (match_info, 1, sw1) &&
mm_get_uint_from_match_info (match_info, 2, sw2))
*hex = mm_get_string_unquoted_from_match_info (match_info, 3);
g_match_info_free (match_info);
g_regex_unref (r);
if (*hex == NULL) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Failed to parse CRSM query result '%s'",
reply);
return FALSE;
}
return TRUE;
}
/*************************************************************************/
/* CGCONTRDP=N response parser */
static gboolean
split_local_address_and_subnet (const gchar *str,
gchar **local_address,
gchar **subnet)
{
const gchar *separator;
guint count = 0;
/* E.g. split: "2.43.2.44.255.255.255.255"
* into:
* local address: "2.43.2.44",
* subnet: "255.255.255.255"
*/
g_assert (str);
g_assert (local_address);
g_assert (subnet);
separator = str;
while (1) {
separator = strchr (separator, '.');
if (separator) {
count++;
if (count == 4) {
if (local_address)
*local_address = g_strndup (str, (separator - str));
if (subnet)
*subnet = g_strdup (++separator);
return TRUE;
}
separator++;
continue;
}
/* Not even the full IP? report error parsing */
if (count < 3)
return FALSE;
if (count == 3) {
if (local_address)
*local_address = g_strdup (str);
if (subnet)
*subnet = NULL;
return TRUE;
}
}
}
gboolean
mm_3gpp_parse_cgcontrdp_response (const gchar *response,
guint *out_cid,
guint *out_bearer_id,
gchar **out_apn,
gchar **out_local_address,
gchar **out_subnet,
gchar **out_gateway_address,
gchar **out_dns_primary_address,
gchar **out_dns_secondary_address,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
GError *inner_error = NULL;
guint cid = 0;
guint bearer_id = 0;
gchar *apn = NULL;
gchar *local_address_and_subnet = NULL;
gchar *local_address = NULL;
gchar *subnet = NULL;
gchar *gateway_address = NULL;
gchar *dns_primary_address = NULL;
gchar *dns_secondary_address = NULL;
guint field_format_extra_index = 0;
/* Response may be e.g.:
* +CGCONTRDP: 4,5,"ibox.tim.it.mnc001.mcc222.gprs","2.197.17.49.255.255.255.255","2.197.17.49","10.207.43.46","10.206.56.132","0.0.0.0","0.0.0.0",0
*
* We assume only ONE line is returned; because we request +CGCONTRDP with
* a specific N CID. Also, we don't parse all fields, we stop after
* secondary DNS.
*
* Only the 3 first parameters (cid, bearer id, apn) are mandatory in the
* response, all the others are optional, but, we'll anyway assume that APN
* may be empty.
*
* The format of the response changed in TS 27.007 v9.4.0, we try to detect
* both formats ('a' if >= v9.4.0, 'b' if < v9.4.0) with a single regex here.
*/
r = g_regex_new ("\\+CGCONTRDP: "
"(\\d+),(\\d+),([^,]*)" /* cid, bearer id, apn */
"(?:,([^,]*))?" /* (a)ip+mask or (b)ip */
"(?:,([^,]*))?" /* (a)gateway or (b)mask */
"(?:,([^,]*))?" /* (a)dns1 or (b)gateway */
"(?:,([^,]*))?" /* (a)dns2 or (b)dns1 */
"(?:,([^,]*))?" /* (a)p-cscf primary or (b)dns2 */
"(?:,(.*))?" /* others, ignored */
"(?:\\r\\n)?",
0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
if (!g_match_info_matches (match_info)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS, "Couldn't match +CGCONTRDP response");
goto out;
}
if (out_cid && !mm_get_uint_from_match_info (match_info, 1, &cid)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing cid");
goto out;
}
if (out_bearer_id && !mm_get_uint_from_match_info (match_info, 2, &bearer_id)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing bearer id");
goto out;
}
/* Remaining strings are optional or empty allowed */
if (out_apn)
apn = mm_get_string_unquoted_from_match_info (match_info, 3);
/*
* The +CGCONTRDP=[cid] response format before version TS 27.007 v9.4.0 had
* the subnet in its own comma-separated field. Try to detect that.
*/
local_address_and_subnet = mm_get_string_unquoted_from_match_info (match_info, 4);
if (local_address_and_subnet && !split_local_address_and_subnet (local_address_and_subnet, &local_address, &subnet)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Error parsing local address and subnet");
goto out;
}
/* If we don't have a subnet in field 4, we're using the old format with subnet in an extra field */
if (!subnet) {
if (out_subnet)
subnet = mm_get_string_unquoted_from_match_info (match_info, 5);
field_format_extra_index = 1;
}
if (out_gateway_address)
gateway_address = mm_get_string_unquoted_from_match_info (match_info, 5 + field_format_extra_index);
if (out_dns_primary_address)
dns_primary_address = mm_get_string_unquoted_from_match_info (match_info, 6 + field_format_extra_index);
if (out_dns_secondary_address)
dns_secondary_address = mm_get_string_unquoted_from_match_info (match_info, 7 + field_format_extra_index);
out:
g_match_info_free (match_info);
g_regex_unref (r);
g_free (local_address_and_subnet);
if (inner_error) {
g_free (apn);
g_free (local_address);
g_free (subnet);
g_free (gateway_address);
g_free (dns_primary_address);
g_free (dns_secondary_address);
g_propagate_error (error, inner_error);
return FALSE;
}
if (out_cid)
*out_cid = cid;
if (out_bearer_id)
*out_bearer_id = bearer_id;
if (out_apn)
*out_apn = apn;
/* Local address and subnet may always be retrieved, even if not requested
* by the caller, as we need them to know which +CGCONTRDP=[cid] response is
* being parsed. So make sure we free them if not needed. */
if (out_local_address)
*out_local_address = local_address;
else
g_free (local_address);
if (out_subnet)
*out_subnet = subnet;
else
g_free (subnet);
if (out_gateway_address)
*out_gateway_address = gateway_address;
if (out_dns_primary_address)
*out_dns_primary_address = dns_primary_address;
if (out_dns_secondary_address)
*out_dns_secondary_address = dns_secondary_address;
return TRUE;
}
/*************************************************************************/
gboolean
mm_3gpp_parse_cfun_query_response (const gchar *response,
guint *out_state,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
GError *inner_error = NULL;
guint state = G_MAXUINT;
g_assert (out_state != NULL);
/* Response may be e.g.:
* +CFUN: 1,0
* ..but we don't care about the second number
*/
r = g_regex_new ("\\+CFUN: (\\d+)(?:,(?:\\d+))?(?:\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (inner_error)
goto out;
if (!g_match_info_matches (match_info)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't parse +CFUN response: %s", response);
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 1, &state)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't read power state value");
goto out;
}
*out_state = state;
out:
g_match_info_free (match_info);
g_regex_unref (r);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
/* +CESQ response parser */
gboolean
mm_3gpp_parse_cesq_response (const gchar *response,
guint *out_rxlev,
guint *out_ber,
guint *out_rscp,
guint *out_ecn0,
guint *out_rsrq,
guint *out_rsrp,
GError **error)
{
GRegex *r;
GMatchInfo *match_info;
GError *inner_error = NULL;
guint rxlev = 99;
guint ber = 99;
guint rscp = 255;
guint ecn0 = 255;
guint rsrq = 255;
guint rsrp = 255;
gboolean success = FALSE;
g_assert (out_rxlev);
g_assert (out_ber);
g_assert (out_rscp);
g_assert (out_ecn0);
g_assert (out_rsrq);
g_assert (out_rsrp);
/* Response may be e.g.:
* +CESQ: 99,99,255,255,20,80
*/
r = g_regex_new ("\\+CESQ: (\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)(?:\\r\\n)?", 0, 0, NULL);
g_assert (r != NULL);
g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
if (!inner_error && g_match_info_matches (match_info)) {
if (!mm_get_uint_from_match_info (match_info, 1, &rxlev)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RXLEV");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 2, &ber)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read BER");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 3, &rscp)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSCP");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 4, &ecn0)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read Ec/N0");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 5, &rsrq)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRQ");
goto out;
}
if (!mm_get_uint_from_match_info (match_info, 6, &rsrp)) {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't read RSRP");
goto out;
}
success = TRUE;
}
out:
g_match_info_free (match_info);
g_regex_unref (r);
if (inner_error) {
g_propagate_error (error, inner_error);
return FALSE;
}
if (!success) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't parse +CESQ response: %s", response);
return FALSE;
}
*out_rxlev = rxlev;
*out_ber = ber;
*out_rscp = rscp;
*out_ecn0 = ecn0;
*out_rsrq = rsrq;
*out_rsrp = rsrp;
return TRUE;
}
gboolean
mm_3gpp_rxlev_to_rssi (guint rxlev,
gdouble *out_rssi)
{
if (rxlev <= 63) {
*out_rssi = -111.0 + rxlev;
return TRUE;
}
if (rxlev != 99)
mm_warn ("unexpected rxlev: %u", rxlev);
return FALSE;
}
gboolean
mm_3gpp_rscp_level_to_rscp (guint rscp_level,
gdouble *out_rscp)
{
if (rscp_level <= 96) {
*out_rscp = -121.0 + rscp_level;
return TRUE;
}
if (rscp_level != 255)
mm_warn ("unexpected rscp level: %u", rscp_level);
return FALSE;
}
gboolean
mm_3gpp_ecn0_level_to_ecio (guint ecn0_level,
gdouble *out_ecio)
{
if (ecn0_level <= 49) {
*out_ecio = -24.5 + (((gdouble) ecn0_level) * 0.5);
return TRUE;
}
if (ecn0_level != 255)
mm_warn ("unexpected Ec/N0 level: %u", ecn0_level);
return FALSE;
}
gboolean
mm_3gpp_rsrq_level_to_rsrq (guint rsrq_level,
gdouble *out_rsrq)
{
if (rsrq_level <= 34) {
*out_rsrq = -20.0 + (((gdouble) rsrq_level) * 0.5);
return TRUE;
}
if (rsrq_level != 255)
mm_warn ("unexpected RSRQ level: %u", rsrq_level);
return FALSE;
}
gboolean
mm_3gpp_rsrp_level_to_rsrp (guint rsrp_level,
gdouble *out_rsrp)
{
if (rsrp_level <= 97) {
*out_rsrp = -141.0 + rsrp_level;
return TRUE;
}
if (rsrp_level != 255)
mm_warn ("unexpected RSRP level: %u", rsrp_level);
return FALSE;
}
gboolean
mm_3gpp_cesq_response_to_signal_info (const gchar *response,
MMSignal **out_gsm,
MMSignal **out_umts,
MMSignal **out_lte,
GError **error)
{
guint rxlev = 0;
guint ber = 0;
guint rscp_level = 0;
guint ecn0_level = 0;
guint rsrq_level = 0;
guint rsrp_level = 0;
gdouble rssi = -G_MAXDOUBLE;
gdouble rscp = -G_MAXDOUBLE;
gdouble ecio = -G_MAXDOUBLE;
gdouble rsrq = -G_MAXDOUBLE;
gdouble rsrp = -G_MAXDOUBLE;
MMSignal *gsm = NULL;
MMSignal *umts = NULL;
MMSignal *lte = NULL;
if (!mm_3gpp_parse_cesq_response (response,
&rxlev, &ber,
&rscp_level, &ecn0_level,
&rsrq_level, &rsrp_level,
error))
return FALSE;
/* GERAN RSSI */
if (mm_3gpp_rxlev_to_rssi (rxlev, &rssi)) {
gsm = mm_signal_new ();
mm_signal_set_rssi (gsm, rssi);
}
/* ignore BER */
/* UMTS RSCP */
if (mm_3gpp_rscp_level_to_rscp (rscp_level, &rscp)) {
umts = mm_signal_new ();
mm_signal_set_rscp (umts, rscp);
}
/* UMTS EcIo (assumed EcN0) */
if (mm_3gpp_ecn0_level_to_ecio (ecn0_level, &ecio)) {
if (!umts)
umts = mm_signal_new ();
mm_signal_set_ecio (umts, ecio);
}
/* LTE RSRQ */
if (mm_3gpp_rsrq_level_to_rsrq (rsrq_level, &rsrq)) {
lte = mm_signal_new ();
mm_signal_set_rsrq (lte, rsrq);
}
/* LTE RSRP */
if (mm_3gpp_rsrp_level_to_rsrp (rsrp_level, &rsrp)) {
if (!lte)
lte = mm_signal_new ();
mm_signal_set_rsrp (lte, rsrp);
}
if (!gsm && !umts && !lte) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Couldn't build detailed signal info");
return FALSE;
}
if (gsm)
*out_gsm = gsm;
if (umts)
*out_umts = umts;
if (lte)
*out_lte = lte;
return TRUE;
}
gboolean
mm_3gpp_parse_cfun_query_generic_response (const gchar *response,
MMModemPowerState *out_state,
GError **error)
{
guint state;
if (!mm_3gpp_parse_cfun_query_response (response, &state, error))
return FALSE;
switch (state) {
case 0:
*out_state = MM_MODEM_POWER_STATE_OFF;
return TRUE;
case 1:
*out_state = MM_MODEM_POWER_STATE_ON;
return TRUE;
case 4:
*out_state = MM_MODEM_POWER_STATE_LOW;
return TRUE;
default:
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Unknown +CFUN state: %u", state);
return FALSE;
}
}
static MMModem3gppEpsUeModeOperation cemode_values[] = {
[0] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_PS_2,
[1] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_CSPS_1,
[2] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_CSPS_2,
[3] = MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_PS_1,
};
gchar *
mm_3gpp_build_cemode_set_request (MMModem3gppEpsUeModeOperation mode)
{
guint i;
g_return_val_if_fail (mode != MM_MODEM_3GPP_EPS_UE_MODE_OPERATION_UNKNOWN, NULL);
for (i = 0; i < G_N_ELEMENTS (cemode_values); i++) {
if (mode == cemode_values[i])
return g_strdup_printf ("+CEMODE=%u", i);
}
g_assert_not_reached ();
return NULL;
}
gboolean
mm_3gpp_parse_cemode_query_response (const gchar *response,
MMModem3gppEpsUeModeOperation *out_mode,
GError **error)
{
guint value = 0;
response = mm_strip_tag (response, "+CEMODE:");
if (mm_get_uint_from_str (response, &value) && value < G_N_ELEMENTS (cemode_values)) {
if (out_mode)
*out_mode = cemode_values[value];
return TRUE;
}
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse UE mode of operation: '%s' (value %u)",
response, value);
return FALSE;
}
/*************************************************************************/
static MMSmsStorage
storage_from_str (const gchar *str)
{
if (g_str_equal (str, "SM"))
return MM_SMS_STORAGE_SM;
if (g_str_equal (str, "ME"))
return MM_SMS_STORAGE_ME;
if (g_str_equal (str, "MT"))
return MM_SMS_STORAGE_MT;
if (g_str_equal (str, "SR"))
return MM_SMS_STORAGE_SR;
if (g_str_equal (str, "BM"))
return MM_SMS_STORAGE_BM;
if (g_str_equal (str, "TA"))
return MM_SMS_STORAGE_TA;
return MM_SMS_STORAGE_UNKNOWN;
}
gboolean
mm_3gpp_parse_cpms_test_response (const gchar *reply,
GArray **mem1,
GArray **mem2,
GArray **mem3)
{
GRegex *r;
gchar **split;
guint i;
GArray *tmp1 = NULL;
GArray *tmp2 = NULL;
GArray *tmp3 = NULL;
g_assert (mem1 != NULL);
g_assert (mem2 != NULL);
g_assert (mem3 != NULL);
#define N_EXPECTED_GROUPS 3
split = mm_split_string_groups (mm_strip_tag (reply, "+CPMS:"));
if (!split)
return FALSE;
if (g_strv_length (split) != N_EXPECTED_GROUPS) {
mm_warn ("Cannot parse +CPMS test response: invalid number of groups (%u != %u)",
g_strv_length (split), N_EXPECTED_GROUPS);
g_strfreev (split);
return FALSE;
}
r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL);
g_assert (r);
for (i = 0; i < N_EXPECTED_GROUPS; i++) {
GMatchInfo *match_info = NULL;
GArray *array;
/* We always return a valid array, even if it may be empty */
array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage));
/* Got a range group to match */
if (g_regex_match_full (r, split[i], strlen (split[i]), 0, 0, &match_info, NULL)) {
while (g_match_info_matches (match_info)) {
gchar *str;
str = g_match_info_fetch (match_info, 1);
if (str) {
MMSmsStorage storage;
storage = storage_from_str (str);
g_array_append_val (array, storage);
g_free (str);
}
g_match_info_next (match_info, NULL);
}
}
g_match_info_free (match_info);
if (!tmp1)
tmp1 = array;
else if (!tmp2)
tmp2 = array;
else if (!tmp3)
tmp3 = array;
else
g_assert_not_reached ();
}
g_strfreev (split);
g_regex_unref (r);
g_warn_if_fail (tmp1 != NULL);
g_warn_if_fail (tmp2 != NULL);
g_warn_if_fail (tmp3 != NULL);
/* Only return TRUE if all sets have been parsed correctly
* (even if the arrays may be empty) */
if (tmp1 && tmp2 && tmp3) {
*mem1 = tmp1;
*mem2 = tmp2;
*mem3 = tmp3;
return TRUE;
}
/* Otherwise, cleanup and return FALSE */
if (tmp1)
g_array_unref (tmp1);
if (tmp2)
g_array_unref (tmp2);
if (tmp3)
g_array_unref (tmp3);
return FALSE;
}
/**********************************************************************
* AT+CPMS?
* +CPMS: <memr>,<usedr>,<totalr>,<memw>,<usedw>,<totalw>, <mems>,<useds>,<totals>
*/
#define CPMS_QUERY_REGEX "\\+CPMS:\\s*\"(?P<memr>.*)\",[0-9]+,[0-9]+,\"(?P<memw>.*)\",[0-9]+,[0-9]+,\"(?P<mems>.*)\",[0-9]+,[0-9]"
gboolean
mm_3gpp_parse_cpms_query_response (const gchar *reply,
MMSmsStorage *memr,
MMSmsStorage *memw,
GError **error)
{
GRegex *r = NULL;
gboolean ret = FALSE;
GMatchInfo *match_info = NULL;
r = g_regex_new (CPMS_QUERY_REGEX, G_REGEX_RAW, 0, NULL);
g_assert (r);
if (!g_regex_match (r, reply, 0, &match_info)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse CPMS query response '%s'", reply);
goto end;
}
if (!g_match_info_matches (match_info)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not find matches in CPMS query reply '%s'", reply);
goto end;
}
if (!mm_3gpp_get_cpms_storage_match (match_info, "memr", memr, error)) {
goto end;
}
if (!mm_3gpp_get_cpms_storage_match (match_info, "memw", memw, error)) {
goto end;
}
ret = TRUE;
end:
if (r != NULL)
g_regex_unref (r);
g_match_info_free (match_info);
return ret;
}
gboolean
mm_3gpp_get_cpms_storage_match (GMatchInfo *match_info,
const gchar *match_name,
MMSmsStorage *storage,
GError **error)
{
gboolean ret = TRUE;
gchar *str = NULL;
str = g_match_info_fetch_named (match_info, match_name);
if (str == NULL || str[0] == '\0') {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not find '%s' from CPMS reply", match_name);
ret = FALSE;
} else {
*storage = storage_from_str (str);
}
g_free (str);
return ret;
}
/*************************************************************************/
gboolean
mm_3gpp_parse_cscs_test_response (const gchar *reply,
MMModemCharset *out_charsets)
{
MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN;
GRegex *r;
GMatchInfo *match_info;
gchar *p, *str;
gboolean success = FALSE;
g_return_val_if_fail (reply != NULL, FALSE);
g_return_val_if_fail (out_charsets != NULL, FALSE);
/* Find the first '(' or '"'; the general format is:
*
* +CSCS: ("IRA","GSM","UCS2")
*
* but some devices (some Blackberries) don't include the ().
*/
p = strchr (reply, '(');
if (p)
p++;
else {
p = strchr (reply, '"');