From 0b2959d8c707888c94f22ad42e0e8acf1257f7d5 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 12 Oct 2018 09:42:26 +0200 Subject: [PATCH 1/5] Fix a potential buffer overflow Check whether there is space available for a complete sample, and not just a single byte! --- src/shearwater_predator_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index dd9992a..f6a7b1d 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -264,7 +264,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) unsigned int offset = headersize; unsigned int length = size - footersize; - while (offset < length) { + while (offset + parser->samplesize <= length) { // Ignore empty samples. if (array_isequal (data + offset, parser->samplesize, 0x00)) { offset += parser->samplesize; @@ -440,7 +440,7 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal unsigned int time = 0; unsigned int offset = parser->headersize; unsigned int length = size - parser->footersize; - while (offset < length) { + while (offset + parser->samplesize <= length) { dc_sample_value_t sample = {0}; // Ignore empty samples. From 864b40cb3d2784a1e54dab67fd78503b80863866 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 7 Sep 2018 15:12:12 -0700 Subject: [PATCH 2/5] Shearwater: skip deleted dives Without this change a deleted dive on device is treated like the end of the dive list. Signed-off-by: Dirk Hohndel --- src/shearwater_petrel.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index 2e87a3f..f0eadd0 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -263,11 +263,17 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int size = dc_buffer_get_size (buffer); // Process the records in the manifest. - unsigned int count = 0; + unsigned int count = 0, deleted = 0; unsigned int offset = 0; while (offset < size) { // Check for a valid dive header. unsigned int header = array_uint16_be (data + offset); + if (header == 0x5A23) { + // this is a deleted dive; keep looking + offset += RECORD_SIZE; + deleted++; + continue; + } if (header != 0xA5C4) break; @@ -281,7 +287,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Update the progress state. current += 1; - maximum -= RECORD_COUNT - count; + maximum -= RECORD_COUNT - count - deleted; // Append the manifest records to the main buffer. if (!dc_buffer_append (manifests, data, count * RECORD_SIZE)) { @@ -292,7 +298,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call } // Stop downloading manifest if there are no more records. - if (count != RECORD_COUNT) + if (count + deleted != RECORD_COUNT) break; } @@ -307,6 +313,11 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call unsigned int offset = 0; while (offset < size) { + // skip deleted dives + if (array_uint16_be(data + offset) == 0x5A23) { + offset += RECORD_SIZE; + continue; + } // Get the address of the dive. unsigned int address = array_uint32_be (data + offset + 20); From 472e73118d4a78f49aeee4eda5989c3c17d6f236 Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Sat, 8 Sep 2018 17:17:47 -0700 Subject: [PATCH 3/5] Shearwater: add Teric to list of supported dive computers Signed-off-by: Dirk Hohndel --- src/descriptor.c | 2 ++ src/shearwater_common.h | 1 + src/shearwater_petrel.c | 3 +++ 3 files changed, 6 insertions(+) diff --git a/src/descriptor.c b/src/descriptor.c index 794b825..f57080d 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -294,6 +294,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Shearwater", "Perdix", DC_FAMILY_SHEARWATER_PETREL, 5, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLUETOOTH | DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Perdix AI", DC_FAMILY_SHEARWATER_PETREL, 6, DC_TRANSPORT_BLE, dc_filter_shearwater}, {"Shearwater", "Nerd 2", DC_FAMILY_SHEARWATER_PETREL, 7, DC_TRANSPORT_BLE, dc_filter_shearwater}, + {"Shearwater", "Teric", DC_FAMILY_SHEARWATER_PETREL, 8, DC_TRANSPORT_BLE, dc_filter_shearwater}, /* Dive Rite NiTek Q */ {"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL}, /* Citizen Hyper Aqualand */ @@ -444,6 +445,7 @@ static int dc_filter_shearwater (dc_transport_t transport, const void *userdata) "Petrel", "Nerd", "Perdix", + "Teric", }; if (transport == DC_TRANSPORT_BLUETOOTH) { diff --git a/src/shearwater_common.h b/src/shearwater_common.h index 767372f..8798853 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -40,6 +40,7 @@ extern "C" { #define PERDIX 5 #define PERDIXAI 6 #define NERD2 7 +#define TERIC 8 #define NSTEPS 10000 #define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n)) diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index f0eadd0..e6b6a59 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -229,6 +229,9 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call case 0x0C0D: model = PERDIXAI; break; + case 0x0F0F: + model = TERIC; + break; default: WARNING (abstract->context, "Unknown hardware type %04x.", hardware); } From ae503626ae8d43d57a8a6ffed79e3ff0e4860ffb Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Fri, 7 Sep 2018 16:15:21 -0700 Subject: [PATCH 4/5] Shearwater: detect which logbook format is support The Log Upload RDBI (Read Data by Identifier) response tells us which format the dive computer supports. Shearwater recommends to use the 'Petrel Native Format' for all dive computers which support it, even those pre-Teric models which (depending on firmware) might support both PNF and the older 'Predator-Like Format'. They also recommend to ignore the 0x90000000 format which is very similar to PNF but without the final record and to use the older Predator Like Format in that case. The 0xDD000000 format is never an option by the time you got here, but in the old code (prior to the PNF addition) we would have fallen back to 0xC0000000, so let's do the same here. Any other value is actually an unknown format and should be treated as such. Which format we use is determined by the base address used to download the logbook entries. Signed-off-by: Dirk Hohndel --- src/shearwater_common.h | 7 ++++--- src/shearwater_petrel.c | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/shearwater_common.h b/src/shearwater_common.h index 8798853..35a0d01 100644 --- a/src/shearwater_common.h +++ b/src/shearwater_common.h @@ -30,9 +30,10 @@ extern "C" { #endif /* __cplusplus */ -#define ID_SERIAL 0x8010 -#define ID_FIRMWARE 0x8011 -#define ID_HARDWARE 0x8050 +#define ID_SERIAL 0x8010 +#define ID_FIRMWARE 0x8011 +#define ID_LOGUPLOAD 0x8021 +#define ID_HARDWARE 0x8050 #define PREDATOR 2 #define PETREL 3 diff --git a/src/shearwater_petrel.c b/src/shearwater_petrel.c index e6b6a59..a8b1995 100644 --- a/src/shearwater_petrel.c +++ b/src/shearwater_petrel.c @@ -26,6 +26,7 @@ #include "shearwater_common.h" #include "context-private.h" #include "device-private.h" +#include "platform.h" #include "array.h" #define ISINSTANCE(device) dc_device_isinstance((device), &shearwater_petrel_device_vtable) @@ -33,7 +34,6 @@ #define MANIFEST_ADDR 0xE0000000 #define MANIFEST_SIZE 0x600 -#define DIVE_ADDR 0xC0000000 #define DIVE_SIZE 0xFFFFFF #define RECORD_SIZE 0x20 @@ -243,6 +243,41 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call devinfo.serial = array_uint32_be (serial); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); + // Read the logbook type + rc = shearwater_common_identifier (&device->base, buffer, ID_LOGUPLOAD); + if (rc != DC_STATUS_SUCCESS) { + ERROR (abstract->context, "Failed to read the logbook type."); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return rc; + } + + if (dc_buffer_get_size (buffer) != 9) { + ERROR (abstract->context, "Unexpected packet size (" DC_PRINTF_SIZE " bytes).", dc_buffer_get_size(buffer)); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return DC_STATUS_DATAFORMAT; + } + + unsigned int base_addr = array_uint32_be (dc_buffer_get_data (buffer) + 1); + switch (base_addr) { + case 0xDD000000: // Predator - we shouldn't get here, we could give up or we can try 0xC0000000 + case 0xC0000000: // Predator-Like Format (what we used to call the Petrel format) + case 0x90000000: // some firmware versions supported an earlier version of PNF without final record + // use the Predator-Like Format instead + base_addr = 0xC0000000; + break; + case 0x80000000: // new Petrel Native Format with final record + // that's the correct address + break; + default: // unknown format + ERROR (abstract->context, "Unknown logbook format %08x", base_addr); + dc_buffer_free (buffer); + dc_buffer_free (manifests); + return DC_STATUS_DATAFORMAT; + } + + // Read the manifest pages while (1) { // Update the progress state. // Assume the worst case scenario of a full manifest, and adjust the @@ -327,7 +362,7 @@ shearwater_petrel_device_foreach (dc_device_t *abstract, dc_dive_callback_t call // Download the dive. progress.current = NSTEPS * current; progress.maximum = NSTEPS * maximum; - rc = shearwater_common_download (&device->base, buffer, DIVE_ADDR + address, DIVE_SIZE, 1, &progress); + rc = shearwater_common_download (&device->base, buffer, base_addr + address, DIVE_SIZE, 1, &progress); if (rc != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to download the dive."); dc_buffer_free (buffer); From 47bb08bbfdda885dcd8e1c36f4e728a7d610eac6 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 23 Nov 2018 15:35:34 +0100 Subject: [PATCH 5/5] Shearwater Petrel Native Format parsing This will allow parsing dives from the Shearwater Teric, but depending on the firmware could also be used on older models. Based on ideas and code from Dirk Hohndel --- src/shearwater_predator_parser.c | 529 ++++++++++++++++++++----------- 1 file changed, 338 insertions(+), 191 deletions(-) diff --git a/src/shearwater_predator_parser.c b/src/shearwater_predator_parser.c index f6a7b1d..7d3a420 100644 --- a/src/shearwater_predator_parser.c +++ b/src/shearwater_predator_parser.c @@ -33,9 +33,30 @@ dc_parser_isinstance((parser), &shearwater_predator_parser_vtable) || \ dc_parser_isinstance((parser), &shearwater_petrel_parser_vtable)) +#define LOG_RECORD_DIVE_SAMPLE 0x01 +#define LOG_RECORD_FREEDIVE_SAMPLE 0x02 +#define LOG_RECORD_OPENING_0 0x10 +#define LOG_RECORD_OPENING_1 0x11 +#define LOG_RECORD_OPENING_2 0x12 +#define LOG_RECORD_OPENING_3 0x13 +#define LOG_RECORD_OPENING_4 0x14 +#define LOG_RECORD_OPENING_5 0x15 +#define LOG_RECORD_OPENING_6 0x16 +#define LOG_RECORD_OPENING_7 0x17 +#define LOG_RECORD_CLOSING_0 0x20 +#define LOG_RECORD_CLOSING_1 0x21 +#define LOG_RECORD_CLOSING_2 0x22 +#define LOG_RECORD_CLOSING_3 0x23 +#define LOG_RECORD_CLOSING_4 0x24 +#define LOG_RECORD_CLOSING_5 0x25 +#define LOG_RECORD_CLOSING_6 0x26 +#define LOG_RECORD_CLOSING_7 0x27 +#define LOG_RECORD_FINAL 0xFF + #define SZ_BLOCK 0x80 #define SZ_SAMPLE_PREDATOR 0x10 #define SZ_SAMPLE_PETREL 0x20 +#define SZ_SAMPLE_FREEDIVE 0x08 #define GASSWITCH 0x01 #define PPO2_EXTERNAL 0x02 @@ -47,10 +68,13 @@ #define IMPERIAL 1 #define NGASMIXES 10 +#define NRECORDS 7 #define PREDATOR 2 #define PETREL 3 +#define UNDEFINED 0xFFFFFFFF + typedef struct shearwater_predator_parser_t shearwater_predator_parser_t; struct shearwater_predator_parser_t { @@ -60,15 +84,22 @@ struct shearwater_predator_parser_t { unsigned int samplesize; // Cached fields. unsigned int cached; + unsigned int pnf; unsigned int logversion; unsigned int headersize; unsigned int footersize; + unsigned int opening[NRECORDS]; + unsigned int closing[NRECORDS]; + unsigned int final; unsigned int ngasmixes; unsigned int oxygen[NGASMIXES]; unsigned int helium[NGASMIXES]; unsigned int calibrated; double calibration[3]; dc_divemode_t mode; + unsigned int units; + unsigned int atmospheric; + unsigned int density; }; static dc_status_t shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char *data, unsigned int size); @@ -76,6 +107,8 @@ static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstrac static dc_status_t shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t type, unsigned int flags, void *value); static dc_status_t shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata); +static dc_status_t shearwater_predator_parser_cache (shearwater_predator_parser_t *parser); + static const dc_parser_vtable_t shearwater_predator_parser_vtable = { sizeof(shearwater_predator_parser_t), DC_FAMILY_SHEARWATER_PREDATOR, @@ -141,9 +174,15 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->petrel = petrel; parser->samplesize = samplesize; parser->cached = 0; + parser->pnf = 0; parser->logversion = 0; parser->headersize = 0; parser->footersize = 0; + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = UNDEFINED; + parser->closing[i] = UNDEFINED; + } + parser->final = UNDEFINED; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; @@ -154,6 +193,9 @@ shearwater_common_parser_create (dc_parser_t **out, dc_context_t *context, unsig parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); *out = (dc_parser_t *) parser; @@ -182,9 +224,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char // Reset the cache. parser->cached = 0; + parser->pnf = 0; parser->logversion = 0; parser->headersize = 0; parser->footersize = 0; + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = UNDEFINED; + parser->closing[i] = UNDEFINED; + } + parser->final = UNDEFINED; parser->ngasmixes = 0; for (unsigned int i = 0; i < NGASMIXES; ++i) { parser->oxygen[i] = 0; @@ -195,6 +243,9 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char parser->calibration[i] = 0.0; } parser->mode = DC_DIVEMODE_OC; + parser->units = METRIC; + parser->density = 1025; + parser->atmospheric = ATM / (BAR / 1000); return DC_STATUS_SUCCESS; } @@ -203,13 +254,15 @@ shearwater_predator_parser_set_data (dc_parser_t *abstract, const unsigned char static dc_status_t shearwater_predator_parser_get_datetime (dc_parser_t *abstract, dc_datetime_t *datetime) { + shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract; const unsigned char *data = abstract->data; - unsigned int size = abstract->size; - if (size < 2 * SZ_BLOCK) - return DC_STATUS_DATAFORMAT; + // Cache the parser data. + dc_status_t rc = shearwater_predator_parser_cache (parser); + if (rc != DC_STATUS_SUCCESS) + return rc; - unsigned int ticks = array_uint32_be (data + 12); + unsigned int ticks = array_uint32_be (data + parser->opening[0] + 12); if (!dc_datetime_gmtime (datetime, ticks)) return DC_STATUS_DATAFORMAT; @@ -231,26 +284,48 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) return DC_STATUS_SUCCESS; } - unsigned int headersize = SZ_BLOCK; - unsigned int footersize = SZ_BLOCK; - if (size < headersize + footersize) { + // Verify the minimum length. + if (size < 2) { ERROR (abstract->context, "Invalid data length."); return DC_STATUS_DATAFORMAT; } - // Log versions before 6 weren't reliably stored in the data, but - // 6 is also the oldest version that we assume in our code - unsigned int logversion = 6; - if (data[127] > 6) - logversion = data[127]; - - // Adjust the footersize for the final block. - if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { - footersize += SZ_BLOCK; + // The Petrel Native Format (PNF) is very similar to the legacy + // Predator and Predator-like format. The samples are simply offset + // by one (so we can use pnf as the offset). For the header and + // footer data, it's more complicated because of the new 32 byte + // block structure. + unsigned int pnf = parser->petrel ? array_uint16_be (data) != 0xFFFF : 0; + unsigned int headersize = 0; + unsigned int footersize = 0; + if (!pnf) { + // Opening and closing blocks. + headersize = SZ_BLOCK; + footersize = SZ_BLOCK; if (size < headersize + footersize) { ERROR (abstract->context, "Invalid data length."); return DC_STATUS_DATAFORMAT; } + + // Adjust the footersize for the final block. + if (parser->petrel || array_uint16_be (data + size - footersize) == 0xFFFD) { + footersize += SZ_BLOCK; + if (size < headersize + footersize) { + ERROR (abstract->context, "Invalid data length."); + return DC_STATUS_DATAFORMAT; + } + + parser->final = size - SZ_BLOCK; + } + + // The Predator and Predator-like format have just one large 128 + // byte opening and closing block. To minimize the differences + // with the PNF format, all record offsets are assigned the same + // value here. + for (unsigned int i = 0; i < NRECORDS; ++i) { + parser->opening[i] = 0; + parser->closing[i] = size - footersize; + } } // Default dive mode. @@ -271,46 +346,76 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) continue; } - // Status flags. - unsigned int status = data[offset + 11]; - if ((status & OC) == 0) { - mode = DC_DIVEMODE_CCR; - } + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 8]; - if (o2 != o2_previous || he != he_previous) { - // Find the gasmix in the list. - unsigned int idx = 0; - while (idx < ngasmixes) { - if (o2 == oxygen[idx] && he == helium[idx]) - break; - idx++; + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Status flags. + unsigned int status = data[offset + 11 + pnf]; + if ((status & OC) == 0) { + mode = DC_DIVEMODE_CCR; } - // Add it to list if not found. - if (idx >= ngasmixes) { - if (idx >= NGASMIXES) { - ERROR (abstract->context, "Maximum number of gas mixes reached."); - return DC_STATUS_NOMEMORY; + // Gaschange. + unsigned int o2 = data[offset + 7 + pnf]; + unsigned int he = data[offset + 8 + pnf]; + if (o2 != o2_previous || he != he_previous) { + // Find the gasmix in the list. + unsigned int idx = 0; + while (idx < ngasmixes) { + if (o2 == oxygen[idx] && he == helium[idx]) + break; + idx++; } - oxygen[idx] = o2; - helium[idx] = he; - ngasmixes = idx + 1; - } - o2_previous = o2; - he_previous = he; + // Add it to list if not found. + if (idx >= ngasmixes) { + if (idx >= NGASMIXES) { + ERROR (abstract->context, "Maximum number of gas mixes reached."); + return DC_STATUS_NOMEMORY; + } + oxygen[idx] = o2; + helium[idx] = he; + ngasmixes = idx + 1; + } + + o2_previous = o2; + he_previous = he; + } + } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { + // Freedive record + mode = DC_DIVEMODE_FREEDIVE; + } else if (type >= LOG_RECORD_OPENING_0 && type <= LOG_RECORD_OPENING_7) { + // Opening record + parser->opening[type - LOG_RECORD_OPENING_0] = offset; + } else if (type >= LOG_RECORD_CLOSING_0 && type <= LOG_RECORD_CLOSING_7) { + // Closing record + parser->closing[type - LOG_RECORD_CLOSING_0] = offset; + } else if (type == LOG_RECORD_FINAL) { + // Final record + parser->final = offset; } offset += parser->samplesize; } + // Verify the required opening/closing records. + for (unsigned int i = 0; i < NRECORDS - 2; ++i) { + if (parser->opening[i] == UNDEFINED || parser->closing[i] == UNDEFINED) { + ERROR (abstract->context, "Opening or closing record %u not found.", i); + return DC_STATUS_DATAFORMAT; + } + } + + // Log versions before 6 weren't reliably stored in the data, but + // 6 is also the oldest version that we assume in our code + unsigned int logversion = data[parser->opening[4] + (pnf ? 16 : 127)]; + // Cache sensor calibration for later use unsigned int nsensors = 0, ndefaults = 0; + unsigned int base = parser->opening[3] + (pnf ? 6 : 86); for (size_t i = 0; i < 3; ++i) { - unsigned int calibration = array_uint16_be(data + 87 + i * 2); + unsigned int calibration = array_uint16_be(data + base + 1 + i * 2); parser->calibration[i] = calibration / 100000.0; if (parser->model == PREDATOR) { // The Predator expects the mV output of the cells to be @@ -319,7 +424,7 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) // sensors lines up and matches the average. parser->calibration[i] *= 2.2; } - if (data[86] & (1 << i)) { + if (data[base] & (1 << i)) { if (calibration == 2100) { ndefaults++; } @@ -335,10 +440,11 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) WARNING (abstract->context, "Disabled all O2 sensors due to a default calibration value."); parser->calibrated = 0; } else { - parser->calibrated = data[86]; + parser->calibrated = data[base]; } // Cache the data for later use. + parser->pnf = pnf; parser->logversion = logversion; parser->headersize = headersize; parser->footersize = footersize; @@ -348,6 +454,9 @@ shearwater_predator_parser_cache (shearwater_predator_parser_t *parser) parser->helium[i] = helium[i]; } parser->mode = mode; + parser->units = data[parser->opening[0] + 8]; + parser->atmospheric = array_uint16_be (data + parser->opening[1] + (parser->pnf ? 16 : 47)); + parser->density = array_uint16_be (data + parser->opening[3] + (parser->pnf ? 3 : 83)); parser->cached = 1; return DC_STATUS_SUCCESS; @@ -359,33 +468,30 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ shearwater_predator_parser_t *parser = (shearwater_predator_parser_t *) abstract; const unsigned char *data = abstract->data; - unsigned int size = abstract->size; // Cache the parser data. dc_status_t rc = shearwater_predator_parser_cache (parser); if (rc != DC_STATUS_SUCCESS) return rc; - // Get the offset to the footer record. - unsigned int footer = size - parser->footersize; - - // Get the unit system. - unsigned int units = data[8]; - dc_gasmix_t *gasmix = (dc_gasmix_t *) value; dc_salinity_t *water = (dc_salinity_t *) value; - unsigned int density = 0; if (value) { switch (type) { case DC_FIELD_DIVETIME: - *((unsigned int *) value) = array_uint16_be (data + footer + 6) * 60; + if (parser->pnf) + *((unsigned int *) value) = array_uint24_be (data + parser->closing[0] + 6); + else + *((unsigned int *) value) = array_uint16_be (data + parser->closing[0] + 6) * 60; break; case DC_FIELD_MAXDEPTH: - if (units == IMPERIAL) - *((double *) value) = array_uint16_be (data + footer + 4) * FEET; + if (parser->units == IMPERIAL) + *((double *) value) = array_uint16_be (data + parser->closing[0] + 4) * FEET; else - *((double *) value) = array_uint16_be (data + footer + 4); + *((double *) value) = array_uint16_be (data + parser->closing[0] + 4); + if (parser->pnf) + *((double *)value) /= 10.0; break; case DC_FIELD_GASMIX_COUNT: *((unsigned int *) value) = parser->ngasmixes; @@ -396,15 +502,14 @@ shearwater_predator_parser_get_field (dc_parser_t *abstract, dc_field_type_t typ gasmix->nitrogen = 1.0 - gasmix->oxygen - gasmix->helium; break; case DC_FIELD_SALINITY: - density = array_uint16_be (data + 83); - if (density == 1000) + if (parser->density == 1000) water->type = DC_WATER_FRESH; else water->type = DC_WATER_SALT; - water->density = density; + water->density = parser->density; break; case DC_FIELD_ATMOSPHERIC: - *((double *) value) = array_uint16_be (data + 47) / 1000.0; + *((double *) value) = parser->atmospheric / 1000.0; break; case DC_FIELD_DIVEMODE: *((dc_divemode_t *) value) = parser->mode; @@ -431,13 +536,22 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal if (rc != DC_STATUS_SUCCESS) return rc; - // Get the unit system. - unsigned int units = data[8]; - // Previous gas mix. unsigned int o2_previous = 0, he_previous = 0; + // Sample interval. unsigned int time = 0; + unsigned int interval = 10; + if (parser->pnf && parser->logversion >= 9) { + interval = array_uint16_be (data + parser->opening[5] + 23); + if (interval % 1000 != 0) { + ERROR (abstract->context, "Unsupported sample interval (%u ms).", interval); + return DC_STATUS_DATAFORMAT; + } + interval /= 1000; + } + + unsigned int pnf = parser->pnf; unsigned int offset = parser->headersize; unsigned int length = size - parser->footersize; while (offset + parser->samplesize <= length) { @@ -449,142 +563,175 @@ shearwater_predator_parser_samples_foreach (dc_parser_t *abstract, dc_sample_cal continue; } - // Time (seconds). - time += 10; - sample.time = time; - if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + // Get the record type. + unsigned int type = pnf ? data[offset] : LOG_RECORD_DIVE_SAMPLE; - // Depth (1/10 m or ft). - unsigned int depth = array_uint16_be (data + offset); - if (units == IMPERIAL) - sample.depth = depth * FEET / 10.0; - else - sample.depth = depth / 10.0; - if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + if (type == LOG_RECORD_DIVE_SAMPLE) { + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); - // Temperature (°C or °F). - int temperature = (signed char) data[offset + 13]; - if (temperature < 0) { - // Fix negative temperatures. - temperature += 102; - if (temperature > 0) { - temperature = 0; - } - } - if (units == IMPERIAL) - sample.temperature = (temperature - 32.0) * (5.0 / 9.0); - else - sample.temperature = temperature; - if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); + // Depth (1/10 m or ft). + unsigned int depth = array_uint16_be (data + pnf + offset); + if (parser->units == IMPERIAL) + sample.depth = depth * FEET / 10.0; + else + sample.depth = depth / 10.0; + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); - // Status flags. - unsigned int status = data[offset + 11]; - - if ((status & OC) == 0) { - // PPO2 - if ((status & PPO2_EXTERNAL) == 0) { -#ifdef SENSOR_AVERAGE - sample.ppo2 = data[offset + 6] / 100.0; - if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); -#else - sample.ppo2 = data[offset + 12] * parser->calibration[0]; - if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 14] * parser->calibration[1]; - if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); - - sample.ppo2 = data[offset + 15] * parser->calibration[2]; - if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); -#endif - } - - // Setpoint - if (parser->petrel) { - sample.setpoint = data[offset + 18] / 100.0; - } else { - if (status & SETPOINT_HIGH) { - sample.setpoint = data[18] / 100.0; - } else { - sample.setpoint = data[17] / 100.0; + // Temperature (°C or °F). + int temperature = (signed char) data[offset + pnf + 13]; + if (temperature < 0) { + // Fix negative temperatures. + temperature += 102; + if (temperature > 0) { + temperature = 0; } } - if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); - } - - // CNS - if (parser->petrel) { - sample.cns = data[offset + 22] / 100.0; - if (callback) callback (DC_SAMPLE_CNS, sample, userdata); - } - - // Gaschange. - unsigned int o2 = data[offset + 7]; - unsigned int he = data[offset + 8]; - if (o2 != o2_previous || he != he_previous) { - unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); - if (idx >= parser->ngasmixes) { - ERROR (abstract->context, "Invalid gas mix."); - return DC_STATUS_DATAFORMAT; - } - - sample.gasmix = idx; - if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); - o2_previous = o2; - he_previous = he; - } - - // Deco stop / NDL. - unsigned int decostop = array_uint16_be (data + offset + 2); - if (decostop) { - sample.deco.type = DC_DECO_DECOSTOP; - if (units == IMPERIAL) - sample.deco.depth = decostop * FEET; + if (parser->units == IMPERIAL) + sample.temperature = (temperature - 32.0) * (5.0 / 9.0); else - sample.deco.depth = decostop; - } else { - sample.deco.type = DC_DECO_NDL; - sample.deco.depth = 0.0; - } - sample.deco.time = data[offset + 9] * 60; - if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + sample.temperature = temperature; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); - // for logversion 7 and newer (introduced for Perdix AI) - // detect tank pressure - if (parser->logversion >= 7) { - // Tank pressure - // Values above 0xFFF0 are special codes: - // 0xFFFF AI is off - // 0xFFFE No comms for 90 seconds+ - // 0xFFFD No comms for 30 seconds - // 0xFFFC Transmitter not paired - // For regular values, the top 4 bits contain the battery - // level (0=normal, 1=critical, 2=warning), and the lower 12 - // bits the tank pressure in units of 2 psi. - unsigned int pressure = array_uint16_be (data + offset + 27); - if (pressure < 0xFFF0) { - pressure &= 0x0FFF; - sample.pressure.tank = 0; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); - } - pressure = array_uint16_be (data + offset + 19); - if (pressure < 0xFFF0) { - pressure &= 0x0FFF; - sample.pressure.tank = 1; - sample.pressure.value = pressure * 2 * PSI / BAR; - if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + // Status flags. + unsigned int status = data[offset + pnf + 11]; + + if ((status & OC) == 0) { + // PPO2 + if ((status & PPO2_EXTERNAL) == 0) { +#ifdef SENSOR_AVERAGE + sample.ppo2 = data[offset + pnf + 6] / 100.0; + if (callback) callback (DC_SAMPLE_PPO2, sample, userdata); +#else + sample.ppo2 = data[offset + pnf + 12] * parser->calibration[0]; + if (callback && (parser->calibrated & 0x01)) callback (DC_SAMPLE_PPO2, sample, userdata); + + sample.ppo2 = data[offset + pnf + 14] * parser->calibration[1]; + if (callback && (parser->calibrated & 0x02)) callback (DC_SAMPLE_PPO2, sample, userdata); + + sample.ppo2 = data[offset + pnf + 15] * parser->calibration[2]; + if (callback && (parser->calibrated & 0x04)) callback (DC_SAMPLE_PPO2, sample, userdata); +#endif + } + + // Setpoint + if (parser->petrel) { + sample.setpoint = data[offset + pnf + 18] / 100.0; + } else { + // this will only ever be called for the actual Predator, so no adjustment needed for PNF + if (status & SETPOINT_HIGH) { + sample.setpoint = data[18] / 100.0; + } else { + sample.setpoint = data[17] / 100.0; + } + } + if (callback) callback (DC_SAMPLE_SETPOINT, sample, userdata); } - // Gas time remaining in minutes - // Values above 0xF0 are special codes: - // 0xFF Not paired - // 0xFE No communication - // 0xFD Not available in current mode - // 0xFC Not available because of DECO - // 0xFB Tank size or max pressure haven’t been set up - if (data[offset + 21] < 0xF0) { - sample.rbt = data[offset + 21]; - if (callback) callback (DC_SAMPLE_RBT, sample, userdata); + // CNS + if (parser->petrel) { + sample.cns = data[offset + pnf + 22] / 100.0; + if (callback) callback (DC_SAMPLE_CNS, sample, userdata); + } + + // Gaschange. + unsigned int o2 = data[offset + pnf + 7]; + unsigned int he = data[offset + pnf + 8]; + if (o2 != o2_previous || he != he_previous) { + unsigned int idx = shearwater_predator_find_gasmix (parser, o2, he); + if (idx >= parser->ngasmixes) { + ERROR (abstract->context, "Invalid gas mix."); + return DC_STATUS_DATAFORMAT; + } + + sample.gasmix = idx; + if (callback) callback (DC_SAMPLE_GASMIX, sample, userdata); + o2_previous = o2; + he_previous = he; + } + + // Deco stop / NDL. + unsigned int decostop = array_uint16_be (data + offset + pnf + 2); + if (decostop) { + sample.deco.type = DC_DECO_DECOSTOP; + if (parser->units == IMPERIAL) + sample.deco.depth = decostop * FEET; + else + sample.deco.depth = decostop; + } else { + sample.deco.type = DC_DECO_NDL; + sample.deco.depth = 0.0; + } + sample.deco.time = data[offset + pnf + 9] * 60; + if (callback) callback (DC_SAMPLE_DECO, sample, userdata); + + // for logversion 7 and newer (introduced for Perdix AI) + // detect tank pressure + if (parser->logversion >= 7) { + // Tank pressure + // Values above 0xFFF0 are special codes: + // 0xFFFF AI is off + // 0xFFFE No comms for 90 seconds+ + // 0xFFFD No comms for 30 seconds + // 0xFFFC Transmitter not paired + // For regular values, the top 4 bits contain the battery + // level (0=normal, 1=critical, 2=warning), and the lower 12 + // bits the tank pressure in units of 2 psi. + unsigned int pressure = array_uint16_be (data + offset + pnf + 27); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = 0; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + pressure = array_uint16_be (data + offset + pnf + 19); + if (pressure < 0xFFF0) { + pressure &= 0x0FFF; + sample.pressure.tank = 1; + sample.pressure.value = pressure * 2 * PSI / BAR; + if (callback) callback (DC_SAMPLE_PRESSURE, sample, userdata); + } + + // Gas time remaining in minutes + // Values above 0xF0 are special codes: + // 0xFF Not paired + // 0xFE No communication + // 0xFD Not available in current mode + // 0xFC Not available because of DECO + // 0xFB Tank size or max pressure haven’t been set up + if (data[offset + pnf + 21] < 0xF0) { + sample.rbt = data[offset + pnf + 21]; + if (callback) callback (DC_SAMPLE_RBT, sample, userdata); + } + } + } else if (type == LOG_RECORD_FREEDIVE_SAMPLE) { + // A freedive record is actually 4 samples, each 8-bytes, + // packed into a standard 32-byte sized record. At the end + // of a dive, unused partial records will be 0 padded. + for (unsigned int i = 0; i < 4; ++i) { + unsigned int idx = offset + i * SZ_SAMPLE_FREEDIVE; + + // Ignore empty samples. + if (array_isequal (data + idx, SZ_SAMPLE_FREEDIVE, 0x00)) { + break; + } + + // Time (seconds). + time += interval; + sample.time = time; + if (callback) callback (DC_SAMPLE_TIME, sample, userdata); + + // Depth (absolute pressure in millibar) + unsigned int depth = array_uint16_be (data + idx + 1); + sample.depth = (depth - parser->atmospheric) * (BAR / 1000.0) / (parser->density * GRAVITY); + if (callback) callback (DC_SAMPLE_DEPTH, sample, userdata); + + // Temperature (1/10 °C). + int temperature = (signed short) array_uint16_be (data + idx + 3); + sample.temperature = temperature / 10.0; + if (callback) callback (DC_SAMPLE_TEMPERATURE, sample, userdata); } }