aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2013-03-25 13:20:13 -0500
committerDan Williams <dcbw@redhat.com>2013-03-27 08:23:52 -0500
commit3b3326bdf03a02822ac8d1e9e0ff08db2324c3ed (patch)
tree207615827c0f252c27a2f6c210283cb8edb88b6f
parentf299a05571f8d9b9f0bf996f20563ed9a13dab02 (diff)
broadband-modem: parse +GSN response for IMEI, MEID, and ESN (bgo #696596)
+GSN response differs widely between modems. Some prefix the MEID and/or ESN with 0x, some have leading zeros, some return the MEID and the ESN, and some append the serial number to the end of the IMEI. Handle that and make the ESN, MEID, IMEI, and EquipmentIdentifier parsing consistent.
-rw-r--r--src/mm-broadband-modem.c81
-rw-r--r--src/mm-modem-helpers.c121
-rw-r--r--src/mm-modem-helpers.h5
-rw-r--r--src/tests/test-modem-helpers.c68
4 files changed, 263 insertions, 12 deletions
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 7b34f78c..396ef974 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -750,16 +750,29 @@ modem_load_equipment_identifier_finish (MMIfaceModem *self,
GError **error)
{
GVariant *result;
- gchar *equip_id = NULL, *tmp;
+ gchar *equip_id = NULL, *esn = NULL, *meid = NULL, *imei = NULL;
result = mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
if (result) {
equip_id = sanitize_info_reply (result, "GSN:");
- /* Some CDMA devices prefix the ESN with "0x" */
- if (strncmp (equip_id, "0x", 2) == 0 && strlen (equip_id) == 10) {
- tmp = g_strdup (equip_id + 2);
+
+ /* Modems put all sorts of things into the GSN response; sanitize it */
+ if (mm_parse_gsn (equip_id, &imei, &meid, &esn)) {
g_free (equip_id);
- equip_id = tmp;
+
+ if (imei)
+ equip_id = g_strdup (imei);
+ else if (meid)
+ equip_id = g_strdup (meid);
+ else if (esn)
+ equip_id = g_strdup (esn);
+ g_free (esn);
+ g_free (meid);
+ g_free (imei);
+
+ g_assert (equip_id);
+ } else {
+ /* Leave whatever the modem returned alone */
}
mm_dbg ("loaded equipment identifier: %s", equip_id);
}
@@ -2942,12 +2955,15 @@ modem_3gpp_load_imei_finish (MMIfaceModem3gpp *self,
GAsyncResult *res,
GError **error)
{
- gchar *imei;
+ const gchar *result;
+ gchar *imei = NULL;
- imei = g_strdup (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error));
- if (!imei)
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
return NULL;
+ result = mm_strip_tag (result, "+CGSN:");
+ mm_parse_gsn (result, &imei, NULL, NULL);
mm_dbg ("loaded IMEI: %s", imei);
return imei;
}
@@ -3084,6 +3100,8 @@ clck_test_ready (MMBaseModem *self,
return;
}
+ctx->facilities &= ~MM_MODEM_3GPP_FACILITY_PH_SIM;
+
/* Go on... */
get_next_facility_lock_status (ctx);
}
@@ -5888,12 +5906,15 @@ modem_cdma_load_esn_finish (MMIfaceModemCdma *self,
GAsyncResult *res,
GError **error)
{
- gchar *esn;
+ const gchar *result;
+ gchar *esn = NULL;
- esn = g_strdup (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error));
- if (!esn)
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
return NULL;
+ result = mm_strip_tag (result, "+GSN:");
+ mm_parse_gsn (result, NULL, NULL, &esn);
mm_dbg ("loaded ESN: %s", esn);
return esn;
}
@@ -5913,6 +5934,42 @@ modem_cdma_load_esn (MMIfaceModemCdma *self,
}
/*****************************************************************************/
+/* MEID loading (CDMA interface) */
+
+static gchar *
+modem_cdma_load_meid_finish (MMIfaceModemCdma *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ const gchar *result;
+ gchar *meid = NULL;
+
+ result = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+ if (!result)
+ return NULL;
+
+ result = mm_strip_tag (result, "+GSN:");
+ mm_parse_gsn (result, NULL, &meid, NULL);
+ mm_dbg ("loaded MEID: %s", meid);
+ return meid;
+}
+
+static void
+modem_cdma_load_meid (MMIfaceModemCdma *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ /* Some devices return both the MEID and the ESN in the +GSN response */
+ mm_dbg ("loading MEID...");
+ mm_base_modem_at_command (MM_BASE_MODEM (self),
+ "+GSN",
+ 3,
+ TRUE,
+ callback,
+ user_data);
+}
+
+/*****************************************************************************/
/* HDR state check (CDMA interface) */
typedef struct {
@@ -9278,6 +9335,8 @@ iface_modem_cdma_init (MMIfaceModemCdma *iface)
/* Initialization steps */
iface->load_esn = modem_cdma_load_esn;
iface->load_esn_finish = modem_cdma_load_esn_finish;
+ iface->load_meid = modem_cdma_load_meid;
+ iface->load_meid_finish = modem_cdma_load_meid_finish;
/* Registration check steps */
iface->setup_registration_checks = modem_cdma_setup_registration_checks;
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index cedd2b7f..b20ff1c6 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -2100,3 +2100,124 @@ mm_cdma_normalize_band (const gchar *long_band,
/* Unknown/not registered */
return 'Z';
}
+
+/*************************************************************************/
+
+/* Caller must strip any "+GSN:" or "+CGSN" from @gsn */
+gboolean
+mm_parse_gsn (const char *gsn,
+ gchar **out_imei,
+ gchar **out_meid,
+ gchar **out_esn)
+{
+ gchar **items, **iter;
+ gchar *meid = NULL, *esn = NULL, *imei = NULL, *p;
+ gboolean success = FALSE;
+
+ if (!gsn || !gsn[0])
+ return FALSE;
+
+ /* IMEI is 15 numeric digits */
+
+ /* ESNs take one of two formats:
+ * (1) 7 or 8 hexadecimal digits
+ * (2) 10 or 11 decimal digits
+ *
+ * In addition, leading zeros may be present or absent, and hexadecimal
+ * ESNs may or may not be prefixed with "0x".
+ */
+
+ /* MEIDs take one of two formats:
+ * (1) 14 hexadecimal digits, sometimes padded to 16 digits with leading zeros
+ * (2) 18 decimal digits
+ *
+ * As with ESNs, leading zeros may be present or absent, and hexadecimal
+ * MEIDs may or may not be prefixed with "0x".
+ */
+
+ items = g_strsplit_set (gsn, "\r\n\t: ,", 0);
+ for (iter = items; iter && *iter && (!esn || !meid); iter++) {
+ gboolean expect_hex = FALSE, is_hex, is_digit;
+ gchar *s = *iter;
+ guint len = 0;
+
+ if (!s[0])
+ continue;
+
+ if (g_str_has_prefix (s, "0x") || g_str_has_prefix (s, "0X")) {
+ expect_hex = TRUE;
+ s += 2;
+
+ /* Skip any leading zeros */
+ while (*s == '0')
+ s++;
+ }
+
+ /* Check whether all digits are hex or decimal */
+ is_hex = is_digit = TRUE;
+ p = s;
+ while (*p && (is_hex || is_digit)) {
+ if (!g_ascii_isxdigit (*p))
+ is_hex = FALSE;
+ if (!g_ascii_isdigit (*p))
+ is_digit = FALSE;
+ p++, len++;
+ }
+
+ /* Note that some hex strings are also valid digit strings */
+
+ if (is_hex) {
+ if (len == 7 || len == 8) {
+ /* ESN */
+ if (!esn) {
+ if (len == 7)
+ esn = g_strdup_printf ("0%s", s);
+ else
+ esn = g_strdup (s);
+ }
+ } else if (len == 14) {
+ /* MEID */
+ if (!meid)
+ meid = g_strdup (s);
+ }
+ }
+
+ if (is_digit) {
+ if (!is_hex)
+ g_warn_if_fail (expect_hex == FALSE);
+
+ if (len == 15) {
+ if (!imei)
+ imei = g_strdup (s);
+ }
+
+ /* Decimal ESN/MEID unhandled for now; conversion from decimal to
+ * hex isn't a straight dec->hex conversion, as the first 2 digits
+ * of the ESN and first 3 digits of the MEID are the manufacturer
+ * identifier and must be converted separately from serial number
+ * and then concatenated with it.
+ */
+ }
+ }
+ g_strfreev (items);
+
+ success = meid || esn || imei;
+
+ if (out_imei)
+ *out_imei = imei;
+ else
+ g_free (imei);
+
+ if (out_meid)
+ *out_meid = meid;
+ else
+ g_free (meid);
+
+ if (out_esn)
+ *out_esn = esn;
+ else
+ g_free (esn);
+
+ return success;
+}
+
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index 63e53a74..aa646993 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -203,4 +203,9 @@ gint mm_cdma_normalize_class (const gchar *orig_class);
gchar mm_cdma_normalize_band (const gchar *long_band,
gint *out_class);
+gboolean mm_parse_gsn (const char *gsn,
+ gchar **out_imei,
+ gchar **out_meid,
+ gchar **out_esn);
+
#endif /* MM_MODEM_HELPERS_H */
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index 6cfac3db..a6e15db8 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -1598,6 +1598,71 @@ test_parse_cds (void *f, gpointer d)
"07914356060013F1065A098136395339F6219011700463802190117004638030");
}
+typedef struct {
+ const char *gsn;
+ const char *expected_imei;
+ const char *expected_esn;
+ const char *expected_meid;
+ gboolean expect_success;
+} TestGsnItem;
+
+static void
+test_cdma_parse_gsn (void *f, gpointer d)
+{
+ static const TestGsnItem items[] = {
+ { "0x6744775\r\n", /* leading zeros skipped, no hex digits */
+ NULL,
+ "06744775",
+ NULL,
+ TRUE },
+ { "0x2214A600\r\n",
+ NULL,
+ "2214A600",
+ NULL,
+ TRUE },
+ { "0x80C98A1\r\n", /* leading zeros skipped, some hex digits */
+ NULL,
+ "080C98A1",
+ NULL,
+ TRUE },
+ { "6030C012\r\n", /* no leading 0x */
+ NULL,
+ "6030C012",
+ NULL,
+ TRUE },
+ { "45317471585658170:2161753034\r\n0x00A1000013FB653A:0x80D9BBCA\r\n",
+ NULL,
+ "80D9BBCA",
+ "A1000013FB653A",
+ TRUE },
+ { "354237065082227\r\n", /* GSM IMEI */
+ "354237065082227",
+ NULL, NULL, TRUE },
+ { "356936001568843,NL2A62Z0N5\r\n", /* IMEI + serial number */
+ "356936001568843",
+ NULL, NULL, TRUE },
+ { "adsfasdfasdfasdf", NULL, NULL, FALSE },
+ { "0x6030Cfgh", NULL, NULL, FALSE },
+ { NULL }
+ };
+
+ const TestGsnItem *iter;
+
+ for (iter = &items[0]; iter && iter->gsn; iter++) {
+ char *imei = NULL, *esn = NULL, *meid = NULL;
+ gboolean success;
+
+ success = mm_parse_gsn (iter->gsn, &imei, &meid, &esn);
+ g_assert_cmpint (success, ==, iter->expect_success);
+ g_assert_cmpstr (iter->expected_imei, ==, imei);
+ g_assert_cmpstr (iter->expected_meid, ==, meid);
+ g_assert_cmpstr (iter->expected_esn, ==, esn);
+ g_free (imei);
+ g_free (meid);
+ g_free (esn);
+ }
+}
+
/*****************************************************************************/
void
@@ -1715,9 +1780,10 @@ int main (int argc, char **argv)
g_test_suite_add (suite, TESTCASE (test_parse_operator_id, NULL));
-
g_test_suite_add (suite, TESTCASE (test_parse_cds, NULL));
+ g_test_suite_add (suite, TESTCASE (test_cdma_parse_gsn, NULL));
+
result = g_test_run ();
reg_test_data_free (reg_data);