On Tue, Apr 24, 2018 at 5:27 AM, Sébastien Dugué
<[email protected]> wrote:
> On Mon, Apr 23, 2018 at 7:07 PM, Linus Torvalds 
> <[email protected]> wrote:
>>
>> On most dive computers, it's 4 bytes, on others it's 5-6, and on some
>> it's 16 bytes of random binary data.
>
>   My bad, I assumed that feature was only present in the Uwatec DCs where it's
> a timestamp.
>
>   And I cannot imagine how something else might be useful for that purpose...

Well, the fingerprint can anything, which is part of the problem. It
can be an offset into the dive computer buffer. It could be just an
index into the dive computer dive table. It could be the first few
bytes of the dive download that contains the unparsed dive data (which
probably *includes* the dive date, but can have a internal dive
computer dive number)

So in practice it really is random data, and it's not useful to
subsurface directly. We actually do use it to generate a "dive ID"
(that is used to check for duplicates), but that is a fairly simple
and well-defined thing (it's the first four bytes of a SHA1 hash -
either of the fingerprint data, or if the dive computer gives a
particular "Dive ID" string.

And I *really* don't want to save it to our dive logs, because it's
not useful long-term. It's not useful to a user, but it's not even
useful to subsurface long term - only the last dive matters. So saving
it for every dive is fundamentally wrong - it's not how it would ever
be used.

But I think I've come up with an approach that is viable, exactly
*because* we do have a dive ID anyway. We can save the fingerprint
*and* the dive ID in the cache file, and then when we *use* the
fingerprint, we just verify that we have a dive that matches the dive
ID associated with that fingerprint.

So at worst, we'll never use the fingerprint at all, because the last
time somebody did a download, they didn't save the dives (or maybe
they just did't save them into the file we're now using, or didn't
save the *last* dive or whatever).

But then we're no worse off than we are now.

And if we *do* have the dive that is associated with the fingerprint,
then we'll use it, and only download new dives up until that point.

The attached patch seems to work in my (very limited!) testing.

Testers welcome. It should apply fine to any modern subsurface tree
(ie you don't have to have the "NG" branch, but it won't hurt, and
it's what I tested and used as a base).

                Linus
 core/libdivecomputer.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++++
 core/libdivecomputer.h |   2 +
 2 files changed, 148 insertions(+)

diff --git a/core/libdivecomputer.c b/core/libdivecomputer.c
index c547a85b2..990904878 100644
--- a/core/libdivecomputer.c
+++ b/core/libdivecomputer.c
@@ -8,6 +8,9 @@
 #include <unistd.h>
 #include <inttypes.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 #include "gettext.h"
 #include "dive.h"
 #include "device.h"
@@ -21,6 +24,9 @@
 
 #include "libdivecomputer.h"
 #include "core/version.h"
+#include "core/qthelper.h"
+#include "core/membuffer.h"
+#include "core/file.h"
 
 //
 // If we have an old libdivecomputer, it doesn't
@@ -785,6 +791,16 @@ static int dive_cb(const unsigned char *data, unsigned int size,
 	dive->dc.model = strdup(devdata->model);
 	dive->dc.diveid = calculate_diveid(fingerprint, fsize);
 
+	/* Should we add it to the cached fingerprint file? */
+	if (fingerprint && fsize && !devdata->fingerprint) {
+		devdata->fingerprint = calloc(fsize, 1);
+		if (devdata->fingerprint) {
+			devdata->fsize = fsize;
+			devdata->fdiveid = dive->dc.diveid;
+			memcpy(devdata->fingerprint, fingerprint, fsize);
+		}
+	}
+
 	// Parse the dive's header data
 	rc = libdc_header_parser (parser, devdata, dive);
 	if (rc != DC_STATUS_SUCCESS) {
@@ -924,6 +940,121 @@ static unsigned int fixup_suunto_versions(device_data_t *devdata, const dc_event
 
 	return serial;
 }
+#ifndef O_BINARY
+  #define O_BINARY 0
+#endif
+static void do_save_fingerprint(device_data_t *devdata, const char *tmp, const char *final)
+{
+	int fd, written = -1;
+
+	fd = subsurface_open(tmp, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, 0666);
+	if (fd < 0)
+		return;
+
+	/* The fingerprint itself.. */
+	written = write(fd, devdata->fingerprint, devdata->fsize);
+
+	/* ..followed by the dive ID of the fingerprinted dive */
+	if (write(fd, &devdata->fdiveid, 4) != 4)
+		written = -1;
+
+	/* I'd like to do fsync() here too, but does Windows support it? */
+	if (close(fd) < 0)
+		written = -1;
+
+	if (written == devdata->fsize) {
+		if (!subsurface_rename(tmp, final))
+			return;
+	}
+	unlink(tmp);
+}
+
+/*
+ * Save the fingerprint after a successful download
+ */
+static void save_fingerprint(device_data_t *devdata)
+{
+	char *dir, *tmp, *final;
+
+	if (!devdata->fingerprint)
+		return;
+
+	dir = format_string("%s/fingerprints", system_default_directory());
+	subsurface_mkdir(dir);
+	tmp = format_string("%s/%04x.tmp", dir, devdata->deviceid);
+	final = format_string("%s/%04x", dir, devdata->deviceid);
+	free(dir);
+
+	do_save_fingerprint(devdata, tmp, final);
+	free(tmp);
+	free(final);
+	free(devdata->fingerprint);
+	devdata->fingerprint = NULL;
+}
+
+static int has_dive(unsigned int deviceid, unsigned int diveid)
+{
+	int i;
+	struct dive *dive;
+
+	for_each_dive (i, dive) {
+		struct divecomputer *dc;
+
+		for_each_dc (dive, dc) {
+			if (dc->deviceid != deviceid)
+				continue;
+			if (dc->diveid != diveid)
+				continue;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * The fingerprint cache files contain the actual libdivecomputer
+ * fingerprint, followed by 4 bytes of diveid data. Before we use
+ * the fingerprint data, verify that we actually do have that
+ * fingerprinted dive.
+ */
+static void verify_fingerprint(dc_device_t *device, device_data_t *devdata, const unsigned char *buffer, size_t size)
+{
+	unsigned int diveid, deviceid;
+
+	if (size <= 4)
+		return;
+	size -= 4;
+
+	/* Get the dive ID from the end of the fingerprint cache file.. */
+	memcpy(&diveid, buffer + size, 4);
+	/* .. and the device ID from the device data */
+	deviceid = devdata->deviceid;
+
+	/* Only use it if we *have* that dive! */
+	if (has_dive(deviceid, diveid))
+		dc_device_set_fingerprint(device, buffer, size);
+}
+
+/*
+ * Look up the fingerprint from the fingerprint caches, and
+ * give it to libdivecomputer to avoid downloading already
+ * downloaded dives.
+ */
+static void lookup_fingerprint(dc_device_t *device, device_data_t *devdata)
+{
+	char *cachename;
+	struct memblock mem;
+
+	if (devdata->force_download)
+		return;
+	cachename = format_string("%s/fingerprints/%04x",
+		system_default_directory(), devdata->deviceid);
+	if (readfile(cachename, &mem) > 0) {
+		verify_fingerprint(device, devdata, mem.buffer, mem.size);
+		free(mem.buffer);
+	}
+	free(cachename);
+}
 
 static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata)
 {
@@ -964,6 +1095,7 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat
 				devinfo->firmware, devinfo->firmware,
 				devinfo->serial, devinfo->serial);
 		}
+
 		/*
 		 * libdivecomputer doesn't give serial numbers in the proper string form,
 		 * so we have to see if we can do some vendor-specific munging.
@@ -977,6 +1109,9 @@ static void event_cb(dc_device_t *device, dc_event_type_t event, const void *dat
 		 * DC_FIELD_STRING interface instead */
 		devdata->libdc_serial = devinfo->serial;
 		devdata->libdc_firmware = devinfo->firmware;
+
+		lookup_fingerprint(device, devdata);
+
 		break;
 	case DC_EVENT_CLOCK:
 		dev_info(devdata, translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"),
@@ -1159,6 +1294,8 @@ const char *do_libdivecomputer_import(device_data_t *data)
 	data->device = NULL;
 	data->context = NULL;
 	data->iostream = NULL;
+	data->fingerprint = NULL;
+	data->fsize = 0;
 
 	if (data->libdc_log && logfile_name)
 		fp = subsurface_fopen(logfile_name, "w");
@@ -1207,6 +1344,15 @@ const char *do_libdivecomputer_import(device_data_t *data)
 		fclose(fp);
 	}
 
+	/*
+	 * Note that we save the fingerprint unconditionally.
+	 * This is ok because we only have fingerprint data if
+	 * we got a dive header, and because we will use the
+	 * dive id to verify that we actually have the dive
+	 * it refers to before we use the fingerprint data.
+	 */
+	save_fingerprint(data);
+
 	return err;
 }
 
diff --git a/core/libdivecomputer.h b/core/libdivecomputer.h
index b9cc20132..d4663334f 100644
--- a/core/libdivecomputer.h
+++ b/core/libdivecomputer.h
@@ -25,6 +25,8 @@ typedef struct dc_user_device_t
 	dc_descriptor_t *descriptor;
 	const char *vendor, *product, *devname;
 	const char *model;
+	unsigned char *fingerprint;
+	unsigned int fsize, fdiveid;
 	uint32_t libdc_firmware, libdc_serial;
 	uint32_t deviceid, diveid;
 	dc_device_t *device;
_______________________________________________
subsurface mailing list
[email protected]
http://lists.subsurface-divelog.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to