libdc/src/suunto_eonsteel.c
Jef Driesen afff8b450f Add BLE support for the Suunto Eon Steel devices
The main difference with the USB HID communication is that the BLE data
stream is encoded using HDLC framing with a 32 bit CRC checksum. Due to
this encoding, the data packets can no longer be processed one by one
(as is done for the USB HID packets). The entire HDLC encoded stream
needs to be received before it can be processed. This requires some
additional buffering.
2018-04-03 21:52:20 +02:00

965 lines
25 KiB
C

/*
* libdivecomputer
*
* Copyright (C) 2014 Linus Torvalds
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "suunto_eonsteel.h"
#include "context-private.h"
#include "device-private.h"
#include "array.h"
#include "platform.h"
#include "checksum.h"
#define EONSTEEL 0
#define EONCORE 1
typedef struct suunto_eonsteel_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned int model;
unsigned int magic;
unsigned short seq;
unsigned char version[0x30];
unsigned char fingerprint[4];
} suunto_eonsteel_device_t;
// The EON Steel implements a small filesystem
#define DIRTYPE_FILE 0x0001
#define DIRTYPE_DIR 0x0002
struct directory_entry {
struct directory_entry *next;
int type;
int namelen;
char name[1];
};
// EON Steel command numbers and other magic field values
#define CMD_INIT 0x0000
#define INIT_MAGIC 0x0001
#define INIT_SEQ 0
#define CMD_READ_STRING 0x0411
#define CMD_FILE_OPEN 0x0010
#define CMD_FILE_READ 0x0110
#define CMD_FILE_STAT 0x0710
#define CMD_FILE_CLOSE 0x0510
#define CMD_DIR_OPEN 0x0810
#define CMD_DIR_READDIR 0x0910
#define CMD_DIR_CLOSE 0x0a10
#define CMD_SET_TIME 0x0003
#define CMD_GET_TIME 0x0103
#define CMD_SET_DATE 0x0203
#define CMD_GET_DATE 0x0303
#define PACKET_SIZE 64
#define HEADER_SIZE 12
#define MAXDATA_SIZE 2048
#define CRC_SIZE 4
// HDLC special characters
#define END 0x7E
#define ESC 0x7D
#define ESC_BIT 0x20
static dc_status_t suunto_eonsteel_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
static dc_status_t suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static dc_status_t suunto_eonsteel_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
static const dc_device_vtable_t suunto_eonsteel_device_vtable = {
sizeof(suunto_eonsteel_device_t),
DC_FAMILY_SUUNTO_EONSTEEL,
suunto_eonsteel_device_set_fingerprint, /* set_fingerprint */
NULL, /* read */
NULL, /* write */
NULL, /* dump */
suunto_eonsteel_device_foreach, /* foreach */
suunto_eonsteel_device_timesync, /* timesync */
NULL /* close */
};
static const char dive_directory[] = "0:/dives";
static void file_list_free (struct directory_entry *de)
{
while (de) {
struct directory_entry *next = de->next;
free (de);
de = next;
}
}
static struct directory_entry *alloc_dirent(int type, int len, const char *name)
{
struct directory_entry *res;
res = (struct directory_entry *) malloc(offsetof(struct directory_entry, name) + len + 1);
if (res) {
res->next = NULL;
res->type = type;
res->namelen = len;
memcpy(res->name, name, len);
res->name[len] = 0;
}
return res;
}
static void put_le16(unsigned short val, unsigned char *p)
{
p[0] = val;
p[1] = val >> 8;
}
static void put_le32(unsigned int val, unsigned char *p)
{
p[0] = val;
p[1] = val >> 8;
p[2] = val >> 16;
p[3] = val >> 24;
}
static dc_status_t
suunto_eonsteel_hdlc_write (suunto_eonsteel_device_t *device, const unsigned char data[], size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
unsigned char buffer[20];
size_t nbytes = 0;
// Start of the packet.
buffer[nbytes++] = END;
for (size_t i = 0; i < size; ++i) {
unsigned char c = data[i];
if (c == END || c == ESC) {
// Append the escape character.
buffer[nbytes++] = ESC;
// Flush the buffer if necessary.
if (nbytes >= sizeof(buffer)) {
status = dc_iostream_write(device->iostream, buffer, nbytes, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to send the packet.");
return status;
}
nbytes = 0;
}
// Escape the character.
c ^= ESC_BIT;
}
// Append the character.
buffer[nbytes++] = c;
// Flush the buffer if necessary.
if (nbytes >= sizeof(buffer)) {
status = dc_iostream_write(device->iostream, buffer, nbytes, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to send the packet.");
return status;
}
nbytes = 0;
}
}
// End of the packet.
buffer[nbytes++] = END;
// Flush the buffer.
status = dc_iostream_write(device->iostream, buffer, nbytes, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to send the packet.");
return status;
}
if (actual)
*actual = size;
return status;
}
static dc_status_t
suunto_eonsteel_hdlc_read (suunto_eonsteel_device_t *device, unsigned char data[], size_t size, size_t *actual)
{
dc_status_t status = DC_STATUS_SUCCESS;
unsigned char buffer[20];
unsigned int initialized = 0;
unsigned int escaped = 0;
size_t nbytes = 0;
while (1) {
// Read a single data packet.
size_t transferred = 0;
status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred);
if (status != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to receive the packet.");
return status;
}
for (size_t i = 0; i < transferred; ++i) {
unsigned char c = buffer[i];
if (c == END) {
if (escaped) {
ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c);
return DC_STATUS_PROTOCOL;
}
if (initialized) {
goto done;
}
initialized = 1;
continue;
}
if (!initialized) {
continue;
}
if (c == ESC) {
if (escaped) {
ERROR (device->base.context, "HDLC frame escaped the special character %02x.", c);
return DC_STATUS_PROTOCOL;
}
escaped = 1;
continue;
}
if (escaped) {
c ^= ESC_BIT;
escaped = 0;
}
if (nbytes < size)
data[nbytes] = c;
nbytes++;
}
}
done:
if (nbytes > size) {
ERROR(device->base.context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
if (actual)
*actual = nbytes;
return status;
}
/*
* Get a single 64-byte packet from the dive computer. This handles packet
* logging and any obvious packet-level errors, and returns the payload of
* packet.
*
* The two first bytes of the packet are packet-level metadata: the report
* type (always 0x3f), and then the size of the valid data in the packet.
*
* The maximum payload is 62 bytes.
*/
static dc_status_t
suunto_eonsteel_receive_usb(suunto_eonsteel_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual)
{
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char buf[PACKET_SIZE];
size_t transferred = 0;
unsigned int len = 0;
rc = dc_iostream_read(device->iostream, buf, sizeof(buf), &transferred);
if (rc != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to receive the packet.");
return rc;
}
if (transferred < 2) {
ERROR(device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred);
return DC_STATUS_PROTOCOL;
}
if (buf[0] != 0x3f) {
ERROR(device->base.context, "Invalid report type (%02x).", buf[0]);
return DC_STATUS_PROTOCOL;
}
len = buf[1];
if (len + 2 > transferred) {
ERROR(device->base.context, "Invalid payload length (%u).", len);
return DC_STATUS_PROTOCOL;
}
if (len > size) {
ERROR(device->base.context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buf + 2, len);
memcpy(data, buf + 2, len);
if (actual)
*actual = len;
return DC_STATUS_SUCCESS;
}
static dc_status_t
suunto_eonsteel_receive_ble(suunto_eonsteel_device_t *device, unsigned char data[], unsigned int size, unsigned int *actual)
{
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char buffer[HEADER_SIZE + MAXDATA_SIZE + CRC_SIZE];
size_t transferred = 0;
rc = suunto_eonsteel_hdlc_read(device, buffer, sizeof(buffer), &transferred);
if (rc != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to receive the packet.");
return rc;
}
if (transferred < CRC_SIZE) {
ERROR(device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred);
return DC_STATUS_PROTOCOL;
}
unsigned int nbytes = transferred - CRC_SIZE;
unsigned int crc = array_uint32_le(buffer + nbytes);
unsigned int ccrc = checksum_crc32(buffer, nbytes);
if (crc != ccrc) {
ERROR(device->base.context, "Invalid checksum (expected %08x, received %08x).", ccrc, crc);
return DC_STATUS_PROTOCOL;
}
if (nbytes > size) {
ERROR(device->base.context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
memcpy(data, buffer, nbytes);
HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, nbytes);
if (actual)
*actual = nbytes;
return DC_STATUS_SUCCESS;
}
static dc_status_t
suunto_eonsteel_send(suunto_eonsteel_device_t *device,
unsigned short cmd,
const unsigned char data[],
unsigned int size)
{
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char buf[PACKET_SIZE + CRC_SIZE];
// Two-byte packet header, followed by 12 bytes of extended header
if (size + 2 + HEADER_SIZE + CRC_SIZE > sizeof(buf)) {
ERROR(device->base.context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
memset(buf, 0, sizeof(buf));
buf[0] = 0x3f;
buf[1] = size + HEADER_SIZE;
// 2-byte LE command word
put_le16(cmd, buf + 2);
// 4-byte LE magic value (starts at 1)
put_le32(device->magic, buf + 4);
// 2-byte LE sequence number;
put_le16(device->seq, buf + 8);
// 4-byte LE length
put_le32(size, buf + 10);
// .. followed by actual data
if (size) {
memcpy(buf + 14, data, size);
}
// 4 byte LE checksum
unsigned int crc = checksum_crc32(buf + 2, size + HEADER_SIZE);
put_le32(crc, buf + 14 + size);
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) {
rc = suunto_eonsteel_hdlc_write(device, buf + 2, size + HEADER_SIZE + CRC_SIZE, NULL);
} else {
rc = dc_iostream_write(device->iostream, buf, sizeof(buf) - CRC_SIZE, NULL);
}
if (rc != DC_STATUS_SUCCESS) {
ERROR(device->base.context, "Failed to send the command.");
return rc;
}
HEXDUMP (device->base.context, DC_LOGLEVEL_DEBUG, "cmd", buf + 2, size + HEADER_SIZE);
return DC_STATUS_SUCCESS;
}
/*
* Send a command, receive a reply
*
* This carefully checks the data fields in the reply for a match
* against the command, and then only returns the actual reply
* data itself.
*
* Also note that receive() function itself will have removed the
* per-packet handshake bytes, so unlike the send() function, this
* functon does not see the two initial 0x3f 0x?? bytes, and thus the
* offsets for the cmd/magic/seq/len are off by two compared to the
* send() side. The offsets are the same in the actual raw packet.
*/
static dc_status_t
suunto_eonsteel_transfer(suunto_eonsteel_device_t *device,
unsigned short cmd,
const unsigned char data[], unsigned int size,
unsigned char answer[], unsigned int asize,
unsigned int *actual)
{
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char header[HEADER_SIZE + MAXDATA_SIZE];
unsigned int len = 0;
// Send the command.
rc = suunto_eonsteel_send(device, cmd, data, size);
if (rc != DC_STATUS_SUCCESS)
return rc;
if (dc_iostream_get_transport(device->iostream) == DC_TRANSPORT_BLE) {
// Receive the entire data packet.
rc = suunto_eonsteel_receive_ble(device, header, sizeof(header), &len);
} else {
// Receive the header and the first part of the data.
rc = suunto_eonsteel_receive_usb(device, header, sizeof(header), &len);
}
if (rc != DC_STATUS_SUCCESS)
return rc;
// Verify the header length.
if (len < HEADER_SIZE) {
ERROR(device->base.context, "Invalid packet length (%u).", len);
return DC_STATUS_PROTOCOL;
}
// Unpack the 12 byte header.
unsigned int reply = array_uint16_le(header);
unsigned int magic = array_uint32_le(header + 2);
unsigned int seq = array_uint16_le(header + 6);
unsigned int length = array_uint32_le(header + 8);
if (cmd != CMD_INIT) {
// Verify the command reply.
if (reply != cmd) {
ERROR(device->base.context, "Unexpected command reply (received %04x, expected %04x).", reply, cmd);
return DC_STATUS_PROTOCOL;
}
// Verify the magic value.
if (magic != device->magic + 5) {
ERROR(device->base.context, "Unexpected magic value (received %08x, expected %08x).", magic, device->magic + 5);
return DC_STATUS_PROTOCOL;
}
}
// Verify the sequence number.
if (seq != device->seq) {
ERROR(device->base.context, "Unexpected sequence number (received %04x, expected %04x).", seq, device->seq);
return DC_STATUS_PROTOCOL;
}
// Verify the length.
if (length > asize) {
ERROR(device->base.context, "Insufficient buffer space available.");
return DC_STATUS_PROTOCOL;
}
// Verify the initial payload length.
unsigned int nbytes = len - HEADER_SIZE;
if (nbytes > length) {
ERROR(device->base.context, "Unexpected number of bytes (received %u, expected %u).", nbytes, length);
return DC_STATUS_PROTOCOL;
}
// Copy the payload data.
memcpy(answer, header + HEADER_SIZE, nbytes);
// Receive the remainder of the data.
if (dc_iostream_get_transport(device->iostream) != DC_TRANSPORT_BLE) {
while (nbytes < length) {
rc = suunto_eonsteel_receive_usb(device, answer + nbytes, length - nbytes, &len);
if (rc != DC_STATUS_SUCCESS)
return rc;
nbytes += len;
if (len < PACKET_SIZE - 2)
break;
}
}
// Verify the total payload length.
if (nbytes != length) {
ERROR(device->base.context, "Unexpected number of bytes (received %u, expected %u).", nbytes, length);
return DC_STATUS_PROTOCOL;
}
// Remember the magic number.
if (cmd == CMD_INIT) {
device->magic = (magic & 0xffff0000) | 0x0005;
}
// Increment the sequence number.
device->seq++;
if (actual)
*actual = nbytes;
return DC_STATUS_SUCCESS;
}
static dc_status_t
read_file(suunto_eonsteel_device_t *eon, const char *filename, dc_buffer_t *buf)
{
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char result[2560];
unsigned char cmdbuf[64];
unsigned int size, offset, len;
unsigned int n = 0;
memset(cmdbuf, 0, sizeof(cmdbuf));
len = strlen(filename) + 1;
if (len + 4 > sizeof(cmdbuf)) {
ERROR(eon->base.context, "too long filename: %s", filename);
return DC_STATUS_PROTOCOL;
}
memcpy(cmdbuf+4, filename, len);
rc = suunto_eonsteel_transfer(eon, CMD_FILE_OPEN,
cmdbuf, len + 4, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "unable to look up %s", filename);
return rc;
}
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "lookup", result, n);
rc = suunto_eonsteel_transfer(eon, CMD_FILE_STAT,
NULL, 0, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "unable to stat %s", filename);
return rc;
}
HEXDUMP (eon->base.context, DC_LOGLEVEL_DEBUG, "stat", result, n);
size = array_uint32_le(result+4);
offset = 0;
while (size > 0) {
unsigned int ask, got, at;
ask = size;
if (ask > 1024)
ask = 1024;
put_le32(1234, cmdbuf+0); // Not file offset, after all
put_le32(ask, cmdbuf+4); // Size of read
rc = suunto_eonsteel_transfer(eon, CMD_FILE_READ,
cmdbuf, 8, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "unable to read %s", filename);
return rc;
}
if (n < 8) {
ERROR(eon->base.context, "got short read reply for %s", filename);
return DC_STATUS_PROTOCOL;
}
// Not file offset, just stays unmodified.
at = array_uint32_le(result);
if (at != 1234) {
ERROR(eon->base.context, "read of %s returned different offset than asked for (%d vs %d)", filename, at, offset);
return DC_STATUS_PROTOCOL;
}
// Number of bytes actually read
got = array_uint32_le(result+4);
if (!got)
break;
if (n < 8 + got) {
ERROR(eon->base.context, "odd read size reply for offset %d of file %s", offset, filename);
return DC_STATUS_PROTOCOL;
}
if (got > size)
got = size;
if (!dc_buffer_append (buf, result + 8, got)) {
ERROR (eon->base.context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
offset += got;
size -= got;
}
rc = suunto_eonsteel_transfer(eon, CMD_FILE_CLOSE,
NULL, 0, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "cmd CMD_FILE_CLOSE failed");
return rc;
}
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "close", result, n);
return DC_STATUS_SUCCESS;
}
/*
* NOTE! This will create the list of dirent's in reverse order,
* with the last dirent first. That's intentional: for dives,
* we will want to look up the last dive first.
*/
static struct directory_entry *parse_dirent(suunto_eonsteel_device_t *eon, int nr, const unsigned char *p, unsigned int len, struct directory_entry *old)
{
while (len > 8) {
unsigned int type = array_uint32_le(p);
unsigned int namelen = array_uint32_le(p+4);
const unsigned char *name = p+8;
struct directory_entry *entry;
if (namelen + 8 + 1 > len || name[namelen] != 0) {
ERROR(eon->base.context, "corrupt dirent entry");
break;
}
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "dir entry", p, 8);
p += 8 + namelen + 1;
len -= 8 + namelen + 1;
entry = alloc_dirent(type, namelen, (const char *) name);
if (!entry) {
ERROR(eon->base.context, "out of memory");
break;
}
entry->next = old;
old = entry;
}
return old;
}
static dc_status_t
get_file_list(suunto_eonsteel_device_t *eon, struct directory_entry **res)
{
dc_status_t rc = DC_STATUS_SUCCESS;
struct directory_entry *de = NULL;
unsigned char cmd[64];
unsigned char result[2048];
unsigned int n = 0;
unsigned int cmdlen;
put_le32(0, cmd);
memcpy(cmd + 4, dive_directory, sizeof(dive_directory));
cmdlen = 4 + sizeof(dive_directory);
rc = suunto_eonsteel_transfer(eon, CMD_DIR_OPEN,
cmd, cmdlen, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "cmd DIR_LOOKUP failed");
return rc;
}
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "DIR_LOOKUP", result, n);
for (;;) {
unsigned int nr, last;
rc = suunto_eonsteel_transfer(eon, CMD_DIR_READDIR,
NULL, 0, result, sizeof(result), &n);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "readdir failed");
file_list_free(de);
return rc;
}
if (n < 8) {
ERROR(eon->base.context, "short readdir result");
file_list_free(de);
return DC_STATUS_PROTOCOL;
}
nr = array_uint32_le(result);
last = array_uint32_le(result+4);
HEXDUMP(eon->base.context, DC_LOGLEVEL_DEBUG, "dir packet", result, 8);
de = parse_dirent(eon, nr, result+8, n-8, de);
if (last)
break;
}
rc = suunto_eonsteel_transfer(eon, CMD_DIR_CLOSE,
NULL, 0, result, sizeof(result), NULL);
if (rc != DC_STATUS_SUCCESS) {
ERROR(eon->base.context, "dir close failed");
file_list_free(de);
return rc;
}
*res = de;
return DC_STATUS_SUCCESS;
}
dc_status_t
suunto_eonsteel_device_open(dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model)
{
dc_status_t status = DC_STATUS_SUCCESS;
suunto_eonsteel_device_t *eon = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
eon = (suunto_eonsteel_device_t *) dc_device_allocate(context, &suunto_eonsteel_device_vtable);
if (!eon)
return DC_STATUS_NOMEMORY;
// Set up the magic handshake fields
eon->iostream = iostream;
eon->model = model;
eon->magic = INIT_MAGIC;
eon->seq = INIT_SEQ;
memset (eon->version, 0, sizeof (eon->version));
memset (eon->fingerprint, 0, sizeof (eon->fingerprint));
status = dc_iostream_set_timeout(eon->iostream, 5000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
}
const unsigned char init[] = {0x02, 0x00, 0x2a, 0x00};
status = suunto_eonsteel_transfer(eon, CMD_INIT,
init, sizeof(init), eon->version, sizeof(eon->version), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR(context, "unable to initialize device");
goto error_free;
}
*out = (dc_device_t *) eon;
return DC_STATUS_SUCCESS;
error_free:
free(eon);
return status;
}
static dc_status_t
suunto_eonsteel_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
suunto_eonsteel_device_t *device = (suunto_eonsteel_device_t *) abstract;
if (size && size != sizeof (device->fingerprint))
return DC_STATUS_INVALIDARGS;
if (size)
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
else
memset (device->fingerprint, 0, sizeof (device->fingerprint));
return DC_STATUS_SUCCESS;
}
static dc_status_t
suunto_eonsteel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_status_t rc = DC_STATUS_SUCCESS;
int skip = 0;
struct directory_entry *de;
suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract;
dc_buffer_t *file;
char pathname[64];
unsigned int time;
unsigned int count = 0;
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
// Emit a device info event.
dc_event_devinfo_t devinfo;
devinfo.model = eon->model;
devinfo.firmware = array_uint32_be (eon->version + 0x20);
devinfo.serial = array_convert_str2num(eon->version + 0x10, 16);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
rc = get_file_list(eon, &de);
if (rc != DC_STATUS_SUCCESS)
return rc;
if (de == NULL) {
return DC_STATUS_SUCCESS;
}
// Locate the most recent dive.
// The filename represent the time of the dive, encoded as a hexadecimal
// number. Thus the most recent dive can be found by simply sorting the
// filenames alphabetically.
struct directory_entry *head = de, *tail = de, *latest = de;
while (de) {
if (strcmp (de->name, latest->name) > 0) {
latest = de;
}
tail = de;
count++;
de = de->next;
}
// Make the most recent dive the head of the list.
// The linked list is made circular, by attaching the head to the tail and
// then cut open again just before the most recent dive.
de = head;
while (de) {
if (de->next == latest) {
de->next = NULL;
tail->next = head;
break;
}
de = de->next;
}
file = dc_buffer_new (16384);
if (file == NULL) {
ERROR (abstract->context, "Insufficient buffer space available.");
file_list_free (latest);
return DC_STATUS_NOMEMORY;
}
progress.maximum = count;
progress.current = 0;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
de = latest;
while (de) {
int len;
struct directory_entry *next = de->next;
unsigned char buf[4];
const unsigned char *data = NULL;
unsigned int size = 0;
if (device_is_cancelled(abstract)) {
dc_status_set_error(&status, DC_STATUS_CANCELLED);
skip = 1;
}
switch (de->type) {
case DIRTYPE_DIR:
/* Ignore subdirectories in the dive directory */
break;
case DIRTYPE_FILE:
if (skip)
break;
if (sscanf(de->name, "%x.LOG", &time) != 1) {
dc_status_set_error(&status, DC_STATUS_PROTOCOL);
break;
}
put_le32(time, buf);
if (memcmp (buf, eon->fingerprint, sizeof (eon->fingerprint)) == 0) {
skip = 1;
break;
}
len = snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name);
if (len < 0 || (unsigned int) len >= sizeof(pathname)) {
dc_status_set_error(&status, DC_STATUS_PROTOCOL);
break;
}
// Reset the membuffer, put the 4-byte length at the head.
dc_buffer_clear(file);
dc_buffer_append(file, buf, 4);
// Then read the filename into the rest of the buffer
rc = read_file(eon, pathname, file);
if (rc != DC_STATUS_SUCCESS) {
dc_status_set_error(&status, rc);
break;
}
data = dc_buffer_get_data(file);
size = dc_buffer_get_size(file);
if (callback && !callback(data, size, data, sizeof(eon->fingerprint), userdata))
skip = 1;
}
progress.current++;
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
free(de);
de = next;
}
dc_buffer_free(file);
return status;
}
static dc_status_t suunto_eonsteel_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
{
suunto_eonsteel_device_t *eon = (suunto_eonsteel_device_t *) abstract;
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned char result[64], cmd[8];
unsigned int year, month, day;
unsigned int hour, min, msec;
year = datetime->year;
month = datetime->month;
day = datetime->day;
hour = datetime->hour;
min = datetime->minute;
msec = datetime->second * 1000;
cmd[0] = year & 0xFF;
cmd[1] = year >> 8;
cmd[2] = month;
cmd[3] = day;
cmd[4] = hour;
cmd[5] = min;
cmd[6] = msec & 0xFF;
cmd[7] = msec >> 8;
rc = suunto_eonsteel_transfer(eon, CMD_SET_TIME, cmd, sizeof(cmd), result, sizeof(result), NULL);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
rc = suunto_eonsteel_transfer(eon, CMD_SET_DATE, cmd, sizeof(cmd), result, sizeof(result), NULL);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
return DC_STATUS_SUCCESS;
}