diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-port-serial-at.c | 45 | ||||
-rw-r--r-- | src/mm-port-serial-gps.c | 11 | ||||
-rw-r--r-- | src/mm-port-serial-qcdm.c | 134 | ||||
-rw-r--r-- | src/mm-port-serial.c | 118 | ||||
-rw-r--r-- | src/mm-port-serial.h | 34 |
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. |