Add support for compressed data packets.

The new Petrel protocol uses a simple data compression scheme to reduce
the transfer times. The data is broken up into blocks of 32 bytes each.
Each block except the first is XOR'ed with the previous block, creating
large runs of zeros due to the similarity of the data. The zeros are
then run-length encoded (RLE) to save space.
This commit is contained in:
Jef Driesen 2013-04-09 23:16:01 +02:00
parent 6f3de69f0d
commit c4d3356b6e
3 changed files with 87 additions and 7 deletions

View File

@ -25,6 +25,7 @@
#include "shearwater_common.h"
#include "context-private.h"
#include "array.h"
#define SZ_PACKET 254
@ -81,6 +82,65 @@ shearwater_common_close (shearwater_common_device_t *device)
}
static int
shearwater_common_decompress_lre (unsigned char *data, unsigned int size, dc_buffer_t *buffer, unsigned int *isfinal)
{
// The RLE decompression algorithm does interpret the binary data as a
// stream of 9 bit values. Therefore, the total number of bits needs to be
// a multiple of 9 bits.
unsigned int nbits = size * 8;
if (nbits % 9 != 0)
return -1;
unsigned int offset = 0;
while (offset + 9 <= nbits) {
// Extract the 9 bit value.
unsigned int byte = offset / 8;
unsigned int bit = offset % 8;
unsigned int shift = 16 - (bit + 9);
unsigned int value = (array_uint16_be (data + byte) >> shift) & 0x1FF;
// The 9th bit indicates whether the remaining 8 bits represent
// a run of zero bytes or not. If the bit is set, the value is
// not a run and doesnt need expansion. If the bit is not set,
// the value contains the number of zero bytes in the run. A
// zero-length run indicates the end of the compressed stream.
if (value & 0x100) {
// Append the data byte directly.
unsigned char c = value & 0xFF;
if (!dc_buffer_append (buffer, &c, 1))
return -1;
} else if (value == 0) {
// Reached the end of the compressed stream.
if (isfinal)
*isfinal = 1;
break;
} else {
// Expand the run with zero bytes.
if (!dc_buffer_resize (buffer, dc_buffer_get_size (buffer) + value))
return -1;
}
offset += 9;
}
return 0;
}
static int
shearwater_common_decompress_xor (unsigned char *data, unsigned int size)
{
// Each block of 32 bytes is XOR'ed (in-place) with the previous block,
// except for the first block, which is passed through unchanged.
for (unsigned int i = 32; i < size; ++i) {
data[i] ^= data[i - 32];
}
return 0;
}
static int
shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned char data[], unsigned int size)
{
@ -256,14 +316,16 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
dc_status_t
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size)
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression)
{
dc_device_t *abstract = (dc_device_t *) device;
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned int n = 0;
unsigned char req_init[] = {
0x35, 0x00, 0x34,
0x35,
(compression ? 0x10 : 0x00),
0x34,
(address >> 24) & 0xFF,
(address >> 16) & 0xFF,
(address >> 8) & 0xFF,
@ -293,7 +355,7 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf
}
// Verify the init response.
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] != 0x82) {
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] > SZ_PACKET) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
@ -302,9 +364,10 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf
progress.current += 3;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
unsigned int done = 0;
unsigned char block = 1;
unsigned int nbytes = 0;
while (nbytes < size) {
while (nbytes < size && !done) {
// Transfer the block request.
req_block[1] = block;
rc = shearwater_common_transfer (device, req_block, sizeof (req_block), response, sizeof (response), &n);
@ -329,12 +392,29 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf
progress.current += length;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
dc_buffer_append (buffer, response + 2, length);
if (compression) {
if (shearwater_common_decompress_lre (response + 2, length, buffer, &done) != 0) {
ERROR (abstract->context, "Decompression error (LRE phase).");
return DC_STATUS_PROTOCOL;
}
} else {
if (!dc_buffer_append (buffer, response + 2, length)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
}
nbytes += length;
block++;
}
if (compression) {
if (shearwater_common_decompress_xor (dc_buffer_get_data (buffer), dc_buffer_get_size (buffer)) != 0) {
ERROR (abstract->context, "Decompression error (XOR phase).");
return DC_STATUS_PROTOCOL;
}
}
// Transfer the quit request.
rc = shearwater_common_transfer (device, req_quit, sizeof (req_quit), response, 2, &n);
if (rc != DC_STATUS_SUCCESS) {

View File

@ -41,7 +41,7 @@ dc_status_t
shearwater_common_close (shearwater_common_device_t *device);
dc_status_t
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size);
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size, unsigned int compression);
#ifdef __cplusplus
}

View File

@ -134,7 +134,7 @@ shearwater_predator_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
shearwater_common_device_t *device = (shearwater_common_device_t *) abstract;
return shearwater_common_download (device, buffer, 0xDD000000, SZ_MEMORY);
return shearwater_common_download (device, buffer, 0xDD000000, SZ_MEMORY, 0);
}