[PATCH v2] core: process SMS +CGMR response with regex

Nick Stevens Nick.Stevens at digi.com
Wed Sep 16 06:21:41 PDT 2015


On Tue, Sep 15, 2015 at 05:26:06PM -0500, Dan Williams wrote:
> On Tue, 2015-09-15 at 20:15 +0000, Nick Stevens wrote:
> > Variability in the response style from certain modems causes the parsing
> > of the +CGMR response to fail. For example, the Telit HE910 inserts an
> > empty string ("") in the second field of the response, causing the
> > sscanf implementation to fail.
> > 
> > This patch converts the parsing of the CGMR response to a regex that
> > allows for more flexibility and robustness, and adds unit tests around
> > the parsing call.
> > 
> > Signed-off-by: Nick Stevens <Nick.Stevens at digi.com>
> > ---
> > V1->V2:
> > * Moved parsing logic to mm-modem-helpers.[ch]
> > * Added unit tests for CGMR parsing call
> >   - Generic case based on CGML test case
> >   - Telit-specific case that contains extra empty string in <alpha> field
> > 
> >  src/mm-broadband-modem.c       | 17 ++++------
> >  src/mm-modem-helpers.c         | 73 +++++++++++++++++++++++++++++++++++++++++-
> >  src/mm-modem-helpers.h         |  5 +++
> >  src/mm-sms-part-3gpp.h         |  2 --
> >  src/tests/test-modem-helpers.c | 64 ++++++++++++++++++++++++++++++++++++
> >  5 files changed, 148 insertions(+), 13 deletions(-)
> > 
> > diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
> > index 9093db5..2013b78 100644
> > --- a/src/mm-broadband-modem.c
> > +++ b/src/mm-broadband-modem.c
> > @@ -5603,8 +5603,7 @@ sms_part_ready (MMBroadbandModem *self,
> >                  SmsPartContext *ctx)
> >  {
> >      MMSmsPart *part;
> > -    gint rv, status, tpdu_len;
> > -    gchar pdu[MM_SMS_PART_3GPP_MAX_PDU_LEN + 1];
> > +    MM3gppPduInfo *info;
> >      const gchar *response;
> >      GError *error = NULL;
> >  
> > @@ -5622,19 +5621,16 @@ sms_part_ready (MMBroadbandModem *self,
> >          return;
> >      }
> >  
> > -    rv = sscanf (response, "+CMGR: %d,,%d %" G_STRINGIFY (MM_SMS_PART_3GPP_MAX_PDU_LEN) "s",
> > -                 &status, &tpdu_len, pdu);
> > -    if (rv != 3) {
> > -        error = g_error_new (MM_CORE_ERROR,
> > -                             MM_CORE_ERROR_FAILED,
> > -                             "Failed to parse CMGR response (parsed %d items)", rv);
> > -        mm_warn ("Couldn't retrieve SMS part: '%s'", error->message);
> > +    info = mm_3gpp_parse_cmgr_read_response (response, ctx->idx, &error);
> > +    if (!info) {
> > +        mm_warn ("Couldn't parse SMS part: '%s'",
> > +                 error->message);
> >          g_simple_async_result_take_error (ctx->result, error);
> >          sms_part_context_complete_and_free (ctx);
> >          return;
> >      }
> >  
> > -    part = mm_sms_part_3gpp_new_from_pdu (ctx->idx, pdu, &error);
> > +    part = mm_sms_part_3gpp_new_from_pdu (info->index, info->pdu, &error);
> >      if (part) {
> >          mm_dbg ("Correctly parsed PDU (%d)", ctx->idx);
> >          mm_iface_modem_messaging_take_part (MM_IFACE_MODEM_MESSAGING (self),
> > @@ -5648,6 +5644,7 @@ sms_part_ready (MMBroadbandModem *self,
> >      }
> >  
> >      /* All done */
> > +    mm_3gpp_pdu_info_free (info);
> >      g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
> >      sms_part_context_complete_and_free (ctx);
> >  }
> > diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
> > index d40082d..ee31942 100644
> > --- a/src/mm-modem-helpers.c
> > +++ b/src/mm-modem-helpers.c
> > @@ -1161,6 +1161,77 @@ mm_3gpp_parse_cmgf_test_response (const gchar *reply,
> >  
> >  /*************************************************************************/
> >  
> > +MM3gppPduInfo *
> > +mm_3gpp_parse_cmgr_read_response (const gchar *reply,
> > +                                  guint index,
> > +                                  GError **error)
> > +{
> > +    GRegex *r;
> > +    GMatchInfo *match_info = NULL;
> > +    GError *match_error = NULL;
> > +    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, error);
> > +    g_assert (r != NULL);
> 
> You could also do g_assert_no_error (error) before the g_assert(r) too.
> 

I am going to go with Aleksander's later suggestion to just pass NULL to the
g_regex_new - checking the error here isn't adding anything.

> > +
> > +    if (g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &match_error)) {
> 
> I think you should also just restructure the function with a 'goto' for
> error handling to break out early.  Yeah it's a goto, but it's OK in
> patterns like this for error handling.
> 
> Also, I'd have local variables for the 'status' and 'pdu' bits so that
> we don't allocate the returned 'info' until we know it's going to be
> valid, that way we don't have to free it in the error cases at all.
> It's usually best not to look too much at 'error'; eg a function
> shouldn't care whether the caller passed in NULL or a valid pointer to
> an error, but the patch has "if (*error) info = NULL;".  We can avoid
> that by using the goto stuff.

That's kind of funny, because I actually had structured v1 of the patch with
gotos. When I moved the code to mm-modem-helpers, I noticed that many of the
parse functions seemed to avoid goto (mm-3gpp_parse_cgdcont_read_response as
an arbitrary example), so I was trying to match the style of that module. I'm
definitely okay switching to gotos for this patch, so long as we're okay with
having the different style. Thoughts?

> 
> gint status;
> gchar *pdu = NULL;
> 
> if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &match_error)) {
>     if (match_error)
>         g_propagate_error (error, match_error);
>     else {
>         g_set_error (...);
>     }
>     goto out;
> }
> 
> count = g_match_info_get_match_count(match_info);
> if (count != 5) {
>      g_set_error (...);
>      goto out;
> }
> 
> if (!mm_get_int_from_match_info (match_info, 1, &status)) {
>      g_set_error (...);
>      goto out;
> }
> 
> pdu = mm_get_string_unquoted_from_match_info (match_info, 4);
> if (!info->pdu) {
>     g_set_error (...);
>     goto out;
> }
> 
> /* success */
> info = g_new0 (MM3gppPduInfo, 1);
> info->index = index;
> info->status = status;
> info->pdu = pdu;
> 
> out:
> g_match_info_free (match_info);
> g_regex_unref (r);
> return info;
> 
> Rest looks OK to me...
> 
> Thanks!
> Dan

Thanks for the review!

Nick

> > +        gint count = 0;
> > +
> > +        /* g_match_info_get_match_count includes match #0 */
> > +        count = g_match_info_get_match_count (match_info);
> > +        if (count != 5) {
> > +            g_set_error (error,
> > +                         MM_CORE_ERROR,
> > +                         MM_CORE_ERROR_FAILED,
> > +                         "Failed to match CMGR fields (matched %d) '%s'",
> > +                         count,
> > +                         reply);
> > +        } else {
> > +            info = g_new0 (MM3gppPduInfo, 1);
> > +            info->index = index;
> > +            if (!mm_get_int_from_match_info (match_info, 1, &info->status)) {
> > +                g_set_error (error,
> > +                             MM_CORE_ERROR,
> > +                             MM_CORE_ERROR_FAILED,
> > +                             "Failed to extract CMGR status field '%s'",
> > +                             reply);
> > +                g_free(info);
> > +            } else {
> > +                info->pdu = mm_get_string_unquoted_from_match_info (match_info, 4);
> > +                if (!(info->pdu)) {
> > +                    g_set_error (error,
> > +                                 MM_CORE_ERROR,
> > +                                 MM_CORE_ERROR_FAILED,
> > +                                 "Failed to extract CMGR pdu field '%s'",
> > +                                 reply);
> > +                    g_free(info);
> > +                }
> > +            }
> > +
> > +        }
> > +    } else if (match_error) {
> > +        g_propagate_error (error, match_error);
> > +    } else {
> > +        g_set_error (error,
> > +                     MM_CORE_ERROR,
> > +                     MM_CORE_ERROR_FAILED,
> > +                     "Failed to parse CMGR read result: response didn't match '%s'",
> > +                     reply);
> > +    }
> > +
> > +    g_match_info_free (match_info);
> > +    g_regex_unref (r);
> > +
> > +    if (*error)
> > +        info = NULL;
> > +
> > +    return info;
> > +}
> > +
> > +/*************************************************************************/
> > +
> >  static MMSmsStorage
> >  storage_from_str (const gchar *str)
> >  {
> > @@ -1658,7 +1729,7 @@ done:
> >  
> >  /*************************************************************************/
> >  
> > -static void
> > +void
> >  mm_3gpp_pdu_info_free (MM3gppPduInfo *info)
> >  {
> >      g_free (info->pdu);
> > diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
> > index 0ec59af..edc33f9 100644
> > --- a/src/mm-modem-helpers.h
> > +++ b/src/mm-modem-helpers.h
> > @@ -181,10 +181,15 @@ typedef struct {
> >      gint status;
> >      gchar *pdu;
> >  } MM3gppPduInfo;
> > +void   mm_3gpp_pdu_info_free           (MM3gppPduInfo *info);
> >  void   mm_3gpp_pdu_info_list_free      (GList *info_list);
> >  GList *mm_3gpp_parse_pdu_cmgl_response (const gchar *str,
> >                                          GError **error);
> >  
> > +/* AT+CMGR (Read message) response parser */
> > +MM3gppPduInfo *mm_3gpp_parse_cmgr_read_response (const gchar *reply,
> > +                                                 guint index,
> > +                                                 GError **error);
> >  
> >  /* Additional 3GPP-specific helpers */
> >  
> > diff --git a/src/mm-sms-part-3gpp.h b/src/mm-sms-part-3gpp.h
> > index 82709a2..a21d734 100644
> > --- a/src/mm-sms-part-3gpp.h
> > +++ b/src/mm-sms-part-3gpp.h
> > @@ -22,8 +22,6 @@
> >  
> >  #include "mm-sms-part.h"
> >  
> > -#define MM_SMS_PART_3GPP_MAX_PDU_LEN 344
> > -
> >  MMSmsPart *mm_sms_part_3gpp_new_from_pdu  (guint index,
> >                                             const gchar *hexpdu,
> >                                             GError **error);
> > diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
> > index b493e6b..db8fc89 100644
> > --- a/src/tests/test-modem-helpers.c
> > +++ b/src/tests/test-modem-helpers.c
> > @@ -196,6 +196,67 @@ test_cmgl_response_pantech_multiple (void *f, gpointer d)
> >  }
> >  
> >  /*****************************************************************************/
> > +/* Test CMGR responses */
> > +
> > +static void
> > +test_cmgr_response (const gchar *str,
> > +                    const MM3gppPduInfo *expected)
> > +{
> > +    MM3gppPduInfo *info;
> > +    GError *error = NULL;
> > +
> > +    info = mm_3gpp_parse_cmgr_read_response (str, 0, &error);
> > +    g_assert_no_error (error);
> > +    g_assert (info != NULL);
> > +
> > +    /* Ignore index, it is not included in CMGR response */
> > +    g_assert_cmpint (info->status, ==, expected->status);
> > +    g_assert_cmpstr (info->pdu, ==, expected->pdu);
> > +}
> > +
> > +static void
> > +test_cmgr_response_generic (void *f, gpointer d)
> > +{
> > +    const gchar *str =
> > +        "+CMGR: 1,,147 07914306073011F00405812261F700003130916191314095C27"
> > +        "4D96D2FBBD3E437280CB2BEC961F3DB5D76818EF2F0381D9E83E06F39A8CC2E9FD372F"
> > +        "77BEE0249CBE37A594E0E83E2F532085E2F93CB73D0B93CA7A7DFEEB01C447F93DF731"
> > +        "0BD3E07CDCB727B7A9C7ECF41E432C8FC96B7C32079189E26874179D0F8DD7E93C3A0B"
> > +        "21B246AA641D637396C7EBBCB22D0FD7E77B5D376B3AB3C07";
> > +
> > +    const MM3gppPduInfo expected = {
> > +        .index = 0,
> > +        .status = 1,
> > +        .pdu = "07914306073011F00405812261F700003130916191314095C27"
> > +        "4D96D2FBBD3E437280CB2BEC961F3DB5D76818EF2F0381D9E83E06F39A8CC2E9FD372F"
> > +        "77BEE0249CBE37A594E0E83E2F532085E2F93CB73D0B93CA7A7DFEEB01C447F93DF731"
> > +        "0BD3E07CDCB727B7A9C7ECF41E432C8FC96B7C32079189E26874179D0F8DD7E93C3A0B"
> > +        "21B246AA641D637396C7EBBCB22D0FD7E77B5D376B3AB3C07"
> > +    };
> > +
> > +    test_cmgr_response (str, &expected);
> > +}
> > +
> > +/* Telit HE910 places empty quotation marks in the <alpha> field and a CR+LF
> > + * before the PDU */
> > +static void
> > +test_cmgr_response_telit (void *f, gpointer d)
> > +{
> > +    const gchar *str =
> > +        "+CMGR: 0,\"\",50\r\n07916163838428F9040B916121021021F7000051905141642"
> > +        "20A23C4B0BCFD5E8740C4B0BCFD5E83C26E3248196687C9A0301D440DBBC3677918";
> > +
> > +    const MM3gppPduInfo expected = {
> > +        .index = 0,
> > +        .status = 0,
> > +        .pdu = "07916163838428F9040B916121021021F7000051905141642"
> > +        "20A23C4B0BCFD5E8740C4B0BCFD5E83C26E3248196687C9A0301D440DBBC3677918"
> > +    };
> > +
> > +    test_cmgr_response (str, &expected);
> > +}
> > +
> > +/*****************************************************************************/
> >  /* Test COPS responses */
> >  
> >  static void
> > @@ -2497,6 +2558,9 @@ int main (int argc, char **argv)
> >      g_test_suite_add (suite, TESTCASE (test_cmgl_response_pantech, NULL));
> >      g_test_suite_add (suite, TESTCASE (test_cmgl_response_pantech_multiple, NULL));
> >  
> > +    g_test_suite_add (suite, TESTCASE (test_cmgr_response_generic, NULL));
> > +    g_test_suite_add (suite, TESTCASE (test_cmgr_response_telit, NULL));
> > +
> >      g_test_suite_add (suite, TESTCASE (test_supported_mode_filter, NULL));
> >  
> >      g_test_suite_add (suite, TESTCASE (test_supported_capability_filter, NULL));
> 
> 


More information about the ModemManager-devel mailing list