[PATCH 1/2] broadband-modem, helpers: implement AT+WS46=? response parser

Aleksander Morgado aleksander at aleksander.es
Sun Mar 12 20:05:29 UTC 2017


We want a parser that returns the full list of combinations found.
---
 src/mm-broadband-modem.c       | 111 ++++++++++++--------------------
 src/mm-modem-helpers.c         | 143 +++++++++++++++++++++++++++++++++++++++++
 src/mm-modem-helpers.h         |   3 +
 src/tests/test-modem-helpers.c | 102 +++++++++++++++++++++++++++++
 4 files changed, 290 insertions(+), 69 deletions(-)

diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 17b3253a..aa84fc7e 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -378,17 +378,28 @@ current_capabilities_ws46_test_ready (MMBaseModem *self,
                                       LoadCapabilitiesContext *ctx)
 {
     const gchar *response;
+    GArray      *modes;
+    guint        i;
 
     /* Completely ignore errors in AT+WS46=? */
     response = mm_base_modem_at_command_finish (self, res, NULL);
-    if (response &&
-        (strstr (response, "28") != NULL ||   /* 4G only */
-         strstr (response, "30") != NULL ||   /* 2G/4G */
-         strstr (response, "31") != NULL)) {  /* 3G/4G */
-        /* Add LTE caps */
-        ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+    if (!response)
+        goto out;
+
+    modes = mm_3gpp_parse_ws46_test_response (response, NULL);
+    if (!modes)
+        goto out;
+
+    /* Add LTE caps if any of the reported modes supports 4G */
+    for (i = 0; i < modes->len; i++) {
+        if (g_array_index (modes, MMModemMode, i) & MM_MODEM_MODE_4G) {
+            ctx->caps |= MM_MODEM_CAPABILITY_LTE;
+            break;
+        }
     }
+    g_array_unref (modes);
 
+out:
     g_simple_async_result_set_op_res_gpointer (
         ctx->result,
         GUINT_TO_POINTER (ctx->caps),
@@ -1430,78 +1441,40 @@ supported_modes_ws46_test_ready (MMBroadbandModem *self,
                                  LoadSupportedModesContext *ctx)
 {
     const gchar *response;
-    GError *error = NULL;
+    GArray      *modes;
+    GError      *error = NULL;
+    guint        i;
 
     response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, &error);
-    if (!error) {
-        MMModemMode mode = MM_MODEM_MODE_NONE;
-
-        /*
-         * More than one numeric ID may appear in the list, that's why
-         * they are checked separately.
-         *
-         * NOTE: Do not skip WS46 prefix; it would break Cinterion handling.
-         *
-         * From 3GPP TS 27.007 v.11.2.0, section 5.9
-         * 12	GSM Digital Cellular Systems (GERAN only)
-         * 22	UTRAN only
-         * 25	3GPP Systems (GERAN, UTRAN and E-UTRAN)
-         * 28	E-UTRAN only
-         * 29	GERAN and UTRAN
-         * 30	GERAN and E-UTRAN
-         * 31	UTRAN and E-UTRAN
-         */
-
-        if (strstr (response, "12") != NULL) {
-            mm_dbg ("Device allows (3GPP) 2G-only network mode");
-            mode |= MM_MODEM_MODE_2G;
-        }
-
-        if (strstr (response, "22") != NULL) {
-            mm_dbg ("Device allows (3GPP) 3G-only network mode");
-            mode |= MM_MODEM_MODE_3G;
-        }
-
-        if (strstr (response, "28") != NULL) {
-            mm_dbg ("Device allows (3GPP) 4G-only network mode");
-            mode |= MM_MODEM_MODE_4G;
-        }
+    if (error) {
+        mm_dbg ("Generic query of supported 3GPP networks with WS46=? failed: '%s'", error->message);
+        g_error_free (error);
+        goto out;
+    }
 
-        if (strstr (response, "29") != NULL) {
-            mm_dbg ("Device allows (3GPP) 2G/3G network mode");
-            mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
-        }
+    modes = mm_3gpp_parse_ws46_test_response (response, &error);
+    if (!modes) {
+        mm_dbg ("Parsing WS46=? response failed: '%s'", error->message);
+        g_error_free (error);
+        goto out;
+    }
 
-        if (strstr (response, "30") != NULL) {
-            mm_dbg ("Device allows (3GPP) 2G/4G network mode");
-            mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_4G);
-        }
+    for (i = 0; i < modes->len; i++) {
+        MMModemMode  mode;
+        gchar       *str;
 
-        if (strstr (response, "31") != NULL) {
-            mm_dbg ("Device allows (3GPP) 3G/4G network mode");
-            mode |= (MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
-        }
+        mode = g_array_index (modes, MMModemMode, i);
 
-        if (strstr (response, "25") != NULL) {
-            if (mm_iface_modem_is_3gpp_lte (MM_IFACE_MODEM (self))) {
-                mm_dbg ("Device allows every supported 3GPP network mode (2G/3G/4G)");
-                mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G);
-            } else {
-                mm_dbg ("Device allows every supported 3GPP network mode (2G/3G)");
-                mode |= (MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
-            }
-        }
+        ctx->mode |= mode;
 
-        /* If no expected ID found, log error */
-        if (mode == MM_MODEM_MODE_NONE)
-            mm_dbg ("Invalid list of supported networks reported by WS46=?: '%s'", response);
-        else
-            ctx->mode |= mode;
-    } else {
-        mm_dbg ("Generic query of supported 3GPP networks with WS46=? failed: '%s'", error->message);
-        g_error_free (error);
+        str = mm_modem_mode_build_string_from_mask (mode);
+        mm_dbg ("Device allows (3GPP) mode combination: %s", str);
+        g_free (str);
     }
 
+    g_array_unref (modes);
+
+out:
     /* Now keep on with the loading, we may need CDMA-specific checks */
     ctx->run_ws46 = FALSE;
     load_supported_modes_step (ctx);
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index bc6e48b4..b1c3326c 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -725,6 +725,149 @@ mm_3gpp_cds_regex_get (void)
 }
 
 /*************************************************************************/
+/* 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;
+    GRegex     *r;
+    GError     *inner_error = NULL;
+    GMatchInfo *match_info = NULL;
+    gchar      *full_list = NULL;
+    gchar     **split;
+    guint       i;
+    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;
+    }
+
+    split = g_strsplit (full_list, ",", -1);
+    modes = g_array_new (FALSE, FALSE, sizeof (MMModemMode));
+
+    for (i = 0; split && split[i]; i++) {
+        guint val;
+        guint j;
+
+        if (!mm_get_uint_from_str (split[i], &val)) {
+            g_warning ("Invalid +WS46 mode reported: %s", split[i]);
+            continue;
+        }
+
+        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: %s", split[i]);
+    }
+
+    g_strfreev (split);
+
+    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 whether LTE is supported or not */
+    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:
+    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)
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index fb7982fe..33af48b6 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -106,6 +106,9 @@ GRegex    *mm_3gpp_cusd_regex_get (void);
 GRegex    *mm_3gpp_cmti_regex_get (void);
 GRegex    *mm_3gpp_cds_regex_get (void);
 
+/* AT+WS46=? response parser: returns array of MMModemMode values */
+GArray *mm_3gpp_parse_ws46_test_response (const gchar  *response,
+                                          GError      **error);
 
 /* AT+COPS=? (network scan) response parser */
 typedef struct {
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index fdcac10e..1fc8c353 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -33,6 +33,102 @@
     g_assert_cmpfloat (fabs (val1 - val2), <, tolerance)
 
 /*****************************************************************************/
+/* Test WS46=? responses */
+
+static void
+test_ws46_response (const gchar       *str,
+                    const MMModemMode *expected,
+                    guint              n_expected)
+{
+    guint   i;
+    GArray *modes;
+    GError *error = NULL;
+
+    modes = mm_3gpp_parse_ws46_test_response (str, &error);
+    g_assert_no_error (error);
+    g_assert (modes != NULL);
+    g_assert_cmpuint (modes->len, ==, n_expected);
+
+    for (i = 0; i < n_expected; i++) {
+        guint j;
+
+        for (j = 0; j < modes->len; j++) {
+            if (expected[i] == g_array_index (modes, MMModemMode, j))
+                break;
+        }
+        g_assert_cmpuint (j, !=, modes->len);
+    }
+    g_array_unref (modes);
+}
+
+static void
+test_ws46_response_generic_2g3g4g (void)
+{
+    static const MMModemMode expected[] = {
+        MM_MODEM_MODE_2G,
+        MM_MODEM_MODE_3G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+        MM_MODEM_MODE_4G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+    };
+    const gchar *str = "+WS46: (12,22,25,28,29)";
+
+    test_ws46_response (str, expected, G_N_ELEMENTS (expected));
+}
+
+static void
+test_ws46_response_generic_2g3g (void)
+{
+    static const MMModemMode expected[] = {
+        MM_MODEM_MODE_2G,
+        MM_MODEM_MODE_3G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+    };
+    const gchar *str = "+WS46: (12,22,25)";
+
+    test_ws46_response (str, expected, G_N_ELEMENTS (expected));
+}
+
+static void
+test_ws46_response_generic_2g3g_v2 (void)
+{
+    static const MMModemMode expected[] = {
+        MM_MODEM_MODE_2G,
+        MM_MODEM_MODE_3G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+    };
+    const gchar *str = "+WS46: (12,22,29)";
+
+    test_ws46_response (str, expected, G_N_ELEMENTS (expected));
+}
+
+static void
+test_ws46_response_cinterion (void)
+{
+    static const MMModemMode expected[] = {
+        MM_MODEM_MODE_2G,
+        MM_MODEM_MODE_3G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G,
+        MM_MODEM_MODE_4G,
+        MM_MODEM_MODE_2G | MM_MODEM_MODE_3G,
+    };
+    const gchar *str = "(12,22,25,28,29)";
+
+    test_ws46_response (str, expected, G_N_ELEMENTS (expected));
+}
+
+static void
+test_ws46_response_telit_le866 (void)
+{
+    static const MMModemMode expected[] = {
+        MM_MODEM_MODE_4G,
+    };
+    const gchar *str = "(28)";
+
+    test_ws46_response (str, expected, G_N_ELEMENTS (expected));
+}
+
+/*****************************************************************************/
 /* Test CMGL responses */
 
 static void
@@ -3392,6 +3488,12 @@ int main (int argc, char **argv)
     suite = g_test_get_root ();
     reg_data = reg_test_data_new ();
 
+    g_test_suite_add (suite, TESTCASE (test_ws46_response_generic_2g3g4g, NULL));
+    g_test_suite_add (suite, TESTCASE (test_ws46_response_generic_2g3g, NULL));
+    g_test_suite_add (suite, TESTCASE (test_ws46_response_generic_2g3g_v2, NULL));
+    g_test_suite_add (suite, TESTCASE (test_ws46_response_cinterion, NULL));
+    g_test_suite_add (suite, TESTCASE (test_ws46_response_telit_le866, NULL));
+
     g_test_suite_add (suite, TESTCASE (test_cops_response_tm506, NULL));
     g_test_suite_add (suite, TESTCASE (test_cops_response_gt3gplus, NULL));
     g_test_suite_add (suite, TESTCASE (test_cops_response_ac881, NULL));
-- 
2.12.0



More information about the ModemManager-devel mailing list