[PATCH v2] port-serial: rework response parsing

Dan Williams dcbw at redhat.com
Fri Jan 22 08:58:43 PST 2016


On Fri, 2016-01-22 at 16:30 +0100, Aleksander Morgado wrote:
> Response parsing was being done in different places for AT and QCDM
> subclasses;
> in the case of AT it was being done early, before returning the byte
> array in
> the mm_serial_port_command_finish() response. In the case of QCDM, it
> was being
> done after mm_serial_port_command_finish(), and that was forcing
> every caller to
> cleanup the response buffer once the response was processed.
> 
> With the new logic in this patch, the response is always parsed (i.e.
> looked for
> a valid response or an error detected) before
> mm_serial_port_command_finish()
> returns, and actually this method now returns a totally different
> GByteArray,
> not the internal response buffer GByteArray, so there's no longer any
> need for
> the caller to explicitly clean it up. The one doing the cleanup is
> the parser
> method itself in every case.
> 
> This change also allows us to return serial port responses in idle,
> but that's
> not changed for now as there's no immediate need.
> ---
> 
> Hey,
> 
> Updated the patch to avoid breaking QCDM :)

Patch looks good to me, I actually have no comments at all, and it
seemed to work OK on 3 modems (including 2 with QCDM) :)  Nice cleanup.

Dan

> ---
>  src/mm-port-serial-at.c   |  40 +++++++++-----
>  src/mm-port-serial-gps.c  |  11 ++--
>  src/mm-port-serial-qcdm.c | 134 +++++++++++++++++++++++-------------
> ----------
>  src/mm-port-serial.c      | 118 +++++++++++++++++++++---------------
> ----
>  src/mm-port-serial.h      |  34 ++++++++----
>  5 files changed, 189 insertions(+), 148 deletions(-)
> 
> diff --git a/src/mm-port-serial-at.c b/src/mm-port-serial-at.c
> index 553596e..9cab855 100644
> --- a/src/mm-port-serial-at.c
> +++ b/src/mm-port-serial-at.c
> @@ -116,14 +116,16 @@ mm_port_serial_at_remove_echo (GByteArray
> *response)
>      }
>  }
> 
> -static gboolean
> +static MMPortSerialResponseType
>  parse_response (MMPortSerial *port,
>                  GByteArray *response,
> +                GByteArray **parsed_response,
>                  GError **error)
>  {
>      MMPortSerialAt *self = MM_PORT_SERIAL_AT (port);
> -    gboolean found;
>      GString *string;
> +    gsize parsed_len;
> +    GError *inner_error = NULL;
> 
>      g_return_val_if_fail (self->priv->response_parser_fn != NULL,
> FALSE);
> 
> @@ -135,17 +137,29 @@ parse_response (MMPortSerial *port,
>      string = g_string_sized_new (response->len + 1);
>      g_string_append_len (string, (const char *) response->data,
> response->len);
> 
> -    /* Parse it */
> -    found = self->priv->response_parser_fn (self->priv
> ->response_parser_user_data, string, error);
> -
> -    /* And copy it back into the response array after the parser has
> removed
> -     * matches and cleaned it up.
> -     */
> -    if (response->len)
> -        g_byte_array_remove_range (response, 0, response->len);
> -    g_byte_array_append (response, (const guint8 *) string->str,
> string->len);
> -    g_string_free (string, TRUE);
> -    return found;
> +    /* Fully cleanup the response array, we'll consider the contents
> we got
> +     * as the full reply that the command may expect. */
> +    g_byte_array_remove_range (response, 0, response->len);
> +
> +    /* Parse it; returns FALSE if there is nothing we can do with
> this
> +     * response yet. */
> +    if (!self->priv->response_parser_fn (self->priv
> ->response_parser_user_data, string, &inner_error)) {
> +        /* Copy what we got back in the response buffer. */
> +        g_byte_array_append (response, (const guint8 *) string->str,
> string->len);
> +        g_string_free (string, TRUE);
> +        return MM_PORT_SERIAL_RESPONSE_NONE;
> +    }
> +
> +    /* If we got an error, propagate it without any further response
> string */
> +    if (inner_error) {
> +        g_propagate_error (error, inner_error);
> +        return MM_PORT_SERIAL_RESPONSE_ERROR;
> +    }
> +
> +    /* Otherwise, build a new GByteArray considered as parsed
> response */
> +    parsed_len = string->len;
> +    *parsed_response = g_byte_array_new_take ((guint8 *)
> g_string_free (string, FALSE), parsed_len);
> +    return MM_PORT_SERIAL_RESPONSE_BUFFER;
>  }
> 
>  /*******************************************************************
> **********/
> diff --git a/src/mm-port-serial-gps.c b/src/mm-port-serial-gps.c
> index 306df1f..b316d05 100644
> --- a/src/mm-port-serial-gps.c
> +++ b/src/mm-port-serial-gps.c
> @@ -68,9 +68,10 @@ remove_eval_cb (const GMatchInfo *match_info,
>      return FALSE;
>  }
> 
> -static gboolean
> +static MMPortSerialResponseType
>  parse_response (MMPortSerial *port,
>                  GByteArray *response,
> +                GByteArray **parsed_response,
>                  GError **error)
>  {
>      MMPortSerialGps *self = MM_PORT_SERIAL_GPS (port);
> @@ -112,7 +113,7 @@ parse_response (MMPortSerial *port,
>      g_match_info_free (match_info);
> 
>      if (!matches)
> -        return FALSE;
> +        return MM_PORT_SERIAL_RESPONSE_NONE;
> 
>      /* Remove matches */
>      result_len = response->len;
> @@ -122,9 +123,11 @@ parse_response (MMPortSerial *port,
>                                  0, 0,
>                                  remove_eval_cb, &result_len, NULL);
> 
> +    /* Cleanup response buffer */
>      g_byte_array_remove_range (response, 0, response->len);
> -    g_byte_array_append (response, (const guint8 *) str,
> result_len);
> -    g_free (str);
> +
> +    /* Build parsed response */
> +    *parsed_response = g_byte_array_new_take ((guint8 *)str,
> result_len);
> 
>      return TRUE;
>  }
> diff --git a/src/mm-port-serial-qcdm.c b/src/mm-port-serial-qcdm.c
> index b63927f..7732851 100644
> --- a/src/mm-port-serial-qcdm.c
> +++ b/src/mm-port-serial-qcdm.c
> @@ -59,10 +59,69 @@ find_qcdm_start (GByteArray *response, gsize
> *start)
>      return FALSE;
>  }
> 
> -static gboolean
> -parse_response (MMPortSerial *port, GByteArray *response, GError
> **error)
> +static MMPortSerialResponseType
> +parse_response (MMPortSerial *port,
> +                GByteArray *response,
> +                GByteArray **parsed_response,
> +                GError **error)
>  {
> -    return find_qcdm_start (response, NULL);
> +    gsize start = 0;
> +    gsize used = 0;
> +    gsize unescaped_len = 0;
> +    guint8 *unescaped_buffer;
> +    qcdmbool more = FALSE;
> +
> +    /* Get the offset into the buffer of where the QCDM frame starts
> */
> +    if (!find_qcdm_start (response, &start)) {
> +        /* Discard the unparsable data right away, we do need a QCDM
> +         * start, and anything that comes before it is unknown data
> +         * that we'll never use. */
> +        return MM_PORT_SERIAL_RESPONSE_NONE;
> +    }
> +
> +    /* If there is anything before the start marker, remove it */
> +    g_byte_array_remove_range (response, 0, start);
> +    if (response->len == 0)
> +        return MM_PORT_SERIAL_RESPONSE_NONE;
> +
> +    /* Try to decapsulate the response into a buffer */
> +    unescaped_buffer = g_malloc (1024);
> +    if (!dm_decapsulate_buffer ((const char *)(response->data),
> +                                response->len,
> +                                (char *)unescaped_buffer,
> +                                1024,
> +                                &unescaped_len,
> +                                &used,
> +                                &more)) {
> +        /* Report an error right away. Not being able to decapsulate
> a QCDM
> +         * packet once we got message start marker likely means that
> this
> +         * data that we got is not a QCDM message. */
> +        g_set_error (error,
> +                     MM_SERIAL_ERROR,
> +                     MM_SERIAL_ERROR_PARSE_FAILED,
> +                     "Failed to unescape QCDM packet");
> +        g_free (unescaped_buffer);
> +        return MM_PORT_SERIAL_RESPONSE_ERROR;
> +    }
> +
> +    if (more) {
> +        /* Need more data, we leave the original byte array
> untouched so that
> +         * we can retry later when more data arrives. */
> +        g_free (unescaped_buffer);
> +        return MM_PORT_SERIAL_RESPONSE_NONE;
> +    }
> +
> +    /* Successfully decapsulated the DM command. We'll build a new
> byte array
> +     * with the response, and leave the input buffer cleaned up. */
> +    g_assert (unescaped_len <= 1024);
> +    unescaped_buffer = g_realloc (unescaped_buffer, unescaped_len);
> +    *parsed_response = g_byte_array_new_take (unescaped_buffer,
> unescaped_len);
> +
> +    /* Remove the data we used from the input buffer, leaving out
> any
> +     * additional data that may already been received (e.g. from the
> following
> +     * message). */
> +    g_byte_array_remove_range (response, 0, used);
> +    return MM_PORT_SERIAL_RESPONSE_BUFFER;
>  }
> 
>  /*******************************************************************
> **********/
> @@ -83,75 +142,14 @@ serial_command_ready (MMPortSerial *port,
>                        GAsyncResult *res,
>                        GSimpleAsyncResult *simple)
>  {
> -    GByteArray *response_buffer;
>      GByteArray *response;
>      GError *error = NULL;
> -    gsize used = 0;
> -    gsize start = 0;
> -    guint8 *unescaped_buffer = NULL;
> -    gboolean success = FALSE;
> -    qcdmbool more = FALSE;
> -    gsize unescaped_len = 0;
> -
> -    response_buffer = mm_port_serial_command_finish (port, res,
> &error);
> -    if (!response_buffer)
> -        goto out;
> -
> -    /* Get the offset into the buffer of where the QCDM frame starts
> */
> -    start = 0;
> -    if (!find_qcdm_start (response_buffer, &start)) {
> -        error = g_error_new_literal (MM_SERIAL_ERROR,
> -                                    
>  MM_SERIAL_ERROR_FRAME_NOT_FOUND,
> -                                     "QCDM frame start not found");
> -        /* Discard the unparsable data */
> -        used = response_buffer->len;
> -        goto out;
> -    }
> -
> -    unescaped_buffer = g_malloc (1024);
> -    success = dm_decapsulate_buffer ((const char *)(response_buffer
> ->data + start),
> -                                     response_buffer->len - start,
> -                                     (char *)unescaped_buffer,
> -                                     1024,
> -                                     &unescaped_len,
> -                                     &used,
> -                                     &more);
> -    if (!success) {
> -        error = g_error_new_literal (MM_SERIAL_ERROR,
> -                                     MM_SERIAL_ERROR_PARSE_FAILED,
> -                                     "Failed to unescape QCDM
> packet");
> -        g_free (unescaped_buffer);
> -        unescaped_buffer = NULL;
> -        goto out;
> -    }
> -
> -    if (more) {
> -        /* Need more data; we shouldn't have gotten here since the
> parse
> -         * function checks for the end-of-frame marker, but
> whatever.
> -         */
> -        error = g_error_new_literal (MM_CORE_ERROR,
> -                                     MM_CORE_ERROR_FAILED,
> -                                     "QCDM packet is not complete");
> -        g_free (unescaped_buffer);
> -        unescaped_buffer = NULL;
> -        goto out;
> -    }
> 
> -    /* Successfully decapsulated the DM command */
> -    g_assert (error == NULL);
> -    g_assert (unescaped_len <= 1024);
> -    unescaped_buffer = g_realloc (unescaped_buffer, unescaped_len);
> -    response = g_byte_array_new_take (unescaped_buffer,
> unescaped_len);
> -    g_simple_async_result_set_op_res_gpointer (simple, response,
> (GDestroyNotify)g_byte_array_unref);
> -
> -out:
> -    if (error)
> +    response = mm_port_serial_command_finish (port, res, &error);
> +    if (!response)
>          g_simple_async_result_take_error (simple, error);
> -    if (start + used)
> -        g_byte_array_remove_range (response_buffer, 0, start +
> used);
> -    if (response_buffer)
> -        g_byte_array_unref (response_buffer);
> -
> +    else
> +        g_simple_async_result_set_op_res_gpointer (simple, response,
> (GDestroyNotify)g_byte_array_unref);
>      g_simple_async_result_complete (simple);
>      g_object_unref (simple);
>  }
> diff --git a/src/mm-port-serial.c b/src/mm-port-serial.c
> index 53f8725..92ad481 100644
> --- a/src/mm-port-serial.c
> +++ b/src/mm-port-serial.c
> @@ -696,10 +696,14 @@ port_serial_schedule_queue_process
> (MMPortSerial *self, guint timeout_ms)
> 
>  static void
>  port_serial_got_response (MMPortSerial *self,
> +                          GByteArray   *parsed_response,
>                            const GError *error)
>  {
>      CommandContext *ctx;
> 
> +    /* Either one or the other, not both */
> +    g_assert ((parsed_response && !error) || (!parsed_response &&
> error));
> +
>      if (self->priv->timeout_id) {
>          g_source_remove (self->priv->timeout_id);
>          self->priv->timeout_id = 0;
> @@ -716,29 +720,15 @@ port_serial_got_response (MMPortSerial *self,
> 
>      ctx = (CommandContext *) g_queue_pop_head (self->priv->queue);
>      if (ctx) {
> -        if (error) {
> -            /* If we're returning an error parsed in a generic way
> from the inputs,
> -             * we fully avoid returning a response bytearray. This
> really applies
> -             * only to AT, not to QCDM, so we shouldn't be worried
> of losing chunks
> -             * of the next QCDM message. And given that the caller
> won't get the
> -             * response array, we're the ones in charge of removing
> the processed
> -             * data (otherwise ERROR replies may get fed to the next
> response
> -             * parser).
> -             */
> +        /* Complete the command context with the appropriate result
> */
> +        if (error)
>              g_simple_async_result_set_from_error (ctx->result,
> error);
> -            g_byte_array_remove_range (self->priv->response, 0, self
> ->priv->response->len);
> -        } else {
> +        else {
>              if (ctx->allow_cached)
> -                port_serial_set_cached_reply (self, ctx->command,
> self->priv->response);
> -
> -            /* Upon completion, it is a task of the caller to remove
> from the response
> -             * buffer the processed data. This may seem unnecessary
> in the case of AT
> -             * commands, as it'll remove the full received string,
> but the step is
> -             * a key thing in the case of QCDM, where we want to
> read just until the
> -             * next marker, not more. */
> +                port_serial_set_cached_reply (self, ctx->command,
> parsed_response);
>              g_simple_async_result_set_op_res_gpointer (ctx->result,
> -                                                      
>  g_byte_array_ref (self->priv->response),
> -                                                      
>  (GDestroyNotify)g_byte_array_unref);
> +                                                      
>  g_byte_array_ref (parsed_response),
> +                                                      
>  (GDestroyNotify) g_byte_array_unref);
>          }
> 
>          /* Don't complete in idle. We need the caller remove the
> response range which
> @@ -767,7 +757,7 @@ port_serial_timed_out (gpointer data)
>      error = g_error_new_literal (MM_SERIAL_ERROR,
>                                   MM_SERIAL_ERROR_RESPONSE_TIMEOUT,
>                                   "Serial command timed out");
> -    port_serial_got_response (self, error);
> +    port_serial_got_response (self, NULL, error);
>      g_error_free (error);
> 
>      /* Emit a timed out signal, used by upper layers to identify a
> disconnected
> @@ -792,7 +782,7 @@ port_serial_response_wait_cancelled (GCancellable
> *cancellable,
>      error = g_error_new_literal (MM_CORE_ERROR,
>                                   MM_CORE_ERROR_CANCELLED,
>                                   "Waiting for the reply cancelled");
> -    port_serial_got_response (self, error);
> +    port_serial_got_response (self, NULL, error);
>      g_error_free (error);
>  }
> 
> @@ -814,18 +804,12 @@ port_serial_queue_process (gpointer data)
> 
>          cached = port_serial_get_cached_reply (self, ctx->command);
>          if (cached) {
> -            /* Ensure the response array is fully empty before
> setting the
> -             * cached response.  */
> -            if (self->priv->response->len > 0) {
> -                mm_warn ("(%s) response array is not empty when
> using cached "
> -                         "reply, cleaning up %u bytes",
> -                         mm_port_get_device (MM_PORT (self)),
> -                         self->priv->response->len);
> -                g_byte_array_set_size (self->priv->response, 0);
> -            }
> +            GByteArray *parsed_response;
> 
> -            g_byte_array_append (self->priv->response, cached->data,
> cached->len);
> -            port_serial_got_response (self, NULL);
> +            parsed_response = g_byte_array_sized_new (cached->len);
> +            g_byte_array_append (parsed_response, cached->data,
> cached->len);
> +            port_serial_got_response (self, parsed_response, NULL);
> +            g_byte_array_unref (parsed_response);
>              return G_SOURCE_REMOVE;
>          }
> 
> @@ -834,7 +818,7 @@ port_serial_queue_process (gpointer data)
> 
>      /* If error, report it */
>      if (!port_serial_process_command (self, ctx, &error)) {
> -        port_serial_got_response (self, error);
> +        port_serial_got_response (self, NULL, error);
>          g_error_free (error);
>          return G_SOURCE_REMOVE;
>      }
> @@ -860,7 +844,7 @@ port_serial_queue_process (gpointer data)
>              error = g_error_new (MM_CORE_ERROR,
>                                   MM_CORE_ERROR_CANCELLED,
>                                   "Won't wait for the reply");
> -            port_serial_got_response (self, error);
> +            port_serial_got_response (self, NULL, error);
>              g_error_free (error);
>              return G_SOURCE_REMOVE;
>          }
> @@ -873,16 +857,50 @@ port_serial_queue_process (gpointer data)
>      return G_SOURCE_REMOVE;
>  }
> 
> -static gboolean
> -parse_response (MMPortSerial *self,
> -                GByteArray *response,
> -                GError **error)
> +static void
> +parse_response_buffer (MMPortSerial *self)
>  {
> -    if (MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited)
> -        MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited (self,
> response);
> +    GError *error = NULL;
> +    GByteArray *parsed_response = NULL;
> 
> -    g_return_val_if_fail (MM_PORT_SERIAL_GET_CLASS (self)
> ->parse_response, FALSE);
> -    return MM_PORT_SERIAL_GET_CLASS (self)->parse_response (self,
> response, error);
> +    /* Parse unsolicited messages in the subclass.
> +     *
> +     * If any message found, it's processed immediately and the
> message is
> +     * removed from the response buffer.
> +     */
> +    if (MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited)
> +        MM_PORT_SERIAL_GET_CLASS (self)->parse_unsolicited (self,
> +                                                            self
> ->priv->response);
> +
> +    /* Parse response in the subclass.
> +     *
> +     * Returns TRUE either if an error is provided or if we really
> have the
> +     * response to process. The parsed string is returned already
> out of the
> +     * response buffer, and the response buffer is cleaned up
> accordingly.
> +     */
> +    g_assert (MM_PORT_SERIAL_GET_CLASS (self)->parse_response !=
> NULL);
> +    switch (MM_PORT_SERIAL_GET_CLASS (self)->parse_response (self,
> +                                                             self
> ->priv->response,
> +                                                            
>  &parsed_response,
> +                                                            
>  &error)) {
> +    case MM_PORT_SERIAL_RESPONSE_BUFFER:
> +        /* We have a valid response to process */
> +        g_assert (parsed_response);
> +        self->priv->n_consecutive_timeouts = 0;
> +        port_serial_got_response (self, parsed_response, NULL);
> +        g_byte_array_unref (parsed_response);
> +        break;
> +    case MM_PORT_SERIAL_RESPONSE_ERROR:
> +        /* We have an error to process */
> +        g_assert (error);
> +        self->priv->n_consecutive_timeouts = 0;
> +        port_serial_got_response (self, NULL, error);
> +        g_error_free (error);
> +        break;
> +    case MM_PORT_SERIAL_RESPONSE_NONE:
> +        /* Nothing to do this time */
> +        break;
> +    }
>  }
> 
>  static gboolean
> @@ -954,8 +972,6 @@ common_input_available (MMPortSerial *self,
>                  status = G_IO_STATUS_NORMAL;
>          }
> 
> -
> -
>          /* If no bytes read, just wait for more data */
>          if (bytes_read == 0)
>              break;
> @@ -971,15 +987,9 @@ common_input_available (MMPortSerial *self,
>              g_byte_array_remove_range (self->priv->response, 0,
> (SERIAL_BUF_SIZE / 2));
>          }
> 
> -        /* Parse response. Returns TRUE either if an error is
> provided or if
> -         * we really have the response to process. */
> -        if (parse_response (self, self->priv->response, &error)) {
> -            /* Reset number of consecutive timeouts only here */
> -            self->priv->n_consecutive_timeouts = 0;
> -            /* Process response retrieved */
> -            port_serial_got_response (self, error);
> -            g_clear_error (&error);
> -        }
> +        /* See if we can parse anything */
> +        parse_response_buffer (self);
> +
>      } while (   (bytes_read == SERIAL_BUF_SIZE || status ==
> G_IO_STATUS_AGAIN)
>               && (self->priv->iochannel_id > 0 || self->priv
> ->socket_source != NULL));
> 
> diff --git a/src/mm-port-serial.h b/src/mm-port-serial.h
> index 8f357ae..708e391 100644
> --- a/src/mm-port-serial.h
> +++ b/src/mm-port-serial.h
> @@ -40,6 +40,12 @@
>  #define MM_PORT_SERIAL_SPEW_CONTROL "spew-control" /* Construct-only
> */
>  #define MM_PORT_SERIAL_FLASH_OK     "flash-ok" /* Construct-only */
> 
> +typedef enum {
> +    MM_PORT_SERIAL_RESPONSE_NONE,
> +    MM_PORT_SERIAL_RESPONSE_BUFFER,
> +    MM_PORT_SERIAL_RESPONSE_ERROR,
> +} MMPortSerialResponseType;
> +
>  typedef struct _MMPortSerial MMPortSerial;
>  typedef struct _MMPortSerialClass MMPortSerialClass;
>  typedef struct _MMPortSerialPrivate MMPortSerialPrivate;
> @@ -58,16 +64,26 @@ struct _MMPortSerialClass {
>       */
>      void     (*parse_unsolicited) (MMPortSerial *self, GByteArray
> *response);
> 
> -    /* Called to parse the device's response to a command or
> determine if the
> -     * response was an error response.  If the response indicates an
> error, an
> -     * appropriate error should be returned in the 'error' argument.
>   The
> -     * function should return FALSE if there is not enough data yet
> to determine
> -     * the device's reply (whether success *or* error), and should
> return TRUE
> -     * when the device's response has been recognized and parsed.
> +    /*
> +     * Called to parse the device's response to a command or
> determine if the
> +     * response was an error response.
> +     *
> +     * If the response indicates an error,
> @MM_PORT_SERIAL_RESPONSE_ERROR will
> +     * be returned and an appropriate GError set in @error.
> +     *
> +     * If the response indicates a valid response,
> @MM_PORT_SERIAL_RESPONSE_BUFFER
> +     * will be returned, and a newly allocated GByteArray set in
> @parsed_response.
> +     *
> +     * If there is no response, @MM_PORT_SERIAL_RESPONSE_NONE will
> be returned,
> +     * and neither @error nor @parsed_response will be set.
> +     *
> +     * The implementation is allowed to cleanup the @response byte
> array, e.g. to
> +     * just remove 1 single response if more than one found.
>       */
> -    gboolean (*parse_response)    (MMPortSerial *self,
> -                                   GByteArray *response,
> -                                   GError **error);
> +    MMPortSerialResponseType (*parse_response) (MMPortSerial *self,
> +                                                GByteArray
> *response,
> +                                                GByteArray
> **parsed_response,
> +                                                GError **error);
> 
>      /* Called to configure the serial port fd after it's opened.  On
> error, should
>       * return FALSE and set 'error' as appropriate.
> --
> 2.6.4


More information about the ModemManager-devel mailing list