From 94606e35fb5ae88546572bea4d87d1c067d9c9e5 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 10:13:19 +1300 Subject: [PATCH 1/8] device: add product_name field to dc_event_devinfo_t Some device families (e.g. Mares Icon HD) distinguish multiple marketed products through a product-name string in the device version packet, but share a single coarse numeric model. The numeric model remains the authoritative parser-layout selector; the product_name field provides supplementary information for consumer-side label refinement. - Add DC_DEVINFO_PRODUCT_NAME_SIZE (17: 16 bytes + NUL terminator). - Add product_name[DC_DEVINFO_PRODUCT_NAME_SIZE] to dc_event_devinfo_t, documented as live-download-only, empty string when not applicable. - Consumers that do not use this field are unaffected; the struct is zero-initialised by callers before population. Signed-off-by: Michael Keller --- include/libdivecomputer/device.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/libdivecomputer/device.h b/include/libdivecomputer/device.h index 9ffce79e..f6b7d8ef 100644 --- a/include/libdivecomputer/device.h +++ b/include/libdivecomputer/device.h @@ -48,6 +48,10 @@ typedef struct dc_event_progress_t { unsigned int maximum; } dc_event_progress_t; +/* Maximum length of the product_name field in dc_event_devinfo_t, + * including the NUL terminator. */ +#define DC_DEVINFO_PRODUCT_NAME_SIZE 17 + typedef struct dc_event_devinfo_t { unsigned int model; unsigned int firmware; @@ -59,6 +63,12 @@ typedef struct dc_event_devinfo_t { * consumer-side sub-model differentiation. Consumers that do not use * this field are unaffected; the model field remains authoritative. */ unsigned int hw_id; + /* Raw product-name string from the device's version packet, NUL-terminated. + * Empty string when not available or not applicable for this device family. + * Populated only on live download; use dc_descriptor_find_by_product_name() + * to map this to a refined descriptor. The model field remains the + * authoritative parser-layout selector. */ + char product_name[DC_DEVINFO_PRODUCT_NAME_SIZE]; } dc_event_devinfo_t; typedef struct dc_event_clock_t { From 963c52caa734795a552d34b2bca0e7b77c706b8a Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 10:13:31 +1300 Subject: [PATCH 2/8] descriptor: add dc_descriptor_find_by_product_name() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some device families share a coarse numeric model across multiple marketed products. For the Mares Icon HD family, the device version packet contains a product-name string (at offset 0x46) that uniquely identifies several marketed variants sharing the same parser model. Add a product-name-to-descriptor mapping table (g_product_name_map) containing only confirmed product-name strings from the mares_iconhd firmware matching table, and a resolver function: dc_descriptor_t *dc_descriptor_find_by_product_name( dc_family_t family, unsigned int model, const char *product_name); Mapped strings (all DC_FAMILY_MARES_ICONHD, evidence: mares_iconhd.c): - model 0x18: "Puck Pro" → "Puck Pro" - model 0x35: "Puck4" → "Puck 4" - model 0x35: "Puck Lite" → "Puck Lite" - model 0x35: "Puck Pro U" → "Puck Pro Ultra" Strings that are ambiguous ("Puck", which is a BLE prefix matching multiple variants) are intentionally omitted; callers fall back to the coarse-model descriptor for those. The coarse model field remains the authoritative parser-layout selector. NULL / empty product_name returns NULL. Exported from libdivecomputer.symbols. Signed-off-by: Michael Keller --- include/libdivecomputer/descriptor.h | 25 +++++++++++ src/descriptor.c | 62 ++++++++++++++++++++++++++++ src/libdivecomputer.symbols | 1 + 3 files changed, 88 insertions(+) diff --git a/include/libdivecomputer/descriptor.h b/include/libdivecomputer/descriptor.h index 62b36738..41486c0d 100644 --- a/include/libdivecomputer/descriptor.h +++ b/include/libdivecomputer/descriptor.h @@ -144,6 +144,31 @@ dc_descriptor_filter (const dc_descriptor_t *descriptor, dc_transport_t transpor dc_descriptor_t * dc_descriptor_find_by_hw_id (dc_family_t family, unsigned int hw_id); +/** + * Find a descriptor by raw product-name string from the device version packet. + * + * Some device families (notably Mares Icon HD) share a coarse numeric model + * across multiple marketed products but expose a distinguishing product-name + * string in their version packet (DC_EVENT_DEVINFO.product_name). This + * function maps that raw string to the best-matching descriptor, while the + * coarse model number remains authoritative for parser layout selection. + * + * The mapping is evidence-based and limited to confirmed product-name strings + * from the device firmware. Returns NULL for an empty, NULL, or unrecognised + * product-name string; callers must fall back to the coarse model descriptor. + * + * The returned descriptor is a direct reference to an internal table entry + * and must be released with dc_descriptor_free(), which is safe to call on + * such a reference. + * + * @param[in] family The device family type. + * @param[in] model The coarse model number from DC_EVENT_DEVINFO. + * @param[in] product_name The raw product-name string from DC_EVENT_DEVINFO. + * @returns A descriptor on success, or NULL if no match. + */ +dc_descriptor_t * +dc_descriptor_find_by_product_name (dc_family_t family, unsigned int model, const char *product_name); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/descriptor.c b/src/descriptor.c index 965405dd..783483db 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -1126,6 +1126,68 @@ dc_descriptor_find_by_hw_id (dc_family_t family, unsigned int hw_id) return NULL; } +/* Product-name to descriptor mapping for Mares Icon HD. + * + * The mares_iconhd driver reads a product-name string from offset 0x46 of + * the version packet and maps it to a coarse numeric model. Several marketed + * products share one numeric model; this table maps the confirmed firmware + * product-name strings to the specific descriptor product name, allowing + * consumers to refine the display label beyond the coarse model. + * + * Evidence: mares_iconhd.c mares_iconhd_get_model() table. Only strings + * that unambiguously identify a single marketed product are listed. */ +typedef struct { + dc_family_t family; + unsigned int model; + const char *firmware_name; + const char *descriptor_product; +} dc_product_name_entry_t; + +static const dc_product_name_entry_t g_product_name_map[] = { + /* PUCKPRO (0x18): "Puck Pro" firmware name — both "Puck Pro" and "Puck Pro +" + * share model 0x18. The version-packet name "Puck Pro" identifies the base + * variant. No confirmed firmware name for the + variant is known; it falls + * back to the coarse model ("Puck Pro"). */ + {DC_FAMILY_MARES_ICONHD, 0x18, "Puck Pro", "Puck Pro"}, + /* PUCK4 (0x35): four marketed products share this model. The firmware + * product-name strings below are taken directly from the mares_iconhd + * matching table and map to specific descriptor product names. */ + {DC_FAMILY_MARES_ICONHD, 0x35, "Puck4", "Puck 4"}, + {DC_FAMILY_MARES_ICONHD, 0x35, "Puck Lite", "Puck Lite"}, + {DC_FAMILY_MARES_ICONHD, 0x35, "Puck Pro U", "Puck Pro Ultra"}, + /* "Puck" (0x35) is ambiguous — the BLE advertisement prefix "Puck" matches + * multiple variants; no single descriptor product is confirmed. Not mapped. */ +}; + +dc_descriptor_t * +dc_descriptor_find_by_product_name (dc_family_t family, unsigned int model, const char *product_name) +{ + if (product_name == NULL || product_name[0] == '\0') + return NULL; + + for (size_t i = 0; i < C_ARRAY_SIZE(g_product_name_map); i++) { + if (g_product_name_map[i].family != family || + g_product_name_map[i].model != model || + strcmp(g_product_name_map[i].firmware_name, product_name) != 0) + continue; + + /* Matched — find the descriptor by family, model, and product name. */ + for (size_t j = 0; j < C_ARRAY_SIZE(g_descriptors); j++) { + if (g_descriptors[j].type == family && + g_descriptors[j].model == model && + strcmp(g_descriptors[j].product, + g_product_name_map[i].descriptor_product) == 0) { + return (dc_descriptor_t *) &g_descriptors[j]; + } + } + /* firmware_name matched but descriptor product not found — g_descriptors[] + * may have been edited without updating g_product_name_map[]. */ + return NULL; + } + + return NULL; +} + const char * dc_descriptor_get_vendor (const dc_descriptor_t *descriptor) { diff --git a/src/libdivecomputer.symbols b/src/libdivecomputer.symbols index 42e9c32b..b6fa32ff 100644 --- a/src/libdivecomputer.symbols +++ b/src/libdivecomputer.symbols @@ -37,6 +37,7 @@ dc_descriptor_get_model dc_descriptor_get_transports dc_descriptor_filter dc_descriptor_find_by_hw_id +dc_descriptor_find_by_product_name dc_iostream_get_transport dc_iostream_set_timeout From 413743f996177005eac9a1e603a0ad8f318c2fd8 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 10:35:18 +1300 Subject: [PATCH 3/8] mares_iconhd: populate product_name in DC_EVENT_DEVINFO The mares_iconhd driver reads a 140-byte version packet during device open; the product-name string lives at offset 0x46 (up to 16 bytes). This string is already used internally by mares_iconhd_get_model() to select the coarse numeric model, but was not previously reported to consumers. Add mares_iconhd_fill_product_name() that copies the version-packet name into devinfo.product_name (NUL-terminated, at most DC_DEVINFO_PRODUCT_NAME_SIZE-1 bytes). Place the helper after mares_iconhd_get_model() to avoid wedging into the layout-struct block that upstream frequently extends. Call it at all three DC_EVENT_DEVINFO emit sites: - mares_iconhd_device_dump() - mares_iconhd_device_foreach_raw() - mares_iconhd_device_foreach_object() Also initialise the dc_event_devinfo_t struct to zero at each site so that fields added after the original code (hw_id, product_name) are always zero/empty when not explicitly set. Signed-off-by: Michael Keller --- src/mares_iconhd.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index 9a55769f..a803982b 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -224,6 +224,23 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) return model; } +static void +mares_iconhd_fill_product_name (const mares_iconhd_device_t *device, dc_event_devinfo_t *devinfo) +{ + /* Copy at most DC_DEVINFO_PRODUCT_NAME_SIZE-1 bytes from the version packet + * product-name field (offset 0x46, up to 16 bytes) and NUL-terminate. + * The buffer is already zeroed by the caller initialising devinfo to {0}. */ + const unsigned char *src = device->version + 0x46; + unsigned int n = sizeof (devinfo->product_name) - 1; + unsigned int i; + for (i = 0; i < n; i++) { + if (src[i] == '\0') + break; + devinfo->product_name[i] = (char) src[i]; + } + devinfo->product_name[i] = '\0'; +} + static dc_status_t mares_iconhd_packet_fixed (mares_iconhd_device_t *device, unsigned char cmd, @@ -818,10 +835,11 @@ mares_iconhd_device_dump (dc_device_t *abstract, dc_buffer_t *buffer) // Emit a device info event. unsigned char *data = dc_buffer_get_data (buffer); - dc_event_devinfo_t devinfo; + dc_event_devinfo_t devinfo = {0}; devinfo.model = device->model; devinfo.firmware = 0; devinfo.serial = array_uint32_le (data + 0x0C); + mares_iconhd_fill_product_name (device, &devinfo); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); return status; @@ -844,10 +862,11 @@ mares_iconhd_device_foreach_raw (dc_device_t *abstract, dc_dive_callback_t callb } // Emit a device info event. - dc_event_devinfo_t devinfo; + dc_event_devinfo_t devinfo = {0}; devinfo.model = device->model; devinfo.firmware = 0; devinfo.serial = array_uint32_le (serial); + mares_iconhd_fill_product_name (device, &devinfo); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Enable progress notifications. @@ -1097,10 +1116,11 @@ mares_iconhd_device_foreach_object (dc_device_t *abstract, dc_dive_callback_t ca unsigned int serial = array_convert_str2num (dc_buffer_get_data (buffer) + 10, 6); // Emit a device info event. - dc_event_devinfo_t devinfo; + dc_event_devinfo_t devinfo = {0}; devinfo.model = device->model; devinfo.firmware = 0; devinfo.serial = serial; + mares_iconhd_fill_product_name (device, &devinfo); device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo); // Erase the buffer. From a860a209efc94ca1392e9378c6f1cfacc92a5b23 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 10:35:33 +1300 Subject: [PATCH 4/8] test: add dc_descriptor_find_by_product_name() unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test/test_descriptor_product_name.c and wire it into the autotools build via a dedicated test/Makefile.am subdirectory, avoiding the need for subdir-objects in the top-level AM_INIT_AUTOMAKE. Build wiring: - test/Makefile.am: check_PROGRAMS / TESTS for the C test binary. - Makefile.am: unconditional 'SUBDIRS += test' so 'make check' from the root runs the test subdirectory. - configure.ac: add test/Makefile to AC_CONFIG_FILES. Test coverage: - Mares Puck Pro (model 0x18): "Puck Pro" firmware name resolves to the "Puck Pro" descriptor; "Puck Pro +" (no confirmed firmware name) returns NULL, so the caller falls back to the coarse model. - Mares Puck 4 family (model 0x35): "Puck4" → "Puck 4", "Puck Lite" → "Puck Lite", "Puck Pro U" → "Puck Pro Ultra". - Coarse-model fallback: ambiguous "Puck", unknown string, NULL, and empty product_name all return NULL. - Wrong family always returns NULL. Signed-off-by: Michael Keller --- Makefile.am | 5 +- configure.ac | 1 + test/Makefile.am | 15 +++ test/test_descriptor_product_name.c | 139 ++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 test/Makefile.am create mode 100644 test/test_descriptor_product_name.c diff --git a/Makefile.am b/Makefile.am index bc07dc91..88688d98 100644 --- a/Makefile.am +++ b/Makefile.am @@ -8,6 +8,8 @@ if ENABLE_DOC SUBDIRS += doc endif +SUBDIRS += test + AM_MAKEFLAGS = -s ACLOCAL_AMFLAGS = -I m4 @@ -25,4 +27,5 @@ EXTRA_DIST = \ test/fixtures/manifest.txt \ test/fixtures/shearwater_petrel2-0001.bin \ test/fixtures/hw_ostc5-0001.bin \ - test/fixtures/garmin_descent_mk1-0001.bin + test/fixtures/garmin_descent_mk1-0001.bin \ + test/test_descriptor_product_name.c diff --git a/configure.ac b/configure.ac index 38d68dd4..4ba6139c 100644 --- a/configure.ac +++ b/configure.ac @@ -264,6 +264,7 @@ AC_CONFIG_FILES([ doc/doxygen.cfg doc/man/Makefile examples/Makefile + test/Makefile ]) AC_OUTPUT AC_MSG_NOTICE([ diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 00000000..8f80e8da --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,15 @@ +AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include +LDADD = $(top_builddir)/src/libdivecomputer.la + +check_PROGRAMS = test_descriptor_product_name + +test_descriptor_product_name_SOURCES = test_descriptor_product_name.c + +TESTS = test_descriptor_product_name + +EXTRA_DIST = \ + run-parse.sh \ + fixtures/manifest.txt \ + fixtures/shearwater_petrel2-0001.bin \ + fixtures/hw_ostc5-0001.bin \ + fixtures/garmin_descent_mk1-0001.bin diff --git a/test/test_descriptor_product_name.c b/test/test_descriptor_product_name.c new file mode 100644 index 00000000..4d0751d8 --- /dev/null +++ b/test/test_descriptor_product_name.c @@ -0,0 +1,139 @@ +/* + * libdivecomputer + * + * Copyright (C) 2024 The libdivecomputer contributors + * + * 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 + */ + +/* + * Unit tests for dc_descriptor_find_by_product_name(). + * + * Covers: + * - Mares Puck Pro vs Puck Pro + (shared model 0x18, PUCKPRO): the + * firmware name "Puck Pro" resolves to the "Puck Pro" descriptor. + * - Mares Puck 4 family (shared model 0x35, PUCK4): "Puck4", "Puck Lite", + * and "Puck Pro U" each resolve to the correct distinct descriptor. + * - Coarse-model fallback: an unrecognised product name returns NULL. + * - NULL / empty product name returns NULL. + */ + +#include +#include + +#include +#include + +#define PUCKPRO_MODEL 0x18 +#define PUCK4_MODEL 0x35 + +static int failures = 0; + +static void +check (const char *test, int condition) +{ + if (!condition) { + fprintf (stderr, "FAIL: %s\n", test); + failures++; + } else { + printf ("PASS: %s\n", test); + } +} + +int main (void) +{ + dc_descriptor_t *desc; + + /* --- Puck Pro (model 0x18) --- */ + + /* "Puck Pro" firmware name must resolve to the "Puck Pro" descriptor. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCKPRO_MODEL, "Puck Pro"); + check ("Puck Pro firmware name resolves to a descriptor", desc != NULL); + if (desc != NULL) { + check ("Puck Pro descriptor product is 'Puck Pro'", + strcmp (dc_descriptor_get_product (desc), "Puck Pro") == 0); + check ("Puck Pro descriptor model is 0x18", + dc_descriptor_get_model (desc) == PUCKPRO_MODEL); + dc_descriptor_free (desc); + } + + /* "Puck Pro +" has no confirmed firmware product-name; must return NULL + * so the caller falls back to the coarse-model descriptor. We pass the + * descriptor product name as the query to verify it is not in the table. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCKPRO_MODEL, "Puck Pro +"); + check ("'Puck Pro +' (not in firmware table) returns NULL", desc == NULL); + + /* --- Puck 4 family (model 0x35) --- */ + + /* "Puck4" must resolve to "Puck 4". */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, "Puck4"); + check ("'Puck4' firmware name resolves to a descriptor", desc != NULL); + if (desc != NULL) { + check ("'Puck4' descriptor product is 'Puck 4'", + strcmp (dc_descriptor_get_product (desc), "Puck 4") == 0); + check ("'Puck4' descriptor model is 0x35", + dc_descriptor_get_model (desc) == PUCK4_MODEL); + dc_descriptor_free (desc); + } + + /* "Puck Lite" must resolve to "Puck Lite". */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, "Puck Lite"); + check ("'Puck Lite' firmware name resolves to a descriptor", desc != NULL); + if (desc != NULL) { + check ("'Puck Lite' descriptor product is 'Puck Lite'", + strcmp (dc_descriptor_get_product (desc), "Puck Lite") == 0); + dc_descriptor_free (desc); + } + + /* "Puck Pro U" must resolve to "Puck Pro Ultra". */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, "Puck Pro U"); + check ("'Puck Pro U' firmware name resolves to a descriptor", desc != NULL); + if (desc != NULL) { + check ("'Puck Pro U' descriptor product is 'Puck Pro Ultra'", + strcmp (dc_descriptor_get_product (desc), "Puck Pro Ultra") == 0); + dc_descriptor_free (desc); + } + + /* --- Coarse-model fallback cases --- */ + + /* "Puck" maps to model 0x35 in the driver but is ambiguous; must return NULL. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, "Puck"); + check ("Ambiguous 'Puck' firmware name returns NULL", desc == NULL); + + /* Completely unknown product name returns NULL. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, "Unknown Product"); + check ("Unknown product name returns NULL", desc == NULL); + + /* NULL product name returns NULL. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, NULL); + check ("NULL product name returns NULL", desc == NULL); + + /* Empty product name returns NULL. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_ICONHD, PUCK4_MODEL, ""); + check ("Empty product name returns NULL", desc == NULL); + + /* Wrong family returns NULL even with a valid name. */ + desc = dc_descriptor_find_by_product_name (DC_FAMILY_MARES_PUCK, PUCK4_MODEL, "Puck4"); + check ("Wrong family returns NULL", desc == NULL); + + if (failures == 0) { + printf ("All tests passed.\n"); + return 0; + } + + fprintf (stderr, "%d test(s) failed.\n", failures); + return 1; +} From b578344a2df9c5d86c0da636933df069d3dcd4b7 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 12:15:53 +1300 Subject: [PATCH 5/8] mares_iconhd: match 'Puck Pro U' before 'Puck Pro' prefix The prefix-match loop in mares_iconhd_get_model() compared the firmware product-name field against each table entry using memcmp of the entry length. 'Puck Pro' (length 8) was listed before 'Puck Pro U' (length 10), so a device reporting 'Puck Pro U...' matched the shorter prefix first and was assigned PUCKPRO (0x18) instead of PUCK4 (0x35). Move 'Puck Pro U' before 'Puck Pro' so the more specific prefix is tested first. All other entries are unaffected. Signed-off-by: Michael Keller --- src/mares_iconhd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mares_iconhd.c b/src/mares_iconhd.c index a803982b..5fc918c0 100644 --- a/src/mares_iconhd.c +++ b/src/mares_iconhd.c @@ -192,6 +192,7 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Smart Apnea", SMARTAPNEA}, {"Icon HD", ICONHD}, {"Icon AIR", ICONHDNET}, + {"Puck Pro U", PUCK4}, {"Puck Pro", PUCKPRO}, {"Nemo Wide 2", NEMOWIDE2}, {"Genius", GENIUS}, @@ -208,7 +209,6 @@ mares_iconhd_get_model (mares_iconhd_device_t *device) {"Puck4", PUCK4}, {"Puck Lite", PUCK4}, {"Puck", PUCK4}, - {"Puck Pro U", PUCK4}, }; // Check the product name in the version packet against the list From 03e10ff0512500ca956799dd80a5d2e50b6db785 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 12:15:57 +1300 Subject: [PATCH 6/8] device: copy product_name when caching DC_EVENT_DEVINFO device_event_emit() cached model, firmware, and serial from the driver's local devinfo into device->devinfo, then passed &device->devinfo to the callback. The product_name field was not copied, so every callback received the zero-initialised cached value rather than the string the driver had populated. Add a memcpy() of the full product_name array to the cache-update block so consumers see the populated field. Signed-off-by: Michael Keller --- src/device.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/device.c b/src/device.c index e09b0eae..57cfec03 100644 --- a/src/device.c +++ b/src/device.c @@ -505,6 +505,8 @@ device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data) device->devinfo.model = devinfo->model; device->devinfo.firmware = devinfo->firmware; device->devinfo.serial = devinfo->serial; + memcpy (device->devinfo.product_name, devinfo->product_name, + sizeof (device->devinfo.product_name)); data = &device->devinfo; } break; From 9383a5f89b312a9fbc0b9bb05020274edbfc1d97 Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 13:06:36 +1300 Subject: [PATCH 7/8] device: zero product_name cache before copy to ensure NUL-termination Other device drivers declare dc_event_devinfo_t on the stack without zero-initialising it, so product_name is indeterminate for those emitters. Reset the cached field to all-zeros before copying from the incoming struct, and copy only DC_DEVINFO_PRODUCT_NAME_SIZE-1 bytes, leaving the final byte as the NUL set by the preceding memset. This guarantees the cached product_name is always a valid NUL-terminated string regardless of whether the emitting driver initialised the field. Signed-off-by: Michael Keller --- src/device.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/device.c b/src/device.c index 57cfec03..1e5780df 100644 --- a/src/device.c +++ b/src/device.c @@ -505,8 +505,9 @@ device_event_emit (dc_device_t *device, dc_event_type_t event, const void *data) device->devinfo.model = devinfo->model; device->devinfo.firmware = devinfo->firmware; device->devinfo.serial = devinfo->serial; + memset (device->devinfo.product_name, 0, sizeof (device->devinfo.product_name)); memcpy (device->devinfo.product_name, devinfo->product_name, - sizeof (device->devinfo.product_name)); + sizeof (device->devinfo.product_name) - 1); data = &device->devinfo; } break; From 33a1d8db7b81f30efdb6ab66901aa0676292f29e Mon Sep 17 00:00:00 2001 From: Michael Keller Date: Sun, 4 Oct 2026 13:24:14 +1300 Subject: [PATCH 8/8] .gitignore: ignore automake-generated test-driver automake's parallel-tests mode (the default since automake 1.13) installs test-driver at the top level of the source tree via 'autoreconf -fi'. The other automake-generated auxiliary files at the same level (ar-lib, depcomp, install-sh, ltmain.sh, missing) are already listed in .gitignore; test-driver was omitted because the test/ subdirectory with TESTS was only added in this branch. Add /test-driver alongside /ar-lib to close the gap. Signed-off-by: Michael Keller --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bce5b9b9..908a96e0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ Makefile.in /aclocal.m4 /ar-lib +/test-driver /autom4te.cache/ /config.guess /config.h