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) { diff --git a/src/oceanic_atom2.c b/src/oceanic_atom2.c index 195e4ca..c892486 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" @@ -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=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_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;