From 91309a3d54b5a41739111472b56bc3c2e8c42261 Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 1 Feb 2019 15:25:38 +0100 Subject: [PATCH 1/8] Remove an unnecessary function --- src/oceanic_atom2.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 195e4ca..a4341fe 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -632,19 +632,6 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm } -static dc_status_t -oceanic_atom2_quit (oceanic_atom2_device_t *device) -{ - // Send the command to the dive computer. - unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); - if (rc != DC_STATUS_SUCCESS) - return rc; - - return DC_STATUS_SUCCESS; -} - - dc_status_t oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) { @@ -827,7 +814,8 @@ oceanic_atom2_device_close (dc_device_t *abstract) dc_status_t rc = DC_STATUS_SUCCESS; // Send the quit command. - rc = oceanic_atom2_quit (device); + unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; + rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) { dc_status_set_error(&status, rc); } From 4b2156d3782d8c06b70508e359083c8aa9b7e56b Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 1 Feb 2019 15:45:49 +0100 Subject: [PATCH 2/8] Fix a bug in the ACK/NAK handling The write command is send as two separate packets. The first packet contains the B2 command and the page number, and the second packet contains the payload and checksum. Because the payload can contain arbitrary data, the first byte of a packet is not necessary a command byte. But the code to select the correct ack byte is based on this assumption. Fixed by passing the expected ack byte. --- src/oceanic_atom2.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index a4341fe..e09b851 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -534,7 +534,7 @@ static const oceanic_common_layout_t aqualung_i450t_layout = { }; static dc_status_t -oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char ack, unsigned char answer[], unsigned int asize, unsigned int crc_size) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; @@ -553,12 +553,6 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman return status; } - // Get the correct ACK byte. - unsigned int ack = ACK; - if (command[0] == CMD_INIT || command[0] == CMD_QUIT) { - ack = NAK; - } - // Receive the response (ACK/NAK) of the dive computer. unsigned char response = 0; status = dc_iostream_read (device->iostream, &response, 1, NULL); @@ -601,7 +595,7 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman static dc_status_t -oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char answer[], unsigned int asize, unsigned int crc_size) +oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char ack, unsigned char answer[], unsigned int asize, unsigned int crc_size) { // Send the command to the device. If the device responds with an // ACK byte, the command was received successfully and the answer @@ -611,7 +605,7 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm unsigned int nretries = 0; dc_status_t rc = DC_STATUS_SUCCESS; - while ((rc = oceanic_atom2_packet (device, command, csize, answer, asize, crc_size)) != DC_STATUS_SUCCESS) { + while ((rc = oceanic_atom2_packet (device, command, csize, ack, answer, asize, crc_size)) != DC_STATUS_SUCCESS) { if (rc != DC_STATUS_TIMEOUT && rc != DC_STATUS_PROTOCOL) return rc; @@ -815,7 +809,7 @@ oceanic_atom2_device_close (dc_device_t *abstract) // Send the quit command. unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; - rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); + rc = oceanic_atom2_transfer (device, command, sizeof (command), NAK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) { dc_status_set_error(&status, rc); } @@ -834,7 +828,7 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract) // Send the command to the dive computer. unsigned char command[4] = {CMD_KEEPALIVE, 0x05, 0xA5, 0x00}; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; @@ -855,7 +849,7 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig unsigned char answer[PAGESIZE + 1] = {0}; unsigned char command[2] = {CMD_VERSION, 0x00}; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), answer, sizeof (answer), 1); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, sizeof (answer), 1); if (rc != DC_STATUS_SUCCESS) return rc; @@ -923,7 +917,7 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned (number >> 8) & 0xFF, // high (number ) & 0xFF, // low 0}; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), answer, pagesize + crc_size, crc_size); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, pagesize + crc_size, crc_size); if (rc != DC_STATUS_SUCCESS) return rc; @@ -970,7 +964,7 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u (number >> 8) & 0xFF, // high (number ) & 0xFF, // low 0x00}; - dc_status_t rc = oceanic_atom2_transfer (device, prepare, sizeof (prepare), NULL, 0, 0); + dc_status_t rc = oceanic_atom2_transfer (device, prepare, sizeof (prepare), ACK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; @@ -978,7 +972,7 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u unsigned char command[PAGESIZE + 2] = {0}; memcpy (command, data, PAGESIZE); command[PAGESIZE] = checksum_add_uint8 (command, PAGESIZE, 0x00); - rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0); + rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; From 437db813d5e3f188c7109ebffff9c3dbb65a561e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 1 Feb 2019 15:48:40 +0100 Subject: [PATCH 3/8] Remove the trailing zero byte from all commands The trailing zero byte is present for historic reasons only. At the time the Oceanic protocol was implemented, the Oceanic application send this extra zero byte too, and we simply copied this behaviour. But more recent versions no longer send it. Probably a small (harmless) bug that was fixed. --- src/oceanic_atom2.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index e09b851..56c28d8 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -808,7 +808,7 @@ oceanic_atom2_device_close (dc_device_t *abstract) dc_status_t rc = DC_STATUS_SUCCESS; // Send the quit command. - unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00}; + unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5}; rc = oceanic_atom2_transfer (device, command, sizeof (command), NAK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) { dc_status_set_error(&status, rc); @@ -827,7 +827,7 @@ oceanic_atom2_device_keepalive (dc_device_t *abstract) return DC_STATUS_INVALIDARGS; // Send the command to the dive computer. - unsigned char command[4] = {CMD_KEEPALIVE, 0x05, 0xA5, 0x00}; + unsigned char command[] = {CMD_KEEPALIVE, 0x05, 0xA5}; dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; @@ -848,7 +848,7 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig return DC_STATUS_INVALIDARGS; unsigned char answer[PAGESIZE + 1] = {0}; - unsigned char command[2] = {CMD_VERSION, 0x00}; + unsigned char command[] = {CMD_VERSION}; dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, sizeof (answer), 1); if (rc != DC_STATUS_SUCCESS) return rc; @@ -913,10 +913,10 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned // Read the package. unsigned int number = highmem ? page : page * device->bigpage; // This is always PAGESIZE, even in big page mode. unsigned char answer[256 + 2] = {0}; // Maximum we support for the known commands. - unsigned char command[4] = {read_cmd, + unsigned char command[] = {read_cmd, (number >> 8) & 0xFF, // high (number ) & 0xFF, // low - 0}; + }; dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, pagesize + crc_size, crc_size); if (rc != DC_STATUS_SUCCESS) return rc; @@ -960,16 +960,16 @@ oceanic_atom2_device_write (dc_device_t *abstract, unsigned int address, const u while (nbytes < size) { // Prepare to write the package. unsigned int number = address / PAGESIZE; - unsigned char prepare[4] = {CMD_WRITE, + unsigned char prepare[] = {CMD_WRITE, (number >> 8) & 0xFF, // high (number ) & 0xFF, // low - 0x00}; + }; dc_status_t rc = oceanic_atom2_transfer (device, prepare, sizeof (prepare), ACK, NULL, 0, 0); if (rc != DC_STATUS_SUCCESS) return rc; // Write the package. - unsigned char command[PAGESIZE + 2] = {0}; + unsigned char command[PAGESIZE + 1] = {0}; memcpy (command, data, PAGESIZE); command[PAGESIZE] = checksum_add_uint8 (command, PAGESIZE, 0x00); rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, NULL, 0, 0); From 4923d3761e4bc3948691b294b6736753c18fe7ea Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Fri, 1 Feb 2019 15:53:14 +0100 Subject: [PATCH 4/8] Read the entire data packet in a single operation Refactor the packet receiving code to read the ack byte, the payload data and the checksum all at once, with just a single read operation. This is not only a bit more efficient, but will also simplify the BLE support. --- src/oceanic_atom2.c | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 56c28d8..82fab6e 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -38,6 +38,7 @@ #define I770R 0x4651 #define GEO40 0x4653 +#define MAXPACKET 256 #define MAXRETRIES 2 #define MAXDELAY 16 #define INVALID 0xFFFFFFFF @@ -539,6 +540,14 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + if (asize > MAXPACKET) { + return DC_STATUS_INVALIDARGS; + } + + if (crc_size > 2 || (crc_size != 0 && asize == 0)) { + return DC_STATUS_INVALIDARGS; + } + if (device_is_cancelled (abstract)) return DC_STATUS_CANCELLED; @@ -553,41 +562,36 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman return status; } - // Receive the response (ACK/NAK) of the dive computer. - unsigned char response = 0; - status = dc_iostream_read (device->iostream, &response, 1, NULL); + // Receive the answer of the dive computer. + unsigned char packet[1 + MAXPACKET + 2]; + status = dc_iostream_read (device->iostream, packet, 1 + asize + crc_size, NULL); if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; } - // Verify the response of the dive computer. - if (response != ack) { + // Verify the ACK byte of the answer. + if (packet[0] != ack) { ERROR (abstract->context, "Unexpected answer start byte(s)."); return DC_STATUS_PROTOCOL; } if (asize) { - // Receive the answer of the dive computer. - status = dc_iostream_read (device->iostream, answer, asize, NULL); - if (status != DC_STATUS_SUCCESS) { - ERROR (abstract->context, "Failed to receive the answer."); - return status; - } - // Verify the checksum of the answer. unsigned short crc, ccrc; if (crc_size == 2) { - crc = array_uint16_le (answer + asize - 2); - ccrc = checksum_add_uint16 (answer, asize - 2, 0x0000); + crc = array_uint16_le (packet + 1 + asize); + ccrc = checksum_add_uint16 (packet + 1, asize, 0x0000); } else { - crc = answer[asize - 1]; - ccrc = checksum_add_uint8 (answer, asize - 1, 0x00); + crc = packet[1 + asize]; + ccrc = checksum_add_uint8 (packet + 1, asize, 0x00); } if (crc != ccrc) { ERROR (abstract->context, "Unexpected answer checksum."); return DC_STATUS_PROTOCOL; } + + memcpy (answer, packet + 1, asize); } return DC_STATUS_SUCCESS; @@ -847,14 +851,11 @@ oceanic_atom2_device_version (dc_device_t *abstract, unsigned char data[], unsig if (size < PAGESIZE) return DC_STATUS_INVALIDARGS; - unsigned char answer[PAGESIZE + 1] = {0}; unsigned char command[] = {CMD_VERSION}; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, sizeof (answer), 1); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, data, PAGESIZE, 1); if (rc != DC_STATUS_SUCCESS) return rc; - memcpy (data, answer, PAGESIZE); - return DC_STATUS_SUCCESS; } @@ -912,17 +913,15 @@ oceanic_atom2_device_read (dc_device_t *abstract, unsigned int address, unsigned if (page != device->cached_page || highmem != device->cached_highmem) { // Read the package. unsigned int number = highmem ? page : page * device->bigpage; // This is always PAGESIZE, even in big page mode. - unsigned char answer[256 + 2] = {0}; // Maximum we support for the known commands. unsigned char command[] = {read_cmd, (number >> 8) & 0xFF, // high (number ) & 0xFF, // low }; - dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, answer, pagesize + crc_size, crc_size); + dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, device->cache, pagesize, crc_size); if (rc != DC_STATUS_SUCCESS) return rc; // Cache the page. - memcpy (device->cache, answer, pagesize); device->cached_page = page; device->cached_highmem = highmem; } From 6ba0726a4299accf6eef1257e283859fdaf15eca Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 5 Feb 2019 14:01:49 +0100 Subject: [PATCH 5/8] Implement the BLE packet sending and receiving Based-on-code-by: Linus Torvalds --- src/oceanic_atom2.c | 142 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 82fab6e..682fec9 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -29,6 +29,7 @@ #include "array.h" #include "ringbuffer.h" #include "checksum.h" +#include "platform.h" #define ISINSTANCE(device) dc_device_isinstance((device), &oceanic_atom2_device_vtable.base) @@ -59,6 +60,7 @@ typedef struct oceanic_atom2_device_t { oceanic_common_device_t base; dc_iostream_t *iostream; + unsigned int sequence; unsigned int delay; unsigned int bigpage; unsigned char cache[256]; @@ -534,11 +536,136 @@ static const oceanic_common_layout_t aqualung_i450t_layout = { 0, /* pt_mode_serial */ }; +/* + * The BLE GATT packet size is up to 20 bytes and the format is: + * + * byte 0: <0xCD> + * Seems to always have this value. Don't ask what it means + * byte 1: + * d=0 means "command", d=1 means "reply from dive computer" + * 1 is always set, afaik + * c=0 means "last packet" in sequence, c=1 means "more packets coming" + * sssss is a 5-bit sequence number for packets + * byte 2: + * starts at 0 for the connection, incremented for each command + * byte 3: + * 1-16 bytes of data per packet. + * byte 4..n: + */ +static dc_status_t +oceanic_atom2_ble_write (oceanic_atom2_device_t *device, const unsigned char data[], unsigned int size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + unsigned char buf[20]; + unsigned char cmd_seq = device->sequence; + unsigned char pkt_seq = 0; + + unsigned int nbytes = 0; + while (nbytes < size) { + unsigned char status = 0x40; + unsigned int length = size - nbytes; + if (length > sizeof(buf) - 4) { + length = sizeof(buf) - 4; + status |= 0x20; + } + buf[0] = 0xcd; + buf[1] = status | (pkt_seq & 0x1F); + buf[2] = cmd_seq; + buf[3] = length; + memcpy (buf + 4, data, length); + + rc = dc_iostream_write (device->iostream, buf, 4 + length, NULL); + if (rc != DC_STATUS_SUCCESS) + return rc; + + nbytes += length; + pkt_seq++; + } + + return DC_STATUS_SUCCESS; +} + +static dc_status_t +oceanic_atom2_ble_read (oceanic_atom2_device_t *device, unsigned char data[], unsigned int size) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + unsigned char buf[20]; + unsigned char cmd_seq = device->sequence; + unsigned char pkt_seq = 0; + + unsigned int nbytes = 0; + while (1) { + size_t transferred = 0; + rc = dc_iostream_read (device->iostream, buf, sizeof(buf), &transferred); + if (rc != DC_STATUS_SUCCESS) + return rc; + + if (transferred < 4) { + ERROR (abstract->context, "Invalid packet size (" DC_PRINTF_SIZE ").", transferred); + return DC_STATUS_PROTOCOL; + } + + // Verify the start byte. + if (buf[0] != 0xcd) { + ERROR (abstract->context, "Unexpected packet start byte (%02x).", buf[0]); + return DC_STATUS_PROTOCOL; + } + + // Verify the status byte. + unsigned char status = buf[1]; + unsigned char expect = 0xc0 | (pkt_seq & 0x1F) | (status & 0x20); + if (status != expect) { + ERROR (abstract->context, "Unexpected packet status byte (%02x %02x).", status, expect); + return DC_STATUS_PROTOCOL; + } + + // Verify the sequence byte. + if (buf[2] != cmd_seq) { + ERROR (abstract->context, "Unexpected packet sequence byte (%02x %02x).", buf[2], cmd_seq); + return DC_STATUS_PROTOCOL; + } + + // Verify the length byte. + unsigned int length = buf[3]; + if (length + 4 > transferred) { + ERROR (abstract->context, "Invalid packet length (%u).", length); + return DC_STATUS_PROTOCOL; + } + + // Append the payload data to the output buffer. If the output + // buffer is too small, the error is not reported immediately + // but delayed until all packets have been received. + if (nbytes < size) { + unsigned int n = length; + if (nbytes + n > size) { + n = size - nbytes; + } + memcpy (data + nbytes, buf + 4, n); + } + nbytes += length; + pkt_seq++; + + // Last packet? + if ((status & 0x20) == 0) + break; + } + + // Verify the expected number of bytes. + if (nbytes != size) { + ERROR (abstract->context, "Unexpected number of bytes received (%u %u).", nbytes, size); + return DC_STATUS_PROTOCOL; + } + + return DC_STATUS_SUCCESS; +} + static dc_status_t oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char command[], unsigned int csize, unsigned char ack, unsigned char answer[], unsigned int asize, unsigned int crc_size) { dc_status_t status = DC_STATUS_SUCCESS; dc_device_t *abstract = (dc_device_t *) device; + dc_transport_t transport = dc_iostream_get_transport (device->iostream); if (asize > MAXPACKET) { return DC_STATUS_INVALIDARGS; @@ -556,7 +683,11 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman } // Send the command to the dive computer. - status = dc_iostream_write (device->iostream, command, csize, NULL); + if (transport == DC_TRANSPORT_BLE) { + status = oceanic_atom2_ble_write (device, command, csize); + } else { + status = dc_iostream_write (device->iostream, command, csize, NULL); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to send the command."); return status; @@ -564,7 +695,11 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman // Receive the answer of the dive computer. unsigned char packet[1 + MAXPACKET + 2]; - status = dc_iostream_read (device->iostream, packet, 1 + asize + crc_size, NULL); + if (transport == DC_TRANSPORT_BLE) { + status = oceanic_atom2_ble_read (device, packet, 1 + asize + crc_size); + } else { + status = dc_iostream_read (device->iostream, packet, 1 + asize + crc_size, NULL); + } if (status != DC_STATUS_SUCCESS) { ERROR (abstract->context, "Failed to receive the answer."); return status; @@ -594,6 +729,8 @@ oceanic_atom2_packet (oceanic_atom2_device_t *device, const unsigned char comman memcpy (answer, packet + 1, asize); } + device->sequence++; + return DC_STATUS_SUCCESS; } @@ -652,6 +789,7 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream // Set the default values. device->iostream = iostream; device->delay = 0; + device->sequence = 0; device->bigpage = 1; // no big pages device->cached_page = INVALID; device->cached_highmem = INVALID; From 4baf140d250c696aa15b2694fe9fa824ae73159f Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 5 Feb 2019 14:05:49 +0100 Subject: [PATCH 6/8] Implement the BLE handshaking The BLE communication sends a handshake packet containing a passphrase based on the serial number of the device. Sadly, we can't actually read the serial number from the device until after this handshake has successfully completed, which makes it a bit of a chicken-and-egg problem from a communication standpoint. However, the serial number is also exposed in the bluetooth device name the device advertizes, which is the reason for the newly added DC_IOCTL_BLE_GET_NAME ioctl. Thanks to Janice McLaughlin for pointing out the logic of this magic handshake. Based-on-code-by: Linus Torvalds --- src/oceanic_atom2.c | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 682fec9..e897259 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -22,6 +22,8 @@ #include // memcpy #include // malloc, free +#include + #include "oceanic_atom2.h" #include "oceanic_common.h" #include "context-private.h" @@ -46,6 +48,7 @@ #define CMD_INIT 0xA8 #define CMD_VERSION 0x84 +#define CMD_HANDSHAKE 0xE5 #define CMD_READ1 0xB1 #define CMD_READ8 0xB4 #define CMD_READ16 0xB8 @@ -766,6 +769,65 @@ oceanic_atom2_transfer (oceanic_atom2_device_t *device, const unsigned char comm return DC_STATUS_SUCCESS; } +/* + * The BLE communication sends a handshake packet that seems + * to be a passphrase based on the BLE name of the device + * (more specifically the serial number encoded in the name). + * + * The packet format is: + * 0xe5 + * < 8 bytes of passphrase > + * one-byte checksum of the passphrase. + */ +static dc_status_t +oceanic_atom2_ble_handshake(oceanic_atom2_device_t *device) +{ + dc_status_t rc = DC_STATUS_SUCCESS; + dc_device_t *abstract = (dc_device_t *) device; + + // Retrieve the bluetooth device name. + // The format of the name is something like 'FQ001124', where the + // two first letters are the ASCII representation of the model + // number (e.g. 'FQ' or 0x4651 for the i770R), and the six digits + // are the serial number. + char name[8 + 1] = {0}; + rc = dc_iostream_ioctl (device->iostream, DC_IOCTL_BLE_GET_NAME, name, sizeof(name)); + if (rc != DC_STATUS_SUCCESS) { + if (rc == DC_STATUS_UNSUPPORTED) { + // Allow skipping the handshake if no name. But the download + // will likely fail. + WARNING (abstract->context, "Bluetooth device name unavailable."); + return DC_STATUS_SUCCESS; + } else { + return rc; + } + } + + // Force a null terminated string. + name[sizeof(name) - 1] = 0; + + // Check the minimum length. + if (strlen (name) < 8) { + ERROR (abstract->context, "Bluetooth device name too short."); + return DC_STATUS_IO; + } + + // Turn ASCII numbers into just raw byte values. + unsigned char handshake[10] = {CMD_HANDSHAKE}; + for (unsigned int i = 0; i < 6; i++) { + handshake[i + 1] = name[i + 2] - '0'; + } + + // Add simple checksum. + handshake[9] = checksum_add_uint8 (handshake + 1, 8, 0x00); + + // Send the command to the dive computer. + rc = oceanic_atom2_transfer (device, handshake, sizeof(handshake), ACK, NULL, 0, 0); + if (rc != DC_STATUS_SUCCESS) + return rc; + + return DC_STATUS_SUCCESS; +} dc_status_t oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model) @@ -857,6 +919,13 @@ oceanic_atom2_device_open (dc_device_t **out, dc_context_t *context, dc_iostream goto error_free; } + if (dc_iostream_get_transport (device->iostream) == DC_TRANSPORT_BLE) { + status = oceanic_atom2_ble_handshake(device); + if (status != DC_STATUS_SUCCESS) { + goto error_free; + } + } + // Override the base class values. if (OCEANIC_COMMON_MATCH (device->base.version, aeris_f10_version)) { device->base.layout = &aeris_f10_layout; From 4bc5ee90ef53d1f573e9fc8101bfd423f5a0012d Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 5 Feb 2019 14:07:43 +0100 Subject: [PATCH 7/8] Fix the BLE device detection for the i770R and Pro Plus X It seems that the BLE communication protocol is somewhat different from the serial one in the version string: while the serial version tends to show the memory size, the BLE version string has some other numeric pattern. Linus Torvalds reports the BLE pattern for the i770R is normally just "0001", allthough he once also observed "0090" with the same dive computer. A communication trace from a Pro Plus X also showed "0001". We don't have enough information to guess the meaning of the number. Regardless, for those two dive computers supporting BLE, make the pattern simply ignore the last four digits, since they clearly vary. Based-on-code-by: Linus Torvalds --- src/oceanic_atom2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index e897259..c892486 100644 --- a/src/oceanic_atom2.c +++ b/src/oceanic_atom2.c @@ -207,11 +207,11 @@ static const oceanic_common_version_t oceanic_reactpro_version[] = { }; static const oceanic_common_version_t oceanic_proplusx_version[] = { - {"OCEANOCX \0\0 2048"}, + {"OCEANOCX \0\0 \0\0\0\0"}, }; static const oceanic_common_version_t aqualung_i770r_version[] = { - {"AQUA770R \0\0 2048"}, + {"AQUA770R \0\0 \0\0\0\0"}, }; static const oceanic_common_version_t aeris_a300cs_version[] = { From cfd54ff80e3514f9b8bad7385541633dfcb3fb0e Mon Sep 17 00:00:00 2001 From: Jef Driesen Date: Tue, 5 Feb 2019 14:08:22 +0100 Subject: [PATCH 8/8] Advertise the BLE support in the device descriptors The bluetooth device filtering is based on the fact that the format of the bluetooth device name is something like 'FQ001124', where the two first letters are the ASCII representation of the model number (e.g. 'FQ' or 0x4651 for the i770R), and the six digits are the serial number. --- src/descriptor.c | 51 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/descriptor.c b/src/descriptor.c index 64323b2..3734df5 100644 --- a/src/descriptor.c +++ b/src/descriptor.c @@ -47,6 +47,7 @@ static int dc_filter_hw (dc_transport_t transport, const void *userdata); static int dc_filter_tecdiving (dc_transport_t transport, const void *userdata); static int dc_filter_mares (dc_transport_t transport, const void *userdata); static int dc_filter_divesystem (dc_transport_t transport, const void *userdata); +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata); static dc_status_t dc_descriptor_iterator_next (dc_iterator_t *iterator, void *item); @@ -225,7 +226,7 @@ static const dc_descriptor_t g_descriptors[] = { {"Aeris", "A300CS", DC_FAMILY_OCEANIC_ATOM2, 0x454C, DC_TRANSPORT_SERIAL, NULL}, {"Tusa", "Talis", DC_FAMILY_OCEANIC_ATOM2, 0x454E, DC_TRANSPORT_SERIAL, NULL}, {"Beuchat", "Mundial 3", DC_FAMILY_OCEANIC_ATOM2, 0x4550, DC_TRANSPORT_SERIAL, NULL}, - {"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL, NULL}, + {"Oceanic", "Pro Plus X", DC_FAMILY_OCEANIC_ATOM2, 0x4552, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Oceanic", "F10", DC_FAMILY_OCEANIC_ATOM2, 0x4553, DC_TRANSPORT_SERIAL, NULL}, {"Oceanic", "F11", DC_FAMILY_OCEANIC_ATOM2, 0x4554, DC_TRANSPORT_SERIAL, NULL}, {"Subgear", "XP-Air", DC_FAMILY_OCEANIC_ATOM2, 0x4555, DC_TRANSPORT_SERIAL, NULL}, @@ -236,14 +237,14 @@ static const dc_descriptor_t g_descriptors[] = { {"Aqualung", "i450T", DC_FAMILY_OCEANIC_ATOM2, 0x4641, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i550", DC_FAMILY_OCEANIC_ATOM2, 0x4642, DC_TRANSPORT_SERIAL, NULL}, {"Aqualung", "i200", DC_FAMILY_OCEANIC_ATOM2, 0x4646, DC_TRANSPORT_SERIAL, NULL}, - {"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL, NULL}, - {"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4649, DC_TRANSPORT_SERIAL, NULL}, + {"Aqualung", "i300C", DC_FAMILY_OCEANIC_ATOM2, 0x4648, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i200C", DC_FAMILY_OCEANIC_ATOM2, 0x4649, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, {"Aqualung", "i100", DC_FAMILY_OCEANIC_ATOM2, 0x464E, DC_TRANSPORT_SERIAL, NULL}, - {"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL, NULL}, - {"Aqualung", "i550C", DC_FAMILY_OCEANIC_ATOM2, 0x4652, DC_TRANSPORT_SERIAL, NULL}, - {"Oceanic", "Geo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4653, DC_TRANSPORT_SERIAL, NULL}, - {"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL, NULL}, - {"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL, NULL}, + {"Aqualung", "i770R", DC_FAMILY_OCEANIC_ATOM2, 0x4651, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Aqualung", "i550C", DC_FAMILY_OCEANIC_ATOM2, 0x4652, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Oceanic", "Geo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4653, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Oceanic", "Veo 4.0", DC_FAMILY_OCEANIC_ATOM2, 0x4654, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, + {"Oceanic", "Pro Plus 4", DC_FAMILY_OCEANIC_ATOM2, 0x4656, DC_TRANSPORT_SERIAL | DC_TRANSPORT_BLE, dc_filter_oceanic}, /* Mares Nemo */ {"Mares", "Nemo", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, {"Mares", "Nemo Steel", DC_FAMILY_MARES_NEMO, 0, DC_TRANSPORT_SERIAL, NULL}, @@ -434,6 +435,20 @@ dc_match_number_with_prefix (const void *key, const void *value) return 1; } +static int +dc_match_oceanic (const void *key, const void *value) +{ + unsigned int model = *(const unsigned int *) value; + + const char prefix[] = { + (model >> 8) & 0xFF, + (model ) & 0xFF, + 0 + }; + + return dc_match_number_with_prefix (key, &prefix); +} + static int dc_filter_internal (const void *key, const void *values, size_t count, size_t size, dc_match_t match) { @@ -590,6 +605,26 @@ static int dc_filter_divesystem (dc_transport_t transport, const void *userdata) return 1; } +static int dc_filter_oceanic (dc_transport_t transport, const void *userdata) +{ + static const unsigned int model[] = { + 0x4552, // Oceanic Pro Plus X + 0x4648, // Aqualung i300C + 0x4649, // Aqualung i200C + 0x4651, // Aqualung i770R + 0x4652, // Aqualung i550C + 0x4653, // Oceanic Geo 4.0 + 0x4654, // Oceanic Veo 4.0 + 0x4656, // Oceanic Pro Plus 4 + }; + + if (transport == DC_TRANSPORT_BLE) { + return DC_FILTER_INTERNAL (userdata, model, 0, dc_match_oceanic); + } + + return 1; +} + dc_status_t dc_descriptor_iterator (dc_iterator_t **out) {