blob: 6019dfe9cdb0f9211ee86e15edac0041edddafb5 [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 <ModemManager.h>
#include <libmm-common.h>
#include "mm-modem-helpers.h"
#include "mm-log.h"
/*****************************************************************************/
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;
}
/*****************************************************************************/
guint
mm_count_bits_set (gulong number)
{
guint c;
for (c = 0; number; c++)
number &= 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 (vid) {
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;
}
/*****************************************************************************/
/* +CREG: <stat> (GSM 07.07 CREG=1 unsolicited) */
#define CREG1 "\\+(CREG|CGREG):\\s*0*([0-9])"
/* +CREG: <n>,<stat> (GSM 07.07 CREG=1 solicited) */
#define CREG2 "\\+(CREG|CGREG):\\s*0*([0-9]),\\s*0*([0-9])"
/* +CREG: <stat>,<lac>,<ci> (GSM 07.07 CREG=2 unsolicited) */
#define CREG3 "\\+(CREG|CGREG):\\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):\\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 CREG5 "\\+(CREG|CGREG):\\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 CREG6 "\\+(CREG|CGREG):\\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 CREG7 "\\+(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 CREG8 "\\+(CREG|CGREG):\\s*0*([0-9])\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*0*([0-9])\\s*,\\s*([^,\\s]*)"
GPtrArray *
mm_3gpp_creg_regex_get (gboolean solicited)
{
GPtrArray *array = g_ptr_array_sized_new (7);
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);
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_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+)\",(\\d+)\\r\\n",
G_REGEX_RAW | G_REGEX_OPTIMIZE,
0,
NULL);
}
/*************************************************************************/
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;
}
/*************************************************************************/
static void
mm_3gpp_pdp_context_free (MM3gppPdpContext *pdp)
{
g_free (pdp->pdp_type);
g_free (pdp->apn);
g_free (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[0])
/* No APNs configured, all done */
return NULL;
list = NULL;
r = g_regex_new ("\\+CGDCONT:\\s*(\\d+)\\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)) {
MM3gppPdpContext *pdp;
pdp = g_new0 (MM3gppPdpContext, 1);
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 = mm_get_string_unquoted_from_match_info (match_info, 2);
pdp->apn = mm_get_string_unquoted_from_match_info (match_info, 3);
list = g_list_prepend (list, pdp);
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 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,
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);
str = g_match_info_fetch (info, 1);
if (str && strstr (str, "CGREG"))
*out_cgreg = TRUE;
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>
*/
/* 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;
}
}
/* Status */
str = g_match_info_fetch (info, istat);
stat = parse_uint (str, 10, 0, 5, &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;
}
/* 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;
}
/* 'roaming' is the last valid state */
if (stat > MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING) {
mm_warn ("Registration State '%lu' is unknown", stat);
stat = MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN;
}
*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;
}
/*************************************************************************/
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;
g_assert (mem1 != NULL);
g_assert (mem2 != NULL);
g_assert (mem3 != NULL);
/*
* +CPMS: ("SM","ME"),("SM","ME"),("SM","ME")
*/
split = g_strsplit_set (mm_strip_tag (reply, "+CPMS:"), "()", -1);
if (!split)
return FALSE;
r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL);
g_assert (r);
for (i = 0; split[i]; i++) {
GMatchInfo *match_info;
/* Got a range group to match */
if (g_regex_match_full (r, split[i], strlen (split[i]), 0, 0, &match_info, NULL)) {
GArray *array = NULL;
while (g_match_info_matches (match_info)) {
gchar *str;
str = g_match_info_fetch (match_info, 1);
if (str) {
MMSmsStorage storage;
if (!array)
array = g_array_new (FALSE, FALSE, sizeof (MMSmsStorage));
storage = storage_from_str (str);
g_array_append_val (array, storage);
g_free (str);
}
g_match_info_next (match_info, NULL);
}
if (!*mem1)
*mem1 = array;
else if (!*mem2)
*mem2 = array;
else if (!*mem3)
*mem3 = array;
}
g_match_info_free (match_info);
if (*mem3 != NULL)
break; /* once we got the last group, exit... */
}
g_strfreev (split);
g_regex_unref (r);
g_warn_if_fail (*mem1 != NULL);
g_warn_if_fail (*mem2 != NULL);
g_warn_if_fail (*mem3 != NULL);
return (*mem1 && *mem2 && *mem3);
}
/*************************************************************************/
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, '"');
if (!p)
return FALSE;
}
/* Now parse each charset */
r = g_regex_new ("\\s*([^,\\)]+)\\s*", 0, 0, NULL);
if (!r)
return FALSE;
if (g_regex_match_full (r, p, strlen (p), 0, 0, &match_info, NULL)) {
while (g_match_info_matches (match_info)) {
str = g_match_info_fetch (match_info, 1);
charsets |= mm_modem_charset_from_string (str);
g_free (str);
g_match_info_next (match_info, NULL);
success = TRUE;
}
}
g_match_info_free (match_info);
g_regex_unref (r);
if (success)
*out_charsets = charsets;
return success;
}
/*************************************************************************/
gboolean
mm_3gpp_parse_clck_test_response (const gchar *reply,
MMModem3gppFacility *out_facilities)
{
GRegex *r;
GMatchInfo *match_info;
g_return_val_if_fail (reply != NULL, FALSE);
g_return_val_if_fail (out_facilities != NULL, FALSE);
/* the general format is:
*
* +CLCK: ("SC","AO","AI","PN")
*/
reply = mm_strip_tag (reply, "+CLCK:");
/* Now parse each facility */
r = g_regex_new ("\\s*\"([^,\\)]+)\"\\s*", 0, 0, NULL);
g_assert (r != NULL);
*out_facilities = MM_MODEM_3GPP_FACILITY_NONE;
if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
while (g_match_info_matches (match_info)) {
gchar *str;
str = g_match_info_fetch (match_info, 1);
if (str) {
*out_facilities |= mm_3gpp_acronym_to_facility (str);
g_free (str);
}
g_match_info_next (match_info, NULL);
}
}
g_match_info_free (match_info);
g_regex_unref (r);
return (*out_facilities != MM_MODEM_3GPP_FACILITY_NONE);
}
/*************************************************************************/
gboolean
mm_3gpp_parse_clck_write_response (const gchar *reply,
gboolean *enabled)
{
GRegex *r;
GMatchInfo *match_info;
gboolean success = FALSE;
g_return_val_if_fail (reply != NULL, FALSE);
g_return_val_if_fail (enabled != NULL, FALSE);
reply = mm_strip_tag (reply, "+CLCK:");
r = g_regex_new ("\\s*([01])\\s*", 0, 0, NULL);
g_assert (r != NULL);
if (g_regex_match (r, reply, 0, &match_info)) {
gchar *str;
str = g_match_info_fetch (match_info, 1);
if (str) {
/* We're trying to match either '0' or '1',
* so we don't expect any other thing */
if (*str == '0')
*enabled = FALSE;
else if (*str == '1')
*enabled = TRUE;
else
g_assert_not_reached ();
g_free (str);
success = TRUE;
}
}
g_match_info_free (match_info);
g_regex_unref (r);
return success;
}
/*************************************************************************/
GStrv
mm_3gpp_parse_cnum_exec_response (const gchar *reply,
GError **error)
{
GArray *array = NULL;
GRegex *r;
GMatchInfo *match_info;
/* Empty strings also return NULL list */
if (!reply || !reply[0])
return NULL;
r = g_regex_new ("\\+CNUM:\\s*((\"([^\"]|(\\\"))*\")|([^,]*)),\"(?<num>\\S+)\",\\d",
G_REGEX_UNGREEDY, 0, NULL);
g_assert (r != NULL);
g_regex_match (r, reply, 0, &match_info);
while (g_match_info_matches (match_info)) {
gchar *number;
number = g_match_info_fetch_named (match_info, "num");
if (number && number[0]) {
if (!array)
array = g_array_new (TRUE, TRUE, sizeof (gchar *));
g_array_append_val (array, number);
} else
g_free (number);
g_match_info_next (match_info, NULL);
}
g_match_info_free (match_info);
g_regex_unref (r);
return (array ? (GStrv) g_array_free (array, FALSE) : NULL);
}
/*************************************************************************/
struct MM3gppCindResponse {
gchar *desc;
guint idx;
gint min;
gint max;
};
static MM3gppCindResponse *
cind_response_new (const gchar *desc, guint idx, gint min, gint max)
{
MM3gppCindResponse *r;
gchar *p;
g_return_val_if_fail (desc != NULL, NULL);
g_return_val_if_fail (idx >= 0, NULL);
r = g_malloc0 (sizeof (MM3gppCindResponse));
/* Strip quotes */
r->desc = p = g_malloc0 (strlen (desc) + 1);
while (*desc) {
if (*desc != '"' && !isspace (*desc))
*p++ = tolower (*desc);
desc++;
}
r->idx = idx;
r->max = max;
r->min = min;
return r;
}
static void
cind_response_free (MM3gppCindResponse *r)
{
g_return_if_fail (r != NULL);
g_free (r->desc);
memset (r, 0, sizeof (MM3gppCindResponse));
g_free (r);
}
const gchar *
mm_3gpp_cind_response_get_desc (MM3gppCindResponse *r)
{
g_return_val_if_fail (r != NULL, NULL);
return r->desc;
}
guint
mm_3gpp_cind_response_get_index (MM3gppCindResponse *r)
{
g_return_val_if_fail (r != NULL, 0);
return r->idx;
}
gint
mm_3gpp_cind_response_get_min (MM3gppCindResponse *r)
{
g_return_val_if_fail (r != NULL, -1);
return r->min;
}
gint
mm_3gpp_cind_response_get_max (MM3gppCindResponse *r)
{
g_return_val_if_fail (r != NULL, -1);
return r->max;
}
#define CIND_TAG "+CIND:"
GHashTable *
mm_3gpp_parse_cind_test_response (const gchar *reply,
GError **error)
{
GHashTable *hash;
GRegex *r;
GMatchInfo *match_info;
guint idx = 1;
g_return_val_if_fail (reply != NULL, NULL);
/* Strip whitespace and response tag */
if (g_str_has_prefix (reply, CIND_TAG))
reply += strlen (CIND_TAG);
while (isspace (*reply))
reply++;
r = g_regex_new ("\\(([^,]*),\\((\\d+)[-,](\\d+).*\\)", G_REGEX_UNGREEDY, 0, NULL);
if (!r) {
g_set_error_literal (error,
MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse scan results.");
return NULL;
}
hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cind_response_free);
if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, NULL)) {
while (g_match_info_matches (match_info)) {
MM3gppCindResponse *resp;
gchar *desc, *tmp;
gint min = 0, max = 0;
desc = g_match_info_fetch (match_info, 1);
tmp = g_match_info_fetch (match_info, 2);
min = atoi (tmp);
g_free (tmp);
tmp = g_match_info_fetch (match_info, 3);
max = atoi (tmp);
g_free (tmp);
resp = cind_response_new (desc, idx++, min, max);
if (resp)
g_hash_table_insert (hash, g_strdup (resp->desc), resp);
g_free (desc);
g_match_info_next (match_info, NULL);
}
}
g_match_info_free (match_info);
g_regex_unref (r);
return hash;
}
/*************************************************************************/
GByteArray *
mm_3gpp_parse_cind_read_response (const gchar *reply,
GError **error)
{
GByteArray *array = NULL;
GRegex *r = NULL;
GMatchInfo *match_info;
GError *inner_error = NULL;
guint8 t;
g_return_val_if_fail (reply != NULL, NULL);
if (!g_str_has_prefix (reply, CIND_TAG)) {
g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse the +CIND response '%s': no CIND tag found",
reply);
return NULL;
}
reply = mm_strip_tag (reply, CIND_TAG);
r = g_regex_new ("(\\d+)[^0-9]+", G_REGEX_UNGREEDY, 0, NULL);
g_assert (r != NULL);
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,
"Could not parse the +CIND response '%s': didn't match",
reply);
goto done;
}
array = g_byte_array_sized_new (g_match_info_get_match_count (match_info));
/* Add a zero element so callers can use 1-based indexes returned by
* mm_3gpp_cind_response_get_index().
*/
t = 0;
g_byte_array_append (array, &t, 1);
while (!inner_error &&
g_match_info_matches (match_info)) {
gchar *str;
guint val = 0;
str = g_match_info_fetch (match_info, 1);
if (mm_get_uint_from_str (str, &val) && val < 255) {
t = (guint8) val;
g_byte_array_append (array, &t, 1);
} else {
inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
"Could not parse the +CIND response: invalid index '%s'",
str);
}
g_free (str);
g_match_info_next (match_info, NULL);
}
if (inner_error) {
g_propagate_error (error, inner_error);
g_byte_array_unref (array);
array = NULL;
}
done:
g_match_info_free (match_info);
g_regex_unref (r);
return array;
}
/*************************************************************************/
/* Map two letter facility codes into flag values. There are
* many more facilities defined (for various flavors of call
* barring); we only map the ones we care about. */
typedef struct {
MMModem3gppFacility facility;
gchar *acronym;
} FacilityAcronym;
static const FacilityAcronym facility_acronyms[] = {
{ MM_MODEM_3GPP_FACILITY_SIM, "SC" },
{ MM_MODEM_3GPP_FACILITY_PH_SIM, "PS" },
{ MM_MODEM_3GPP_FACILITY_PH_FSIM, "PF" },
{ MM_MODEM_3GPP_FACILITY_FIXED_DIALING, "FD" },
{ MM_MODEM_3GPP_FACILITY_NET_PERS, "PN" },
{ MM_MODEM_3GPP_FACILITY_NET_SUB_PERS, "PU" },
{ MM_MODEM_3GPP_FACILITY_PROVIDER_PERS, "PP" },
{ MM_MODEM_3GPP_FACILITY_CORP_PERS, "PC" }
};
MMModem3gppFacility
mm_3gpp_acronym_to_facility (const gchar *str)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (facility_acronyms); i++) {
if (g_str_equal (facility_acronyms[i].acronym, str))
return facility_acronyms[i].facility;
}
return MM_MODEM_3GPP_FACILITY_NONE;
}
gchar *
mm_3gpp_facility_to_acronym (MMModem3gppFacility facility)
{
guint i;
for (i = 0; i < G_N_ELEMENTS (facility_acronyms); i++) {
if (facility_acronyms[i].facility == facility)
return facility_acronyms[i].acronym;
}
return NULL;
}
/*************************************************************************/
MMModemAccessTechnology
mm_string_to_access_tech (const gchar *string)
{
MMModemAccessTechnology act = MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN;
g_return_val_if_fail (string != NULL, MM_MODEM_ACCESS_TECHNOLOGY_UNKNOWN);
/* We're returning a MASK of technologies found; so we can include more
* than one technology in the result */
if (strcasestr (string, "LTE"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_LTE;
if (strcasestr (string, "HSPA+"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_HSPA_PLUS;
else if (strcasestr (string, "HSPA"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_HSPA;
if (strcasestr (string, "HSUPA"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_HSUPA;
if (strcasestr (string, "HSDPA"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_HSDPA;
if (strcasestr (string, "UMTS"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_UMTS;
if (strcasestr (string, "EDGE"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_EDGE;
if (strcasestr (string, "GPRS"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_GPRS;
if (strcasestr (string, "GSM"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_GSM;
if (strcasestr (string, "EvDO Rel0"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDO0;
if (strcasestr (string, "EvDO RelA"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_EVDOA;
if (strcasestr (string, "1xRTT"))
act |= MM_MODEM_ACCESS_TECHNOLOGY_1XRTT;
return act;
}
/*************************************************************************/
gchar *
mm_3gpp_parse_operator (const gchar *reply,
MMModemCharset cur_charset)
{
gchar *operator = NULL;
if (reply && !strncmp (reply, "+COPS: ", 7)) {
/* Got valid reply */
GRegex *r;
GMatchInfo *match_info;
reply += 7;
r = g_regex_new ("(\\d),(\\d),\"(.+)\"", G_REGEX_UNGREEDY, 0, NULL);
if (!r)
return NULL;
g_regex_match (r, reply, 0, &match_info);
if (g_match_info_matches (match_info))
operator = g_match_info_fetch (match_info, 3);
g_match_info_free (match_info);
g_regex_unref (r);
}
if (operator) {
/* Some modems (Option & HSO) return the operator name as a hexadecimal
* string of the bytes of the operator name as encoded by the current
* character set.
*/
if (cur_charset == MM_MODEM_CHARSET_UCS2)
operator = mm_charset_take_and_convert_to_utf8 (operator, MM_MODEM_CHARSET_UCS2);
/* Ensure the operator name is valid UTF-8 so that we can send it
* through D-Bus and such.
*/
if (!g_utf8_validate (operator, -1, NULL)) {
g_free (operator);
operator = NULL;
}
}
return operator;
}
/*************************************************************************/
gboolean
mm_cdma_parse_spservice_read_response (const gchar *reply,
MMModemCdmaRegistrationState *out_cdma_1x_state,
MMModemCdmaRegistrationState *out_evdo_state)
{
const gchar *p;
g_return_val_if_fail (reply != NULL, FALSE);
g_return_val_if_fail (out_cdma_1x_state != NULL, FALSE);
g_return_val_if_fail (out_evdo_state != NULL, FALSE);
p = mm_strip_tag (reply, "+SPSERVICE:");
if (!isdigit (*p))
return FALSE;
switch (atoi (p)) {
case 0: /* no service */
*out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
*out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
break;
case 1: /* 1xRTT */
*out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
*out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
break;
case 2: /* EVDO rev 0 */
case 3: /* EVDO rev A */
*out_cdma_1x_state = MM_MODEM_CDMA_REGISTRATION_STATE_UNKNOWN;
*out_evdo_state = MM_MODEM_CDMA_REGISTRATION_STATE_REGISTERED;
break;
default:
return FALSE;
}
return TRUE;
}
/*************************************************************************/
typedef struct {
gint num;
gboolean roam_ind;
const gchar *banner;
} EriItem;
/* NOTE: these may be Sprint-specific for now... */
static const EriItem eris[] = {
{ 0, TRUE, "Digital or Analog Roaming" },
{ 1, FALSE, "Home" },
{ 2, TRUE, "Digital or Analog Roaming" },
{ 3, TRUE, "Out of neighborhood" },
{ 4, TRUE, "Out of building" },
{ 5, TRUE, "Preferred system" },
{ 6, TRUE, "Available System" },
{ 7, TRUE, "Alliance Partner" },
{ 8, TRUE, "Premium Partner" },
{ 9, TRUE, "Full Service Functionality" },
{ 10, TRUE, "Partial Service Functionality" },
{ 64, TRUE, "Preferred system" },
{ 65, TRUE, "Available System" },
{ 66, TRUE, "Alliance Partner" },
{ 67, TRUE, "Premium Partner" },
{ 68, TRUE, "Full Service Functionality" },
{ 69, TRUE, "Partial Service Functionality" },
{ 70, TRUE, "Analog A" },
{ 71, TRUE, "Analog B" },
{ 72, TRUE, "CDMA 800 A" },
{ 73, TRUE, "CDMA 800 B" },
{ 74, TRUE, "International Roaming" },
{ 75, TRUE, "Extended Network" },
{ 76, FALSE, "Campus" },
{ 77, FALSE, "In Building" },
{ 78, TRUE, "Regional" },
{ 79, TRUE, "Community" },
{ 80, TRUE, "Business" },
{ 81, TRUE, "Zone 1" },
{ 82, TRUE, "Zone 2" },
{ 83, TRUE, "National" },
{ 84, TRUE, "Local" },
{ 85, TRUE, "City" },
{ 86, TRUE, "Government" },
{ 87, TRUE, "USA" },
{ 88, TRUE, "State" },
{ 89, TRUE, "Resort" },
{ 90, TRUE, "Headquarters" },
{ 91, TRUE, "Personal" },
{ 92, FALSE, "Home" },
{ 93, TRUE, "Residential" },
{ 94, TRUE, "University" },
{ 95, TRUE, "College" },
{ 96, TRUE, "Hotel Guest" },
{ 97, TRUE, "Rental" },
{ 98, FALSE, "Corporate" },
{ 99, FALSE, "Home Provider" },
{ 100, FALSE, "Campus" },
{ 101, FALSE, "In Building" },
{ 102, TRUE, "Regional" },
{ 103, TRUE, "Community" },
{ 104, TRUE, "Business" },
{ 105, TRUE, "Zone 1" },
{ 106, TRUE, "Zone 2" },
{ 107, TRUE, "National" },
{ 108, TRUE, "Local" },
{ 109, TRUE, "City" },
{ 110, TRUE, "Government" },
{ 111, TRUE, "USA" },
{ 112, TRUE, "State" },
{ 113, TRUE, "Resort" },
{ 114, TRUE, "Headquarters" },
{ 115, TRUE, "Personal" },
{ 116, FALSE, "Home" },
{ 117, TRUE, "Residential" },
{ 118, TRUE, "University" },
{ 119, TRUE, "College" },
{ 120, TRUE, "Hotel Guest" },
{ 121, TRUE, "Rental" },
{ 122, FALSE, "Corporate" },
{ 123, FALSE, "Home Provider" },
{ 124, TRUE, "International" },
{ 125, TRUE, "International" },
{ 126, TRUE, "International" },
{ 127, FALSE, "Premium Service" },
{ 128, FALSE, "Enhanced Service" },
{ 129, FALSE, "Enhanced Digital" },
{ 130, FALSE, "Enhanced Roaming" },
{ 131, FALSE, "Alliance Service" },
{ 132, FALSE, "Alliance Network" },
{ 133, FALSE, "Data Roaming" }, /* Sprint: Vision Roaming */
{ 134, FALSE, "Extended Service" },
{ 135, FALSE, "Expanded Services" },
{ 136, FALSE, "Expanded Network" },
{ 137, TRUE, "Premium Service" },
{ 138, TRUE, "Enhanced Service" },
{ 139, TRUE, "Enhanced Digital" },
{ 140, TRUE, "Enhanced Roaming" },
{ 141, TRUE, "Alliance Service" },
{ 142, TRUE, "Alliance Network" },
{ 143, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */
{ 144, TRUE, "Extended Service" },
{ 145, TRUE, "Expanded Services" },
{ 146, TRUE, "Expanded Network" },
{ 147, TRUE, "Premium Service" },
{ 148, TRUE, "Enhanced Service" },
{ 149, TRUE, "Enhanced Digital" },
{ 150, TRUE, "Enhanced Roaming" },
{ 151, TRUE, "Alliance Service" },
{ 152, TRUE, "Alliance Network" },
{ 153, TRUE, "Data Roaming" }, /* Sprint: Vision Roaming */
{ 154, TRUE, "Extended Service" },
{ 155, TRUE, "Expanded Services" },
{ 156, TRUE, "Expanded Network" },
{ 157, TRUE, "Premium International" },
{ 158, TRUE, "Premium International" },
{ 159, TRUE, "Premium International" },
{ 160, TRUE, NULL },
{ 161, TRUE, NULL },
{ 162, FALSE, NULL },
{ 163, FALSE, NULL },
{ 164, FALSE, "Extended Voice/Data Network" },
{ 165, FALSE, "Extended Voice/Data Network" },
{ 166, TRUE, "Extended Voice/Data Network" },
{ 167, FALSE, "Extended Broadband" },
{ 168, FALSE, "Extended Broadband" },
{ 169, TRUE, "Extended Broadband" },
{ 170, FALSE, "Extended Data" },
{ 171, FALSE, "Extended Data" },
{ 172, TRUE, "Extended Data" },
{ 173, FALSE, "Extended Data Network" },
{ 174, FALSE, "Extended Data Network" },
{ 175, TRUE, "Extended Data Network" },
{ 176, FALSE, "Extended Network" },
{ 177, FALSE, "Extended Network" },
{ 178, TRUE, "Extended Network" },
{ 179, FALSE, "Extended Service" },
{ 180, TRUE, "Extended Service" },
{ 181, FALSE, "Extended Voice" },
{ 182, FALSE, "Extended Voice" },
{ 183, TRUE, "Extended Voice" },
{ 184, FALSE, "Extended Voice/Data" },
{ 185, FALSE, "Extended Voice/Data" },
{ 186, TRUE, "Extended Voice/Data" },
{ 187, FALSE, "Extended Voice Network" },
{ 188, FALSE, "Extended Voice Network" },
{ 189, TRUE, "Extended Voice Network" },
{ 190, FALSE, "Extended Voice/Data" },
{ 191, FALSE, "Extended Voice/Data" },
{ 192, TRUE, "Extended Voice/Data" },
{ 193, TRUE, "International" },
{ 194, FALSE, "International Services" },
{ 195, FALSE, "International Voice" },
{ 196, FALSE, "International Voice/Data" },
{ 197, FALSE, "International Voice/Data" },
{ 198, TRUE, "International Voice/Data" },
{ 199, FALSE, "Extended Voice/Data Network" },
{ 200, TRUE, "Extended Voice/Data Network" },
{ 201, TRUE, "Extended Voice/Data Network" },
{ 202, FALSE, "Extended Broadband" },
{ 203, TRUE, "Extended Broadband" },
{ 204, TRUE, "Extended Broadband" },
{ 205, FALSE, "Extended Data" },
{ 206, TRUE, "Extended Data" },
{ 207, TRUE, "Extended Data" },
{ 208, FALSE, "Extended Data Network" },
{ 209, TRUE, "Extended Data Network" },
{ 210, TRUE, "Extended Data Network" },
{ 211, FALSE, "Extended Network" },
{ 212, TRUE, "Extended Network" },
{ 213, FALSE, "Extended Service" },
{ 214, TRUE, "Extended Service" },
{ 215, TRUE, "Extended Service" },
{ 216, FALSE, "Extended Voice" },
{ 217, TRUE, "Extended Voice" },
{ 218, TRUE, "Extended Voice" },
{ 219, FALSE, "Extended Voice/Data" },
{ 220, TRUE, "Extended Voice/Data" },
{ 221, TRUE, "Extended Voice/Data" },
{ 222, FALSE, "Extended Voice Network" },
{ 223, FALSE, "Extended Voice Network" },
{ 224, TRUE, "Extended Voice Network" },
{ 225, FALSE, "Extended Voice/Data" },
{ 226, TRUE, "Extended Voice/Data" },
{ 227, TRUE, "Extended Voice/Data" },
{ 228, TRUE, "International" },
{ 229, TRUE, "International" },
{ 230, TRUE, "International Services" },
{ 231, TRUE, "International Voice" },
{ 232, FALSE, "International Voice/Data" },
{ 233, TRUE, "International Voice/Data" },
{ 234, TRUE, "International Voice/Data" },
{ 235, TRUE, "Premium International" },
{ 236, TRUE, NULL },
{ 237, TRUE, NULL },
{ 238, FALSE, NULL },
{ 239, FALSE, NULL },
{ -1, FALSE, NULL },
};
gboolean
mm_cdma_parse_speri_read_response (const gchar *reply,
gboolean *out_roaming,
guint *out_ind,
const gchar **out_desc)
{
guint ind;
const EriItem *iter = &eris[0];
gboolean found = FALSE;
g_return_val_if_fail (reply != NULL, FALSE);
g_return_val_if_fail (out_roaming != NULL, FALSE);
if (mm_get_uint_from_str (reply, &ind)) {
if (out_ind)
*out_ind = ind;
while (iter->num != -1) {
if (iter->num == ind) {
*out_roaming = iter->roam_ind;
if (out_desc)
*out_desc = iter->banner;
found = TRUE;
break;
}
iter++;
}
}
return found;
}
/*************************************************************************/
gboolean
mm_cdma_parse_crm_test_response (const gchar *reply,
MMModemCdmaRmProtocol *min,
MMModemCdmaRmProtocol *max,
GError **error)
{
gboolean result = FALSE;
GRegex *r;
/* Expected reply format is:
* ---> AT+CRM=?
* <--- +CRM: (0-2)
*/
r = g_regex_new ("\\+CRM:\\s*\\((\\d+)-(\\d+)\\)",
G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
0, error);
if (r) {
GMatchInfo *match_info = NULL;
if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, error)) {
gchar *aux;
guint min_val = 0;
guint max_val = 0;
aux = g_match_info_fetch (match_info, 1);
min_val = (guint) atoi (aux);
g_free (aux);
aux = g_match_info_fetch (match_info, 2);
max_val = (guint) atoi (aux);
g_free (aux);
if (min_val == 0 ||
max_val == 0 ||
min_val >= max_val) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Couldn't parse CRM range: "
"Unexpected range of RM protocols (%u,%u)",
min_val,
max_val);
} else {
*min = mm_cdma_get_rm_protocol_from_index (min_val, error);
if (*min != MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) {
*max = mm_cdma_get_rm_protocol_from_index (max_val, error);
if (*max != MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN)
result = TRUE;
}
}
}
g_match_info_free (match_info);
g_regex_unref (r);
}
return result;
}
/*************************************************************************/
MMModemCdmaRmProtocol
mm_cdma_get_rm_protocol_from_index (guint index,
GError **error)
{
guint protocol;
/* just adding 1 from the index value should give us the enum */
protocol = index + 1 ;
if (protocol > MM_MODEM_CDMA_RM_PROTOCOL_STU_III) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Unexpected RM protocol index (%u)",
index);
protocol = MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN;
}
return (MMModemCdmaRmProtocol)protocol;
}
guint
mm_cdma_get_index_from_rm_protocol (MMModemCdmaRmProtocol protocol,
GError **error)
{
if (protocol == MM_MODEM_CDMA_RM_PROTOCOL_UNKNOWN) {
g_set_error (error,
MM_CORE_ERROR,
MM_CORE_ERROR_FAILED,
"Unexpected RM protocol (%s)",
mm_modem_cdma_rm_protocol_get_string (protocol));
return 0;
}
/* just substracting 1 from the enum value should give us the index */
return (protocol - 1);
}
/*************************************************************************/
gint
mm_cdma_normalize_class (const gchar *orig_class)
{
gchar class;
g_return_val_if_fail (orig_class != NULL, '0');
class = toupper (orig_class[0]);
/* Cellular (850MHz) */
if (class == '1' || class == 'C')
return 1;
/* PCS (1900MHz) */
if (class == '2' || class == 'P')
return 2;
/* Unknown/not registered */
return 0;
}
/*************************************************************************/
gchar
mm_cdma_normalize_band (const gchar *long_band,
gint *out_class)
{
gchar band;
g_return_val_if_fail (long_band != NULL, 'Z');
/* There are two response formats for the band; one includes the band
* class and the other doesn't. For modems that include the band class
* (ex Novatel S720) you'll see "Px" or "Cx" depending on whether the modem
* is registered on a PCS/1900 (P) or Cellular/850 (C) system.
*/
band = toupper (long_band[0]);
/* Possible band class in first position; return it */
if (band == 'C' || band == 'P') {
gchar tmp[2] = { band, '\0' };
*out_class = mm_cdma_normalize_class (tmp);
band = toupper (long_band[1]);
}
/* normalize to A - F, and Z */
if (band >= 'A' && band <= 'F')
return band;
/* Unknown/not registered */
return 'Z';
}