diff options
Diffstat (limited to 'src/mm-modem-helpers.c')
-rw-r--r-- | src/mm-modem-helpers.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c index 1741b5f..6e76f46 100644 --- a/src/mm-modem-helpers.c +++ b/src/mm-modem-helpers.c @@ -14,13 +14,32 @@ * Copyright (C) 2009 - 2010 Red Hat, Inc. */ +#include <config.h> #include <glib.h> #include <string.h> #include <ctype.h> +#include <stdlib.h> +#include <errno.h> #include "mm-errors.h" #include "mm-modem-helpers.h" +const char * +mm_strip_tag (const char *str, const char *cmd) +{ + const char *p = str; + + if (p) { + if (!strncmp (p, cmd, strlen (cmd))) + p += strlen (cmd); + while (isspace (*p)) + p++; + } + return p; +} + +/*************************************************************************/ + static void save_scan_value (GHashTable *hash, const char *key, GMatchInfo *info, guint32 num) { @@ -201,3 +220,586 @@ mm_gsm_destroy_scan_data (gpointer data) g_ptr_array_free (results, TRUE); } +/*************************************************************************/ + +/* +CREG: <stat> (GSM 07.07 CREG=1 unsolicited) */ +#define CREG1 "\\+(CREG|CGREG):\\s*(\\d{1})" + +/* +CREG: <n>,<stat> (GSM 07.07 CREG=1 solicited) */ +#define CREG2 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})" + +/* +CREG: <stat>,<lac>,<ci> (GSM 07.07 CREG=2 unsolicited) */ +#define CREG3 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: <n>,<stat>,<lac>,<ci> (GSM 07.07 solicited and some CREG=2 unsolicited) */ +#define CREG4 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)" + +/* +CREG: <stat>,<lac>,<ci>,<AcT> (ETSI 27.007 CREG=2 unsolicited) */ +#define CREG5 "\\+(CREG|CGREG):\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +/* +CREG: <n>,<stat>,<lac>,<ci>,<AcT> (ETSI 27.007 solicited and some CREG=2 unsolicited) */ +#define CREG6 "\\+(CREG|CGREG):\\s*(\\d{1}),\\s*(\\d{1})\\s*,\\s*([^,\\s]*)\\s*,\\s*([^,\\s]*)\\s*,\\s*(\\d{1,2})" + +GPtrArray * +mm_gsm_creg_regex_get (gboolean solicited) +{ + GPtrArray *array = g_ptr_array_sized_new (6); + 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); + + return array; +} + +void +mm_gsm_creg_regex_destroy (GPtrArray *array) +{ + g_ptr_array_foreach (array, (GFunc) g_regex_unref, NULL); + g_ptr_array_free (array, TRUE); +} + +/*************************************************************************/ + +static gulong +parse_uint (char *str, int base, glong nmin, glong nmax, gboolean *valid) +{ + gulong ret = 0; + char *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; +} + +gboolean +mm_gsm_parse_creg_response (GMatchInfo *info, + guint32 *out_reg_state, + gulong *out_lac, + gulong *out_ci, + gint *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; + char *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; + + /* 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> + */ + + /* To distinguish, check length of the third match item. If it's + * more than one digit or has quotes in it then it's a LAC and we + * got the first format. + */ + str = g_match_info_fetch (info, 3); + if (str && (strchr (str, '"') || strlen (str) > 1)) { + g_free (str); + istat = 2; + ilac = 3; + ici = 4; + iact = 5; + } else { + istat = 3; + ilac = 4; + ici = 5; + } + } else if (n_matches == 7) { + /* CREG=2 (non-standard): +CREG: <n>,<stat>,<lac>,<ci>,<AcT> */ + 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_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, + "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; + } + + *out_reg_state = (guint32) stat; + if (stat != 4) { + /* Don't fill in lac/ci/act if the device's state is unknown */ + *out_lac = lac; + *out_ci = ci; + *out_act = act; + } + return TRUE; +} + +/*************************************************************************/ + +gboolean +mm_cdma_parse_spservice_response (const char *reply, + MMModemCdmaRegistrationState *out_cdma_1x_state, + MMModemCdmaRegistrationState *out_evdo_state) +{ + const char *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 { + int num; + gboolean roam_ind; + const char *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_eri (const char *reply, + gboolean *out_roaming, + guint32 *out_ind, + const char **out_desc) +{ + long int 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); + + errno = 0; + ind = strtol (reply, NULL, 10); + if (errno == 0) { + 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_gsm_parse_cscs_support_response (const char *reply, + MMModemCharset *out_charsets) +{ + MMModemCharset charsets = MM_MODEM_CHARSET_UNKNOWN; + GRegex *r; + GMatchInfo *match_info; + char *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; +} + +/*************************************************************************/ + +MMModemGsmAccessTech +mm_gsm_string_to_access_tech (const char *string) +{ + g_return_val_if_fail (string != NULL, MM_MODEM_GSM_ACCESS_TECH_UNKNOWN); + + /* Better technologies are listed first since modems sometimes say + * stuff like "GPRS/EDGE" and that should be handled as EDGE. + */ + if (strcasestr (string, "HSPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSDPA/HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSPA; + else if (strcasestr (string, "HSUPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSUPA; + else if (strcasestr (string, "HSDPA")) + return MM_MODEM_GSM_ACCESS_TECH_HSDPA; + else if (strcasestr (string, "UMTS")) + return MM_MODEM_GSM_ACCESS_TECH_UMTS; + else if (strcasestr (string, "EDGE")) + return MM_MODEM_GSM_ACCESS_TECH_EDGE; + else if (strcasestr (string, "GPRS")) + return MM_MODEM_GSM_ACCESS_TECH_GPRS; + else if (strcasestr (string, "GSM")) + return MM_MODEM_GSM_ACCESS_TECH_GSM; + + return MM_MODEM_GSM_ACCESS_TECH_UNKNOWN; +} + |