/* * 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 #include #include #include #include #include #include #include #include #include "context-private.h" #include "parser-private.h" #include "array.h" struct type_desc { const char *desc, *format, *mod; }; #define MAXTYPE 512 #define MAXGASES 16 typedef struct suunto_eonsteel_parser_t { 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; } suunto_eonsteel_parser_t; static int report_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 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(suunto_eonsteel_parser_t *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 report_error("Unexpected type description: %.*s", len, name); p = malloc(len-4); if (!p) return report_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 report_error("Unknown type descriptor: %.*s", len, name); } } while ((name = next) != NULL); if (type > MAXTYPE) return report_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(suunto_eonsteel_parser_t *eon, const unsigned char *p, int len, eon_data_cb_t callback, void *user) { const unsigned char *name, *data, *end, *last, *one_past_end = p + len; int textlen, type; int rc; // First two bytes: zero and text length if (p[0]) { debug("next", p, 8); return report_error("Bad dive entry (%02x)", p[0]); } textlen = p[1]; name = p + 2; if (textlen == 0xff) { textlen = array_uint32_le(name); name += 4; } // Two bytes of 'type' followed by the name/descriptor, followed by the data data = name + textlen; type = array_uint16_le(name); name += 2; if (*name != '<') { debug("bad", p, 16); return -1; } record_type(eon, type, name, textlen-3); end = data; last = data; while (end < one_past_end && *end) { const unsigned char *begin = end; unsigned int type = *end++; unsigned int len; if (type == 0xff) { type = array_uint16_le(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 = array_uint32_le(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(suunto_eonsteel_parser_t *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; } struct sample_data { suunto_eonsteel_parser_t *eon; dc_sample_callback_t callback; void *userdata; unsigned int time; }; static void sample_time(struct sample_data *info, unsigned short time_delta) { dc_sample_value_t sample = {0}; info->time += time_delta; sample.time = info->time / 1000; if (info->callback) info->callback(DC_SAMPLE_TIME, sample, info->userdata); } static void sample_depth(struct sample_data *info, unsigned short depth) { dc_sample_value_t sample = {0}; if (depth == 0xffff) return; sample.depth = depth / 100.0; if (info->callback) info->callback(DC_SAMPLE_DEPTH, sample, info->userdata); } static void sample_temp(struct sample_data *info, short temp) { dc_sample_value_t sample = {0}; if (temp < -3000) return; sample.temperature = temp / 10.0; if (info->callback) info->callback(DC_SAMPLE_TEMPERATURE, sample, info->userdata); } static void sample_deco(struct sample_data *info, short ndl, unsigned short tts, unsigned ceiling) { dc_sample_value_t sample = {0}; /* Are we in deco? */ if (ndl < 0) { sample.deco.type = DC_DECO_DECOSTOP; if (tts != 0xffff) sample.deco.time = tts; if (ceiling != 0xffff) sample.deco.depth = ceiling / 100.0; } else { sample.deco.type = DC_DECO_NDL; sample.deco.time = ndl; } if (info->callback) info->callback(DC_SAMPLE_DECO, sample, info->userdata); } static void sample_cylinder_pressure(struct sample_data *info, unsigned char idx, unsigned short pressure) { dc_sample_value_t sample = {0}; if (pressure == 0xffff) return; sample.pressure.tank = idx-1; sample.pressure.value = pressure / 100.0; if (info->callback) info->callback(DC_SAMPLE_PRESSURE, sample, info->userdata); } static int traverse_samples(unsigned short type, const struct type_desc *desc, const void *data, int len, void *user) { struct sample_data *info = user; switch (type) { case 0x0001: // group: time in first word, depth in second sample_time(info, array_uint16_le(data)); sample_depth(info, array_uint16_le(data+2)); sample_temp(info, array_uint16_le(data+4)); sample_deco(info, array_uint16_le(data+8), array_uint16_le(data+10), array_uint16_le(data+12)); break; case 0x0002: // time in first word sample_time(info, array_uint16_le(data)); break; case 0x0003: // depth in first word sample_depth(info, array_uint16_le(data)); break; case 0x000a: // cylinder idx in first byte, pressure in next word sample_cylinder_pressure(info, get_u8(data), array_uint16_le(data+1)); break; } return 0; } static dc_status_t suunto_eonsteel_parser_samples_foreach(dc_parser_t *abstract, dc_sample_callback_t callback, void *userdata) { suunto_eonsteel_parser_t *eon = (void *)abstract; struct sample_data data = { eon, callback, userdata, 0 }; traverse_data(eon, traverse_samples, &data); return DC_STATUS_SUCCESS; } // Ugly define thing makes the code much easier to read // I'd love to use __typeof__, but that's a gcc'ism #define field_value(p, set) \ memcpy((p), &(set), sizeof(set)) static dc_status_t suunto_eonsteel_parser_get_field(dc_parser_t *parser, dc_field_type_t type, unsigned int flags, void *value) { suunto_eonsteel_parser_t *eon = (suunto_eonsteel_parser_t *)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 suunto_eonsteel_parser_get_datetime(dc_parser_t *parser, dc_datetime_t *datetime) { if (parser->size < 4) return DC_STATUS_UNSUPPORTED; dc_datetime_gmtime(datetime, array_uint32_le(parser->data)); return DC_STATUS_SUCCESS; } // time in ms static void add_time_field(suunto_eonsteel_parser_t *eon, unsigned short time_delta_ms) { eon->cache.divetime += time_delta_ms; } // depth in cm static void set_depth_field(suunto_eonsteel_parser_t *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(suunto_eonsteel_parser_t *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(suunto_eonsteel_parser_t *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(suunto_eonsteel_parser_t *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) { suunto_eonsteel_parser_t *eon = user; switch (type) { case 0x0001: // group: time in first word, depth in second add_time_field(eon, array_uint16_le(data)); set_depth_field(eon, array_uint16_le(data+2)); break; case 0x0002: // time in first word add_time_field(eon, array_uint16_le(data)); break; case 0x0003: // depth in first word set_depth_field(eon, array_uint16_le(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(suunto_eonsteel_parser_t *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 suunto_eonsteel_parser_set_data(dc_parser_t *parser, const unsigned char *data, unsigned int size) { suunto_eonsteel_parser_t *eon = (void *)parser; memset(eon->type_desc, 0, sizeof(eon->type_desc)); initialize_field_caches(eon); return DC_STATUS_SUCCESS; } static dc_status_t suunto_eonsteel_parser_destroy(dc_parser_t *parser) { free(parser); return DC_STATUS_SUCCESS; } static const dc_parser_vtable_t suunto_eonsteel_parser_vtable = { DC_FAMILY_SUUNTO_EONSTEEL, suunto_eonsteel_parser_set_data, /* set_data */ suunto_eonsteel_parser_get_datetime, /* datetime */ suunto_eonsteel_parser_get_field, /* fields */ suunto_eonsteel_parser_samples_foreach, /* samples_foreach */ suunto_eonsteel_parser_destroy /* destroy */ }; dc_status_t suunto_eonsteel_parser_create(dc_parser_t **out, dc_context_t *context, unsigned int model) { suunto_eonsteel_parser_t *eon; if (out == NULL) return DC_STATUS_INVALIDARGS; eon = calloc(1, sizeof(*eon)); parser_init(&eon->base, context, &suunto_eonsteel_parser_vtable); *out = (dc_parser_t *) eon; return DC_STATUS_SUCCESS; }