Fix the download of dives without a profile

At the moment, trying to download an old dive for which the profile data
has already been overwritten with newer data fails. This used to work
fine, but around hwOS firmware v3.10, the behaviour described in commit
76187c550a806fe422920eb8795fa687244513f1 changed.

When downloading the compact/full headers, the firmware always sends the
headers without inspecting their content. Next, libdivecomputer uses the
length field in these headers to determine how many bytes to expect when
downloading the dive. However, when downloading the entire dive, the
hwOS firmware now checks whether the profile data of the dive is still
available. If that's no longer the case, the firmware sends a modified
dive header (with the begin/end pointer fields reset to zero, and the
length field reduced to 8 bytes), along with an empty dive profile.
Since libdivecomputer expects to receive the full profile as indicated
in the original header, the download fails with a timeout.

To workaround this problem, download the dive data in two steps. First,
download the 256 byte header and check whether it has been modified. If
that's the case, reduce the length to that of the 5 byte empty profile.

The header check is also updated to exclude the modified fields. For the
progress events, just pretend the full profile has been downloaded.
This commit is contained in:
Jef Driesen 2022-10-28 22:12:04 +02:00
parent 89ae8b94cf
commit 9508401971

View File

@ -99,6 +99,7 @@
#define HDR_FULL_NUMBER 80 // 2 bytes #define HDR_FULL_NUMBER 80 // 2 bytes
#define HDR_FULL_VERSION 8 // 1 byte #define HDR_FULL_VERSION 8 // 1 byte
#define HDR_FULL_POINTERS 2 // 6 bytes
#define HDR_FULL_FIRMWARE 48 // 2 bytes #define HDR_FULL_FIRMWARE 48 // 2 bytes
typedef enum hw_ostc3_state_t { typedef enum hw_ostc3_state_t {
@ -128,6 +129,7 @@ typedef struct hw_ostc3_logbook_t {
unsigned int profile; unsigned int profile;
unsigned int fingerprint; unsigned int fingerprint;
unsigned int number; unsigned int number;
unsigned int version;
} hw_ostc3_logbook_t; } hw_ostc3_logbook_t;
typedef struct hw_ostc3_firmware_t { typedef struct hw_ostc3_firmware_t {
@ -170,6 +172,7 @@ static const hw_ostc3_logbook_t hw_ostc3_logbook_compact = {
HDR_COMPACT_LENGTH, /* profile */ HDR_COMPACT_LENGTH, /* profile */
HDR_COMPACT_SUMMARY, /* fingerprint */ HDR_COMPACT_SUMMARY, /* fingerprint */
HDR_COMPACT_NUMBER, /* number */ HDR_COMPACT_NUMBER, /* number */
HDR_COMPACT_VERSION, /* version */
}; };
static const hw_ostc3_logbook_t hw_ostc3_logbook_full = { static const hw_ostc3_logbook_t hw_ostc3_logbook_full = {
@ -177,6 +180,7 @@ static const hw_ostc3_logbook_t hw_ostc3_logbook_full = {
HDR_FULL_LENGTH, /* profile */ HDR_FULL_LENGTH, /* profile */
HDR_FULL_SUMMARY, /* fingerprint */ HDR_FULL_SUMMARY, /* fingerprint */
HDR_FULL_NUMBER, /* number */ HDR_FULL_NUMBER, /* number */
HDR_FULL_VERSION, /* version */
}; };
@ -291,10 +295,15 @@ hw_ostc3_transfer (hw_ostc3_device_t *device,
unsigned int isize, unsigned int isize,
unsigned char output[], unsigned char output[],
unsigned int osize, unsigned int osize,
unsigned int *actual,
unsigned int delay) unsigned int delay)
{ {
dc_device_t *abstract = (dc_device_t *) device; dc_device_t *abstract = (dc_device_t *) device;
dc_status_t status = DC_STATUS_SUCCESS; dc_status_t status = DC_STATUS_SUCCESS;
unsigned int length = osize;
if (cmd == DIVE && length < RB_LOGBOOK_SIZE_FULL)
return DC_STATUS_INVALIDARGS;
if (device_is_cancelled (abstract)) if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED; return DC_STATUS_CANCELLED;
@ -357,11 +366,44 @@ hw_ostc3_transfer (hw_ostc3_device_t *device,
} }
if (output) { if (output) {
// Read the output data packet. if (cmd == DIVE) {
status = hw_ostc3_read (device, progress, output, osize); // Read the dive header.
if (status != DC_STATUS_SUCCESS) { status = hw_ostc3_read (device, progress, output, RB_LOGBOOK_SIZE_FULL);
ERROR (abstract->context, "Failed to receive the answer."); if (status != DC_STATUS_SUCCESS) {
return status; ERROR (abstract->context, "Failed to receive the dive header.");
return status;
}
// When the hwOS firmware detects the dive profile is no longer
// valid, it sends a modified dive header (with the begin/end
// pointer fields reset to zero, and the length field reduced to 8
// bytes), along with an empty dive profile. Detect this condition
// and adjust the expected length.
if (array_isequal (output + HDR_FULL_POINTERS, 6, 0x00) &&
array_uint24_le (output + HDR_FULL_LENGTH) == 8 &&
length > RB_LOGBOOK_SIZE_FULL + 5) {
length = RB_LOGBOOK_SIZE_FULL + 5;
}
// Read the dive profile.
status = hw_ostc3_read (device, progress, output + RB_LOGBOOK_SIZE_FULL, length - RB_LOGBOOK_SIZE_FULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the dive profile.");
return status;
}
// Update and emit a progress event.
if (progress && osize > length) {
progress->current += osize - length;
device_event_emit ((dc_device_t *) device, DC_EVENT_PROGRESS, progress);
}
} else {
// Read the output data packet.
status = hw_ostc3_read (device, progress, output, length);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
} }
} }
@ -385,6 +427,9 @@ hw_ostc3_transfer (hw_ostc3_device_t *device,
} }
} }
if (actual)
*actual = length;
return DC_STATUS_SUCCESS; return DC_STATUS_SUCCESS;
} }
@ -457,9 +502,9 @@ hw_ostc3_device_id (hw_ostc3_device_t *device, unsigned char data[], unsigned in
// Send the command. // Send the command.
unsigned char hardware[SZ_HARDWARE2] = {0}; unsigned char hardware[SZ_HARDWARE2] = {0};
status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NODELAY); status = hw_ostc3_transfer (device, NULL, HARDWARE2, NULL, 0, hardware, SZ_HARDWARE2, NULL, NODELAY);
if (status == DC_STATUS_UNSUPPORTED) { if (status == DC_STATUS_UNSUPPORTED) {
status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NODELAY); status = hw_ostc3_transfer (device, NULL, HARDWARE, NULL, 0, hardware + 1, SZ_HARDWARE, NULL, NODELAY);
} }
if (status != DC_STATUS_SUCCESS) if (status != DC_STATUS_SUCCESS)
return status; return status;
@ -481,7 +526,7 @@ hw_ostc3_device_init_download (hw_ostc3_device_t *device)
dc_context_t *context = (abstract ? abstract->context : NULL); dc_context_t *context = (abstract ? abstract->context : NULL);
// Send the init command. // Send the init command.
dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NODELAY); dc_status_t status = hw_ostc3_transfer (device, NULL, INIT, NULL, 0, NULL, 0, NULL, NODELAY);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to send the command."); ERROR (context, "Failed to send the command.");
return status; return status;
@ -574,7 +619,7 @@ hw_ostc3_device_init (hw_ostc3_device_t *device, hw_ostc3_state_t state)
// Read the version information. // Read the version information.
unsigned char version[SZ_VERSION] = {0}; unsigned char version[SZ_VERSION] = {0};
rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NODELAY); rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, version, sizeof(version), NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the version information."); ERROR (abstract->context, "Failed to read the version information.");
return rc; return rc;
@ -604,7 +649,7 @@ hw_ostc3_device_close (dc_device_t *abstract)
// Send the exit command // Send the exit command
if (device->state == DOWNLOAD || device->state == SERVICE) { if (device->state == DOWNLOAD || device->state == SERVICE) {
rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, EXIT, NULL, 0, NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command."); ERROR (abstract->context, "Failed to send the command.");
dc_status_set_error(&status, rc); dc_status_set_error(&status, rc);
@ -648,7 +693,7 @@ hw_ostc3_device_version (dc_device_t *abstract, unsigned char data[], unsigned i
return rc; return rc;
// Send the command. // Send the command.
rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NODELAY); rc = hw_ostc3_transfer (device, NULL, IDENTITY, NULL, 0, data, size, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -721,11 +766,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi
// This is slower, but also works for older firmware versions. // This is slower, but also works for older firmware versions.
unsigned int compact = 1; unsigned int compact = 1;
rc = hw_ostc3_transfer (device, &progress, COMPACT, rc = hw_ostc3_transfer (device, &progress, COMPACT,
NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NODELAY); NULL, 0, header, RB_LOGBOOK_SIZE_COMPACT * RB_LOGBOOK_COUNT, NULL, NODELAY);
if (rc == DC_STATUS_UNSUPPORTED) { if (rc == DC_STATUS_UNSUPPORTED) {
compact = 0; compact = 0;
rc = hw_ostc3_transfer (device, &progress, HEADER, rc = hw_ostc3_transfer (device, &progress, HEADER,
NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NODELAY); NULL, 0, header, RB_LOGBOOK_SIZE_FULL * RB_LOGBOOK_COUNT, NULL, NODELAY);
} }
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the header."); ERROR (abstract->context, "Failed to read the header.");
@ -837,7 +882,7 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi
// Download the dive. // Download the dive.
unsigned char number[1] = {idx}; unsigned char number[1] = {idx};
rc = hw_ostc3_transfer (device, &progress, DIVE, rc = hw_ostc3_transfer (device, &progress, DIVE,
number, sizeof (number), profile, length, NODELAY); number, sizeof (number), profile, length, &length, NODELAY);
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the dive."); ERROR (abstract->context, "Failed to read the dive.");
free (profile); free (profile);
@ -846,12 +891,11 @@ hw_ostc3_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, voi
} }
// Verify the header in the logbook and profile are identical. // Verify the header in the logbook and profile are identical.
if (compact ? if (memcmp (profile + HDR_FULL_VERSION, header + offset + logbook->version, 1) != 0 ||
memcmp (profile + HDR_FULL_LENGTH, header + offset + HDR_COMPACT_LENGTH, 3) != 0 || compact ?
memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 || memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_COMPACT_SUMMARY, 10) != 0 ||
memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 || memcmp (profile + HDR_FULL_NUMBER, header + offset + HDR_COMPACT_NUMBER, 2) != 0 :
memcmp (profile + HDR_FULL_VERSION, header + offset + HDR_COMPACT_VERSION, 1) != 0 : memcmp (profile + HDR_FULL_SUMMARY, header + offset + HDR_FULL_SUMMARY, RB_LOGBOOK_SIZE_FULL - HDR_FULL_SUMMARY) != 0) {
memcmp (profile, header + offset, RB_LOGBOOK_SIZE_FULL) != 0) {
ERROR (abstract->context, "Unexpected profile header."); ERROR (abstract->context, "Unexpected profile header.");
free (profile); free (profile);
free (header); free (header);
@ -907,7 +951,7 @@ hw_ostc3_device_timesync (dc_device_t *abstract, const dc_datetime_t *datetime)
unsigned char packet[6] = { unsigned char packet[6] = {
datetime->hour, datetime->minute, datetime->second, datetime->hour, datetime->minute, datetime->second,
datetime->month, datetime->day, datetime->year - 2000}; datetime->month, datetime->day, datetime->year - 2000};
rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, CLOCK, packet, sizeof (packet), NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -935,7 +979,7 @@ hw_ostc3_device_display (dc_device_t *abstract, const char *text)
return rc; return rc;
// Send the command. // Send the command.
rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, DISPLAY, packet, sizeof (packet), NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -963,7 +1007,7 @@ hw_ostc3_device_customtext (dc_device_t *abstract, const char *text)
return rc; return rc;
// Send the command. // Send the command.
rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, CUSTOMTEXT, packet, sizeof (packet), NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -989,7 +1033,7 @@ hw_ostc3_device_config_read (dc_device_t *abstract, unsigned int config, unsigne
// Send the command. // Send the command.
unsigned char command[1] = {config}; unsigned char command[1] = {config};
rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NODELAY); rc = hw_ostc3_transfer (device, NULL, READ, command, sizeof (command), data, size, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -1016,7 +1060,7 @@ hw_ostc3_device_config_write (dc_device_t *abstract, unsigned int config, const
// Send the command. // Send the command.
unsigned char command[SZ_CONFIG + 1] = {config}; unsigned char command[SZ_CONFIG + 1] = {config};
memcpy(command + 1, data, size); memcpy(command + 1, data, size);
rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, WRITE, command, size + 1, NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -1036,7 +1080,7 @@ hw_ostc3_device_config_reset (dc_device_t *abstract)
return rc; return rc;
// Send the command. // Send the command.
rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, RESET, NULL, 0, NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) if (rc != DC_STATUS_SUCCESS)
return rc; return rc;
@ -1262,7 +1306,7 @@ hw_ostc3_firmware_erase (hw_ostc3_device_t *device, unsigned int addr, unsigned
array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer, addr);
buffer[3] = blocks; buffer[3] = blocks;
return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, delay); return hw_ostc3_transfer (device, NULL, S_ERASE, buffer, sizeof (buffer), NULL, 0, NULL, delay);
} }
static dc_status_t static dc_status_t
@ -1272,7 +1316,7 @@ hw_ostc3_firmware_block_read (hw_ostc3_device_t *device, unsigned int addr, unsi
array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer, addr);
array_uint24_be_set (buffer + 3, block_size); array_uint24_be_set (buffer + 3, block_size);
return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NODELAY); return hw_ostc3_transfer (device, NULL, S_BLOCK_READ, buffer, sizeof (buffer), block, block_size, NULL, NODELAY);
} }
static dc_status_t static dc_status_t
@ -1287,7 +1331,7 @@ hw_ostc3_firmware_block_write1 (hw_ostc3_device_t *device, unsigned int addr, co
array_uint24_be_set (buffer, addr); array_uint24_be_set (buffer, addr);
memcpy (buffer + 3, block, block_size); memcpy (buffer + 3, block, block_size);
return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, TIMEOUT); return hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE, buffer, 3 + block_size, NULL, 0, NULL, TIMEOUT);
} }
static dc_status_t static dc_status_t
@ -1306,7 +1350,7 @@ hw_ostc3_firmware_block_write2 (hw_ostc3_device_t *device, unsigned int address,
array_uint24_be_set (buffer, address); array_uint24_be_set (buffer, address);
memcpy (buffer + 3, data + nbytes, SZ_FIRMWARE_BLOCK2); memcpy (buffer + 3, data + nbytes, SZ_FIRMWARE_BLOCK2);
status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NODELAY); status = hw_ostc3_transfer (device, NULL, S_BLOCK_WRITE2, buffer, sizeof(buffer), NULL, 0, NULL, NODELAY);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
return status; return status;
} }
@ -1347,7 +1391,7 @@ hw_ostc3_firmware_upgrade (dc_device_t *abstract, unsigned int checksum)
buffer[4] = (buffer[4]<<1 | buffer[4]>>7); buffer[4] = (buffer[4]<<1 | buffer[4]>>7);
} }
rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NODELAY); rc = hw_ostc3_transfer (device, NULL, S_UPGRADE, buffer, sizeof (buffer), NULL, 0, NULL, NODELAY);
if (rc != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to send flash firmware command"); ERROR (context, "Failed to send flash firmware command");
return rc; return rc;
@ -1529,7 +1573,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename)
// Read the firmware version info. // Read the firmware version info.
unsigned char fwinfo[SZ_FWINFO] = {0}; unsigned char fwinfo[SZ_FWINFO] = {0};
status = hw_ostc3_transfer (device, NULL, S_FWINFO, status = hw_ostc3_transfer (device, NULL, S_FWINFO,
data + offset + 4, 1, fwinfo, sizeof(fwinfo), NODELAY); data + offset + 4, 1, fwinfo, sizeof(fwinfo), NULL, NODELAY);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to read the firmware info."); ERROR (abstract->context, "Failed to read the firmware info.");
goto error; goto error;
@ -1542,7 +1586,7 @@ hw_ostc3_device_fwupdate4 (dc_device_t *abstract, const char *filename)
!array_isequal(fwinfo, sizeof(fwinfo), 0xFF)) !array_isequal(fwinfo, sizeof(fwinfo), 0xFF))
{ {
status = hw_ostc3_transfer (device, &progress, S_UPLOAD, status = hw_ostc3_transfer (device, &progress, S_UPLOAD,
data + offset, length, NULL, 0, usecs / 1000); data + offset, length, NULL, 0, NULL, usecs / 1000);
if (status != DC_STATUS_SUCCESS) { if (status != DC_STATUS_SUCCESS) {
goto error; goto error;
} }