aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mm-port-serial-at.c45
-rw-r--r--src/mm-port-serial-gps.c11
-rw-r--r--src/mm-port-serial-qcdm.c134
-rw-r--r--src/mm-port-serial.c118
-rw-r--r--src/mm-port-serial.h34
5 files changed, 194 insertions, 148 deletions
diff --git a/src/mm-port-serial-at.c b/src/mm-port-serial-at.c
index 553596e8..8e2aa2aa 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);
@@ -131,21 +133,38 @@ parse_response (MMPortSerial *port,
if (self->priv->remove_echo)
mm_port_serial_at_remove_echo (response);
+ /* If there's no response to receive, we're done; e.g. if we only got
+ * unsolicited messages */
+ if (!response->len)
+ return MM_PORT_SERIAL_RESPONSE_NONE;
+
/* Construct the string that AT-parsing functions expect */
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 306df1fe..b316d05e 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 b63927ff..7732851f 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 53f8725b..92ad4812 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 8f357ae0..708e3912 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.