Refactor the Mares Genius and Horizon parser

The current implementation assumes a fixed order for the different
record types to share some code with the older models. See commits [1]
and [2] for more details. The main disadvantages of this approach are
the extra complexity and the limited flexibility to adapt to future
changes to the data format.

To make the parsing code more future proof, split the code into separate
functions for the Genius/Horizon and the older models.

[1] Commit 8b06f2c31d437d6e067c21e7263b5ccc33539537 (Horizon)
[2] Commit feec939a2924095f07e030aa98d839b40d4cb6cc (Genius)
This commit is contained in:
Jef Driesen 2024-04-19 19:15:34 +02:00
parent 4516842a5a
commit f20d9cb972

View File

@ -289,28 +289,6 @@ static const dc_parser_vtable_t mares_iconhd_parser_vtable = {
NULL /* destroy */
};
static unsigned int
mares_genius_isvalid (const unsigned char data[], size_t size, unsigned int type)
{
if (size < 10) {
return 0;
}
unsigned int head = array_uint32_be(data);
unsigned int tail = array_uint32_be(data + size - 4);
if (head != type || tail != type) {
return 0;
}
unsigned short crc = array_uint16_le(data + size - 6);
unsigned short ccrc = checksum_crc16_ccitt(data + 4, size - 10, 0x0000, 0x0000);
if (crc != ccrc) {
return 0;
}
return 1;
}
static dc_status_t
mares_iconhd_cache (mares_iconhd_parser_t *parser)
{
@ -554,30 +532,22 @@ mares_genius_cache (mares_iconhd_parser_t *parser)
// Get the dive mode.
unsigned int mode = settings & 0xF;
// Get the sample size.
unsigned int samplesize = logformat == 1 ? SDPT_SIZE: DPRS_SIZE;
// Calculate the total number of bytes for this dive.
unsigned int nbytes = headersize + 4 + DSTR_SIZE + TISS_SIZE + nsamples * samplesize + (nsamples / 4) * AIRS_SIZE + DEND_SIZE;
if (nbytes > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
// Get the profile type and version.
unsigned int profile_type = array_uint16_le (data + headersize);
unsigned int profile_minor = data[headersize + 2];
unsigned int profile_major = data[headersize + 3];
// Get the surface timeout setting (in minutes).
// For older firmware versions the value is hardcoded to 3 minutes, but
// starting with the newer v01.02.00 firmware the value is configurable and
// stored in the settings. To detect whether the setting is available, we
// need to check the profile version instead of the header version.
unsigned int surftime = 3;
if (profile_type == 0 &&
OBJVERSION(profile_major,profile_minor) >= OBJVERSION(1,0)) {
surftime = (settings >> 13) & 0x3F;
if (headersize + 4 <= size) {
// Get the profile type and version.
unsigned int profile_type = array_uint16_le (data + headersize);
unsigned int profile_minor = data[headersize + 2];
unsigned int profile_major = data[headersize + 3];
if (profile_type == 0 &&
OBJVERSION(profile_major,profile_minor) >= OBJVERSION(1,0)) {
surftime = (settings >> 13) & 0x3F;
}
}
// Gas mixes and tanks.
@ -628,7 +598,7 @@ mares_genius_cache (mares_iconhd_parser_t *parser)
parser->logformat = logformat;
parser->mode = mode;
parser->nsamples = nsamples;
parser->samplesize = samplesize;
parser->samplesize = 0;
parser->headersize = headersize;
parser->settings = settings;
parser->surftime = surftime * 60;
@ -926,15 +896,9 @@ mares_iconhd_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsi
static dc_status_t
mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
mares_iconhd_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
mares_iconhd_parser_t *parser = (mares_iconhd_parser_t *) abstract;
// Cache the parser data.
dc_status_t rc = mares_iconhd_parser_cache (parser);
if (rc != DC_STATUS_SUCCESS)
return rc;
const unsigned char *data = abstract->data;
// Previous gas mix - initialize with impossible value
@ -942,40 +906,6 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
unsigned int offset = 4;
unsigned int marker = 0;
if (ISGENIUS(parser->model)) {
// Skip the dive header.
data += parser->headersize;
// Check the profile type and version.
unsigned int type = array_uint16_le (data);
unsigned int minor = data[2];
unsigned int major = data[3];
if (type > 1 ||
(type == 0 && OBJVERSION(major,minor) > OBJVERSION(1,0)) ||
(type == 1 && OBJVERSION(major,minor) > OBJVERSION(0,2))) {
ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).",
type, major, minor);
return DC_STATUS_DATAFORMAT;
}
// Skip the DSTR record.
if (!mares_genius_isvalid (data + offset, DSTR_SIZE, DSTR_TYPE)) {
ERROR (abstract->context, "Invalid DSTR record.");
return DC_STATUS_DATAFORMAT;
}
offset += DSTR_SIZE;
// Skip the TISS record.
if (!mares_genius_isvalid (data + offset, TISS_SIZE, TISS_TYPE)) {
ERROR (abstract->context, "Invalid TISS record.");
return DC_STATUS_DATAFORMAT;
}
offset += TISS_SIZE;
// Size of the record type marker.
marker = 4;
}
unsigned int time = 0;
unsigned int nsamples = 0;
while (nsamples < parser->nsamples) {
@ -1012,7 +942,7 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
offset += 2;
}
} else if (!ISGENIUS(parser->model) && parser->mode == ICONHD_FREEDIVE) {
} else if (parser->mode == ICONHD_FREEDIVE) {
unsigned int maxdepth = array_uint16_le (data + offset + 0);
unsigned int divetime = array_uint16_le (data + offset + 2);
unsigned int surftime = array_uint16_le (data + offset + 4);
@ -1038,54 +968,179 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
offset += parser->samplesize;
nsamples++;
} else {
unsigned int depth = array_uint16_le (data + offset + 0);
unsigned int temperature = array_uint16_le (data + offset + 2) & 0x0FFF;
unsigned int gasmix = (data[offset + 3] & 0xF0) >> 4;
// Time (seconds).
time += parser->interval;
sample.time = time;
if (callback) callback (DC_SAMPLE_TIME, &sample, userdata);
// Depth (1/10 m).
sample.depth = depth / 10.0;
if (callback) callback (DC_SAMPLE_DEPTH, &sample, userdata);
// Temperature (1/10 °C).
sample.temperature = temperature / 10.0;
if (callback) callback (DC_SAMPLE_TEMPERATURE, &sample, userdata);
// Current gas mix
if (parser->ngasmixes > 0) {
if (gasmix >= parser->ngasmixes) {
ERROR (abstract->context, "Invalid gas mix index.");
return DC_STATUS_DATAFORMAT;
}
if (gasmix != gasmix_previous) {
sample.gasmix = gasmix;
if (callback) callback (DC_SAMPLE_GASMIX, &sample, userdata);
gasmix_previous = gasmix;
}
}
offset += parser->samplesize;
nsamples++;
// Some extra data.
if (parser->layout->tanks != UNSUPPORTED && (nsamples % 4) == 0) {
// Pressure (1/100 bar).
unsigned int pressure = array_uint16_le(data + offset + marker + 0);
if (gasmix < parser->ntanks) {
sample.pressure.tank = gasmix;
sample.pressure.value = pressure / 100.0;
if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata);
} else if (pressure != 0) {
WARNING (abstract->context, "Invalid tank with non-zero pressure.");
}
offset += 8;
}
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_genius_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
mares_iconhd_parser_t *parser = (mares_iconhd_parser_t *) abstract;
const unsigned char *data = abstract->data;
const unsigned int size = abstract->size;
// Previous gas mix - initialize with impossible value
unsigned int gasmix_previous = 0xFFFFFFFF;
unsigned int tank = 0xFFFFFFFF;
// Skip the dive header.
unsigned int offset = parser->headersize;
if (offset + 4 > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
// Check the profile type and version.
unsigned int profile_type = array_uint16_le (data + offset);
unsigned int profile_minor = data[offset + 2];
unsigned int profile_major = data[offset + 3];
if (profile_type > 1 ||
(profile_type == 0 && OBJVERSION(profile_major,profile_minor) > OBJVERSION(1,0)) ||
(profile_type == 1 && OBJVERSION(profile_major,profile_minor) > OBJVERSION(0,2))) {
ERROR (abstract->context, "Unsupported object type (%u) or version (%u.%u).",
profile_type, profile_major, profile_minor);
return DC_STATUS_DATAFORMAT;
}
offset += 4;
unsigned int time = 0;
unsigned int marker = 4;
while (offset < size) {
dc_sample_value_t sample = {0};
if (offset + 10 > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
// Get the record type and length.
unsigned int type = array_uint32_be(data + offset);
unsigned int length = 0;
switch (type) {
case DSTR_TYPE:
length = DSTR_SIZE;
break;
case TISS_TYPE:
length = TISS_SIZE;
break;
case DPRS_TYPE:
length = DPRS_SIZE;
break;
case SDPT_TYPE:
length = SDPT_SIZE;
break;
case AIRS_TYPE:
length = AIRS_SIZE;
break;
case DEND_TYPE:
length = DEND_SIZE;
break;
default:
ERROR (abstract->context, "Unknown record type (%08x).", type);
return DC_STATUS_DATAFORMAT;
}
if (offset + length > size) {
ERROR (abstract->context, "Buffer overflow detected!");
return DC_STATUS_DATAFORMAT;
}
unsigned int etype = array_uint32_be(data + offset + length - 4);
if (etype != type) {
ERROR (abstract->context, "Invalid record end type (%08x).", etype);
return DC_STATUS_DATAFORMAT;
}
unsigned short crc = array_uint16_le(data + offset + length - 6);
unsigned short ccrc = checksum_crc16_ccitt(data + offset + 4, length - 10, 0x0000, 0x0000);
if (crc != ccrc) {
ERROR (abstract->context, "Invalid record checksum (%04x %04x).", crc, ccrc);
return DC_STATUS_DATAFORMAT;
}
if (type == DPRS_TYPE || type == SDPT_TYPE) {
unsigned int depth = 0, temperature = 0;
unsigned int gasmix = 0, alarms = 0;
unsigned int decostop = 0, decodepth = 0, decotime = 0, tts = 0;
unsigned int bookmark = 0;
if (ISGENIUS(parser->model)) {
if (parser->logformat == 1) {
if (!mares_genius_isvalid (data + offset, SDPT_SIZE, SDPT_TYPE)) {
ERROR (abstract->context, "Invalid SDPT record.");
return DC_STATUS_DATAFORMAT;
}
unsigned int misc = 0, deco = 0;
depth = array_uint16_le (data + offset + marker + 2);
temperature = array_uint16_le (data + offset + marker + 6);
alarms = array_uint32_le (data + offset + marker + 0x14);
misc = array_uint32_le (data + offset + marker + 0x18);
deco = array_uint32_le (data + offset + marker + 0x1C);
bookmark = (misc >> 2) & 0x0F;
gasmix = (misc >> 6) & 0x0F;
decostop = (misc >> 10) & 0x01;
if (decostop) {
decodepth = (deco >> 3) & 0x7F;
decotime = (deco >> 10) & 0xFF;
tts = (deco >> 18) & 0x3FFF;
} else {
decotime = deco & 0xFF;
}
if (type == SDPT_TYPE) {
unsigned int misc = 0, deco = 0;
depth = array_uint16_le (data + offset + marker + 2);
temperature = array_uint16_le (data + offset + marker + 6);
alarms = array_uint32_le (data + offset + marker + 0x14);
misc = array_uint32_le (data + offset + marker + 0x18);
deco = array_uint32_le (data + offset + marker + 0x1C);
bookmark = (misc >> 2) & 0x0F;
gasmix = (misc >> 6) & 0x0F;
decostop = (misc >> 10) & 0x01;
if (decostop) {
decodepth = (deco >> 3) & 0x7F;
decotime = (deco >> 10) & 0xFF;
tts = (deco >> 18) & 0x3FFF;
} else {
if (!mares_genius_isvalid (data + offset, DPRS_SIZE, DPRS_TYPE)) {
ERROR (abstract->context, "Invalid DPRS record.");
return DC_STATUS_DATAFORMAT;
}
unsigned int misc = 0;
depth = array_uint16_le (data + offset + marker + 0);
temperature = array_uint16_le (data + offset + marker + 4);
decotime = array_uint16_le (data + offset + marker + 0x0A);
alarms = array_uint32_le (data + offset + marker + 0x0C);
misc = array_uint32_le (data + offset + marker + 0x14);
bookmark = (misc >> 2) & 0x0F;
gasmix = (misc >> 6) & 0x0F;
decostop = (misc >> 18) & 0x01;
decodepth = (misc >> 19) & 0x7F;
decotime = deco & 0xFF;
}
} else {
depth = array_uint16_le (data + offset + 0);
temperature = array_uint16_le (data + offset + 2) & 0x0FFF;
gasmix = (data[offset + 3] & 0xF0) >> 4;
unsigned int misc = 0;
depth = array_uint16_le (data + offset + marker + 0);
temperature = array_uint16_le (data + offset + marker + 4);
decotime = array_uint16_le (data + offset + marker + 0x0A);
alarms = array_uint32_le (data + offset + marker + 0x0C);
misc = array_uint32_le (data + offset + marker + 0x14);
bookmark = (misc >> 2) & 0x0F;
gasmix = (misc >> 6) & 0x0F;
decostop = (misc >> 18) & 0x01;
decodepth = (misc >> 19) & 0x7F;
}
// Time (seconds).
@ -1114,6 +1169,9 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
}
}
// Current tank
tank = gasmix;
// Bookmark
if (bookmark) {
sample.event.type = SAMPLE_EVENT_BOOKMARK;
@ -1123,82 +1181,76 @@ mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t
if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata);
}
if (ISGENIUS(parser->model)) {
// Deco stop / NDL.
if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = decodepth;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0;
// Deco stop / NDL.
if (decostop) {
sample.deco.type = DC_DECO_DECOSTOP;
sample.deco.depth = decodepth;
} else {
sample.deco.type = DC_DECO_NDL;
sample.deco.depth = 0.0;
}
sample.deco.time = decotime * 60;
sample.deco.tts = tts;
if (callback) callback (DC_SAMPLE_DECO, &sample, userdata);
// Alarms
for (unsigned int v = alarms, i = 0; v; v >>= 1, ++i) {
if ((v & 1) == 0) {
continue;
}
sample.deco.time = decotime * 60;
sample.deco.tts = tts;
if (callback) callback (DC_SAMPLE_DECO, &sample, userdata);
// Alarms
for (unsigned int v = alarms, i = 0; v; v >>= 1, ++i) {
if ((v & 1) == 0) {
continue;
}
switch (i) {
case ALARM_FAST_ASCENT:
case ALARM_UNCONTROLLED_ASCENT:
sample.event.type = SAMPLE_EVENT_ASCENT;
break;
case ALARM_MISSED_DECO:
case ALARM_DIVE_VIOLATION_DECO:
sample.event.type = SAMPLE_EVENT_CEILING;
break;
default:
sample.event.type = SAMPLE_EVENT_NONE;
break;
}
switch (i) {
case ALARM_FAST_ASCENT:
case ALARM_UNCONTROLLED_ASCENT:
sample.event.type = SAMPLE_EVENT_ASCENT;
break;
case ALARM_MISSED_DECO:
case ALARM_DIVE_VIOLATION_DECO:
sample.event.type = SAMPLE_EVENT_CEILING;
break;
default:
sample.event.type = SAMPLE_EVENT_NONE;
break;
}
if (sample.event.type != SAMPLE_EVENT_NONE) {
sample.event.time = 0;
sample.event.flags = 0;
sample.event.value = 0;
if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata);
}
if (sample.event.type != SAMPLE_EVENT_NONE) {
sample.event.time = 0;
sample.event.flags = 0;
sample.event.value = 0;
if (callback) callback (DC_SAMPLE_EVENT, &sample, userdata);
}
}
offset += parser->samplesize;
nsamples++;
// Some extra data.
if (parser->layout->tanks != UNSUPPORTED && (nsamples % 4) == 0) {
if (ISGENIUS(parser->model) &&
!mares_genius_isvalid (data + offset, AIRS_SIZE, AIRS_TYPE)) {
ERROR (abstract->context, "Invalid AIRS record.");
return DC_STATUS_DATAFORMAT;
}
// Pressure (1/100 bar).
unsigned int pressure = array_uint16_le(data + offset + marker + 0);
if (gasmix < parser->ntanks) {
sample.pressure.tank = gasmix;
sample.pressure.value = pressure / 100.0;
if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata);
} else if (pressure != 0) {
WARNING (abstract->context, "Invalid tank with non-zero pressure.");
}
offset += ISGENIUS(parser->model) ? AIRS_SIZE : 8;
} else if (type == AIRS_TYPE) {
// Pressure (1/100 bar).
unsigned int pressure = array_uint16_le(data + offset + marker + 0);
if (tank < parser->ntanks) {
sample.pressure.tank = tank;
sample.pressure.value = pressure / 100.0;
if (callback) callback (DC_SAMPLE_PRESSURE, &sample, userdata);
} else if (pressure != 0) {
WARNING (abstract->context, "Invalid tank with non-zero pressure.");
}
}
}
if (ISGENIUS(parser->model)) {
// Skip the DEND record.
if (!mares_genius_isvalid (data + offset, DEND_SIZE, DEND_TYPE)) {
ERROR (abstract->context, "Invalid DEND record.");
return DC_STATUS_DATAFORMAT;
}
offset += DEND_SIZE;
offset += length;
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
mares_iconhd_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
{
mares_iconhd_parser_t *parser = (mares_iconhd_parser_t *) abstract;
// Cache the parser data.
dc_status_t rc = mares_iconhd_parser_cache (parser);
if (rc != DC_STATUS_SUCCESS)
return rc;
if (ISGENIUS(parser->model)) {
return mares_genius_foreach (abstract, callback, userdata);
} else {
return mares_iconhd_foreach (abstract, callback, userdata);
}
}