Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/libdivecomputer_plugin/test/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,48 @@ target_include_directories(test_hw_ostc3_read PRIVATE
${CONFIG_DIR}
)

# test_parse_raw_dive requires the full libdivecomputer + wrapper linked in.
# Use the Windows config on Windows, macOS config otherwise.
if(WIN32)
set(PLATFORM_CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../windows/config")
else()
set(PLATFORM_CONFIG_DIR "${CONFIG_DIR}")
endif()

# Collect libdivecomputer sources needed for parsing (exclude platform I/O
# files that don't compile cross-platform). CONFIGURE_DEPENDS re-runs the glob
# on every build so the target stays in sync when the submodule adds/removes
# sources, without a manual CMake cache wipe.
file(GLOB LIBDC_ALL_SOURCES CONFIGURE_DEPENDS "${LIBDC_DIR}/src/*.c")
list(FILTER LIBDC_ALL_SOURCES EXCLUDE REGEX "serial_posix\\.c$")
if(NOT WIN32)
list(FILTER LIBDC_ALL_SOURCES EXCLUDE REGEX "serial_win32\\.c$")
endif()

add_executable(test_parse_raw_dive
test_parse_raw_dive.c
${WRAPPER_DIR}/libdc_wrapper.c
${WRAPPER_DIR}/libdc_download.c
${LIBDC_ALL_SOURCES}
)
target_include_directories(test_parse_raw_dive PRIVATE
${WRAPPER_DIR}
${LIBDC_DIR}/include
${LIBDC_DIR}/src
${PLATFORM_CONFIG_DIR}
)
target_compile_definitions(test_parse_raw_dive PRIVATE HAVE_CONFIG_H)
if(WIN32)
target_compile_definitions(test_parse_raw_dive PRIVATE _CRT_SECURE_NO_WARNINGS)
target_link_libraries(test_parse_raw_dive PRIVATE SetupAPI.lib ws2_32.lib)
endif()

enable_testing()
add_test(NAME test_dive_converter COMMAND test_dive_converter)
add_test(NAME test_serial_callbacks COMMAND test_serial_callbacks)
add_test(NAME test_descriptor_matcher COMMAND test_descriptor_matcher)
add_test(NAME test_descriptor_match_integration COMMAND test_descriptor_match_integration)
add_test(NAME test_hw_ostc3_read COMMAND test_hw_ostc3_read)
add_test(NAME test_parse_raw_dive COMMAND test_parse_raw_dive
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
Binary file not shown.
113 changes: 113 additions & 0 deletions packages/libdivecomputer_plugin/test/native/test_parse_raw_dive.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libdc_wrapper.h"

/* Load a binary file into a malloc'd buffer. On success returns the byte count
and sets *out to the malloc'd buffer (caller frees). On any failure returns 0
and sets *out to NULL, so the caller can safely free/assert without touching
an uninitialized or partially-filled buffer. */
static unsigned int load_fixture(const char *path, unsigned char **out) {
*out = NULL;
FILE *f = fopen(path, "rb");
if (!f) return 0;
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len <= 0) { fclose(f); return 0; }
unsigned char *buf = (unsigned char *)malloc((size_t)len);
if (!buf) { fclose(f); return 0; }
size_t read = fread(buf, 1, (size_t)len, f);
fclose(f);
if (read != (size_t)len) { free(buf); return 0; }
*out = buf;
return (unsigned int)read;
}

/* Error path: NULL arguments should return INVALIDARGS. */
static void test_null_args(void) {
libdc_parsed_dive_t result;
char err[256] = {0};

int rc = libdc_parse_raw_dive(NULL, "Leonardo", 1, (const unsigned char *)"x", 1, &result, err, sizeof(err));
assert(rc != 0);

rc = libdc_parse_raw_dive("Cressi", "Leonardo", 1, NULL, 0, &result, err, sizeof(err));
assert(rc != 0);

rc = libdc_parse_raw_dive("Cressi", "Leonardo", 1, (const unsigned char *)"x", 1, NULL, err, sizeof(err));
assert(rc != 0);

printf("PASS: test_null_args\n");
}

/* Error path: a missing fixture must report failure and null the out pointer. */
static void test_load_fixture_missing(void) {
unsigned char *data = (unsigned char *)0x1; /* poison: must be overwritten */
unsigned int size = load_fixture("fixtures/does_not_exist.bin", &data);
assert(size == 0);
assert(data == NULL);
printf("PASS: test_load_fixture_missing\n");
}

/* Error path: unknown descriptor should return NODEVICE. */
static void test_unknown_descriptor(void) {
libdc_parsed_dive_t result;
char err[256] = {0};
unsigned char dummy[16] = {0};

int rc = libdc_parse_raw_dive("BogusVendor", "BogusProduct", 9999, dummy, sizeof(dummy), &result, err, sizeof(err));
assert(rc != 0);
assert(strlen(err) > 0);
printf("PASS: test_unknown_descriptor (error: %s)\n", err);
}

/* Happy path: parse real Cressi Leonardo dive data from fixture. */
static void test_parse_cressi_leonardo(void) {
unsigned char *data = NULL;
unsigned int size = load_fixture("fixtures/dive1_raw.bin", &data);
assert(size == 400);
assert(data != NULL);

libdc_parsed_dive_t result;
char err[256] = {0};

int rc = libdc_parse_raw_dive("Cressi", "Leonardo", 1, data, size, &result, err, sizeof(err));
if (rc != 0) {
fprintf(stderr, "FAIL: parse returned %d: %s\n", rc, err);
free(data);
assert(0 && "libdc_parse_raw_dive failed");
}

/* Basic sanity checks on parsed output. */
assert(result.max_depth > 0.0);
assert(result.duration > 0);
assert(result.sample_count > 0);
assert(result.samples != NULL);

/* Verify samples are time-ordered and depths are non-negative. */
for (unsigned int i = 0; i < result.sample_count; i++) {
assert(result.samples[i].depth >= 0.0);
if (i > 0) {
assert(result.samples[i].time_ms >= result.samples[i - 1].time_ms);
}
}

printf("PASS: test_parse_cressi_leonardo (depth=%.1fm, duration=%us, samples=%u)\n",
result.max_depth, result.duration, result.sample_count);

free(result.samples);
free(result.events);
free(data);
}

int main(void) {
test_null_args();
test_load_fixture_missing();
test_unknown_descriptor();
test_parse_cressi_leonardo();
printf("\nAll parse_raw_dive tests passed.\n");
return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "dive_converter.h"
#include "serial_scanner.h"

#include <climits>
#include <cmath>
#include <cstdio>
#include <cstdlib>
Expand Down Expand Up @@ -160,8 +161,41 @@ void DiveComputerHostApiImpl::ParseRawDiveData(
int64_t model,
const std::vector<uint8_t>& data,
std::function<void(ErrorOr<ParsedDive> reply)> result) {
result(FlutterError("UNSUPPORTED",
"Raw dive parsing not yet implemented on Windows"));
// model arrives as an int64 across the Pigeon boundary but libdivecomputer
// expects an unsigned int descriptor id. Reject out-of-range values up front
// so a corrupt/unexpected model yields a clear error instead of a silently
// wrapped cast and a misleading "no descriptor" failure downstream.
// UINT_MAX (not std::numeric_limits::max()) avoids the windows.h max() macro,
// and the signed 64-bit bound keeps the comparison free of /W4 warnings.
constexpr int64_t kMaxModel = UINT_MAX;
if (model < 0 || model > kMaxModel) {
result(FlutterError(
"PARSE_ERROR",
"Invalid dive computer model number: " + std::to_string(model)));
return;
}

libdc_parsed_dive_t dive = {};
char error_buf[256] = {};

int rc = libdc_parse_raw_dive(
vendor.c_str(), product.c_str(),
static_cast<unsigned int>(model),
data.data(), static_cast<unsigned int>(data.size()),
&dive, error_buf, sizeof(error_buf));

if (rc != 0) {
std::string msg = std::string("Failed to parse raw dive data: ") + error_buf;
free(dive.samples);
free(dive.events);
result(FlutterError("PARSE_ERROR", msg));
return;
}

auto parsed = ConvertParsedDive(dive);
free(dive.samples);
free(dive.events);
result(parsed);
}

ErrorOr<std::string> DiveComputerHostApiImpl::GetLibdivecomputerVersion() {
Expand Down
Loading