aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Williams <dcbw@redhat.com>2010-10-14 10:22:59 -0500
committerDan Williams <dcbw@redhat.com>2010-10-14 10:22:59 -0500
commitdd8a061d28ecaf286bfba3bdbd2a495c45cfb4a4 (patch)
tree85a4ecd53f7eb02d227d98d906e9d927febc0d04
parent8a4f621245c7b5a1a06471260f5f75422e9098cc (diff)
serial: make QCDM frame parsing more robust
Ensure that valid HDLC frames that are not valid QCDM frames are correctly rejected, and that their data is correctly discarded. The core bug was that Sierra CnS frames have leading and trailing HDLC frame terminator bytes (0x7E), and the code was incorrectly treating the leading terminator as the end of a frame, not the beginning. Thus it would consider the outstanding serial request finished without actually parsing the response packet. Now, we make sure we don't tell the serial receive code that we have a full QCDM frame until we actually do have one, which is at least 3 bytes + 0x7E.
-rw-r--r--src/mm-qcdm-serial-port.c107
1 files changed, 66 insertions, 41 deletions
diff --git a/src/mm-qcdm-serial-port.c b/src/mm-qcdm-serial-port.c
index 72463fbf..7f4302c5 100644
--- a/src/mm-qcdm-serial-port.c
+++ b/src/mm-qcdm-serial-port.c
@@ -37,22 +37,38 @@ typedef struct {
/*****************************************************************************/
static gboolean
-parse_response (MMSerialPort *port, GByteArray *response, GError **error)
+find_qcdm_start (GByteArray *response, gsize *start)
{
- int i;
+ int i, last = -1;
- /* Look for the QCDM packet termination character; if we found it, treat
- * the buffer as a qcdm command.
+ /* Look for 3 bytes and a QCDM frame marker, ie enough data for a valid
+ * frame. There will usually be three cases here; (1) a QCDM frame
+ * starting with data and terminated by 0x7E, and (2) a QCDM frame starting
+ * with 0x7E and ending with 0x7E, and (3) a non-QCDM frame that still
+ * uses HDLC framing (like Sierra CnS) that starts and ends with 0x7E.
*/
for (i = 0; i < response->len; i++) {
- if (response->data[i] == 0x7E)
- return TRUE;
+ if (response->data[i] == 0x7E) {
+ if (i > last + 3) {
+ /* Got a full QCDM frame; 3 non-0x7E bytes and a terminator */
+ if (start)
+ *start = last + 1;
+ return TRUE;
+ }
+
+ /* Save position of the last QCDM frame marker */
+ last = i;
+ }
}
-
- /* Otherwise, need more data from the device */
return FALSE;
}
+static gboolean
+parse_response (MMSerialPort *port, GByteArray *response, GError **error)
+{
+ return find_qcdm_start (response, NULL);
+}
+
static gsize
handle_response (MMSerialPort *port,
GByteArray *response,
@@ -64,41 +80,49 @@ handle_response (MMSerialPort *port,
GByteArray *unescaped = NULL;
GError *dm_error = NULL;
gsize used = 0;
+ gsize start = 0;
+ gboolean success = FALSE, more = FALSE;
+ gsize unescaped_len = 0;
+
+ if (error)
+ goto callback;
+
+ /* Get the offset into the buffer of where the QCDM frame starts */
+ if (!find_qcdm_start (response, &start)) {
+ g_set_error_literal (&dm_error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to parse QCDM packet.");
+ /* Discard the unparsable data */
+ used = response->len;
+ goto callback;
+ }
- /* Ignore empty frames */
- if (response->len > 0 && response->data[0] == 0x7E)
- return 1;
-
- if (!error) {
- gboolean more = FALSE, success;
- gsize unescaped_len = 0;
-
- /* FIXME: don't munge around with byte array internals */
- unescaped = g_byte_array_sized_new (1024);
- success = dm_decapsulate_buffer ((const char *) response->data,
- response->len,
- (char *) unescaped->data,
- 1024,
- &unescaped_len,
- &used,
- &more);
- if (!success) {
- g_set_error_literal (&dm_error,
- MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
- "Failed to unescape QCDM packet.");
- g_byte_array_free (unescaped, TRUE);
- unescaped = NULL;
- } else if (more) {
- /* Need more data; we shouldn't have gotten here since the parse
- * function checks for the end-of-frame marker, but whatever.
- */
- return 0;
- } else {
- /* Successfully decapsulated the DM command */
- unescaped->len = (guint) unescaped_len;
- }
+ /* FIXME: don't munge around with byte array internals */
+ unescaped = g_byte_array_sized_new (1024);
+ success = dm_decapsulate_buffer ((const char *) (response->data + start),
+ response->len - start,
+ (char *) unescaped->data,
+ 1024,
+ &unescaped_len,
+ &used,
+ &more);
+ if (!success) {
+ g_set_error_literal (&dm_error,
+ MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+ "Failed to unescape QCDM packet.");
+ g_byte_array_free (unescaped, TRUE);
+ unescaped = NULL;
+ } else if (more) {
+ /* Need more data; we shouldn't have gotten here since the parse
+ * function checks for the end-of-frame marker, but whatever.
+ */
+ return 0;
+ } else {
+ /* Successfully decapsulated the DM command */
+ unescaped->len = (guint) unescaped_len;
}
+callback:
response_callback (MM_QCDM_SERIAL_PORT (port),
unescaped,
dm_error ? dm_error : error,
@@ -106,8 +130,9 @@ handle_response (MMSerialPort *port,
if (unescaped)
g_byte_array_free (unescaped, TRUE);
+ g_clear_error (&dm_error);
- return used;
+ return start + used;
}
/*****************************************************************************/