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.
This commit is contained in:
Dan Williams
2010-10-14 10:22:59 -05:00
parent 8a4f621245
commit dd8a061d28
+65 -40
View File
@@ -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;
}
/* Otherwise, need more data from the device */
/* Save position of the last QCDM frame marker */
last = i;
}
}
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;
/* Ignore empty frames */
if (response->len > 0 && response->data[0] == 0x7E)
return 1;
if (error)
goto callback;
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;
}
/* 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;
}
/* 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;
}
/*****************************************************************************/