Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/descriptor.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ static const dc_descriptor_t g_descriptors[] = {
{"Shearwater", "Tern", DC_FAMILY_SHEARWATER_PETREL, 12, DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Tern TX", DC_FAMILY_SHEARWATER_PETREL, 12, DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Peregrine TX", DC_FAMILY_SHEARWATER_PETREL, 13, DC_TRANSPORT_BLE, dc_filter_shearwater},
{"Shearwater", "Perdix 3", DC_FAMILY_SHEARWATER_PETREL, 14, DC_TRANSPORT_BLE, dc_filter_shearwater},
/* Dive Rite NiTek Q */
{"Dive Rite", "NiTek Q", DC_FAMILY_DIVERITE_NITEKQ, 0, DC_TRANSPORT_SERIAL, NULL},
/* Citizen Hyper Aqualand */
Expand Down Expand Up @@ -764,6 +765,7 @@ dc_filter_shearwater (const dc_descriptor_t *descriptor, dc_transport_t transpor
"NERD 2",
"Perdix",
"Perdix 2",
"Perdix 3",
"Teric",
"Peregrine",
"Peregrine TX",
Expand Down
2 changes: 1 addition & 1 deletion src/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ dc_device_open (dc_device_t **out, dc_context_t *context, dc_descriptor_t *descr
rc = shearwater_predator_device_open (&device, context, iostream);
break;
case DC_FAMILY_SHEARWATER_PETREL:
rc = shearwater_petrel_device_open (&device, context, iostream);
rc = shearwater_petrel_device_open (&device, context, iostream, dc_descriptor_get_model (descriptor));
break;
case DC_FAMILY_DIVERITE_NITEKQ:
rc = diverite_nitekq_device_open (&device, context, iostream);
Expand Down
134 changes: 113 additions & 21 deletions src/shearwater_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@
#include "platform.h"
#include "array.h"

#define SZ_PACKET 254
// The Perdix 3 framing uses a 24-bit length field and negotiates larger
// download blocks than the classic 254 byte packets: 514 bytes (including
// the 2 byte block header) for uncompressed manifest reads, and 2306 bytes
// for compressed dive downloads (init response 75 20 0902 observed live).
#define SZ_PACKET 4096
Comment on lines +31 to +35

// SLIP special character codes
#define END 0xC0
Expand All @@ -50,6 +54,7 @@ shearwater_common_setup (shearwater_common_device_t *device, dc_context_t *conte
dc_status_t status = DC_STATUS_SUCCESS;

device->iostream = iostream;
device->framing = SHEARWATER_FRAMING_CLASSIC;

// Set the serial communication protocol (115200 8N1).
status = dc_iostream_configure (device->iostream, 115200, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);
Expand Down Expand Up @@ -139,7 +144,13 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned
unsigned char buffer[32];
unsigned int nbytes = 0;

if (transport == DC_TRANSPORT_BLE) {
// The classic BLE framing prefixes every packet with a 2-byte
// [nframes, index] header. The Perdix 3 framing sends the raw SLIP
// stream without any per-packet header.
unsigned int ble_classic = (transport == DC_TRANSPORT_BLE &&
device->framing == SHEARWATER_FRAMING_CLASSIC);

if (ble_classic) {
// Calculate the total number of bytes.
unsigned int count = 1;
for (unsigned int i = 0; i < size; ++i) {
Expand Down Expand Up @@ -174,7 +185,7 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned
return status;
}

if (transport == DC_TRANSPORT_BLE) {
if (ble_classic) {
buffer[1]++;
nbytes = 2;
} else {
Expand All @@ -201,7 +212,7 @@ shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned
return status;
}

if (transport == DC_TRANSPORT_BLE) {
if (ble_classic) {
buffer[1]++;
nbytes = 2;
} else {
Expand Down Expand Up @@ -229,10 +240,16 @@ shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char d
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_transport_t transport = dc_iostream_get_transport(device->iostream);
unsigned char buffer[256];
// The Perdix 3 negotiates a large MTU and sends notifications of up to
// several hundred bytes, so the read buffer must be large enough to
// receive a full notification in one dc_iostream_read call.
unsigned char buffer[1024];
unsigned int escaped = 0;
unsigned int nbytes = 0;

unsigned int ble_classic = (transport == DC_TRANSPORT_BLE &&
device->framing == SHEARWATER_FRAMING_CLASSIC);

// Get the packet size.
size_t packetsize = (transport == DC_TRANSPORT_BLE) ? sizeof(buffer) : 1;

Expand All @@ -249,7 +266,9 @@ shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char d
}

size_t offset = 0;
if (transport == DC_TRANSPORT_BLE) {
if (ble_classic) {
// Skip the classic 2-byte [nframes, index] packet header.
// The Perdix 3 framing has no per-packet header.
if (transferred < 2) {
ERROR (device->base.context, "Invalid packet length (" DC_PRINTF_SIZE ").", transferred);
return DC_STATUS_PROTOCOL;
Expand Down Expand Up @@ -330,7 +349,8 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
unsigned char packet[SZ_PACKET + 4];
unsigned char packet[SZ_PACKET + 5];
unsigned int hlen = 0;
unsigned int n = 0;

if (isize > SZ_PACKET || osize > SZ_PACKET)
Expand All @@ -340,14 +360,29 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
return DC_STATUS_CANCELLED;

// Setup the request packet.
packet[0] = 0xFF;
packet[1] = 0x01;
packet[2] = isize + 1;
packet[3] = 0x00;
memcpy (packet + 4, input, isize);
// AI-generated (Claude)
if (device->framing == SHEARWATER_FRAMING_PERDIX3) {
// FF 01 <length:24 be> <payload>
packet[0] = 0xFF;
packet[1] = 0x01;
packet[2] = (isize >> 16) & 0xFF;
packet[3] = (isize >> 8) & 0xFF;
packet[4] = (isize ) & 0xFF;
hlen = 5;
} else {
// FF 01 <length + 1:8> 00 <payload>
if (isize + 1 > 0xFF)
return DC_STATUS_INVALIDARGS;
packet[0] = 0xFF;
packet[1] = 0x01;
packet[2] = isize + 1;
packet[3] = 0x00;
hlen = 4;
}
memcpy (packet + hlen, input, isize);

// Send the request packet.
status = shearwater_common_slip_write (device, packet, isize + 4);
status = shearwater_common_slip_write (device, packet, isize + hlen);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the request packet.");
return status;
Expand All @@ -367,6 +402,28 @@ shearwater_common_transfer (shearwater_common_device_t *device, const unsigned c
return status;
}

// AI-generated (Claude)
if (device->framing == SHEARWATER_FRAMING_PERDIX3) {
// Validate the packet header: 01 FF <length:24 be> <payload>
if (n < 5 || packet[0] != 0x01 || packet[1] != 0xFF) {
ERROR (abstract->context, "Invalid packet header.");
return DC_STATUS_PROTOCOL;
}

// Validate the packet length.
unsigned int length = (packet[2] << 16) | (packet[3] << 8) | packet[4];
if (length + 5 != n || length > osize) {
ERROR (abstract->context, "Invalid packet length.");
return DC_STATUS_PROTOCOL;
}

memcpy (output, packet + 5, length);
if (actual)
*actual = length;

return DC_STATUS_SUCCESS;
}

// Validate the packet header.
if (n < 4 || packet[0] != 0x01 || packet[1] != 0xFF || packet[3] != 0x00) {
ERROR (abstract->context, "Invalid packet header.");
Expand Down Expand Up @@ -406,6 +463,21 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf
(size >> 16) & 0xFF,
(size >> 8) & 0xFF,
(size ) & 0xFF};
// The Perdix 3 uses a 16-bit size field (record type 0x24 instead of
// 0x34), so the requested size is capped at 0xFFFF. The download still
// terminates correctly, either by the exact size or by the final record
// of the compressed stream.
unsigned int size16 = (size > 0xFFFF) ? 0xFFFF : size;
unsigned char req_init_p3[] = {
0x35,
(compression ? 0x10 : 0x00),
0x24,
(address >> 24) & 0xFF,
(address >> 16) & 0xFF,
(address >> 8) & 0xFF,
(address ) & 0xFF,
(size16 >> 8) & 0xFF,
(size16 ) & 0xFF};
unsigned char req_block[] = {0x36, 0x00};
unsigned char req_quit[] = {0x37};
unsigned char response[SZ_PACKET];
Expand All @@ -424,15 +496,30 @@ shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buf
}

// Transfer the init request.
rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, 3, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// AI-generated (Claude)
if (device->framing == SHEARWATER_FRAMING_PERDIX3) {
rc = shearwater_common_transfer (device, req_init_p3, sizeof (req_init_p3), response, 4, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}

// Verify the init response.
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] > SZ_PACKET) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
// Verify the init response: 75 20 <blocksize:16 be>
if (n != 4 || response[0] != 0x75 || response[1] != 0x20 ||
array_uint16_be (response + 2) > SZ_PACKET) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
} else {
rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, 3, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}

// Verify the init response.
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] > SZ_PACKET) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
}

// Update and emit a progress event.
Expand Down Expand Up @@ -777,6 +864,11 @@ dc_status_t shearwater_common_get_model(shearwater_common_device_t *device, unsi
case 0xC0E0:
*model = TERN;
break;
case 0x39C2:
// value captured from a live Perdix 3
// (RDBI 0x8050 response) via an Android HCI snoop log.
*model = PERDIX3;
break;
default:
WARNING (device->base.context, "Unknown hardware type 0x%04x.", hardware);
}
Expand Down
11 changes: 11 additions & 0 deletions src/shearwater_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,24 @@ extern "C" {
#define PERDIX2 11
#define TERN 12
#define PEREGRINE_TX 13
#define PERDIX3 14

#define NSTEPS 10000
#define STEP(i,n) ((NSTEPS * (i) + (n) / 2) / (n))

// The Perdix 3 keeps the classic Shearwater application protocol (RDBI/WDBI,
// SLIP, block download), but changes the framing on BLE: the 2-byte
// [nframes, index] prefix on every BLE packet is gone (raw SLIP stream), and
// the transfer header uses a 24-bit big-endian payload length instead of the
// 8-bit "length + 1" field: FF 01 <len24> <payload> (requests) and
// 01 FF <len24> <payload> (responses).
#define SHEARWATER_FRAMING_CLASSIC 0
#define SHEARWATER_FRAMING_PERDIX3 1

typedef struct shearwater_common_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned int framing;
} shearwater_common_device_t;

dc_status_t
Expand Down
9 changes: 8 additions & 1 deletion src/shearwater_petrel.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ str2num (unsigned char data[], unsigned int size, unsigned int offset)


dc_status_t
shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream, unsigned int model)
{
dc_status_t status = DC_STATUS_SUCCESS;
shearwater_petrel_device_t *device = NULL;
Expand All @@ -103,6 +103,13 @@ shearwater_petrel_device_open (dc_device_t **out, dc_context_t *context, dc_iost
goto error_free;
}

// The Perdix 3 speaks the classic application protocol with a new
// framing. The framing must be selected before the first packet is
// exchanged, so it is derived from the descriptor model.
if (model == PERDIX3) {
device->base.framing = SHEARWATER_FRAMING_PERDIX3;
}

*out = (dc_device_t *) device;

return DC_STATUS_SUCCESS;
Expand Down
2 changes: 1 addition & 1 deletion src/shearwater_petrel.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extern "C" {
#endif /* __cplusplus */

dc_status_t
shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream);
shearwater_petrel_device_open (dc_device_t **device, dc_context_t *context, dc_iostream_t *iostream, unsigned int model);

dc_status_t
shearwater_petrel_parser_create (dc_parser_t **parser, dc_context_t *context, const unsigned char data[], size_t size, unsigned int model, unsigned int serial);
Expand Down