diff --git a/src/deepsix_excursion.c b/src/deepsix_excursion.c index c6eaaa8..de0bb64 100644 --- a/src/deepsix_excursion.c +++ b/src/deepsix_excursion.c @@ -32,7 +32,8 @@ #define MAXPACKET 255 -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 +#define HEADERSIZE_V0 156 #define NSTEPS 1000 #define STEP(i,n) (NSTEPS * (i) / (n)) @@ -55,9 +56,14 @@ #define CMD_SETTINGS_STORE 0x27 #define CMD_SETTINGS_LOAD 0x28 -#define GRP_DIVE 0xC0 -#define CMD_DIVE_HEADER 0x02 -#define CMD_DIVE_PROFILE 0x03 +#define GRP_DIVE 0xC0 +#define CMD_DIVE_HEADER 0x02 +#define CMD_DIVE_PROFILE 0x03 +#define CMD_DIVE_COUNT 0x05 +#define CMD_DIVE_INDEX_LAST 0x06 +#define CMD_DIVE_INDEX_FIRST 0x07 +#define CMD_DIVE_INDEX_PREVIOUS 0x08 +#define CMD_DIVE_INDEX_NEXT 0x09 typedef struct deepsix_excursion_device_t { dc_device_t base; @@ -216,8 +222,8 @@ deepsix_excursion_device_open (dc_device_t **out, dc_context_t *context, dc_iost goto error_free; } - // Set the timeout for receiving data (1000ms). - status = dc_iostream_set_timeout (device->iostream, 1000); + // Set the timeout for receiving data (3000ms). + status = dc_iostream_set_timeout (device->iostream, 3000); if (status != DC_STATUS_SUCCESS) { ERROR (context, "Failed to set the timeout."); goto error_free; @@ -300,17 +306,41 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call devinfo.serial = array_convert_str2num (rsp_serial + 3, sizeof(rsp_serial) - 3); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); - // Read the index of the last dive. - const unsigned char cmd_index[2] = {0}; - unsigned char rsp_index[2] = {0}; - status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_index, sizeof(cmd_index), rsp_index, sizeof(rsp_index), NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to read the last dive index."); - return status; - } + // Firmware version 6+ uses the new commands. + unsigned int fw6 = memcmp(rsp_software, "D01", 3) == 0 && rsp_software[4] >= '6'; - // Calculate the number of dives. - unsigned int ndives = array_uint16_le (rsp_index); + unsigned int ndives = 0, last = 0; + if (fw6) { + // Read the number of dives. + unsigned char rsp_ndives[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_COUNT, DIR_READ, NULL, 0, rsp_ndives, sizeof(rsp_ndives), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the number of dives"); + return status; + } + + // Read the index of the last dive. + unsigned char rsp_last[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_LAST, DIR_READ, NULL, 0, rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = array_uint16_le (rsp_ndives); + last = array_uint16_le (rsp_last); + } else { + // Read the index of the last dive. + const unsigned char cmd_last[2] = {0}; + unsigned char rsp_last[2] = {0}; + status = deepsix_excursion_transfer (device, GRP_INFO, CMD_INFO_LASTDIVE, DIR_READ, cmd_last, sizeof(cmd_last), rsp_last, sizeof(rsp_last), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the last dive index."); + return status; + } + + ndives = last = array_uint16_le (rsp_last); + } // Update and emit a progress event. progress.current = 1 * NSTEPS; @@ -323,32 +353,58 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call return DC_STATUS_NOMEMORY; } + unsigned int number = last; for (unsigned int i = 0; i < ndives; ++i) { - unsigned int number = ndives - i; + if (fw6) { + if (i > 0) { + const unsigned char cmd_previous[] = { + (number ) & 0xFF, + (number >> 8) & 0xFF, + (number >> 16) & 0xFF, + (number >> 24) & 0xFF}; + unsigned char rsp_previous[4] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_INDEX_PREVIOUS, DIR_READ, + cmd_previous, sizeof(cmd_previous), rsp_previous, sizeof(rsp_previous), NULL); + if (status != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the previous dive index"); + return status; + } + number = array_uint32_le (rsp_previous); + } + } else { + number = ndives - i; + } + unsigned int headersize = 0; const unsigned char cmd_header[] = { (number ) & 0xFF, (number >> 8) & 0xFF}; - unsigned char rsp_header[HEADERSIZE] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), NULL); + unsigned char rsp_header[MAXPACKET] = {0}; + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_HEADER, DIR_READ, + cmd_header, sizeof(cmd_header), rsp_header, sizeof(rsp_header), &headersize); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive header."); goto error_free; } + if (headersize < HEADERSIZE_MIN || headersize != (fw6 ? rsp_header[2] : HEADERSIZE_V0)) { + ERROR (abstract->context, "Unexpected size of the dive header (%u).", headersize); + goto error_free; + } + if (memcmp(rsp_header + FP_OFFSET, device->fingerprint, sizeof(device->fingerprint)) == 0) break; unsigned int length = array_uint32_le (rsp_header + 8); // Update and emit a progress event. - progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header), sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); dc_buffer_clear(buffer); - dc_buffer_reserve(buffer, sizeof(rsp_header) + length); + dc_buffer_reserve(buffer, headersize + length); - if (!dc_buffer_append(buffer, rsp_header, sizeof(rsp_header))) { + if (!dc_buffer_append(buffer, rsp_header, headersize)) { ERROR (abstract->context, "Insufficient buffer space available."); status = DC_STATUS_NOMEMORY; goto error_free; @@ -365,7 +421,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call (offset >> 16) & 0xFF, (offset >> 24) & 0xFF}; unsigned char rsp_profile[MAXPACKET] = {0}; - status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); + status = deepsix_excursion_transfer (device, GRP_DIVE, CMD_DIVE_PROFILE, DIR_READ, + cmd_profile, sizeof(cmd_profile), rsp_profile, sizeof(rsp_profile), &len); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to read the dive profile."); goto error_free; @@ -378,8 +435,8 @@ deepsix_excursion_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Update and emit a progress event. - progress.current = (i + 1) * NSTEPS + STEP(sizeof(rsp_header) + offset + n, sizeof(rsp_header) + length); - device_event_emit (abstract, DC_EVENT_PROGRESS, &progress); + progress.current = (i + 1) * NSTEPS + STEP(headersize + offset + n, headersize + length); + device_event_emit(abstract, DC_EVENT_PROGRESS, &progress); if (!dc_buffer_append(buffer, rsp_profile, n)) { ERROR (abstract->context, "Insufficient buffer space available."); diff --git a/src/deepsix_excursion_parser.c b/src/deepsix_excursion_parser.c index 687f86f..2fc8c2c 100644 --- a/src/deepsix_excursion_parser.c +++ b/src/deepsix_excursion_parser.c @@ -31,7 +31,11 @@ #include "parser-private.h" #include "array.h" -#define HEADERSIZE 156 +#define HEADERSIZE_MIN 128 + +#define MAX_SAMPLES 7 +#define MAX_EVENTS 7 +#define MAX_GASMIXES 20 #define ALARM 0x0001 #define TEMPERATURE 0x0002 @@ -39,20 +43,81 @@ #define CEILING 0x0004 #define CNS 0x0005 -#define DENSITY 1024.0 +#define SAMPLE_TEMPERATURE 0 +#define SAMPLE_DECO_NDL 1 +#define SAMPLE_CNS 2 + +#define EVENT_CHANGE_GAS 7 +#define EVENT_ALARMS 8 +#define EVENT_CHANGE_SETPOINT 9 +#define EVENT_SAMPLES_MISSED 10 +#define EVENT_RESERVED 15 + +#define ALARM_ASCENTRATE 0 +#define ALARM_CEILING 1 +#define ALARM_PO2 2 +#define ALARM_MAXDEPTH 3 +#define ALARM_DIVETIME 4 +#define ALARM_CNS 5 + +#define DECOSTOP 0x02 +#define SAFETYSTOP 0x04 + +#define DENSITY 1024 + +#define UNDEFINED 0xFFFFFFFF #define FWVERSION(major,minor) ( \ ((((major) + '0') & 0xFF) << 8) | \ ((minor) & 0xFF)) +typedef struct deepsix_excursion_layout_t { + unsigned int headersize; + unsigned int version; + unsigned int divemode; + unsigned int samplerate; + unsigned int salinity; + unsigned int datetime; + unsigned int divetime; + unsigned int maxdepth; + unsigned int temperature_min; + unsigned int avgdepth; + unsigned int firmware; + unsigned int temperature_surf; + unsigned int atmospheric; + unsigned int gf; +} deepsix_excursion_layout_t; + +typedef struct deepsix_excursion_gasmix_t { + unsigned int id; + unsigned int oxygen; + unsigned int helium; +} deepsix_excursion_gasmix_t; + +typedef struct deepsix_excursion_sample_info_t { + unsigned int type; + unsigned int divisor; + unsigned int size; +} deepsix_excursion_sample_info_t; + +typedef struct deepsix_excursion_event_info_t { + unsigned int type; + unsigned int size; +} deepsix_excursion_event_info_t; + typedef struct deepsix_excursion_parser_t { dc_parser_t base; + unsigned int cached; + unsigned int ngasmixes; + deepsix_excursion_gasmix_t gasmix[MAX_GASMIXES]; } deepsix_excursion_parser_t; static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); static dc_status_t deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime); static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); static const dc_parser_vtable_t deepsix_parser_vtable = { sizeof(deepsix_excursion_parser_t), @@ -67,6 +132,58 @@ static const dc_parser_vtable_t deepsix_parser_vtable = { NULL /* destroy */ }; +static const deepsix_excursion_layout_t deepsix_excursion_layout_v0 = { + 156,/* headersize */ + UNDEFINED, /* version */ + 4, /* divemode */ + 24, /* samplerate */ + UNDEFINED, /* salinity */ + 12, /* datetime */ + 20, /* divetime */ + 28, /* maxdepth */ + 32, /* temperature_min */ + UNDEFINED, /* avgdepth */ + 48, /* firmware */ + UNDEFINED, /* temperature_surf */ + 56, /* atmospheric */ + UNDEFINED, /* gf */ +}; + +static const deepsix_excursion_layout_t deepsix_excursion_layout_v1 = { + 129,/* headersize */ + 3, /* version */ + 4, /* divemode */ + 5, /* samplerate */ + 7, /* salinity */ + 12, /* datetime */ + 19, /* divetime */ + 29, /* maxdepth */ + 31, /* temperature_min */ + 33, /* avgdepth */ + 35, /* firmware */ + 43, /* temperature_surf */ + 45, /* atmospheric */ + 127, /* gf */ +}; + +static double +pressure_to_depth(unsigned int depth, unsigned int atmospheric, unsigned int density) +{ + return ((signed int)(depth - atmospheric)) * (BAR / 1000.0) / (density * GRAVITY); +} + +static unsigned int +deepsix_excursion_find_gasmix(deepsix_excursion_parser_t *parser, unsigned int o2, unsigned int he, unsigned int id) +{ + unsigned int i = 0; + while (i < parser->ngasmixes) { + if (o2 == parser->gasmix[i].oxygen && he == parser->gasmix[i].helium && id == parser->gasmix[i].id) + break; + i++; + } + return i; +} + dc_status_t deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) { @@ -82,6 +199,15 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) return DC_STATUS_NOMEMORY; } + // Set the default values. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + *out = (dc_parser_t *) parser; return DC_STATUS_SUCCESS; @@ -90,6 +216,17 @@ deepsix_excursion_parser_create (dc_parser_t **out, dc_context_t *context) static dc_status_t deepsix_excursion_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + + // Reset the cache. + parser->cached = 0; + parser->ngasmixes = 0; + for (unsigned int i = 0; i < MAX_GASMIXES; ++i) { + parser->gasmix[i].id = 0; + parser->gasmix[i].oxygen = 0; + parser->gasmix[i].helium = 0; + } + return DC_STATUS_SUCCESS; } @@ -99,23 +236,36 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; - unsigned int firmware = array_uint16_be (data + 48 + 4); + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int firmware = array_uint16_be (data + layout->firmware + 4); + + const unsigned char *p = data + layout->datetime; if (datetime) { - datetime->year = data[12] + 2000; - datetime->month = data[13]; - datetime->day = data[14]; - datetime->hour = data[15]; - datetime->minute = data[16]; - datetime->second = data[17]; + datetime->year = p[0] + 2000; + datetime->month = p[1]; + datetime->day = p[2]; + datetime->hour = p[3]; + datetime->minute = p[4]; + datetime->second = p[5]; - if (firmware >= FWVERSION(5, 'B')) { - datetime->timezone = (data[18] - 12) * 3600; + if (version == 0) { + if (firmware >= FWVERSION(5, 'B')) { + datetime->timezone = (p[6] - 12) * 3600; + } else { + datetime->timezone = DC_TIMEZONE_NONE; + } } else { - datetime->timezone = DC_TIMEZONE_NONE; + datetime->timezone = ((signed char) p[6]) * 900; } } @@ -125,37 +275,74 @@ deepsix_excursion_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *dat static dc_status_t deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value) { + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; const unsigned char *data = abstract->data; unsigned int size = abstract->size; - if (size < HEADERSIZE) + dc_gasmix_t *gasmix = (dc_gasmix_t *) value; + dc_salinity_t *water = (dc_salinity_t *) value; + dc_decomodel_t *decomodel = (dc_decomodel_t *) value; + + if (size < HEADERSIZE_MIN) return DC_STATUS_DATAFORMAT; - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int version = data[3]; + const deepsix_excursion_layout_t *layout = version == 0 ? + &deepsix_excursion_layout_v0 : &deepsix_excursion_layout_v1; - dc_salinity_t *water = (dc_salinity_t *) value; + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + if (version != 0 && !parser->cached) { + dc_status_t rc = deepsix_excursion_parser_samples_foreach_v1(abstract, NULL, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + } + + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = DENSITY; + if (layout->salinity != UNDEFINED) { + density = 1000 + data[layout->salinity] * 10; + } if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint32_le(data + 20); + *((unsigned int *) value) = array_uint32_le(data + layout->divetime); break; case DC_FIELD_MAXDEPTH: - *((double *) value) = (signed int)(array_uint32_le(data + 28) - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->maxdepth), atmospheric, density); break; + case DC_FIELD_AVGDEPTH: + if (layout->avgdepth == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = pressure_to_depth(array_uint16_le(data + layout->avgdepth), atmospheric, density); + break; + case DC_FIELD_GASMIX_COUNT: + *((unsigned int *) value) = parser->ngasmixes; + break; + case DC_FIELD_GASMIX: + gasmix->oxygen = parser->gasmix[flags].oxygen / 100.0; + gasmix->helium = parser->gasmix[flags].helium / 100.0; + gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_TEMPERATURE_MINIMUM: - *((double *) value) = (signed int) array_uint32_le(data + 32) / 10.0; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_min) / 10.0; + break; + case DC_FIELD_TEMPERATURE_SURFACE: + if (layout->temperature_surf == UNDEFINED) + return DC_STATUS_UNSUPPORTED; + *((double *) value) = (signed int) array_uint16_le(data + layout->temperature_surf) / 10.0; break; case DC_FIELD_ATMOSPHERIC: *((double *) value) = atmospheric / 1000.0; break; case DC_FIELD_SALINITY: - water->type = DC_WATER_SALT; - water->density = DENSITY; + water->type = (density == 1000) ? DC_WATER_FRESH : DC_WATER_SALT; + water->density = density; break; case DC_FIELD_DIVEMODE: - switch (array_uint32_le(data + 4)) { + switch (data[layout->divemode]) { case 0: *((dc_divemode_t *) value) = DC_DIVEMODE_OC; break; @@ -169,6 +356,17 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, return DC_STATUS_DATAFORMAT; } break; + case DC_FIELD_DECOMODEL: + decomodel->type = DC_DECOMODEL_BUHLMANN; + decomodel->conservatism = 0; + if (layout->gf != UNDEFINED) { + decomodel->params.gf.low = data[layout->gf + 0]; + decomodel->params.gf.high = data[layout->gf + 1]; + } else { + decomodel->params.gf.low = 0; + decomodel->params.gf.high = 0; + } + break; default: return DC_STATUS_UNSUPPORTED; } @@ -178,23 +376,24 @@ deepsix_excursion_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, } static dc_status_t -deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +deepsix_excursion_parser_samples_foreach_v0 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { const unsigned char *data = abstract->data; unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v0; - if (size < HEADERSIZE) + if (size < layout->headersize) return DC_STATUS_DATAFORMAT; - int firmware4c = memcmp(data + 48, "D01-4C", 6) == 0; + int firmware4c = memcmp(data + layout->firmware, "D01-4C", 6) == 0; unsigned int maxtype = firmware4c ? TEMPERATURE : CNS; - unsigned int interval = array_uint32_le(data + 24); - unsigned int atmospheric = array_uint32_le(data + 56); + unsigned int interval = array_uint32_le(data + layout->samplerate); + unsigned int atmospheric = array_uint32_le(data + layout->atmospheric); unsigned int time = 0; - unsigned int offset = HEADERSIZE; + unsigned int offset = layout->headersize; while (offset + 1 < size) { dc_sample_value_t sample = {0}; @@ -227,7 +426,7 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb sample.time = time; if (callback) callback(DC_SAMPLE_TIME, sample, userdata); - sample.depth = (signed int)(depth - atmospheric) * (BAR / 1000.0) / (DENSITY * GRAVITY); + sample.depth = pressure_to_depth(depth, atmospheric, DENSITY); if (callback) callback(DC_SAMPLE_DEPTH, sample, userdata); } @@ -263,3 +462,325 @@ deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callb return DC_STATUS_SUCCESS; } + +static dc_status_t +deepsix_excursion_parser_samples_foreach_v1 (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + deepsix_excursion_parser_t *parser = (deepsix_excursion_parser_t *) abstract; + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + const deepsix_excursion_layout_t *layout = &deepsix_excursion_layout_v1; + + if (size < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int headersize = data[2]; + if (headersize < layout->headersize) + return DC_STATUS_DATAFORMAT; + + unsigned int samplerate = data[layout->samplerate]; + unsigned int atmospheric = array_uint16_le(data + layout->atmospheric); + unsigned int density = 1000 + data[layout->salinity] * 10; + + unsigned int offset = headersize; + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nconfig = data[offset]; + if (nconfig > MAX_SAMPLES) { + ERROR(abstract->context, "Too many sample descriptors (%u).", nconfig); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 3 * nconfig > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_sample_info_t sample_info[MAX_SAMPLES] = {{0}}; + for (unsigned int i = 0; i < nconfig; i++) { + sample_info[i].type = data[offset + 3 * i + 0]; + sample_info[i].size = data[offset + 3 * i + 1]; + sample_info[i].divisor = data[offset + 3 * i + 2]; + + if (sample_info[i].divisor) { + switch (sample_info[i].type) { + case SAMPLE_CNS: + case SAMPLE_TEMPERATURE: + if (sample_info[i].size != 2) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case SAMPLE_DECO_NDL: + if (sample_info[i].size != 7) { + ERROR(abstract->context, "Unexpected sample size (%u).", sample_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown sample descriptor (%u %u %u).", + sample_info[i].type, sample_info[i].size, sample_info[i].divisor); + break; + } + } + } + + offset += 3 * nconfig; + + if (offset + 1 > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int nevents = data[offset]; + if (nevents > MAX_EVENTS) { + ERROR(abstract->context, "Too many event descriptors (%u).", nevents); + return DC_STATUS_DATAFORMAT; + } + + offset += 1; + + if (offset + 2 * nevents > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + deepsix_excursion_event_info_t event_info[MAX_EVENTS] = {{0}}; + for (unsigned int i = 0; i < nevents; i++) { + event_info[i].type = data[offset + 2 * i]; + event_info[i].size = data[offset + 2 * i + 1]; + + switch (event_info[i].type) { + case EVENT_ALARMS: + if (event_info[i].size != 1) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_CHANGE_GAS: + if (event_info[i].size != 3) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + case EVENT_SAMPLES_MISSED: + if (event_info[i].size != 6) { + ERROR(abstract->context, "Unexpected event size (%u).", event_info[i].size); + return DC_STATUS_DATAFORMAT; + } + break; + default: + WARNING (abstract->context, "Unknown event descriptor (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + } + + offset += 2 * nevents; + + unsigned int time = 0; + unsigned int nsamples = 0; + while (offset + 3 <= size) { + dc_sample_value_t sample = {0}; + nsamples++; + + // Time (seconds). + time += samplerate; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + unsigned int depth = array_uint16_le (data + offset); + sample.depth = pressure_to_depth(depth, atmospheric, density); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + offset += 2; + + // event info + unsigned int length = data[offset]; + offset += 1; + + if (offset + length > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + if (length) { + if (length < 2) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int events = array_uint16_le (data + offset); + unsigned int event_offset = 2; + + for (unsigned int i = 0; i < nevents; i++) { + if ((events & (1 << event_info[i].type)) == 0) + continue; + + if (event_offset + event_info[i].size > length) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int alarms = 0; + unsigned int id = 0, o2 = 0, he = 0; + unsigned int mix_idx = 0; + unsigned int count = 0, timestamp = 0; + switch (event_info[i].type) { + case EVENT_ALARMS: + alarms = data[offset + event_offset]; + for (unsigned int v = alarms, j = 0; v; v >>= 1, ++j) { + if ((v & 1) == 0) + continue; + + sample.event.type = SAMPLE_EVENT_NONE; + sample.event.time = 0; + sample.event.flags = 0; + sample.event.value = 0; + switch (j) { + case ALARM_ASCENTRATE: + sample.event.type = SAMPLE_EVENT_ASCENT; + break; + case ALARM_CEILING: + sample.event.type = SAMPLE_EVENT_CEILING; + break; + case ALARM_PO2: + sample.event.type = SAMPLE_EVENT_PO2; + break; + case ALARM_MAXDEPTH: + sample.event.type = SAMPLE_EVENT_MAXDEPTH; + break; + case ALARM_DIVETIME: + sample.event.type = SAMPLE_EVENT_DIVETIME; + break; + case ALARM_CNS: + break; + default: + WARNING (abstract->context, "Unknown event (%u).", j); + break; + } + if (sample.event.type != SAMPLE_EVENT_NONE) { + if (callback) callback (DC_SAMPLE_EVENT, sample, userdata); + } + } + break; + case EVENT_CHANGE_GAS: + id = data[offset + event_offset]; + o2 = data[offset + event_offset + 1]; + he = data[offset + event_offset + 2]; + + mix_idx = deepsix_excursion_find_gasmix(parser, o2, he, id); + if (mix_idx >= parser->ngasmixes) { + if (mix_idx >= MAX_GASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + parser->gasmix[mix_idx].oxygen = o2; + parser->gasmix[mix_idx].helium = he; + parser->gasmix[mix_idx].id = id; + parser->ngasmixes = mix_idx + 1; + } + + sample.gasmix = mix_idx; + if (callback) callback(DC_SAMPLE_GASMIX, sample, userdata); + break; + case EVENT_SAMPLES_MISSED: + count = array_uint16_le(data + offset + event_offset); + timestamp = array_uint32_le(data + offset + event_offset + 2); + if (timestamp < time) { + ERROR (abstract->context, "Timestamp moved backwards (%u %u).", timestamp, time); + return DC_STATUS_DATAFORMAT; + } + nsamples += count; + time = timestamp; + break; + default: + WARNING (abstract->context, "Unknown event (%u %u).", + event_info[i].type, event_info[i].size); + break; + } + event_offset += event_info[i].size; + } + + // Skip remaining sample bytes (if any). + if (event_offset < length) { + WARNING (abstract->context, "Remaining %u bytes skipped.", length - event_offset); + } + offset += length; + } + + for (unsigned int i = 0; i < nconfig; ++i) { + if (sample_info[i].divisor && (nsamples % sample_info[i].divisor) == 0) { + if (offset + sample_info[i].size > size) { + ERROR (abstract->context, "Buffer overflow detected!"); + return DC_STATUS_DATAFORMAT; + } + + unsigned int value = 0; + unsigned int deco_flags = 0, deco_ndl_tts = 0; + unsigned int deco_depth = 0, deco_time = 0; + switch (sample_info[i].type) { + case SAMPLE_TEMPERATURE: + value = array_uint16_le(data + offset); + sample.temperature = value / 10.0; + if (callback) callback(DC_SAMPLE_TEMPERATURE, sample, userdata); + break; + case SAMPLE_CNS: + value = array_uint16_le(data + offset); + sample.cns = value / 10000.0; + if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + break; + case SAMPLE_DECO_NDL: + deco_flags = data[offset]; + deco_ndl_tts = array_uint16_le(data + offset + 1); + deco_depth = array_uint16_le(data + offset + 3); + deco_time = array_uint16_le(data + offset + 5); + if (deco_flags & DECOSTOP) { + sample.deco.type = DC_DECO_DECOSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else if (deco_flags & SAFETYSTOP) { + sample.deco.type = DC_DECO_SAFETYSTOP; + sample.deco.depth = pressure_to_depth(deco_depth, atmospheric, density); + sample.deco.time = deco_time; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0; + sample.deco.time = deco_ndl_tts; + } + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + break; + default: + break; + } + offset += sample_info[i].size; + } + } + } + + parser->cached = 1; + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +deepsix_excursion_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) +{ + const unsigned char *data = abstract->data; + unsigned int size = abstract->size; + + if (size < HEADERSIZE_MIN) + return DC_STATUS_DATAFORMAT; + + unsigned int version = data[3]; + + if (version == 0) { + return deepsix_excursion_parser_samples_foreach_v0(abstract, callback, userdata); + } else { + return deepsix_excursion_parser_samples_foreach_v1(abstract, callback, userdata); + } +}