From 844abcacfa111a00b8c0491dbfaa2aa70d1f38c2 Mon Sep 17 00:00:00 2001
From: Linus Torvalds <torvalds@linux-foundation.org>
Date: Mon, 20 Oct 2014 10:34:37 -0700
Subject: [PATCH 1/2] Add Suunto EON Steel support infrastructure

Very roughly copied from my test application.  It kind of works, but I'm
not actually importing samples yet, and the dc fields are incomplete.
But the basic data is there (dive time and duration, max depth,
gasmixes)

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
---
 examples/universal.c                       |   1 +
 include/libdivecomputer/common.h           |   1 +
 include/libdivecomputer/suunto.h           |   1 +
 include/libdivecomputer/suunto_eon_steel.h |  39 ++
 msvc/libdivecomputer.vcproj                |   8 +
 src/Makefile.am                            |   1 +
 src/descriptor.c                           |   4 +
 src/device.c                               |   3 +
 src/parser.c                               |   3 +
 src/suunto_eon_steel.c                     | 663 +++++++++++++++++++++++++++++
 src/suunto_eon_steel_parser.c              | 468 ++++++++++++++++++++
 11 files changed, 1192 insertions(+)
 create mode 100644 include/libdivecomputer/suunto_eon_steel.h
 create mode 100644 src/suunto_eon_steel.c
 create mode 100644 src/suunto_eon_steel_parser.c

diff --git a/examples/universal.c b/examples/universal.c
index 1e0307b25576..50f59a7c62ef 100644
--- a/examples/universal.c
+++ b/examples/universal.c
@@ -78,6 +78,7 @@ static const backend_table_t g_backends[] = {
 	{"vyper",       DC_FAMILY_SUUNTO_VYPER},
 	{"vyper2",      DC_FAMILY_SUUNTO_VYPER2},
 	{"d9",          DC_FAMILY_SUUNTO_D9},
+	{"eonsteel",    DC_FAMILY_SUUNTO_EON_STEEL},
 	{"aladin",      DC_FAMILY_UWATEC_ALADIN},
 	{"memomouse",   DC_FAMILY_UWATEC_MEMOMOUSE},
 	{"smart",       DC_FAMILY_UWATEC_SMART},
diff --git a/include/libdivecomputer/common.h b/include/libdivecomputer/common.h
index 983ec0716861..f600151b1391 100644
--- a/include/libdivecomputer/common.h
+++ b/include/libdivecomputer/common.h
@@ -49,6 +49,7 @@ typedef enum dc_family_t {
 	DC_FAMILY_SUUNTO_VYPER,
 	DC_FAMILY_SUUNTO_VYPER2,
 	DC_FAMILY_SUUNTO_D9,
+	DC_FAMILY_SUUNTO_EON_STEEL,
 	/* Reefnet */
 	DC_FAMILY_REEFNET_SENSUS = (2 << 16),
 	DC_FAMILY_REEFNET_SENSUSPRO,
diff --git a/include/libdivecomputer/suunto.h b/include/libdivecomputer/suunto.h
index 337dffd6f42f..87f1dc7583db 100644
--- a/include/libdivecomputer/suunto.h
+++ b/include/libdivecomputer/suunto.h
@@ -27,5 +27,6 @@
 #include "suunto_vyper.h"
 #include "suunto_vyper2.h"
 #include "suunto_d9.h"
+#include "suunto_eon_steel.h"
 
 #endif /* SUUNTO_H */
diff --git a/include/libdivecomputer/suunto_eon_steel.h b/include/libdivecomputer/suunto_eon_steel.h
new file mode 100644
index 000000000000..025dbce050f2
--- /dev/null
+++ b/include/libdivecomputer/suunto_eon_steel.h
@@ -0,0 +1,39 @@
+/*
+ * 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
+ */
+
+#ifndef EON_STEEL_H
+#define EON_STEEL_H
+
+#include "context.h"
+#include "device.h"
+#include "parser.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+dc_status_t suunto_eon_steel_device_open(dc_device_t **device, dc_context_t *context, const char *name, unsigned int model);
+dc_status_t suunto_eon_steel_parser_create(dc_parser_t **parser, dc_context_t *context, unsigned int model);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* EON_STEEL_H */
diff --git a/msvc/libdivecomputer.vcproj b/msvc/libdivecomputer.vcproj
index 78a8190ccda2..d5943c6f34c9 100644
--- a/msvc/libdivecomputer.vcproj
+++ b/msvc/libdivecomputer.vcproj
@@ -395,6 +395,14 @@
 				>
 			</File>
 			<File
+				RelativePath="..\src\suunto_eon_steel.c"
+				>
+			</File>
+			<File
+				RelativePath="..\src\suunto_eon_steel_parser.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\suunto_eon.c"
 				>
 			</File>
diff --git a/src/Makefile.am b/src/Makefile.am
index 595f0c7dc1c0..91265ebe043a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,6 +24,7 @@ libdivecomputer_la_SOURCES = \
 	suunto_vyper.c suunto_vyper_parser.c \
 	suunto_vyper2.c \
 	suunto_d9.c suunto_d9_parser.c \
+	suunto_eon_steel.c suunto_eon_steel_parser.c \
 	reefnet_sensus.c reefnet_sensus_parser.c \
 	reefnet_sensuspro.c reefnet_sensuspro_parser.c \
 	reefnet_sensusultra.c reefnet_sensusultra_parser.c \
diff --git a/src/descriptor.c b/src/descriptor.c
index 403b67a8746f..bc7ffaf71840 100644
--- a/src/descriptor.c
+++ b/src/descriptor.c
@@ -77,6 +77,10 @@ static const dc_descriptor_t g_descriptors[] = {
 	{"Suunto", "D6i",  DC_FAMILY_SUUNTO_D9, 0x1A},
 	{"Suunto", "D9tx", DC_FAMILY_SUUNTO_D9, 0x1B},
 	{"Suunto", "DX",   DC_FAMILY_SUUNTO_D9, 0x1C},
+	/* Suunto EON Steel */
+#ifdef HAVE_LIBUSB
+	{"Suunto", "EON Steel", DC_FAMILY_SUUNTO_EON_STEEL, 0},
+#endif
 	/* Uwatec Aladin */
 	{"Uwatec", "Aladin Air Twin",     DC_FAMILY_UWATEC_ALADIN, 0x1C},
 	{"Uwatec", "Aladin Sport Plus",   DC_FAMILY_UWATEC_ALADIN, 0x3E},
diff --git a/src/device.c b/src/device.c
index 9fad7a238bc4..35f0b6b90979 100644
--- a/src/device.c
+++ b/src/device.c
@@ -82,6 +82,9 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
 	case DC_FAMILY_SUUNTO_D9:
 		rc = suunto_d9_device_open (&device, context, name, dc_descriptor_get_model (descriptor));
 		break;
+	case DC_FAMILY_SUUNTO_EON_STEEL:
+		rc = suunto_eon_steel_device_open (&device, context, name, dc_descriptor_get_model (descriptor));
+		break;
 	case DC_FAMILY_UWATEC_ALADIN:
 		rc = uwatec_aladin_device_open (&device, context, name);
 		break;
diff --git a/src/parser.c b/src/parser.c
index 7cc7f4ed2cfd..1d9f023705bb 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -66,6 +66,9 @@ dc_parser_new (dc_parser_t **out, dc_device_t *device)
 	case DC_FAMILY_SUUNTO_D9:
 		rc = suunto_d9_parser_create (&parser, context, device->devinfo.model);
 		break;
+	case DC_FAMILY_SUUNTO_EON_STEEL:
+		rc = suunto_eon_steel_parser_create(&parser, context, device->devinfo.model);
+		break;
 	case DC_FAMILY_UWATEC_ALADIN:
 	case DC_FAMILY_UWATEC_MEMOMOUSE:
 		rc = uwatec_memomouse_parser_create (&parser, context, device->clock.devtime, device->clock.systime);
diff --git a/src/suunto_eon_steel.c b/src/suunto_eon_steel.c
new file mode 100644
index 000000000000..5ddeecf01d07
--- /dev/null
+++ b/src/suunto_eon_steel.c
@@ -0,0 +1,663 @@
+/*
+ * 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
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_LIBUSB
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libusb-1.0/libusb.h>
+
+#include <libdivecomputer/suunto_eon_steel.h>
+
+#include "context-private.h"
+#include "device-private.h"
+
+struct eon_steel {
+	dc_device_t base;
+
+	libusb_context *ctx;
+	libusb_device_handle *handle;
+	unsigned int magic;
+	unsigned short seq;
+};
+
+// 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[0];
+};
+
+// EON Steel command numbers and other magic field values
+#define INIT_CMD   0x00
+#define INIT_MAGIC 0x0001
+#define INIT_SEQ   0
+#define INIT_LEN   4
+#define INIT_DATA  "\x02\x00\x2a\x00"
+
+#define READ_STRING_CMD 0x0411
+
+#define FILE_LOOKUP_CMD 0x0010
+#define FILE_READ_CMD   0x0110
+#define FILE_STAT_CMD   0x0710
+#define FILE_CLOSE_CMD  0x0510
+
+#define DIR_LOOKUP_CMD 0x0810
+#define READDIR_CMD    0x0910
+#define DIR_CLOSE_CMD  0x0a10
+
+static const char *dive_directory = "0:/dives";
+
+// Minimal "struct membuffer" interface taken from subsurface
+struct membuffer {
+	unsigned int len, alloc;
+	char *buffer;
+};
+
+static void free_buffer(struct membuffer *b)
+{
+	free(b->buffer);
+	b->buffer = NULL;
+	b->len = 0;
+	b->alloc = 0;
+}
+
+/*
+ * Running out of memory isn't really an issue these days.
+ * So rather than do insane error handling and making the
+ * interface very complex, we'll just die. It won't happen
+ * unless you're running on a potato.
+ */
+static void oom(void)
+{
+	fprintf(stderr, "Out of memory\n");
+	exit(1);
+}
+
+static void make_room(struct membuffer *b, unsigned int size)
+{
+	unsigned int needed = b->len + size;
+	if (needed > b->alloc) {
+		char *n;
+		/* round it up to not reallocate all the time.. */
+		needed = needed * 9 / 8 + 1024;
+		n = realloc(b->buffer, needed);
+		if (!n)
+			oom();
+		b->buffer = n;
+		b->alloc = needed;
+	}
+}
+
+static void put_bytes(struct membuffer *b, const char *str, int len)
+{
+	make_room(b, len);
+	memcpy(b->buffer + b->len, str, len);
+	b->len += len;
+}
+
+static void put_string(struct membuffer *b, const char *str)
+{
+	put_bytes(b, str, strlen(str));
+}
+
+
+static struct directory_entry *alloc_dirent(int type, int len, const char *name)
+{
+	struct directory_entry *res;
+
+	res = malloc(sizeof(*res)+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 int error(const char *fmt, ...)
+{
+	char buffer[128];
+	va_list args;
+
+	va_start(args, fmt);
+	vsnprintf(buffer, sizeof(buffer)-1, fmt, args);
+	va_end(args);
+	fprintf(stderr, "%.*s\n", (int)sizeof(buffer), buffer);
+	return -1;
+}
+
+static unsigned short get_le16(const void *src)
+{
+	const unsigned char *p = src;
+	return p[0] + (p[1] << 8);
+}
+
+static void put_le16(unsigned short val, void *dst)
+{
+	unsigned char *p = dst;
+	p[0] = val;
+	p[1] = val >> 8;
+}
+
+static unsigned int get_le32(const void *src)
+{
+	const unsigned char *p = src;
+	return p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24);
+}
+
+static void put_le32(unsigned int val, void *dst)
+{
+	unsigned char *p = dst;
+	p[0] = val;
+	p[1] = val >> 8;
+	p[2] = val >> 16;
+	p[3] = val >> 24;
+}
+
+static void debug(const char *name, const char *buf, int len)
+{
+	int i;
+
+	fprintf(stderr, "%4d %s:", len, name);
+	for (i = 0; i < len; i++)
+		fprintf(stderr, " %02x", (unsigned char) buf[i]);
+	fprintf(stderr, "\n");
+}
+
+static void debug_text(const char *name, const char *buf, int len)
+{
+	int i;
+
+	printf("text of %s:\n", name);
+	for (i = 0; i < len; i++) {
+		unsigned char c = buf[i];
+		if (c > 31 && c < 127)
+			putchar(c);
+		else if (c == '\n') {
+			putchar('\\');
+			putchar('n');
+		} else {
+			static const char hex[16]="0123456789abcdef";
+			putchar('\\');
+			putchar('x');
+			putchar(hex[c>>4]);
+			putchar(hex[c&15]);
+		}
+	}
+	printf("\nend of text\n");
+}
+
+static int receive_data(struct eon_steel *eon, unsigned char *buffer, int size)
+{
+	const int InEndpoint = 0x82;
+	unsigned char buf[64];
+	int ret = 0;
+
+	for (;;) {
+		int rc, transferred,  len;
+
+		rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 5000);
+		if (rc || transferred != sizeof(buf))
+			return error("incomplete read interrupt transfer");
+// dump every incoming packet?
+// debug("rcv", buf, transferred);
+		if (buf[0] != 0x3f)
+			return error("read interrupt transfer returns wrong report type");
+		len = buf[1];
+		if (len > sizeof(buf)-2)
+			return error("read interrupt transfer reports short length");
+		if (len > size)
+			return error("read interrupt transfer reports excessive length");
+		memcpy(buffer+ret, buf+2, len);
+		size -= len;
+		ret += len;
+		if (len < sizeof(buf)-2)
+			break;
+	}
+
+	return ret;
+}
+
+static int send_cmd(struct eon_steel *eon,
+	unsigned short cmd,
+	unsigned int len,
+	const unsigned char *buffer)
+{
+	const int OutEndpoint = 0x02;
+	unsigned char buf[64];
+	int transferred, rc;
+	unsigned short seq = eon->seq;
+	unsigned int magic = eon->magic;
+
+	// Two-byte packet header, followed by 12 bytes of extended header
+	if (len > sizeof(buf)-2-12)
+		return error("send command with too much long");
+
+	memset(buf, 0, sizeof(buf));
+
+	buf[0] = 0x3f;
+	buf[1] = len + 12;
+
+	// 2-byte LE command word
+	put_le16(cmd, buf+2);
+
+	// 4-byte LE magic value (starts at 1)
+	put_le32(magic, buf+4);
+
+	// 2-byte LE sequence number;
+	put_le16(seq, buf+8);
+
+	// 4-byte LE length
+	put_le32(len, buf+10);
+
+	// .. followed by actual data
+	memcpy(buf+14, buffer, len);
+
+	rc = libusb_interrupt_transfer(eon->handle, OutEndpoint, buf, sizeof(buf), &transferred, 5000);
+	if (rc < 0)
+		return error("write interrupt transfer failed");
+
+// dump every outgoing packet?
+// debug("cmd", buf, sizeof(buf));
+	return 0;
+}
+
+/*
+ * 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_data()" itself will have removed the
+ * per-packet handshake bytes, so unlike "send_cmd()", this does
+ * not see the two initial 0x3f 0x?? bytes, and this the offsets
+ * for the cmd/magic/seq/len are off by two compared to the
+ * send_cmd() side. The offsets are the same in the actual raw
+ * packet.
+ */
+static int send_receive(struct eon_steel *eon,
+	unsigned short cmd,
+	unsigned int len_out, const unsigned char *out,
+	unsigned int len_in, unsigned char *in)
+{
+	int len, actual;
+	unsigned char buf[2048];
+
+	if (send_cmd(eon, cmd, len_out, out) < 0)
+		return -1;
+	len = receive_data(eon, buf, sizeof(buf));
+	if (len < 10)
+		return error("short command reply (%d)", len);
+	if (get_le16(buf) != cmd)
+		return error("command reply doesn't match command");
+	if (get_le32(buf+2) != eon->magic + 5)
+		return error("command reply doesn't match magic (got %08x, expected %08x)", get_le32(buf+2), eon->magic + 5);
+	if (get_le16(buf+6) != eon->seq)
+		return error("command reply doesn't match sequence number");
+	actual = get_le32(buf+8);
+	if (actual + 12 != len)
+		return error("command reply length mismatch (got %d, claimed %d)", len-12, actual);
+	if (len_in < actual)
+		return error("command reply returned too much data (got %d, had %d)", actual, len_in);
+
+	// Successful command - increment sequence number
+	eon->seq++;
+	memcpy(in, buf+12, actual);
+	return actual;
+}
+
+static int read_file(struct eon_steel *eon, const char *filename, struct membuffer *buf)
+{
+	unsigned char result[2560];
+	unsigned char cmdbuf[64];
+	unsigned int size, offset;
+	int rc, len;
+
+	memset(cmdbuf, 0, sizeof(cmdbuf));
+	len = strlen(filename) + 1;
+	if (len + 4 > sizeof(cmdbuf))
+		return error("too long filename: %s", filename);
+	memcpy(cmdbuf+4, filename, len);
+	rc = send_receive(eon, FILE_LOOKUP_CMD,
+		len+4, cmdbuf,
+		sizeof(result), result);
+	if (rc < 0)
+		return error("unable to look up %s", filename);
+// debug("lookup", result, rc);
+
+	rc = send_receive(eon, FILE_STAT_CMD,
+		0, "",
+		sizeof(result), result);
+	if (rc < 0)
+		return error("unable to stat %s", filename);
+// debug("stat", result, rc);
+
+	size = get_le32(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 = send_receive(eon, FILE_READ_CMD,
+			8, cmdbuf,
+			sizeof(result), result);
+		if (rc < 0)
+			return error("unable to read %s", filename);
+		if (rc < 8)
+			return error("got short read reply for %s", filename);
+
+		// Not file offset, just stays unmodified.
+		at = get_le32(result);
+		if (at != 1234)
+			return error("read of %s returned different offset than asked for (%d vs %d)", filename, at, offset);
+
+		// Number of bytes actually read
+		got = get_le32(result+4);
+		if (!got)
+			break;
+		if (rc < 8 + got)
+			return error("odd read size reply for offset %d of file %s", offset, filename);
+
+		if (got > size)
+			got = size;
+		put_bytes(buf, result+8, got);
+		offset += got;
+		size -= got;
+	}
+
+	// Hack. Make sure there are zeroes at the end.
+	// The parser isn't very careful about the length.
+	put_bytes(buf, "\0\0\0\0\0\0\0\0", 8);
+	buf->len -= 8;
+
+	rc = send_receive(eon, FILE_CLOSE_CMD,
+		0, "",
+		sizeof(result), result);
+	if (rc < 0)
+		error("cmd 0510 failed");
+// debug("close", result, rc);
+
+	return offset;
+}
+
+/*
+ * 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(int nr, const unsigned char *p, int len, struct directory_entry *old)
+{
+	while (len > 8) {
+		unsigned int type = get_le32(p);
+		unsigned int namelen = get_le32(p+4);
+		const unsigned char *name = p+8;
+		struct directory_entry *entry;
+
+		if (namelen + 8 + 1 > len || name[namelen] != 0) {
+			error("corrupt dirent entry");
+			break;
+		}
+// debug("dir entry", p, 8);
+		p += 8 + namelen + 1;
+		len -= 8 + namelen + 1;
+		entry = alloc_dirent(type, namelen, name);
+		entry->next = old;
+		old = entry;
+	}
+	return old;
+}
+
+static int get_file_list(struct eon_steel *eon, struct directory_entry **res)
+{
+	struct directory_entry *de = NULL;
+	unsigned char cmd[64];
+	unsigned char result[2048];
+	int rc, cmdlen;
+
+
+	*res = NULL;
+	put_le32(0, cmd);
+	strcpy(cmd+4, dive_directory);
+	cmdlen = 4+strlen(dive_directory)+1;
+	rc = send_receive(eon, DIR_LOOKUP_CMD,
+		cmdlen, cmd,
+		sizeof(result), result);
+	if (rc < 0)
+		error("cmd DIR_LOOKUP failed");
+// debug("DIR_LOOKUP", result, rc);
+
+	for (;;) {
+		unsigned int nr, last;
+
+		rc = send_receive(eon, READDIR_CMD,
+			0, "",
+			sizeof(result), result);
+		if (rc < 0)
+			return error("readdir failed");
+		if (rc < 8)
+			return error("short readdir result");
+		nr = get_le32(result);
+		last = get_le32(result+4);
+// debug("dir packet", result, 8);
+
+		de = parse_dirent(nr, result+8, rc-8, de);
+		if (last)
+			break;
+	}
+
+	rc = send_receive(eon, DIR_CLOSE_CMD,
+		0, "",
+		sizeof(result), result);
+	if (rc < 0)
+		error("dir close failed");
+
+	*res = de;
+	return 0;
+}
+
+
+static dc_status_t eon_steel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
+static dc_status_t eon_steel_device_close(dc_device_t *abstract);
+
+static const dc_device_vtable_t eon_steel_device_vtable = {
+	DC_FAMILY_SUUNTO_EON_STEEL,
+	NULL, /* set_fingerprint */
+	NULL, /* read */
+	NULL, /* write */
+	NULL, /* dump */
+	eon_steel_device_foreach, /* foreach */
+	eon_steel_device_close /* close */
+};
+
+static int initialize_eon_steel(struct eon_steel *eon)
+{
+	const int InEndpoint = 0x82;
+	unsigned char buf[64];
+
+	/* Get rid of any pending stale input first */
+	for (;;) {
+		int transferred;
+
+		int rc = libusb_interrupt_transfer(eon->handle, InEndpoint, buf, sizeof(buf), &transferred, 10);
+		if (rc < 0)
+			break;
+		if (!transferred)
+			break;
+	}
+
+	if (send_cmd(eon, INIT_CMD, INIT_LEN, INIT_DATA))
+		return error("Failed to send initialization command");
+	if (receive_data(eon, buf, sizeof(buf)) < 0)
+		return error("Failed to receive initial reply");
+
+	// Don't ask
+	eon->magic = 0x00000005 | (buf[4] << 16) | (buf[5] << 24);
+	// Increment the sequence number for every command sent
+	eon->seq++;
+	return 0;
+}
+
+dc_status_t suunto_eon_steel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model)
+{
+	int rc;
+	struct eon_steel *eon;
+
+	if (out == NULL)
+		return DC_STATUS_INVALIDARGS;
+
+	eon = calloc(1, sizeof(struct eon_steel));
+	if (!eon)
+		return DC_STATUS_NOMEMORY;
+
+	// Set up the magic handshake fields
+	eon->magic = INIT_MAGIC;
+	eon->seq = INIT_SEQ;
+
+	// Set up the libdivecomputer interfaces
+	device_init(&eon->base, context, &eon_steel_device_vtable);
+	*out = (void *) eon;
+
+	if (libusb_init(&eon->ctx)) {
+		ERROR(context, "libusb_init() failed");
+		return DC_STATUS_IO;
+	}
+
+	eon->handle = libusb_open_device_with_vid_pid(eon->ctx, 0x1493, 0x0030);
+	if (!eon->handle) {
+		ERROR(context, "unable to open EON Steel device (%s)", strerror(errno));
+		libusb_exit(eon->ctx);
+		return DC_STATUS_IO;
+	}
+
+	libusb_set_auto_detach_kernel_driver(eon->handle, 1);
+	libusb_claim_interface(eon->handle, 0);
+
+	if (initialize_eon_steel(eon) < 0) {
+		ERROR(context, "unable to initialize EON Steel device (%s)", strerror(errno));
+		libusb_exit(eon->ctx);
+		return DC_STATUS_IO;
+	}
+
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t eon_steel_device_foreach(dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
+{
+	int skip = 0, rc;
+	struct directory_entry *de;
+	struct eon_steel *eon = (struct eon_steel *) abstract;
+	struct membuffer file = { 0 };
+	char pathname[64];
+	unsigned int time;
+
+	if (get_file_list(eon, &de) < 0)
+		return DC_STATUS_IO;
+
+	while (de) {
+		int len;
+		struct directory_entry *next = de->next;
+
+		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)
+				break;
+			len = snprintf(pathname, sizeof(pathname), "%s/%s", dive_directory, de->name);
+			if (len >= sizeof(pathname))
+				break;
+
+			// Reset the membuffer, put the 4-byte length at the head.
+			file.len = 0;
+			make_room(&file, 4);
+			put_le32(time, file.buffer);
+			file.len = 4;
+
+			// Then read the filename into the rest of the buffer
+			rc = read_file(eon, pathname, &file);
+			if (rc < 0)
+				break;
+			if (!callback)
+				break;
+			if (!callback(file.buffer, file.len, NULL, 0, userdata))
+				skip = 1;
+
+			// We've used up the buffer, so disconnect it
+			file.buffer = NULL;
+			file.alloc = 0;
+			file.len = 0;
+		}
+		free(de);
+		de = next;
+	}
+	free_buffer(&file);
+
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t eon_steel_device_close(dc_device_t *abstract)
+{
+	struct eon_steel *eon = (struct eon_steel *) abstract;
+
+	libusb_close(eon->handle);
+	libusb_exit(eon->ctx);
+	free(eon);
+
+	return DC_STATUS_SUCCESS;
+}
+
+#else // no LIBUSB support
+
+dc_status_t suunto_eon_steel_device_open(dc_device_t **out, dc_context_t *context, const char *name, unsigned int model)
+{
+	ERROR(context, "The Suunto EON Steel backend needs libusb-1.0");
+	return DC_STATUS_UNSUPPORTED;
+}
+
+#endif
diff --git a/src/suunto_eon_steel_parser.c b/src/suunto_eon_steel_parser.c
new file mode 100644
index 000000000000..cec8f0b43823
--- /dev/null
+++ b/src/suunto_eon_steel_parser.c
@@ -0,0 +1,468 @@
+/*
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libdivecomputer/suunto_eon_steel.h>
+
+#include "context-private.h"
+#include "parser-private.h"
+
+struct type_desc {
+	const char *desc, *format, *mod;
+};
+
+#define MAXTYPE 512
+#define MAXGASES 16
+
+struct eon_parser {
+	dc_parser_t base;
+	struct type_desc type_desc[MAXTYPE];
+	// field cache
+	struct {
+		unsigned int initialized;
+		unsigned int divetime;
+		double maxdepth;
+		double avgdepth;
+		unsigned int ngases;
+		dc_gasmix_t gasmix[MAXGASES];
+		dc_salinity_t salinity;
+		double surface_pressure;
+	} cache;
+};
+
+
+static int error(const char *fmt, ...)
+{
+	char buffer[128];
+	va_list args;
+
+	va_start(args, fmt);
+	vsnprintf(buffer, sizeof(buffer)-1, fmt, args);
+	va_end(args);
+	fprintf(stderr, "%.*s\n", (int)sizeof(buffer), buffer);
+	return -1;
+}
+
+static unsigned char get_u8(const void *src)
+{
+	return *(const unsigned char *)src;
+}
+
+static unsigned short get_le16(const void *src)
+{
+	const unsigned char *p = src;
+	return p[0] + (p[1] << 8);
+}
+
+static void put_le16(unsigned short val, void *dst)
+{
+	unsigned char *p = dst;
+	p[0] = val;
+	p[1] = val >> 8;
+}
+
+static unsigned int get_le32(const void *src)
+{
+	const unsigned char *p = src;
+	return p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24);
+}
+
+static void put_le32(unsigned int val, void *dst)
+{
+	unsigned char *p = dst;
+	p[0] = val;
+	p[1] = val >> 8;
+	p[2] = val >> 16;
+	p[3] = val >> 24;
+}
+
+static void debug(const char *name, const char *buf, int len)
+{
+	int i;
+
+	fprintf(stderr, "%4d %s:", len, name);
+	for (i = 0; i < len; i++)
+		fprintf(stderr, " %02x", (unsigned char) buf[i]);
+	fprintf(stderr, "\n");
+}
+
+static void debug_text(const char *name, const char *buf, int len)
+{
+	int i;
+
+	printf("text of %s:\n", name);
+	for (i = 0; i < len; i++) {
+		unsigned char c = buf[i];
+		if (c > 31 && c < 127)
+			putchar(c);
+		else if (c == '\n') {
+			putchar('\\');
+			putchar('n');
+		} else {
+			static const char hex[16]="0123456789abcdef";
+			putchar('\\');
+			putchar('x');
+			putchar(hex[c>>4]);
+			putchar(hex[c&15]);
+		}
+	}
+	printf("\nend of text\n");
+}
+
+typedef int (*eon_data_cb_t)(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user);
+
+static int record_type(struct eon_parser *eon, unsigned short type, const char *name, int namelen)
+{
+	struct type_desc desc;
+	const char *next;
+
+	desc.desc = desc.format = desc.mod = "";
+	do {
+		int len;
+		char *p;
+
+		next = strchr(name, '\n');
+		if (next) {
+			len = next - name;
+			next++;
+		} else {
+			len = strlen(name);
+		}
+
+		if (len < 5 || name[0] != '<' || name[4] != '>')
+			return error("Unexpected type description: %.*s", len, name);
+		p = malloc(len-4);
+		if (!p)
+			return error("out of memory");
+		memcpy(p, name+5, len-5);
+		p[len-5] = 0;
+
+		// PTH, GRP, FRM, MOD
+		switch (name[1]) {
+		case 'P': case 'G':
+			desc.desc = p;
+			break;
+		case 'F':
+			desc.format = p;
+			break;
+		case 'M':
+			desc.mod = p;
+			break;
+		default:
+			return error("Unknown type descriptor: %,*s", len, name);
+		}
+	} while ((name = next) != NULL);
+
+	if (type > MAXTYPE)
+		return error("Type out of range (%04x: '%s' '%s' '%s')",
+			     type, desc.desc, desc.format, desc.mod);
+
+	eon->type_desc[type] = desc;
+	return 0;
+}
+
+static int traverse_entry(struct eon_parser *eon, const unsigned char *p, int len, eon_data_cb_t callback, void *user)
+{
+	const unsigned char *name, *data, *end, *last;
+	int textlen, type;
+	int rc;
+
+	// First two bytes: zero and text length
+	if (p[0]) {
+		debug("next", p, 8);
+		return error("Bad dive entry (%02x)", p[0]);
+	}
+	textlen = p[1];
+
+	name = p + 2;
+	if (textlen == 0xff) {
+		textlen = get_le32(name);
+		name += 4;
+	}
+
+	// Two bytes of 'type' followed by the name/descriptor, followed by the data
+	data = name + textlen;
+	type = get_le16(name);
+	name += 2;
+
+	if (*name != '<') {
+		fflush(NULL);
+		debug("bad", p, 16);
+		exit(1);
+	}
+
+	record_type(eon, type, name, textlen-3);
+
+	end = data;
+	last = data;
+	while (*end) {
+		const char *begin = end;
+		unsigned int type = *end++;
+		unsigned int len;
+		if (type == 0xff) {
+			type = get_le16(end);
+			end += 2;
+		}
+		len = *end++;
+
+		// I've never actually seen this case yet..
+		// Just assuming from the other cases.
+		if (len == 0xff) {
+			debug("len-ff", end, 8);
+			len = get_le32(end);
+			end += 4;
+		}
+
+		if (type > MAXTYPE || !eon->type_desc[type].desc) {
+			debug("last", last, 16);
+			debug("this", begin, 16);
+		} else {
+			rc = callback(type, eon->type_desc+type, end, len, user);
+			if (rc < 0)
+				return rc;
+		}
+
+		last = begin;
+		end += len;
+	}
+
+	return end - p;
+}
+
+static int traverse_data(struct eon_parser *eon, eon_data_cb_t callback, void *user)
+{
+	const unsigned char *data = eon->base.data;
+	int len = eon->base.size;
+
+	// Dive files start with "SBEM" and four NUL characters
+	// Additionally, we've prepended the time as an extra
+	// 4-byte pre-header
+	if (len < 12 || memcmp(data+4, "SBEM", 4))
+		return 0;
+
+	data += 12;
+	len -= 12;
+
+	while (len > 4) {
+		int i = traverse_entry(eon, data, len, callback, user);
+		if (i < 0)
+			return 1;
+		len -= i;
+		data += i;
+	}
+	return 0;
+}
+
+static dc_status_t eon_steel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata)
+{
+	return DC_STATUS_SUCCESS;
+}
+
+static int traverse_get_field(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user)
+{
+	struct get_field_info *info = user;
+
+}
+
+// Ugly typeof thing makes the code much easier to read
+#define field_value(p, set) \
+	do { *(__typeof__(set) *)(p) = (set); } while (0)
+
+static dc_status_t eon_steel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value)
+{
+	struct eon_parser *eon = (struct eon_parser *)parser;
+
+	if (!(eon->cache.initialized >> type))
+		return DC_STATUS_UNSUPPORTED;
+
+	switch (type) {
+	case DC_FIELD_DIVETIME:
+		field_value(value, eon->cache.divetime);
+		break;
+	case DC_FIELD_MAXDEPTH:
+		field_value(value, eon->cache.maxdepth);
+		break;
+	case DC_FIELD_AVGDEPTH:
+		field_value(value, eon->cache.avgdepth);
+		break;
+	case DC_FIELD_GASMIX_COUNT:
+		field_value(value, eon->cache.ngases);
+		break;
+	case DC_FIELD_GASMIX:
+		if (flags >= MAXGASES)
+			return DC_STATUS_UNSUPPORTED;
+		field_value(value, eon->cache.gasmix[flags]);
+		break;
+	case DC_FIELD_SALINITY:
+		field_value(value, eon->cache.salinity);
+		break;
+	case DC_FIELD_ATMOSPHERIC:
+		field_value(value, eon->cache.surface_pressure);
+		break;
+	}
+	return DC_STATUS_SUCCESS;
+}
+
+/*
+ * The time of the dive is encoded in the filename,
+ * and we've saved it off as the four first bytes
+ * of the dive data (in little-endian format).
+ */
+static dc_status_t eon_steel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime)
+{
+	unsigned int t;
+
+	if (parser->size < 4)
+		return DC_STATUS_UNSUPPORTED;
+
+	dc_datetime_gmtime(datetime, get_le32(parser->data));
+	return DC_STATUS_SUCCESS;
+}
+
+// time in ms
+static void add_time_field(struct eon_parser *eon, unsigned short time_delta_ms)
+{
+	eon->cache.divetime += time_delta_ms;
+}
+
+// depth in cm
+static void set_depth_field(struct eon_parser *eon, unsigned short d)
+{
+	if (d != 0xffff) {
+		double depth = d / 100.0;
+		if (depth > eon->cache.maxdepth)
+			eon->cache.maxdepth = depth;
+		eon->cache.initialized |= 1 << DC_FIELD_MAXDEPTH;
+	}
+}
+
+// gas type: 0=Off,1=Primary,2=?,3=Diluent
+static void add_gas_type(struct eon_parser *eon, unsigned char type)
+{
+	if (eon->cache.ngases < MAXGASES)
+		eon->cache.ngases++;
+	eon->cache.initialized |= 1 << DC_FIELD_GASMIX_COUNT;
+}
+
+// O2 percentage as a byte
+static void add_gas_o2(struct eon_parser *eon, unsigned char o2)
+{
+	int idx = eon->cache.ngases-1;
+	if (idx >= 0)
+		eon->cache.gasmix[idx].oxygen = o2 / 100.0;
+	eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
+}
+
+// He percentage as a byte
+static void add_gas_he(struct eon_parser *eon, unsigned char he)
+{
+	int idx = eon->cache.ngases-1;
+	if (idx >= 0)
+		eon->cache.gasmix[idx].helium = he / 100.0;
+	eon->cache.initialized |= 1 << DC_FIELD_GASMIX;
+}
+
+static int traverse_fields(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user)
+{
+	struct eon_parser *eon = user;
+
+	switch (type) {
+	case 0x0001: // group: time in first word, depth in second
+		add_time_field(eon, get_le16(data));
+		set_depth_field(eon, get_le16(data+2));
+		break;
+	case 0x0002: // time in first word
+		add_time_field(eon, get_le16(data));
+		break;
+	case 0x0003: // depth in first word
+		set_depth_field(eon, get_le16(data));
+		break;
+	case 0x000d: // gas state in first byte
+		add_gas_type(eon, get_u8(data));
+		break;
+	case 0x000e: // Oxygen percentage in first byte
+		add_gas_o2(eon, get_u8(data));
+		break;
+	case 0x000f: // Helium percentage in first byte
+		add_gas_he(eon, get_u8(data));
+		break;
+	}
+	return 0;
+}
+
+
+static void initialize_field_caches(struct eon_parser *eon)
+{
+	memset(&eon->cache, 0, sizeof(eon->cache));
+	eon->cache.initialized = 1 << DC_FIELD_DIVETIME;
+
+	traverse_data(eon, traverse_fields, eon);
+
+	// The internal time fields are in ms and have to be added up
+	// like that. At the end, we translate it back to seconds.
+	eon->cache.divetime /= 1000;
+}
+
+static dc_status_t eon_steel_parser_set_data(dc_parser_t *parser, const unsigned char *data, unsigned int size)
+{
+	struct eon_parser *eon = (void *)parser;
+	memset(eon->type_desc, 0, sizeof(eon->type_desc));
+	initialize_field_caches(eon);
+	return DC_STATUS_SUCCESS;
+}
+
+static dc_status_t eon_steel_parser_destroy(dc_parser_t *parser)
+{
+	free(parser);
+	return DC_STATUS_SUCCESS;
+}
+
+static const dc_parser_vtable_t eon_steel_parser_vtable = {
+	DC_FAMILY_SUUNTO_EON_STEEL,
+	eon_steel_parser_set_data, /* set_data */
+	eon_steel_parser_get_datetime, /* datetime */
+	eon_steel_parser_get_field, /* fields */
+	eon_steel_parser_samples_foreach, /* samples_foreach */
+	eon_steel_parser_destroy /* destroy */
+};
+
+dc_status_t suunto_eon_steel_parser_create(dc_parser_t **out, dc_context_t *context, unsigned int model)
+{
+	struct eon_parser *eon;
+
+	if (out == NULL)
+		return DC_STATUS_INVALIDARGS;
+
+	eon = calloc(1, sizeof(*eon));
+	parser_init(&eon->base, context, &eon_steel_parser_vtable);
+	*out = &eon->base;
+
+	return DC_STATUS_SUCCESS;
+}
-- 
2.1.2.452.gd5ca7ae

