/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Encapsulates exchange protocol between the emulator, and an Android device * that is connected to the host via USB. The communication is established over * a TCP port forwarding, enabled by ADB. */ #include "qemu-common.h" #include "android/async-utils.h" #include "android/utils/debug.h" #include "android/async-socket-connector.h" #include "android/async-socket.h" #include "android/sdk-controller-socket.h" #include "utils/panic.h" #include "iolooper.h" #define E(...) derror(__VA_ARGS__) #define W(...) dwarning(__VA_ARGS__) #define D(...) VERBOSE_PRINT(sdkctlsocket,__VA_ARGS__) #define D_ACTIVE VERBOSE_CHECK(sdkctlsocket) #define TRACE_ON 1 #if TRACE_ON #define T(...) VERBOSE_PRINT(sdkctlsocket,__VA_ARGS__) #else #define T(...) #endif /* Recycling memory descriptor. */ typedef struct SDKCtlRecycled SDKCtlRecycled; struct SDKCtlRecycled { union { /* Next recycled descriptor (while listed in recycler). */ SDKCtlRecycled* next; /* Allocated memory size (while outside of the recycler). */ uint32_t size; }; }; /******************************************************************************** * SDKCtlPacket declarations *******************************************************************************/ /* * Types of the packets of data sent via SDK controller socket. */ /* The packet is a message. */ #define SDKCTL_PACKET_MESSAGE 1 /* The packet is a query. */ #define SDKCTL_PACKET_QUERY 2 /* The packet is a response to a query. */ #define SDKCTL_PACKET_QUERY_RESPONSE 3 /* Data packet descriptor. * * All packets, sent and received via SDK controller socket begin with this * header, with packet data immediately following this header. */ typedef struct SDKCtlPacketHeader { /* Total size of the data to transfer with this packet, including this * header. The transferring data should immediatelly follow this header. */ int size; /* Encodes packet type. See SDKCTL_PACKET_XXX for the list of packet types * used by SDK controller. */ int type; } SDKCtlPacketHeader; /* Packet descriptor, allocated by this API for data packets to be sent to SDK * controller service on the device. * * When packet descriptors are allocated by this API, they are allocated large * enough to contain this header, and packet data to send to the service, * immediately following this descriptor. */ struct SDKCtlPacket { /* Supports recycling. Don't put anything in front: recycler expects this * to be the first field in recyclable descriptor. */ SDKCtlRecycled recycling; /* Next packet in the list of packets to send. */ SDKCtlPacket* next; /* SDK controller socket that transmits this packet. */ SDKCtlSocket* sdkctl; /* Number of outstanding references to the packet. */ int ref_count; /* Common packet header. Packet data immediately follows this header, so it * must be last field in SDKCtlPacket descriptor. */ SDKCtlPacketHeader header; }; /******************************************************************************** * SDKCtlQuery declarations *******************************************************************************/ /* * Types of queries sent via SDK controller socket. */ /* Handshake query. * This query is sent to SDK controller service as part of the connection * protocol implementation. */ #define SDKCTL_QUERY_HANDSHAKE -1 /* Query packet descriptor. * * All queries, sent and received via SDK controller socket begin with this * header, with query data immediately following this header. */ typedef struct SDKCtlQueryHeader { /* Data packet header for this query. */ SDKCtlPacketHeader packet; /* A unique query identifier. This ID is used to track the query in the * asynchronous environment in whcih SDK controller socket operates. */ int query_id; /* Query type. See SDKCTL_QUERY_XXX for the list of query types used by SDK * controller. */ int query_type; } SDKCtlQueryHeader; /* Query descriptor, allocated by this API for queries to be sent to SDK * controller service on the device. * * When query descriptors are allocated by this API, they are allocated large * enough to contain this header, and query data to send to the service, * immediately following this descriptor. */ struct SDKCtlQuery { /* Supports recycling. Don't put anything in front: recycler expects this * to be the first field in recyclable descriptor. */ SDKCtlRecycled recycling; /* Next query in the list of active, or recycled queries. */ SDKCtlQuery* next; /* A timer to run time out on this query after it has been sent. */ LoopTimer timer[1]; /* Absolute time for this query's deadline. This is the value that query's * timer is set for after query has been transmitted to the service. */ Duration deadline; /* SDK controller socket that owns the query. */ SDKCtlSocket* sdkctl; /* A callback to invoke on query state changes. */ on_sdkctl_query_cb query_cb; /* An opaque pointer associated with this query. */ void* query_opaque; /* Points to an address of a buffer where to save query response. */ void** response_buffer; /* Points to a variable containing size of the response buffer (on the way in), * or actual query response size (when query is completed). */ uint32_t* response_size; /* Internal response buffer, allocated if query creator didn't provide its * own. This field is valid only if response_buffer field is NULL, or is * pointing to this field. */ void* internal_resp_buffer; /* Internal response buffer size This field is valid only if response_size * field is NULL, or is pointing to this field. */ uint32_t internal_resp_size; /* Number of outstanding references to the query. */ int ref_count; /* Common packet header. Query data immediately follows this header, so it * must be last field in SDKCtlQuery descriptor. */ SDKCtlQueryHeader header; }; /* Query reply descriptor. * * All replies to a query, sent and received via SDK controller socket begin with * this header, with query reply data immediately following this header. */ typedef struct SDKCtlQueryReplyHeader { /* Data packet header for this reply. */ SDKCtlPacketHeader packet; /* An identifier for the query that is addressed with this reply. */ int query_id; } SDKCtlQueryReplyHeader; /******************************************************************************** * SDK Control Socket declarations *******************************************************************************/ /* Enumerates SDKCtlSocket states. */ typedef enum SDKCtlSocketState { /* Socket is disconnected from SDK controller. */ SDKCTL_SOCKET_DISCONNECTED, /* Connection to SDK controller is in progress. */ SDKCTL_SOCKET_CONNECTING, /* Socket is connected to an SDK controller service. */ SDKCTL_SOCKET_CONNECTED } SDKCtlSocketState; /* Enumerates SDKCtlSocket I/O dispatcher states. */ typedef enum SDKCtlIODispatcherState { /* I/O dispatcher expects a packet header. */ SDKCTL_IODISP_EXPECT_HEADER, /* I/O dispatcher expects packet data. */ SDKCTL_IODISP_EXPECT_DATA, /* I/O dispatcher expects query response header. */ SDKCTL_IODISP_EXPECT_QUERY_REPLY_HEADER, /* I/O dispatcher expects query response data. */ SDKCTL_IODISP_EXPECT_QUERY_REPLY_DATA, } SDKCtlIODispatcherState; /* SDKCtlSocket I/O dispatcher descriptor. */ typedef struct SDKCtlIODispatcher { /* SDKCtlSocket instance for this dispatcher. */ SDKCtlSocket* sdkctl; /* Dispatcher state. */ SDKCtlIODispatcherState state; /* Unites all types of headers used in SDK controller data exchange. */ union { /* Common packet header. */ SDKCtlPacketHeader header; /* Header for a query packet. */ SDKCtlQueryHeader query_header; /* Header for a query response packet. */ SDKCtlQueryReplyHeader query_reply_header; }; /* Descriptor of a packet packet received from SDK controller. */ SDKCtlPacket* packet; /* A query for which a reply is currently being received. */ SDKCtlQuery* current_query; } SDKCtlIODispatcher; /* SDK controller socket descriptor. */ struct SDKCtlSocket { /* SDK controller socket state */ SDKCtlSocketState state; /* I/O dispatcher for the socket. */ SDKCtlIODispatcher io_dispatcher; /* Asynchronous socket connected to SDK Controller on the device. */ AsyncSocket* as; /* Client callback that monitors this socket connection. */ on_sdkctl_connection_cb on_connection; /* A callback to invoke when handshake message is received from the * SDK controller. */ on_sdkctl_handshake_cb on_handshake; /* A callback to invoke when a message is received from the SDK controller. */ on_sdkctl_message_cb on_message; /* An opaque pointer associated with this socket. */ void* opaque; /* Name of an SDK controller service this socket is connected to. */ char* service_name; /* I/O looper for timers. */ Looper* looper; /* Head of the active query list. */ SDKCtlQuery* query_head; /* Tail of the active query list. */ SDKCtlQuery* query_tail; /* Query ID generator that gets incremented for each new query. */ int next_query_id; /* Timeout before trying to reconnect after disconnection. */ int reconnect_to; /* Number of outstanding references to this descriptor. */ int ref_count; /* Head of the recycled memory */ SDKCtlRecycled* recycler; /* Recyclable block size. */ uint32_t recycler_block_size; /* Maximum number of blocks to recycle. */ int recycler_max; /* Number of blocs in the recycler. */ int recycler_count; }; /******************************************************************************** * SDKCtlSocket recycling management *******************************************************************************/ /* Gets a recycled block for a given SDKCtlSocket, or allocates new memory * block. */ static void* _sdkctl_socket_alloc_recycler(SDKCtlSocket* sdkctl, uint32_t size) { SDKCtlRecycled* block = NULL; if (sdkctl->recycler != NULL && size <= sdkctl->recycler_block_size) { /* There are blocks in the recycler, and requested size fits. */ block = sdkctl->recycler; sdkctl->recycler = block->next; block->size = sdkctl->recycler_block_size; sdkctl->recycler_count--; } else if (size <= sdkctl->recycler_block_size) { /* There are no blocks in the recycler, but requested size fits. */ block = malloc(sdkctl->recycler_block_size); if (block == NULL) { APANIC("SDKCtl %s: Unable to allocate %d bytes block", sdkctl->service_name, sdkctl->recycler_block_size); } block->size = sdkctl->recycler_block_size; } else { /* Requested size doesn't fit the recycler. */ block = malloc(size); if (block == NULL) { APANIC("SDKCtl %s: Unable to allocate %d bytes block", sdkctl->service_name, size); } block->size = size; } return block; } /* Recycles, or frees a block of memory for a given SDKCtlSocket. */ static void _sdkctl_socket_free_recycler(SDKCtlSocket* sdkctl, void* mem) { SDKCtlRecycled* block = (SDKCtlRecycled*)mem; if (sdkctl->recycler_count == sdkctl->recycler_max || block->size != sdkctl->recycler_block_size) { /* Recycler is full, or block cannot be recycled. */ free(mem); return; } block->next = sdkctl->recycler; sdkctl->recycler = block; sdkctl->recycler_count++; } /* Empties the recycler for a given SDKCtlSocket. */ static void _sdkctl_socket_empty_recycler(SDKCtlSocket* sdkctl) { SDKCtlRecycled* block = sdkctl->recycler; while (block != NULL) { void* to_free = block; block = block->next; free(to_free); } sdkctl->recycler = NULL; sdkctl->recycler_count = 0; } /******************************************************************************** * SDKCtlSocket query list management *******************************************************************************/ /* Adds a query to the list of active queries. * Param: * sdkctl - SDKCtlSocket instance for the query. * query - Query to add to the list. */ static void _sdkctl_socket_add_query(SDKCtlQuery* query) { SDKCtlSocket* const sdkctl = query->sdkctl; if (sdkctl->query_head == NULL) { sdkctl->query_head = sdkctl->query_tail = query; } else { sdkctl->query_tail->next = query; sdkctl->query_tail = query; } /* Keep the query referenced while it's in the list. */ sdkctl_query_reference(query); } /* Removes a query from the list of active queries. * Param: * query - Query to remove from the list of active queries. * Return: * Boolean: 1 if query has been removed, or 0 if query has not been found in the * list of active queries. */ static int _sdkctl_socket_remove_query(SDKCtlQuery* query) { SDKCtlSocket* const sdkctl = query->sdkctl; SDKCtlQuery* prev = NULL; SDKCtlQuery* head = sdkctl->query_head; /* Quick check: the query could be currently handled by dispatcher. */ if (sdkctl->io_dispatcher.current_query == query) { /* Release the query from dispatcher. */ sdkctl_query_release(query); sdkctl->io_dispatcher.current_query = NULL; return 1; } /* Remove query from the list. */ while (head != NULL && query != head) { prev = head; head = head->next; } if (head != NULL) { if (prev == NULL) { /* Query is at the head of the list. */ assert(query == sdkctl->query_head); sdkctl->query_head = query->next; } else { /* Query is in the middle / at the end of the list. */ assert(query != sdkctl->query_head); prev->next = query->next; } if (sdkctl->query_tail == query) { /* Query is at the tail of the list. */ assert(query->next == NULL); sdkctl->query_tail = prev; } query->next = NULL; /* Release query that is now removed from the list. Note that query * passed to this routine should hold an extra reference, owned by the * caller. */ sdkctl_query_release(query); return 1; } else { D("%s: Query %p is not found in the list.", sdkctl->service_name, query); return 0; } } /* Removes a query (based on query ID) from the list of active queries. * Param: * sdkctl - SDKCtlSocket instance that owns the query. * query_id - Identifies the query to remove. * Return: * A query removed from the list of active queries, or NULL if query with the * given ID has not been found in the list. */ static SDKCtlQuery* _sdkctl_socket_remove_query_id(SDKCtlSocket* sdkctl, int query_id) { SDKCtlQuery* prev = NULL; SDKCtlQuery* head = sdkctl->query_head; /* Quick check: the query could be currently handled by dispatcher. */ if (sdkctl->io_dispatcher.current_query != NULL && sdkctl->io_dispatcher.current_query->header.query_id == query_id) { /* Release the query from dispatcher. */ SDKCtlQuery* const query = sdkctl->io_dispatcher.current_query; sdkctl->io_dispatcher.current_query = NULL; return query; } /* Remove query from the list. */ while (head != NULL && head->header.query_id != query_id) { prev = head; head = head->next; } if (head != NULL) { /* Query is found in the list. */ SDKCtlQuery* const query = head; if (prev == NULL) { /* Query is at the head of the list. */ assert(query == sdkctl->query_head); sdkctl->query_head = query->next; } else { /* Query is in the middle, or at the end of the list. */ assert(query != sdkctl->query_head); prev->next = query->next; } if (sdkctl->query_tail == query) { /* Query is at the tail of the list. */ assert(query->next == NULL); sdkctl->query_tail = prev; } query->next = NULL; return query; } else { D("%s: Query ID %d is not found in the list.", sdkctl->service_name, query_id); return NULL; } } /* Pulls the first query from the list of active queries. * Param: * sdkctl - SDKCtlSocket instance that owns the query. * Return: * A query removed pulled from the list of active queries, or NULL if query * list is empty. */ static SDKCtlQuery* _sdkctl_socket_pull_first_query(SDKCtlSocket* sdkctl) { SDKCtlQuery* const query = sdkctl->query_head; if (query != NULL) { sdkctl->query_head = query->next; if (sdkctl->query_head == NULL) { sdkctl->query_tail = NULL; } } return query; } /* Generates new query ID for the given SDKCtl. */ static int _sdkctl_socket_next_query_id(SDKCtlSocket* sdkctl) { return ++sdkctl->next_query_id; } /******************************************************************************** * SDKCtlPacket implementation *******************************************************************************/ /* Alocates a packet. */ static SDKCtlPacket* _sdkctl_packet_new(SDKCtlSocket* sdkctl, int size, int type) { const uint32_t total_size = sizeof(SDKCtlPacket) + size; SDKCtlPacket* const packet = _sdkctl_socket_alloc_recycler(sdkctl, total_size); packet->sdkctl = sdkctl; packet->ref_count = 1; packet->header.size = size; packet->header.type = type; /* Refence SDKCTlSocket that owns this packet. */ sdkctl_socket_reference(sdkctl); return packet; } /* Frees a packet. */ static void _sdkctl_packet_free(SDKCtlPacket* packet) { SDKCtlSocket* const sdkctl = packet->sdkctl; /* Free allocated resources. */ _sdkctl_socket_free_recycler(packet->sdkctl, packet); /* Release SDKCTlSocket that owned this packet. */ sdkctl_socket_release(sdkctl); } int sdkctl_packet_reference(SDKCtlPacket* packet) { assert(packet->ref_count > 0); packet->ref_count++; return packet->ref_count; } int sdkctl_packet_release(SDKCtlPacket* packet) { assert(packet->ref_count > 0); packet->ref_count--; if (packet->ref_count == 0) { /* Last reference has been dropped. Destroy this object. */ _sdkctl_packet_free(packet); return 0; } return packet->ref_count; } /******************************************************************************** * SDKCtlQuery implementation *******************************************************************************/ /* Frees query descriptor. */ static void _sdkctl_query_free(SDKCtlQuery* query) { if (query != NULL) { SDKCtlSocket* const sdkctl = query->sdkctl; T("SDKCtl %s: Query %p ID %d is freed.", sdkctl->service_name, query, query->header.query_id); /* Free allocated resources. */ if (query->internal_resp_buffer != NULL && (query->response_buffer == NULL || query->response_buffer == &query->internal_resp_buffer)) { free(query->internal_resp_buffer); } loopTimer_done(query->timer); _sdkctl_socket_free_recycler(sdkctl, query); /* Release socket that owned this query. */ sdkctl_socket_release(sdkctl); } } /* Cancels timeout for the query. * * For the simplicity of implementation, the dispatcher will cancel query timer * when query response data begins to flow in. If we let the timer to expire at * that stage, we will end up with data flowing in without real place to * accomodate it. */ static void _sdkctl_query_cancel_timeout(SDKCtlQuery* query) { loopTimer_stop(query->timer); T("SDKCtl %s: Query %p ID %d deadline is cancelled.", query->sdkctl->service_name, query, query->header.query_id); } /* * Query I/O callbacks. */ /* Callback that is invoked by the I/O dispatcher when query is successfuly * completed (i.e. response to the query is received). */ static void _on_sdkctl_query_completed(SDKCtlQuery* query) { T("SDKCtl %s: Query %p ID %d is completed.", query->sdkctl->service_name, query, query->header.query_id); /* Cancel deadline, and inform the client about query completion. */ _sdkctl_query_cancel_timeout(query); query->query_cb(query->query_opaque, query, ASIO_STATE_SUCCEEDED); } /* A callback that is invoked on query cancellation. */ static void _on_sdkctl_query_cancelled(SDKCtlQuery* query) { /* * Query cancellation means that SDK controller is disconnected. In turn, * this means that SDK controller socket will handle disconnection in its * connection callback. So, at this point all we need to do here is to inform * the client, and then unlist the query. */ /* Cancel deadline, and inform the client about query cancellation. */ _sdkctl_query_cancel_timeout(query); query->query_cb(query->query_opaque, query, ASIO_STATE_CANCELLED); } /* A timer callback that is invoked on query timeout. * Param: * opaque - SDKCtlQuery instance. */ static void _on_skdctl_query_timeout(void* opaque) { SDKCtlQuery* const query = (SDKCtlQuery*)opaque; D("SDKCtl %s: Query %p ID %d with deadline %lld has timed out at %lld", query->sdkctl->service_name, query, query->header.query_id, query->deadline, async_socket_deadline(query->sdkctl->as, 0)); /* Reference the query while we're in this callback. */ sdkctl_query_reference(query); /* Inform the client about deadline expiration. Note that client may * extend the deadline, and retry the query. */ const AsyncIOAction action = query->query_cb(query->query_opaque, query, ASIO_STATE_TIMED_OUT); /* For actions other than retry we will destroy the query. */ if (action != ASIO_ACTION_RETRY) { _sdkctl_socket_remove_query(query); } sdkctl_query_release(query); } /* A callback that is invoked when query has been sent to the SDK controller * service. */ static void _on_sdkctl_query_sent(SDKCtlQuery* query) { T("SDKCtl %s: sent %d bytes of query %p ID %d of type %d", query->sdkctl->service_name, query->header.packet.size, query, query->header.query_id, query->header.query_type); /* Inform the client about the event. */ query->query_cb(query->query_opaque, query, ASIO_STATE_CONTINUES); /* Set a timer to expire at query's deadline, and let the response to come * through the dispatcher loop. */ loopTimer_startAbsolute(query->timer, query->deadline); } /* An I/O callback invoked on query transmission. * Param: * io_opaque SDKCtlQuery instance of the query that's being sent with this I/O. * asio - Write I/O descriptor. * status - I/O status. */ static AsyncIOAction _on_sdkctl_query_send_io(void* io_opaque, AsyncSocketIO* asio, AsyncIOState status) { SDKCtlQuery* const query = (SDKCtlQuery*)io_opaque; AsyncIOAction action = ASIO_ACTION_DONE; /* Reference the query while we're in this callback. */ sdkctl_query_reference(query); if (status == ASIO_STATE_SUCCEEDED) { /* Query has been sent to the service. */ _on_sdkctl_query_sent(query); sdkctl_query_release(query); return ASIO_ACTION_DONE; } /* Lets see what's going on with query transmission. */ switch (status) { case ASIO_STATE_CANCELLED: T("SDKCtl %s: Query %p ID %d is cancelled in %s I/O.", query->sdkctl->service_name, query, query->header.query_id, async_socket_io_is_read(asio) ? "READ" : "WRITE"); /* Remove the query from the list of active queries. */ _sdkctl_socket_remove_query(query); _on_sdkctl_query_cancelled(query); break; case ASIO_STATE_TIMED_OUT: D("SDKCtl %s: Query %p ID %d with deadline %lld has timed out in %s I/O at %lld", query->sdkctl->service_name, query, query->header.query_id, query->deadline, async_socket_io_is_read(asio) ? "READ" : "WRITE", async_socket_deadline(query->sdkctl->as, 0)); /* Invoke query's callback. */ action = query->query_cb(query->query_opaque, query, status); /* For actions other than retry we need to stop the query. */ if (action != ASIO_ACTION_RETRY) { _sdkctl_socket_remove_query(query); } break; case ASIO_STATE_FAILED: T("SDKCtl %s: Query %p ID %d failed in %s I/O: %d -> %s", query->sdkctl->service_name, query, query->header.query_id, async_socket_io_is_read(asio) ? "READ" : "WRITE", errno, strerror(errno)); /* Invoke query's callback. Note that we will let the client to * decide what to do on I/O failure. */ action = query->query_cb(query->query_opaque, query, status); /* For actions other than retry we need to stop the query. */ if (action != ASIO_ACTION_RETRY) { _sdkctl_socket_remove_query(query); } break; case ASIO_STATE_FINISHED: /* Time to disassociate with the I/O. */ sdkctl_query_release(query); break; default: /* Transitional state. */ break; } sdkctl_query_release(query); return action; } /******************************************************************************** * SDKCtlQuery public API implementation ********************************************************************************/ SDKCtlQuery* sdkctl_query_new(SDKCtlSocket* sdkctl, int query_type, uint32_t in_data_size) { const uint32_t total_size = sizeof(SDKCtlQuery) + in_data_size; SDKCtlQuery* const query = _sdkctl_socket_alloc_recycler(sdkctl, total_size); query->next = NULL; query->sdkctl = sdkctl; query->response_buffer = NULL; query->response_size = NULL; query->internal_resp_buffer = NULL; query->internal_resp_size = 0; query->query_cb = NULL; query->query_opaque = NULL; query->deadline = DURATION_INFINITE; query->ref_count = 1; query->header.packet.size = sizeof(SDKCtlQueryHeader) + in_data_size; query->header.packet.type = SDKCTL_PACKET_QUERY; query->header.query_id = _sdkctl_socket_next_query_id(sdkctl); query->header.query_type = query_type; /* Initialize timer to fire up on query deadline expiration. */ loopTimer_init(query->timer, sdkctl->looper, _on_skdctl_query_timeout, query); /* Reference socket that owns this query. */ sdkctl_socket_reference(sdkctl); T("SDKCtl %s: Query %p ID %d type %d is created for %d bytes of data.", query->sdkctl->service_name, query, query->header.query_id, query_type, in_data_size); return query; } SDKCtlQuery* sdkctl_query_new_ex(SDKCtlSocket* sdkctl, int query_type, uint32_t in_data_size, const void* in_data, void** response_buffer, uint32_t* response_size, on_sdkctl_query_cb query_cb, void* query_opaque) { SDKCtlQuery* const query = sdkctl_query_new(sdkctl, query_type, in_data_size); query->response_buffer = response_buffer; if (query->response_buffer == NULL) { /* Creator didn't supply a buffer. Use internal one instead. */ query->response_buffer = &query->internal_resp_buffer; query->internal_resp_buffer = NULL; } query->response_size = response_size; if (query->response_size == NULL) { /* Creator didn't supply a buffer for response size. Use internal one * instead. */ query->response_size = &query->internal_resp_size; query->internal_resp_size = 0; } query->query_cb = query_cb; query->query_opaque = query_opaque; /* Init query's input buffer. */ if (in_data_size != 0 && in_data != NULL) { memcpy(query + 1, in_data, in_data_size); } return query; } void sdkctl_query_send(SDKCtlQuery* query, int to) { SDKCtlSocket* const sdkctl = query->sdkctl; /* Initialize the deadline. */ query->deadline = async_socket_deadline(query->sdkctl->as, to); /* List the query in the list of active queries. */ _sdkctl_socket_add_query(query); /* Reference query associated with write I/O. */ sdkctl_query_reference(query); /* Transmit the query to SDK controller. */ async_socket_write_abs(sdkctl->as, &query->header, query->header.packet.size, _on_sdkctl_query_send_io, query, query->deadline); T("SDKCtl %s: Query %p ID %d type %d is sent with deadline at %lld", query->sdkctl->service_name, query, query->header.query_id, query->header.query_type, query->deadline); } SDKCtlQuery* sdkctl_query_build_and_send(SDKCtlSocket* sdkctl, int query_type, uint32_t in_data_size, const void* in_data, void** response_buffer, uint32_t* response_size, on_sdkctl_query_cb query_cb, void* query_opaque, int to) { SDKCtlQuery* const query = sdkctl_query_new_ex(sdkctl, query_type, in_data_size, in_data, response_buffer, response_size, query_cb, query_opaque); sdkctl_query_send(query, to); return query; } int sdkctl_query_reference(SDKCtlQuery* query) { assert(query->ref_count > 0); query->ref_count++; return query->ref_count; } int sdkctl_query_release(SDKCtlQuery* query) { assert(query->ref_count > 0); query->ref_count--; if (query->ref_count == 0) { /* Last reference has been dropped. Destroy this object. */ _sdkctl_query_free(query); return 0; } return query->ref_count; } /******************************************************************************** * SDKCtlPacket implementation *******************************************************************************/ /* A packet has been received from SDK controller. */ static void _on_sdkctl_packet_received(SDKCtlSocket* sdkctl, SDKCtlPacket* packet) { T("SDKCtl %s: Received packet size: %d, type: %d", sdkctl->service_name, packet->header.size, packet->header.type); /* Dispatch received packet to the client. */ sdkctl->on_message(sdkctl->opaque, sdkctl, packet, packet->header.type, packet + 1, packet->header.size - sizeof(SDKCtlPacketHeader)); } /******************************************************************************** * SDKCtlIODispatcher implementation *******************************************************************************/ /* An I/O callback invoked when data gets received from the socket. * Param: * io_opaque SDKCtlIODispatcher instance associated with the reader. * asio - Read I/O descriptor. * status - I/O status. */ static AsyncIOAction _on_sdkctl_io_dispatcher_io(void* io_opaque, AsyncSocketIO* asio, AsyncIOState status); /* Starts I/O dispatcher for SDK controller socket. */ static void _sdkctl_io_dispatcher_start(SDKCtlSocket* sdkctl) { SDKCtlIODispatcher* const dispatcher = &sdkctl->io_dispatcher; dispatcher->state = SDKCTL_IODISP_EXPECT_HEADER; dispatcher->sdkctl = sdkctl; dispatcher->packet = NULL; dispatcher->current_query = NULL; /* Register a packet header reader with the socket. */ async_socket_read_rel(dispatcher->sdkctl->as, &dispatcher->header, sizeof(SDKCtlPacketHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); } /* Resets I/O dispatcher for SDK controller socket. */ static void _sdkctl_io_dispatcher_reset(SDKCtlSocket* sdkctl) { SDKCtlIODispatcher* const dispatcher = &sdkctl->io_dispatcher; /* Cancel current query. */ if (dispatcher->current_query != NULL) { SDKCtlQuery* const query = dispatcher->current_query; dispatcher->current_query = NULL; _on_sdkctl_query_cancelled(query); sdkctl_query_release(query); } /* Free packet data buffer. */ if (dispatcher->packet != NULL) { sdkctl_packet_release(dispatcher->packet); dispatcher->packet = NULL; } /* Reset dispatcher state. */ dispatcher->state = SDKCTL_IODISP_EXPECT_HEADER; } /* * I/O dispatcher callbacks. */ /* A callback that is invoked when a failure occurred while dispatcher was * reading data from the socket. */ static void _on_io_dispatcher_io_failure(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { SDKCtlSocket* const sdkctl = dispatcher->sdkctl; D("SDKCtl %s: Dispatcher I/O failure: %d -> %s", sdkctl->service_name, errno, strerror(errno)); /* We treat all I/O failures same way we treat disconnection. Just cancel * everything, disconnect, and let the client to decide what to do next. */ sdkctl_socket_disconnect(sdkctl); /* Report disconnection to the client, and let it restore connection in this * callback. */ sdkctl->on_connection(sdkctl->opaque, sdkctl, ASIO_STATE_FAILED); } /* A callback that is invoked when dispatcher's reader has been cancelled. */ static void _on_io_dispatcher_io_cancelled(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { T("SDKCtl %s: Dispatcher I/O cancelled.", dispatcher->sdkctl->service_name); /* If we're in the middle of receiving query reply we need to cancel the * query. */ if (dispatcher->current_query != NULL) { SDKCtlQuery* const query = dispatcher->current_query; dispatcher->current_query = NULL; _on_sdkctl_query_cancelled(query); sdkctl_query_release(query); } /* Discard packet data we've received so far. */ if (dispatcher->packet != NULL) { sdkctl_packet_release(dispatcher->packet); dispatcher->packet = NULL; } } /* A generic packet header has been received by I/O dispatcher. */ static AsyncIOAction _on_io_dispatcher_packet_header(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { SDKCtlSocket* const sdkctl = dispatcher->sdkctl; T("SDKCtl %s: Packet header type %d, size %d is received.", dispatcher->sdkctl->service_name, dispatcher->header.type, dispatcher->header.size); /* Here we have three choices for the packet, that define the rest of * the data that follow it: * - Regular packet, * - Response to a query that has been sent to SDK controller, * - A query from SDK controller. * Update the state accordingly, and initiate reading of the * remaining of the packet. */ if (dispatcher->header.type == SDKCTL_PACKET_QUERY_RESPONSE) { /* This is a response to the query. Before receiving response data we * need to locate the relevant query, and use its response buffer to read * the data. For that we need to obtain query ID firts. So, initiate * reading of the remaining part of SDKCtlQueryReplyHeader. */ dispatcher->state = SDKCTL_IODISP_EXPECT_QUERY_REPLY_HEADER; async_socket_read_rel(sdkctl->as, &dispatcher->query_reply_header.query_id, sizeof(SDKCtlQueryReplyHeader) - sizeof(SDKCtlPacketHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); } else { /* For regular packets, as well as queries, we simply allocate buffer, * that fits the entire packet, and read the remainder of the data in * there. */ dispatcher->state = SDKCTL_IODISP_EXPECT_DATA; dispatcher->packet = _sdkctl_packet_new(sdkctl, dispatcher->header.size, dispatcher->header.type); /* Initiate reading of the packet data. */ async_socket_read_rel(sdkctl->as, dispatcher->packet + 1, dispatcher->header.size - sizeof(SDKCtlPacketHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); } return ASIO_ACTION_DONE; } /* A generic packet has been received by I/O dispatcher. */ static AsyncIOAction _on_io_dispatcher_packet(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { SDKCtlSocket* const sdkctl = dispatcher->sdkctl; T("SDKCtl %s: Packet type %d, size %d is received.", dispatcher->sdkctl->service_name, dispatcher->header.type, dispatcher->header.size); _on_sdkctl_packet_received(sdkctl, dispatcher->packet); sdkctl_packet_release(dispatcher->packet); dispatcher->packet = NULL; /* Get ready for the next I/O cycle. */ dispatcher->state = SDKCTL_IODISP_EXPECT_HEADER; async_socket_read_rel(sdkctl->as, &dispatcher->header, sizeof(SDKCtlPacketHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); return ASIO_ACTION_DONE; } /* A query reply header has been received by I/O dispatcher. */ static AsyncIOAction _on_io_dispatcher_query_reply_header(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { SDKCtlSocket* const sdkctl = dispatcher->sdkctl; SDKCtlQuery* query; T("SDKCtl %s: Query reply header is received for query ID %d", dispatcher->sdkctl->service_name, dispatcher->query_reply_header.query_id); /* Pull the query out of the list of active queries. It's the dispatcher that * owns this query now. */ dispatcher->current_query = _sdkctl_socket_remove_query_id(sdkctl, dispatcher->query_reply_header.query_id); query = dispatcher->current_query; if (query == NULL) { D("%s: Query #%d is not found by dispatcher", dispatcher->sdkctl->service_name, dispatcher->query_reply_header.query_id); /* Query is not found. Just read the remainder of reply up in the air, * and then discard when it's over. */ dispatcher->state = SDKCTL_IODISP_EXPECT_QUERY_REPLY_DATA; dispatcher->packet = _sdkctl_packet_new(sdkctl, dispatcher->header.size, dispatcher->header.type); /* Copy query reply info to the packet. */ memcpy(&dispatcher->packet->header, &dispatcher->query_reply_header, sizeof(SDKCtlQueryReplyHeader)); async_socket_read_rel(sdkctl->as, &dispatcher->query_header + 1, dispatcher->header.size - sizeof(SDKCtlQueryReplyHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); } else { /* Prepare to receive query reply. For the simplicity sake, cancel query * time out, so it doesn't expire on us while we're in the middle of * receiving query's reply. */ _sdkctl_query_cancel_timeout(query); /* Adjust the reply buffer set for the query (if needed). */ const uint32_t query_data_size = dispatcher->header.size - sizeof(SDKCtlQueryReplyHeader); if (*query->response_size < query_data_size) { *query->response_buffer = malloc(query_data_size); if (*query->response_buffer == NULL) { APANIC("%s: Unable to allocate %d bytes for query response", sdkctl->service_name, query_data_size); } } /* Save the actual query response size. */ *query->response_size = query_data_size; /* Start reading query response. */ dispatcher->state = SDKCTL_IODISP_EXPECT_QUERY_REPLY_DATA; async_socket_read_rel(sdkctl->as, *query->response_buffer, *query->response_size, _on_sdkctl_io_dispatcher_io, dispatcher, -1); } return ASIO_ACTION_DONE; } /* A query reply header has been received by I/O dispatcher. */ static AsyncIOAction _on_io_dispatcher_query_reply(SDKCtlIODispatcher* dispatcher, AsyncSocketIO* asio) { SDKCtlSocket* const sdkctl = dispatcher->sdkctl; SDKCtlQuery* const query = dispatcher->current_query; dispatcher->current_query = NULL; if (query != NULL) { _ANDROID_ASSERT(query->header.query_id == dispatcher->query_reply_header.query_id, "SDKCtl %s: Query ID mismatch in I/O dispatcher", sdkctl->service_name); T("SDKCtl %s: Query reply is received for query %p ID %d. Reply size is %d", dispatcher->sdkctl->service_name, query, query->header.query_id, *query->response_size); /* Complete the query, and release it from the dispatcher. */ _on_sdkctl_query_completed(query); sdkctl_query_release(query); } else { /* This was "read up in the air" for a cancelled query. Just discard the * read data. */ if (dispatcher->packet != NULL) { sdkctl_packet_release(dispatcher->packet); dispatcher->packet = NULL; } } /* Get ready for the next I/O cycle. */ dispatcher->state = SDKCTL_IODISP_EXPECT_HEADER; async_socket_read_rel(sdkctl->as, &dispatcher->header, sizeof(SDKCtlPacketHeader), _on_sdkctl_io_dispatcher_io, dispatcher, -1); return ASIO_ACTION_DONE; } /* An I/O callback invoked when data gets received from the socket. * This is main I/O dispatcher loop. * Param: * io_opaque SDKCtlIODispatcher instance associated with the reader. * asio - Read I/O descriptor. * status - I/O status. */ static AsyncIOAction _on_sdkctl_io_dispatcher_io(void* io_opaque, AsyncSocketIO* asio, AsyncIOState status) { AsyncIOAction action = ASIO_ACTION_DONE; SDKCtlIODispatcher* const dispatcher = (SDKCtlIODispatcher*)io_opaque; SDKCtlSocket* const sdkctl = dispatcher->sdkctl; /* Reference SDKCtlSocket while we're in this callback. */ sdkctl_socket_reference(sdkctl); if (status != ASIO_STATE_SUCCEEDED) { /* Something going on with I/O other than receiving data.. */ switch (status) { case ASIO_STATE_STARTED: /* Data has started flowing in. Cancel timeout on I/O that has * started, so we can complete the current state of the * dispatcher without interruptions other than I/O failures. */ async_socket_io_cancel_time_out(asio); break; case ASIO_STATE_FAILED: /* I/O failure has occurred. Handle the failure. */ _on_io_dispatcher_io_failure(dispatcher, asio); break; case ASIO_STATE_TIMED_OUT: /* The way I/O dispatcher is implemented, this should never * happen, because dispatcher doesn't set I/O expiration time * when registering its readers. */ _ANDROID_ASSERT(0, "SDKCtl %s: We should never receive ASIO_STATE_TIMED_OUT in SDKCtl I/O dispatcher.", sdkctl->service_name); break; case ASIO_STATE_CANCELLED: /* Cancellation means that we're in the middle of handling * disconnection. Sooner or later, this dispatcher will be reset, * so we don't really care about keeping its state at this point. */ _on_io_dispatcher_io_cancelled(dispatcher, asio); break; case ASIO_STATE_FINISHED: break; default: _ANDROID_ASSERT(0, "SDKCtl %s: Unexpected I/O status %d in the dispatcher", sdkctl->service_name, status); /* Handle this as protocol failure. */ errno = EINVAL; _on_io_dispatcher_io_failure(dispatcher, asio); action = ASIO_ACTION_ABORT; break; } sdkctl_socket_release(sdkctl); return action; } /* Requested data has been read. Handle the chunk depending on dispatcher's * state. */ switch (dispatcher->state) { case SDKCTL_IODISP_EXPECT_HEADER: /* A generic packet header is received. */ action = _on_io_dispatcher_packet_header(dispatcher, asio); break; case SDKCTL_IODISP_EXPECT_QUERY_REPLY_HEADER: /* Query reply header is received. */ action = _on_io_dispatcher_query_reply_header(dispatcher, asio); break; case SDKCTL_IODISP_EXPECT_QUERY_REPLY_DATA: /* Query reply is received. Complete the query. */ action = _on_io_dispatcher_query_reply(dispatcher, asio); break; case SDKCTL_IODISP_EXPECT_DATA: /* A generic packet is received. */ action = _on_io_dispatcher_packet(dispatcher, asio); break; default: _ANDROID_ASSERT(0, "SDKCtl %s: Unexpected I/O dispacher state %d", sdkctl->service_name, dispatcher->state); break; } sdkctl_socket_release(sdkctl); return action; } /******************************************************************************** * SDKCtlSocket internals. *******************************************************************************/ /* Cancels all queries that is active on this socket. */ static void _sdkctl_socket_cancel_all_queries(SDKCtlSocket* sdkctl) { SDKCtlIODispatcher* const dispatcher = &sdkctl->io_dispatcher; SDKCtlQuery* query; /* Cancel query that is being completed in dispatcher. */ if (dispatcher->current_query != NULL) { SDKCtlQuery* const query = dispatcher->current_query; dispatcher->current_query = NULL; _on_sdkctl_query_cancelled(query); sdkctl_query_release(query); } /* One by one empty query list cancelling pulled queries. */ query = _sdkctl_socket_pull_first_query(sdkctl); while (query != NULL) { _sdkctl_query_cancel_timeout(query); query->query_cb(query->query_opaque, query, ASIO_STATE_CANCELLED); sdkctl_query_release(query); query = _sdkctl_socket_pull_first_query(sdkctl); } } /* Cancels all packets that is active on this socket. */ static void _sdkctl_socket_cancel_all_packets(SDKCtlSocket* sdkctl) { } /* Cancels all I/O that is active on this socket. */ static void _sdkctl_socket_cancel_all_io(SDKCtlSocket* sdkctl) { /* Cancel all queries, and packets that are active for this I/O. */ _sdkctl_socket_cancel_all_queries(sdkctl); _sdkctl_socket_cancel_all_packets(sdkctl); } /* Disconnects AsyncSocket for SDKCtlSocket. */ static void _sdkctl_socket_disconnect_socket(SDKCtlSocket* sdkctl) { if (sdkctl->as != NULL) { /* Disconnect the socket. This will trigger I/O cancellation callbacks. */ async_socket_disconnect(sdkctl->as); /* Cancel all I/O that is active on this socket. */ _sdkctl_socket_cancel_all_io(sdkctl); /* Reset I/O dispatcher. */ _sdkctl_io_dispatcher_reset(sdkctl); } sdkctl->state = SDKCTL_SOCKET_DISCONNECTED; } /* Frees SDKCtlSocket instance. */ static void _sdkctl_socket_free(SDKCtlSocket* sdkctl) { if (sdkctl != NULL) { /* Disconnect, and release the socket. */ if (sdkctl->as != NULL) { async_socket_disconnect(sdkctl->as); async_socket_release(sdkctl->as); } /* Free allocated resources. */ if (sdkctl->looper != NULL) { looper_free(sdkctl->looper); } if (sdkctl->service_name != NULL) { free(sdkctl->service_name); } _sdkctl_socket_empty_recycler(sdkctl); AFREE(sdkctl); } } /******************************************************************************** * SDK Control Socket connection callbacks. *******************************************************************************/ /* Initiates handshake query when SDK controller socket is connected. */ static void _sdkctl_do_handshake(SDKCtlSocket* sdkctl); /* A socket connection is established. * Here we will start I/O dispatcher, and will initiate a handshake with * the SdkController service for this socket. */ static AsyncIOAction _on_async_socket_connected(SDKCtlSocket* sdkctl) { D("SDKCtl %s: Socket is connected.", sdkctl->service_name); /* Notify the client that connection is established. */ const AsyncIOAction action = sdkctl->on_connection(sdkctl->opaque, sdkctl, ASIO_STATE_SUCCEEDED); if (action == ASIO_ACTION_DONE) { /* Initialize, and start main I/O dispatcher. */ _sdkctl_io_dispatcher_start(sdkctl); /* Initiate handshake. */ _sdkctl_do_handshake(sdkctl); return action; } else { /* Client didn't like something about this connection. */ return action; } } /* Handles lost connection with SdkController service. */ static AsyncIOAction _on_async_socket_disconnected(SDKCtlSocket* sdkctl) { D("SDKCtl %s: Socket has been disconnected.", sdkctl->service_name); _sdkctl_socket_disconnect_socket(sdkctl); AsyncIOAction action = sdkctl->on_connection(sdkctl->opaque, sdkctl, ASIO_STATE_FAILED); if (action == ASIO_ACTION_DONE) { /* Default action for disconnect is to reestablish the connection. */ action = ASIO_ACTION_RETRY; } if (action == ASIO_ACTION_RETRY) { sdkctl->state = SDKCTL_SOCKET_CONNECTING; } return action; } /* An entry point for all socket connection events. * Here we will dispatch connection events to appropriate handlers. * Param: * client_opaque - SDKCtlSocket isntance. */ static AsyncIOAction _on_async_socket_connection(void* client_opaque, AsyncSocket* as, AsyncIOState status) { AsyncIOAction action = ASIO_ACTION_DONE; SDKCtlSocket* const sdkctl = (SDKCtlSocket*)client_opaque; /* Reference the socket while in this callback. */ sdkctl_socket_reference(sdkctl); switch (status) { case ASIO_STATE_SUCCEEDED: sdkctl->state = SDKCTL_SOCKET_CONNECTED; _on_async_socket_connected(sdkctl); break; case ASIO_STATE_FAILED: if (sdkctl->state == SDKCTL_SOCKET_CONNECTED) { /* This is disconnection condition. */ action = _on_async_socket_disconnected(sdkctl); } else { /* An error has occurred while attempting to connect to socket. * Lets try again... */ action = ASIO_ACTION_RETRY; } break; case ASIO_STATE_RETRYING: default: action = ASIO_ACTION_RETRY; break; } sdkctl_socket_release(sdkctl); return action; } /******************************************************************************** * SDK Control Socket public API *******************************************************************************/ SDKCtlSocket* sdkctl_socket_new(int reconnect_to, const char* service_name, on_sdkctl_connection_cb on_connection, on_sdkctl_handshake_cb on_handshake, on_sdkctl_message_cb on_message, void* opaque) { SDKCtlSocket* sdkctl; ANEW0(sdkctl); sdkctl->state = SDKCTL_SOCKET_DISCONNECTED; sdkctl->opaque = opaque; sdkctl->service_name = ASTRDUP(service_name); sdkctl->on_connection = on_connection; sdkctl->on_handshake = on_handshake; sdkctl->on_message = on_message; sdkctl->reconnect_to = reconnect_to; sdkctl->as = NULL; sdkctl->next_query_id = 0; sdkctl->query_head = sdkctl->query_tail = NULL; sdkctl->ref_count = 1; sdkctl->recycler = NULL; sdkctl->recycler_block_size = 0; sdkctl->recycler_max = 0; sdkctl->recycler_count = 0; sdkctl->looper = looper_newCore(); if (sdkctl->looper == NULL) { E("Unable to create I/O looper for SDKCtl socket '%s'", service_name); on_connection(opaque, sdkctl, ASIO_STATE_FAILED); _sdkctl_socket_free(sdkctl); return NULL; } return sdkctl; } int sdkctl_socket_reference(SDKCtlSocket* sdkctl) { assert(sdkctl->ref_count > 0); sdkctl->ref_count++; return sdkctl->ref_count; } int sdkctl_socket_release(SDKCtlSocket* sdkctl) { assert(sdkctl->ref_count > 0); sdkctl->ref_count--; if (sdkctl->ref_count == 0) { /* Last reference has been dropped. Destroy this object. */ _sdkctl_socket_free(sdkctl); return 0; } return sdkctl->ref_count; } void sdkctl_init_recycler(SDKCtlSocket* sdkctl, uint32_t data_size, int max_recycled_num) { if (sdkctl->recycler != NULL) { D("SDKCtl %s: Recycler is already initialized. Ignoring recycler init.", sdkctl->service_name); return; } /* SDKCtlQuery is max descriptor sizeof. */ data_size += sizeof(SDKCtlQuery); sdkctl->recycler_block_size = data_size; sdkctl->recycler_max = max_recycled_num; sdkctl->recycler_count = 0; } void sdkctl_socket_connect(SDKCtlSocket* sdkctl, int port, int retry_to) { T("SDKCtl %s: Handling connect request to port %d, retrying in %dms...", sdkctl->service_name, port, retry_to); sdkctl->state = SDKCTL_SOCKET_CONNECTING; sdkctl->as = async_socket_new(port, sdkctl->reconnect_to, _on_async_socket_connection, sdkctl, sdkctl->looper); if (sdkctl->as == NULL) { E("Unable to allocate AsyncSocket for SDKCtl socket '%s'", sdkctl->service_name); sdkctl->on_connection(sdkctl->opaque, sdkctl, ASIO_STATE_FAILED); } else { async_socket_connect(sdkctl->as, retry_to); } } void sdkctl_socket_reconnect(SDKCtlSocket* sdkctl, int port, int retry_to) { T("SDKCtl %s: Handling reconnection request to port %d, retrying in %dms...", sdkctl->service_name, port, retry_to); _sdkctl_socket_disconnect_socket(sdkctl); if (sdkctl->as == NULL) { sdkctl_socket_connect(sdkctl, port, retry_to); } else { sdkctl->state = SDKCTL_SOCKET_CONNECTING; async_socket_reconnect(sdkctl->as, retry_to); } } void sdkctl_socket_disconnect(SDKCtlSocket* sdkctl) { T("SDKCtl %s: Handling disconnect request.", sdkctl->service_name); _sdkctl_socket_disconnect_socket(sdkctl); } /******************************************************************************** * Handshake query *******************************************************************************/ /* A callback that is ivoked on handshake I/O events. */ static AsyncIOAction _on_handshake_io(void* query_opaque, SDKCtlQuery* query, AsyncIOState status) { SDKCtlSocket* const sdkctl = (SDKCtlSocket*)query_opaque; if (status == ASIO_STATE_SUCCEEDED) { D("SDKCtl %s: %d bytes of handshake reply is received.", sdkctl->service_name, *query->response_size); /* Handshake is received. Inform the client. */ sdkctl->on_handshake(sdkctl->opaque, sdkctl, *query->response_buffer, *query->response_size, status); } else { /* Something is going on with the handshake... */ switch (status) { case ASIO_STATE_FAILED: case ASIO_STATE_TIMED_OUT: case ASIO_STATE_CANCELLED: D("SDKCtl %s: Handshake failed: I/O state %d. Error: %d -> %s", sdkctl->service_name, status, errno, strerror(errno)); sdkctl->on_handshake(sdkctl->opaque, sdkctl, *query->response_buffer, *query->response_size, status); break; default: break; } } return ASIO_ACTION_DONE; } static AsyncIOAction _on_sdkctl_endianness_io(void* io_opaque, AsyncSocketIO* asio, AsyncIOState status) { SDKCtlSocket* const sdkctl = (SDKCtlSocket*)io_opaque; if (status == ASIO_STATE_SUCCEEDED) { /* Now it's time to initiate handshake message. */ D("SDKCtl %s: Sending handshake query...", sdkctl->service_name); SDKCtlQuery* query = sdkctl_query_build_and_send(sdkctl, SDKCTL_QUERY_HANDSHAKE, strlen(sdkctl->service_name), sdkctl->service_name, NULL, NULL, _on_handshake_io, sdkctl, 3000); sdkctl_query_release(query); return ASIO_ACTION_DONE; } else { /* Something is going on with the endianness... */ switch (status) { case ASIO_STATE_FAILED: case ASIO_STATE_TIMED_OUT: case ASIO_STATE_CANCELLED: D("SDKCtl %s: endianness failed: I/O state %d. Error: %d -> %s", sdkctl->service_name, status, errno, strerror(errno)); sdkctl->on_handshake(sdkctl->opaque, sdkctl, NULL, 0, status); break; default: break; } } return ASIO_ACTION_DONE; } static void _sdkctl_do_handshake(SDKCtlSocket* sdkctl) { #ifndef HOST_WORDS_BIGENDIAN static const char _host_end = 0; #else static const char _host_end = 1; #endif D("SDKCtl %s: Sending endianness: %d...", sdkctl->service_name, _host_end); /* Before we can send any structured data to the SDK controller we need to * report endianness of the host. */ async_socket_write_rel(sdkctl->as, &_host_end, 1, _on_sdkctl_endianness_io, sdkctl, 3000); }