CID 350142: Resource leak CID 350154: Resource leak CID 360641: Resource leak CID 350147: Possibly not null-terminated strings Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
501 lines
15 KiB
C
501 lines
15 KiB
C
/*
|
|
* Deepblu Cosmiq+ downloading
|
|
*
|
|
* Copyright (C) 2019 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 <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "deepblu.h"
|
|
#include "context-private.h"
|
|
#include "device-private.h"
|
|
#include "array.h"
|
|
|
|
// "Write state"
|
|
#define CMD_SETTIME 0x20 // Send 6 byte date-time, get single-byte 00x00 ack
|
|
#define CMD_23 0x23 // Send 00/01 byte, get ack back? Some metric/imperial setting?
|
|
|
|
// "Read dives"?
|
|
#define CMD_GETDIVENR 0x40 // Send empty byte, get single-byte number of dives back
|
|
#define CMD_GETDIVE 0x41 // Send dive number (1-nr) byte, get dive stat length byte back
|
|
#define RSP_DIVESTAT 0x42 // .. followed by packets of dive stat for that dive of that length
|
|
#define CMD_GETPROFILE 0x43 // Send dive number (1-nr) byte, get dive profile length BE word back
|
|
#define RSP_DIVEPROF 0x44 // .. followed by packets of dive profile of that length
|
|
|
|
// "Read state"
|
|
#define CMD_GETTIME 0x50 // Send empty byte, get six-byte bcd date-time back
|
|
#define CMD_51 0x51 // Send empty byte, get four bytes back (03 dc 00 e3)
|
|
#define CMD_52 0x52 // Send empty byte, get two bytes back (bf 8d)
|
|
#define CMD_53 0x53 // Send empty byte, get six bytes back (0e 81 00 03 00 00)
|
|
#define CMD_54 0x54 // Send empty byte, get byte back (00)
|
|
#define CMD_55 0x55 // Send empty byte, get byte back (00)
|
|
#define CMD_56 0x56 // Send empty byte, get byte back (00)
|
|
#define CMD_57 0x57 // Send empty byte, get byte back (00)
|
|
#define CMD_58 0x58 // Send empty byte, get byte back (52)
|
|
#define CMD_59 0x59 // Send empty byte, get six bytes back (00 00 07 00 00 00)
|
|
// (00 00 00 00 00 00)
|
|
#define CMD_5a 0x5a // Send empty byte, get six bytes back (23 1b 09 d8 37 c0)
|
|
#define CMD_5b 0x5b // Send empty byte, get six bytes back (00 21 00 14 00 01)
|
|
// (00 00 00 14 00 01)
|
|
#define CMD_5c 0x5c // Send empty byte, get six bytes back (13 88 00 46 20 00)
|
|
// (13 88 00 3c 15 00)
|
|
#define CMD_5d 0x5d // Send empty byte, get six bytes back (19 00 23 0C 02 0E)
|
|
// (14 14 14 0c 01 0e)
|
|
#define CMD_5f 0x5f // Send empty byte, get six bytes back (00 00 07 00 00 00)
|
|
|
|
#define COSMIQ_HDR_SIZE 36
|
|
|
|
typedef struct deepblu_device_t {
|
|
dc_device_t base;
|
|
dc_iostream_t *iostream;
|
|
unsigned char fingerprint[COSMIQ_HDR_SIZE];
|
|
} deepblu_device_t;
|
|
|
|
static dc_status_t deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size);
|
|
static dc_status_t deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
|
|
static dc_status_t deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime);
|
|
static dc_status_t deepblu_device_close (dc_device_t *abstract);
|
|
|
|
static const dc_device_vtable_t deepblu_device_vtable = {
|
|
sizeof(deepblu_device_t),
|
|
DC_FAMILY_DEEPBLU,
|
|
deepblu_device_set_fingerprint, /* set_fingerprint */
|
|
NULL, /* read */
|
|
NULL, /* write */
|
|
NULL, /* dump */
|
|
deepblu_device_foreach, /* foreach */
|
|
deepblu_device_timesync, /* timesync */
|
|
deepblu_device_close, /* close */
|
|
};
|
|
|
|
// Maximum data in a packet. It's actually much
|
|
// less than this, since BLE packets are small and
|
|
// with the 7 bytes of headers and final newline
|
|
// and the HEX encoding, the actual maximum is
|
|
// just something like 6 bytes.
|
|
//
|
|
// But in theory the data could be done over
|
|
// multiple packets. That doesn't seem to be
|
|
// the case in anything I've seen so far.
|
|
//
|
|
// Pick something small and easy to use for
|
|
// stack buffers.
|
|
#define MAX_DATA 20
|
|
|
|
static char *
|
|
write_hex_byte(unsigned char data, char *p)
|
|
{
|
|
static const char hex[16] = "0123456789ABCDEF";
|
|
*p++ = hex[data >> 4];
|
|
*p++ = hex[data & 0xf];
|
|
return p;
|
|
}
|
|
|
|
//
|
|
// Send a cmd packet.
|
|
//
|
|
// The format of the cmd on the "wire" is:
|
|
// - byte '#'
|
|
// - HEX char of cmd
|
|
// - HEX char two's complement modular sum of packet data (including cmd/size)
|
|
// - HEX char size of data as encoded in HEX
|
|
// - n * HEX char data
|
|
// - byte '\n'
|
|
// so you end up having 8 bytes of header/trailer overhead, and two bytes
|
|
// for every byte of data sent due to the HEX encoding.
|
|
//
|
|
static dc_status_t
|
|
deepblu_send_cmd(deepblu_device_t *device, const unsigned char cmd, const unsigned char data[], size_t size)
|
|
{
|
|
char buffer[8+2*MAX_DATA], *p;
|
|
unsigned char csum;
|
|
int i;
|
|
|
|
if (size > MAX_DATA)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Calculate packet csum
|
|
csum = cmd + 2*size;
|
|
for (i = 0; i < size; i++)
|
|
csum += data[i];
|
|
csum = -csum;
|
|
|
|
// Fill the data buffer
|
|
p = buffer;
|
|
*p++ = '#';
|
|
p = write_hex_byte(cmd, p);
|
|
p = write_hex_byte(csum, p);
|
|
p = write_hex_byte(size*2, p);
|
|
for (i = 0; i < size; i++)
|
|
p = write_hex_byte(data[i], p);
|
|
*p++ = '\n';
|
|
|
|
// .. and send it out
|
|
return dc_iostream_write(device->iostream, buffer, p-buffer, NULL);
|
|
}
|
|
|
|
//
|
|
// Receive one 'line' of data
|
|
//
|
|
// The deepblu BLE protocol is ASCII line based and packetized.
|
|
// Normally one packet is one line, but it looks like the Nordic
|
|
// Semi BLE chip will sometimes send packets early (some internal
|
|
// serial buffer timeout?) with incompete data.
|
|
//
|
|
// So read packets until you get newline.
|
|
static dc_status_t
|
|
deepblu_recv_line(deepblu_device_t *device, unsigned char *buf, size_t size)
|
|
{
|
|
while (1) {
|
|
unsigned char buffer[20];
|
|
size_t transferred = 0;
|
|
dc_status_t status;
|
|
|
|
status = dc_iostream_read(device->iostream, buffer, sizeof(buffer), &transferred);
|
|
if (status != DC_STATUS_SUCCESS) {
|
|
ERROR(device->base.context, "Failed to receive Deepblu reply packet.");
|
|
return status;
|
|
}
|
|
if (transferred > size) {
|
|
ERROR(device->base.context, "Deepblu reply packet with too much data (got %zu, expected %zu)", transferred, size);
|
|
return DC_STATUS_IO;
|
|
}
|
|
if (!transferred) {
|
|
ERROR(device->base.context, "Empty Deepblu reply packet");
|
|
return DC_STATUS_IO;
|
|
}
|
|
memcpy(buf, buffer, transferred);
|
|
buf += transferred;
|
|
size -= transferred;
|
|
if (buf[-1] == '\n')
|
|
break;
|
|
}
|
|
buf[-1] = 0;
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static int
|
|
hex_nibble(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
if (c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
read_hex_byte(char *p)
|
|
{
|
|
// This is negative if either of the nibbles is invalid
|
|
return (hex_nibble(p[0]) << 4) | hex_nibble(p[1]);
|
|
}
|
|
|
|
|
|
//
|
|
// Receive a reply packet
|
|
//
|
|
// The reply packet has the same format as the cmd packet we
|
|
// send, except the first byte is '$' instead of '#'.
|
|
static dc_status_t
|
|
deepblu_recv_data(deepblu_device_t *device, const unsigned char expected, unsigned char *buf, size_t size, size_t *received)
|
|
{
|
|
int len, i;
|
|
dc_status_t status;
|
|
char buffer[8+2*MAX_DATA];
|
|
int cmd, csum, ndata;
|
|
|
|
status = deepblu_recv_line(device, buffer, sizeof(buffer));
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
|
|
// deepblu_recv_line() always zero-terminates the result
|
|
// if it returned success, and has removed the final newline.
|
|
len = strlen(buffer);
|
|
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "rcv", buffer, len);
|
|
|
|
// A valid reply should always be at least 7 characters: the
|
|
// initial '$' and the three header HEX bytes.
|
|
if (len < 8 || buffer[0] != '$') {
|
|
ERROR(device->base.context, "Invalid Deepblu reply packet");
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
cmd = read_hex_byte(buffer+1);
|
|
csum = read_hex_byte(buffer+3);
|
|
ndata = read_hex_byte(buffer+5);
|
|
if ((cmd | csum | ndata) < 0) {
|
|
ERROR(device->base.context, "non-hex Deepblu reply packet header");
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
// Verify the data length: it's the size of the HEX data,
|
|
// and should also match the line length we got (the 7
|
|
// is for the header data we already decoded above).
|
|
if ((ndata & 1) || ndata != len - 7) {
|
|
ERROR(device->base.context, "Deepblu reply packet data length does not match (claimed %d, got %d)", ndata, len-7);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
if (ndata >> 1 > size) {
|
|
ERROR(device->base.context, "Deepblu reply packet too big for buffer (ndata=%d, size=%zu)", ndata, size);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
csum += cmd + ndata;
|
|
|
|
for (i = 7; i < len; i += 2) {
|
|
int byte = read_hex_byte(buffer + i);
|
|
if (byte < 0) {
|
|
ERROR(device->base.context, "Deepblu reply packet data not valid hex");
|
|
return DC_STATUS_IO;
|
|
}
|
|
*buf++ = byte;
|
|
csum += byte;
|
|
}
|
|
|
|
if (csum & 255) {
|
|
ERROR(device->base.context, "Deepblu reply packet csum not valid (%x)", csum);
|
|
return DC_STATUS_IO;
|
|
}
|
|
|
|
*received = ndata >> 1;
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
// Common communication pattern: send a command, expect data back with the same
|
|
// command byte.
|
|
static dc_status_t
|
|
deepblu_send_recv(deepblu_device_t *device, const unsigned char cmd,
|
|
const unsigned char *data, size_t data_size,
|
|
unsigned char *result, size_t result_size)
|
|
{
|
|
dc_status_t status;
|
|
size_t got;
|
|
|
|
status = deepblu_send_cmd(device, cmd, data, data_size);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
status = deepblu_recv_data(device, cmd, result, result_size, &got);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
if (got != result_size) {
|
|
ERROR(device->base.context, "Deepblu result size didn't match expected (expected %zu, got %zu)",
|
|
result_size, got);
|
|
return DC_STATUS_IO;
|
|
}
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static dc_status_t
|
|
deepblu_recv_bulk(deepblu_device_t *device, const unsigned char cmd, unsigned char *buf, size_t len)
|
|
{
|
|
while (len) {
|
|
dc_status_t status;
|
|
size_t got;
|
|
|
|
status = deepblu_recv_data(device, cmd, buf, len, &got);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
if (got > len) {
|
|
ERROR(device->base.context, "Deepblu bulk receive overflow");
|
|
return DC_STATUS_IO;
|
|
}
|
|
buf += got;
|
|
len -= got;
|
|
}
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
dc_status_t
|
|
deepblu_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
|
|
{
|
|
deepblu_device_t *device;
|
|
|
|
if (out == NULL)
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
// Allocate memory.
|
|
device = (deepblu_device_t *) dc_device_allocate (context, &deepblu_device_vtable);
|
|
if (device == NULL) {
|
|
ERROR (context, "Failed to allocate memory.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// Set the default values.
|
|
device->iostream = iostream;
|
|
memset(device->fingerprint, 0, sizeof(device->fingerprint));
|
|
|
|
*out = (dc_device_t *) device;
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static dc_status_t
|
|
deepblu_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
|
|
{
|
|
deepblu_device_t *device = (deepblu_device_t *)abstract;
|
|
|
|
HEXDUMP(device->base.context, DC_LOGLEVEL_DEBUG, "set_fingerprint", data, size);
|
|
|
|
if (size && size != sizeof (device->fingerprint))
|
|
return DC_STATUS_INVALIDARGS;
|
|
|
|
if (size)
|
|
memcpy (device->fingerprint, data, sizeof (device->fingerprint));
|
|
else
|
|
memset (device->fingerprint, 0, sizeof (device->fingerprint));
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static unsigned char bcd(int val)
|
|
{
|
|
if (val >= 0 && val < 100) {
|
|
int high = val / 10;
|
|
int low = val % 10;
|
|
return (high << 4) | low;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static dc_status_t
|
|
deepblu_device_timesync(dc_device_t *abstract, const dc_datetime_t *datetime)
|
|
{
|
|
deepblu_device_t *device = (deepblu_device_t *)abstract;
|
|
unsigned char result[1], data[6];
|
|
dc_status_t status;
|
|
size_t len;
|
|
|
|
data[0] = bcd(datetime->year - 2000);
|
|
data[1] = bcd(datetime->month);
|
|
data[2] = bcd(datetime->day);
|
|
data[3] = bcd(datetime->hour);
|
|
data[4] = bcd(datetime->minute);
|
|
data[5] = bcd(datetime->second);
|
|
|
|
// Maybe also check that we received one zero byte (ack?)
|
|
return deepblu_send_recv(device, CMD_SETTIME,
|
|
data, sizeof(data),
|
|
result, sizeof(result));
|
|
}
|
|
|
|
static dc_status_t
|
|
deepblu_device_close (dc_device_t *abstract)
|
|
{
|
|
deepblu_device_t *device = (deepblu_device_t *) abstract;
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static const char zero[MAX_DATA];
|
|
|
|
static dc_status_t
|
|
deepblu_download_dive(deepblu_device_t *device, unsigned char nr, dc_dive_callback_t callback, void *userdata)
|
|
{
|
|
unsigned char header_len;
|
|
unsigned char profilebytes[2];
|
|
unsigned int profile_len;
|
|
dc_status_t status;
|
|
char header[256];
|
|
unsigned char *profile;
|
|
|
|
status = deepblu_send_recv(device, CMD_GETDIVE, &nr, 1, &header_len, 1);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
status = deepblu_recv_bulk(device, RSP_DIVESTAT, header, header_len);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
memset(header + header_len, 0, 256 - header_len);
|
|
|
|
/* The header is the fingerprint. If we've already seen this header, we're done */
|
|
if (memcmp(header, device->fingerprint, sizeof (device->fingerprint)) == 0)
|
|
return DC_STATUS_DONE;
|
|
|
|
status = deepblu_send_recv(device, CMD_GETPROFILE, &nr, 1, profilebytes, sizeof(profilebytes));
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
profile_len = (profilebytes[0] << 8) | profilebytes[1];
|
|
|
|
profile = malloc(256 + profile_len);
|
|
if (!profile) {
|
|
ERROR (device->base.context, "Insufficient buffer space available.");
|
|
return DC_STATUS_NOMEMORY;
|
|
}
|
|
|
|
// We make the dive data be 256 bytes of header, followed by the profile data
|
|
memcpy(profile, header, 256);
|
|
|
|
status = deepblu_recv_bulk(device, RSP_DIVEPROF, profile+256, profile_len);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
|
|
if (callback) {
|
|
if (!callback(profile, profile_len+256, header, header_len, userdata))
|
|
return DC_STATUS_DONE;
|
|
}
|
|
free(profile);
|
|
return DC_STATUS_SUCCESS;
|
|
}
|
|
|
|
static dc_status_t
|
|
deepblu_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
|
|
{
|
|
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
|
|
deepblu_device_t *device = (deepblu_device_t *) abstract;
|
|
unsigned char nrdives, val;
|
|
dc_status_t status;
|
|
int i;
|
|
|
|
val = 0;
|
|
status = deepblu_send_recv(device, CMD_GETDIVENR, &val, 1, &nrdives, 1);
|
|
if (status != DC_STATUS_SUCCESS)
|
|
return status;
|
|
|
|
if (!nrdives)
|
|
return DC_STATUS_SUCCESS;
|
|
|
|
progress.maximum = nrdives;
|
|
progress.current = 0;
|
|
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
|
|
|
for (i = 1; i <= nrdives; i++) {
|
|
if (device_is_cancelled(abstract)) {
|
|
dc_status_set_error(&status, DC_STATUS_CANCELLED);
|
|
break;
|
|
}
|
|
|
|
status = deepblu_download_dive(device, i, callback, userdata);
|
|
switch (status) {
|
|
case DC_STATUS_DONE:
|
|
i = nrdives;
|
|
break;
|
|
case DC_STATUS_SUCCESS:
|
|
break;
|
|
default:
|
|
return status;
|
|
}
|
|
progress.current = i;
|
|
device_event_emit(abstract, DC_EVENT_PROGRESS, &progress);
|
|
}
|
|
|
|
return DC_STATUS_SUCCESS;
|
|
}
|