Merge branch 'oceanic-ble'

This commit is contained in:
Jef Driesen 2020-01-06 21:30:32 +01:00
commit 4fe1b96689
2 changed files with 281 additions and 58 deletions

View File

@ -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)
{

View File

@ -22,6 +22,8 @@
#include <string.h> // memcpy
#include <stdlib.h> // malloc, free
#include <libdivecomputer/ble.h>
#include "oceanic_atom2.h"
#include "oceanic_common.h"
#include "context-private.h"
@ -29,6 +31,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)
@ -38,12 +41,14 @@
#define I770R 0x4651
#define GEO40 0x4653
#define MAXPACKET 256
#define MAXRETRIES 2
#define MAXDELAY 16
#define INVALID 0xFFFFFFFF
#define CMD_INIT 0xA8
#define CMD_VERSION 0x84
#define CMD_HANDSHAKE 0xE5
#define CMD_READ1 0xB1
#define CMD_READ8 0xB4
#define CMD_READ16 0xB8
@ -58,6 +63,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];
@ -201,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[] = {
@ -533,11 +539,144 @@ 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 1 c s s s s s>
* 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: <cmd seq>
* starts at 0 for the connection, incremented for each command
* byte 3: <length of data>
* 1-16 bytes of data per packet.
* byte 4..n: <data>
*/
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_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;
}
if (crc_size > 2 || (crc_size != 0 && asize == 0)) {
return DC_STATUS_INVALIDARGS;
}
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
@ -547,61 +686,60 @@ 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;
}
// Get the correct ACK byte.
unsigned int ack = ACK;
if (command[0] == CMD_INIT || command[0] == CMD_QUIT) {
ack = NAK;
// Receive the answer of the dive computer.
unsigned char packet[1 + MAXPACKET + 2];
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);
}
// Receive the response (ACK/NAK) of the dive computer.
unsigned char response = 0;
status = dc_iostream_read (device->iostream, &response, 1, 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);
}
device->sequence++;
return DC_STATUS_SUCCESS;
}
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 +749,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;
@ -631,20 +769,66 @@ 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_quit (oceanic_atom2_device_t *device)
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.
unsigned char command[4] = {CMD_QUIT, 0x05, 0xA5, 0x00};
dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0);
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)
{
@ -667,6 +851,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;
@ -734,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;
@ -827,7 +1019,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};
rc = oceanic_atom2_transfer (device, command, sizeof (command), NAK, NULL, 0, 0);
if (rc != DC_STATUS_SUCCESS) {
dc_status_set_error(&status, rc);
}
@ -845,8 +1038,8 @@ 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};
dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), NULL, 0, 0);
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;
@ -865,14 +1058,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[2] = {CMD_VERSION, 0x00};
dc_status_t rc = oceanic_atom2_transfer (device, command, sizeof (command), answer, sizeof (answer), 1);
unsigned char command[] = {CMD_VERSION};
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;
}
@ -930,17 +1120,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[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), 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;
}
@ -978,19 +1166,19 @@ 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), 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;
// 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), NULL, 0, 0);
rc = oceanic_atom2_transfer (device, command, sizeof (command), ACK, NULL, 0, 0);
if (rc != DC_STATUS_SUCCESS)
return rc;