The new names make it easier to identify the normal and reflected variant of the CRC function.
970 lines
25 KiB
C
970 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_crc32r(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_crc32r(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;
|
|
}
|
|
|
|
/*
|
|
* Insert a directory entry in the sorted list, most recent entry
|
|
* first.
|
|
*
|
|
* The directory entry names are the timestamps as hex, so ordering
|
|
* in alphabetical order ends up also ordering in date order!
|
|
*/
|
|
static struct directory_entry *insert_dirent(struct directory_entry *entry, struct directory_entry *list)
|
|
{
|
|
struct directory_entry **pos = &list, *next;
|
|
|
|
while ((next = *pos) != NULL) {
|
|
/* Is this bigger (more recent) than the next entry? We're good! */
|
|
if (strcmp(entry->name, next->name) > 0)
|
|
break;
|
|
pos = &next->next;
|
|
}
|
|
entry->next = next;
|
|
*pos = entry;
|
|
|
|
return list;
|
|
}
|
|
|
|
/*
|
|
* 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 *list)
|
|
{
|
|
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;
|
|
}
|
|
list = insert_dirent(entry, list);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int
|
|
count_file_list(struct directory_entry *list)
|
|
{
|
|
int count = 0;
|
|
|
|
while (list) {
|
|
count++;
|
|
list = list->next;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
file = dc_buffer_new (16384);
|
|
if (file == NULL) {
|
|
ERROR (abstract->context, "Insufficient buffer space available.");
|
|
file_list_free(de);
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
progress.maximum = count_file_list(de);
|
|
progress.current = 0;
|
|
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
|
|
|
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 = dc_platform_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;
|
|
}
|