/* * Copyright 2009, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // This file contains the definition of the MessageQueue, the class that handles // the communication of external code (clients) with O3D (server) via the // NativeClient IMC library. #if defined(OS_MACOSX) | defined(OS_LINUX) #include #include #endif #include "core/cross/message_queue.h" #include "core/cross/client_info.h" #include "core/cross/object_manager.h" #include "core/cross/bitmap.h" #include "core/cross/texture.h" #include "core/cross/error.h" #include "core/cross/pointer_utils.h" #include "core/cross/renderer.h" #ifdef OS_WIN #include "core/cross/core_metrics.h" #endif namespace o3d { int MessageQueue::next_message_queue_id_ = 0; // Prefix used to name all server socket addresses for O3D. const char kServerSocketAddressPrefix[] = "o3d"; // Writes any nacl IMC errors to the log with a descriptive message. // NOTE: macros used to make sure the LOG calls note the // correct line number and source file. #define LOG_IMC_ERROR(message) { \ char buffer[256]; \ if (nacl::GetLastErrorString(buffer, sizeof(buffer)) == 0) { \ LOG(ERROR) << message << " : " << buffer; \ } else { \ LOG(ERROR) << message; \ } \ } ConnectedClient::ConnectedClient(nacl::Handle handle) : client_handle_(handle) { } ConnectedClient::~ConnectedClient() { std::vector::const_iterator iter; // Unmap and close shared memory. for (iter = shared_memory_array_.begin(); iter < shared_memory_array_.end(); ++iter) { nacl::Unmap(iter->mapped_address, iter->size); nacl::Close(iter->shared_memory_handle); } } // Registers a newly created shared memory buffer with the // ConnectedClient by adding the buffer info into the // shared_memory_array_. void ConnectedClient::RegisterSharedMemory(int32 buffer_id, nacl::Handle handle, void *address, int32 size) { SharedMemoryInfo shared_mem; shared_mem.buffer_id = buffer_id; shared_mem.shared_memory_handle = handle; shared_mem.mapped_address = address; shared_mem.size = size; shared_memory_array_.push_back(shared_mem); } // Unregisters a shared memory buffer for a given client-allocated // memory region, unmapping and closing it in the process. bool ConnectedClient::UnregisterSharedMemory(int32 buffer_id) { std::vector::iterator iter; for (iter = shared_memory_array_.begin(); iter < shared_memory_array_.end(); ++iter) { if (iter->buffer_id == buffer_id) { nacl::Unmap(iter->mapped_address, iter->size); nacl::Close(iter->shared_memory_handle); shared_memory_array_.erase(iter); return true; } } return false; } // Returns the SharedMemoryInfo corresponding to the given shared // memory buffer id. The buffer must first be created by the // MessageQueue on behalf of this ConnectedClient. const SharedMemoryInfo* ConnectedClient::GetSharedMemoryInfo(int32 id) const { std::vector::const_iterator iter; for (iter = shared_memory_array_.begin(); iter < shared_memory_array_.end(); ++iter) { if (iter->buffer_id == id) { return &(*iter); } } return NULL; } MessageQueue::MessageQueue(ServiceLocator* service_locator) : service_locator_(service_locator), object_manager_(service_locator->GetService()), server_socket_handle_(nacl::kInvalidHandle), next_shared_memory_id_(0) { #if defined(OS_WIN) DWORD proc_id = GetCurrentProcessId(); #endif #if defined(OS_MACOSX) | defined(OS_LINUX) pid_t proc_id = getpid(); #endif // Create a unique name for the socket used by the message queue. // We use part of the process id to distinguish between different // browsers running o3d at the same time as well as a count to // distinguish between multiple instances of o3d running in the same // browser. ::base::snprintf(server_socket_address_.path, sizeof(server_socket_address_.path), "%s%d%d", kServerSocketAddressPrefix, (proc_id & 0xFFFF), next_message_queue_id_); next_message_queue_id_++; } MessageQueue::~MessageQueue() { // Clean up the ConnectedClient array. std::vector::const_iterator iter; for (iter = connected_clients_.begin(); iter != connected_clients_.end(); ++iter) { nacl::Close((*iter)->client_handle()); delete *iter; } // Close the socket. nacl::Close(server_socket_handle_); } // Creates a bound socket that corresponds to the communcation channel // for this Client. bool MessageQueue::Initialize() { server_socket_handle_ = nacl::BoundSocket(&server_socket_address_); if (server_socket_handle_ == nacl::kInvalidHandle) { LOG_IMC_ERROR("Failed to create a bound socket for the MessageQueue"); return false; } return true; } String MessageQueue::GetSocketAddress() const { return &server_socket_address_.path[0]; } // Checks the message queue for an incoming message. If one is found // then it processes it, otherwise it just returns. bool MessageQueue::CheckForNewMessages() { // NOTE: This code uses reasonable defaults for the max // sizes of the received messages. If a message uses more memory or // transmits more data handles then it will appear as truncated. If // we find that there are valid messages with size larger than // what's defined here we should adjust the constants accordingly. const int kBufferLength = 1024; // max 1K of memory transfered per message // max handles transfered per message const int kMaxNumHandles = nacl::kHandleCountMax; char message_buffer[kBufferLength]; nacl::Handle handles[kMaxNumHandles]; // All received messages are read as containing a single buffer for data // a number of handles. nacl::IOVec io_vec[1]; io_vec[0].base = message_buffer; io_vec[0].length = kBufferLength; nacl::MessageHeader header; header.iov = io_vec; header.iov_length = 1; header.handles = handles; header.handle_count = kMaxNumHandles; header.flags = 0; // First check for a message in the server socket. The only // messages that we should be receiving here are the HELLO messages // received from clients that want to connect. Note that // ReceiveMessageFromSocket also returns true if there are no // messages in the queue in which case message_length will be equal // to -1. imc::MessageId message_id; int message_length = 0; if (ReceiveMessageFromSocket(server_socket_handle_, &header, &message_id, &message_length)) { if (message_id == imc::HELLO) { ProcessHelloMessage(&header, handles); #ifdef OS_WIN metric_imc_hello_msg.Set(true); #endif } else if (message_length != -1) { DLOG(INFO) << "Received a non-HELLO message from server queue"; } } // Check all the sockets of the connected clients to see if they contain any // messages. std::vector::iterator iter; for (iter = connected_clients_.begin(); iter < connected_clients_.end();) { // Must reset the available buffer length and number of handles each time // so NaCl's IMC knows how much space is available for reading io_vec[0].length = kBufferLength; header.handle_count = kMaxNumHandles; if (ReceiveMessageFromSocket((*iter)->client_handle(), &header, &message_id, &message_length)) { if (message_length == 0) { // Message length of 0 means EOF (i.e., client closed its handle). nacl::Close((*iter)->client_handle()); delete *iter; iter = connected_clients_.erase(iter); // Advances the iterator too. continue; } if (message_length != -1) { // Else no message waiting ProcessClientRequest(*iter, message_length, message_id, &header, handles); } } ++iter; } return true; } // Checks the socket for messages. If none are found then it returns // right away. If a message is found, it checks to make sure that the // first 4 bytes contain a valid message ID. If they do, then it // return the ID in message_id. bool MessageQueue::ReceiveMessageFromSocket(nacl::Handle socket, nacl::MessageHeader* header, imc::MessageId* message_id, int* length) { *message_id = imc::INVALID_ID; // Check if there's a new message but don't block waiting for it. int message_length = nacl::ReceiveDatagram(socket, header, nacl::kDontWait); // If result==-1 then either there are no messages in the queue in // which case we can just return, or the message read failed in // which case we need to log the failure. if (message_length == -1) { if (nacl::WouldBlock()) { *length = message_length; return true; #if defined(OS_WIN) } else if (GetLastError() == ERROR_BROKEN_PIPE) { // On Windows, the NACL library treats EOF as a failure with this failure // code. We convert it to the traditional format of a successful read that // returns zero bytes to match the Mac & Linux case below. *length = 0; return true; #endif } else { LOG_IMC_ERROR("nacl::ReceiveMessage failed"); return false; } } #if defined(OS_MACOSX) | defined(OS_LINUX) if (message_length == 0) { // EOF *length = 0; return true; } #endif // Valid messages must always contain at least the ID of the message if (message_length >= static_cast(sizeof(*message_id))) { // Check if the incoming message requires more space than we have // currently allocated. if (header->flags & nacl::kMessageTruncated) { LOG(ERROR) << "Incoming message was truncated"; return false; } // Extract the ID of the message just received. imc::MessageId id_found = *(reinterpret_cast(header->iov[0].base)); if (id_found <= imc::INVALID_ID || id_found >= imc::MAX_NUM_IDS) { LOG(ERROR) << "Unknown ID found in message :" << id_found; } *message_id = id_found; *length = message_length; return true; } else { LOG(ERROR) << "Incoming message too short (length:" << message_length << ")"; return false; } } bool MessageQueue::ProcessClientRequest(ConnectedClient* client, int message_length, imc::MessageId message_id, nacl::MessageHeader* header, nacl::Handle* handles) { static int expected_message_lengths[] = { #define O3D_IMC_MESSAGE_OP(id, class_name) sizeof(class_name::Msg), O3D_IMC_MESSAGE_LIST(O3D_IMC_MESSAGE_OP) #undef O3D_IMC_MESSAGE_OP }; if (message_id == imc::INVALID_ID || static_cast(message_id) >= arraysize(expected_message_lengths)) { LOG(ERROR) << "Unrecognized message id " << message_id; return false; } if (message_length != expected_message_lengths[message_id]) { LOG(ERROR) << "Bad message length for " << imc::GetMessageDescription(message_id); return false; } switch (message_id) { #define O3D_IMC_MESSAGE_OP(id, class_name) \ case imc::id: return Process ## class_name( \ client, message_length, header, handles, \ *static_cast(header->iov[0].base)); O3D_IMC_MESSAGE_LIST(O3D_IMC_MESSAGE_OP) #undef O3D_IMC_MESSAGE_OP default: return false; } return true; } bool MessageQueue::SendBooleanResponse(nacl::Handle client_handle, bool value) { int response = (value ? 1 : 0); nacl::IOVec vec; vec.base = &response; vec.length = sizeof(response); nacl::MessageHeader header; header.iov = &vec; header.iov_length = 1; header.handles = NULL; header.handle_count = 0; int result = nacl::SendDatagram(client_handle, &header, 0); if (result != sizeof(response)) { LOG_IMC_ERROR("Failed to send boolean response to client handle"); return false; } return true; } // Processes a HELLO message received from a client. If everything goes well // it adds the client to the ConnectedClient list and sends back a positive // response. bool MessageQueue::ProcessHelloMessage(nacl::MessageHeader *header, nacl::Handle *handles) { // HELLO is the first message that should be send by a client. It should // contain a single handle corresponding to the client's socket. if (header->handle_count == 1) { nacl::Handle client_handle = header->handles[0]; // Make sure the handle is not already being used (i.e. only allow // a single HELLO message from a client) // TODO : please check correctness of this line std::vector::const_iterator find_iter = find( connected_clients_.begin(), connected_clients_.end(), reinterpret_cast(client_handle)); if (find_iter != connected_clients_.end()) { LOG(WARNING) << "Received HELLO from client that's already connected"; // Tell the client that the handshake failed. SendBooleanResponse(client_handle, false); return true; } // Send an acknowledgement back to the client that the handshake succeeded. if (!SendBooleanResponse(client_handle, true)) return false; // TODO Is there any way to verify that the handle we got // passed here actually corresponds to the socket handle of the client? ConnectedClient* new_client = new ConnectedClient(client_handle); // Add the new client to the list. connected_clients_.push_back(new_client); return true; } return false; } // All emums need a Process function. bool MessageQueue::ProcessMessageInvalidId( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageInvalidId::Msg& message) { return false; } bool MessageQueue::ProcessMessageHello( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageHello::Msg& message) { // Hello is handled special. return false; } // Processes a request to allocate a shared memory buffer on behalf of a // connected client. Parses the arguments of the message to determine how // much space is requested, it creates the shared memory buffer, maps it in // the local address space and sends a message back to the client with the // newly created memory handle. bool MessageQueue::ProcessMessageAllocateSharedMemory( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageAllocateSharedMemory::Msg& message) { if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for ALLOCATE_SHARED_MEMORY"; return false; } int32 mem_size = message.mem_size; if (mem_size <= 0 || mem_size > MessageAllocateSharedMemory::Msg::kMaxSharedMemSize) { LOG(ERROR) << "Invalid mem size requested: " << mem_size << "(max size = " << MessageAllocateSharedMemory::Msg::kMaxSharedMemSize << ")"; return false; } // Create the shared memory object. nacl::Handle shared_memory = nacl::CreateMemoryObject(mem_size); if (shared_memory == nacl::kInvalidHandle) { LOG_IMC_ERROR("Failed to create shared memory object"); return false; } // Map it in local address space. void* shared_region = nacl::Map(0, mem_size, nacl::kProtRead | nacl::kProtWrite, nacl::kMapShared, shared_memory, 0); if (shared_region == nacl::kMapFailed) { LOG_IMC_ERROR("Failed to map shared memory"); nacl::Close(shared_memory); return false; } // Create a unique id for the shared memory buffer. MessageAllocateSharedMemory::Response response(next_shared_memory_id_++); // Send the shared memory handle and the buffer id back to the client. nacl::MessageHeader response_header; nacl::IOVec id_vec; id_vec.base = &response.data; id_vec.length = sizeof(response.data); response_header.iov = &id_vec; response_header.iov_length = 1; response_header.handles = &shared_memory; response_header.handle_count = 1; int result = nacl::SendDatagram(client->client_handle(), &response_header, 0); if (result != sizeof(response.data)) { LOG_IMC_ERROR("Failed to send shared memory handle back to the client"); nacl::Unmap(shared_region, mem_size); nacl::Close(shared_memory); return false; } // Register the newly created shared memory with the connected client. client->RegisterSharedMemory(response.data.buffer_id, shared_memory, shared_region, mem_size); return true; } // Processes a request by a client to update the contents of a Texture object // bitmap using data stored in a shared memory region. The client sends the // id of the shared memory region, an offset in that region, the id of the // Texture object, the level to be modified and the number of bytes to copy. // This is essentially asynchronous as the client will not receive a response // back from the server bool MessageQueue::ProcessMessageUpdateTexture2D( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageUpdateTexture2D::Msg& message) { // Check the length of the message to make sure it contains the size of // the requested buffer. if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for UPDATE_TEXTURE2D"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that this client did actually allocate the shared memory // corresponding to this handle. const SharedMemoryInfo* info = client->GetSharedMemoryInfo(message.shared_memory_id); if (info == NULL) { O3D_ERROR(service_locator_) << "shared memory id " << message.shared_memory_id << " not found"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that the Id passed in actually corresponds to a texture. Texture2D* texture_object = object_manager_->GetById(message.texture_id); if (texture_object == NULL) { O3D_ERROR(service_locator_) << "Texture with id " << message.texture_id << " not found"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that we will not be reading past the end of the allocated shared // memory. if (message.offset + message.number_of_bytes > info->size || message.offset + message.number_of_bytes < message.offset) { O3D_ERROR(service_locator_) << "Offset + texture size exceeds allocated shared memory size (" << message.offset << " + " << message.number_of_bytes << " > " << info->size; SendBooleanResponse(client->client_handle(), false); return false; } unsigned int mip_width = image::ComputeMipDimension(message.level, texture_object->width()); void *target_address = PointerFromVoidPointer(info->mapped_address, message.offset); int pitch = image::ComputePitch(texture_object->format(), mip_width); int rows = message.number_of_bytes / pitch; texture_object->SetRect( message.level, 0, 0, mip_width, rows, target_address, pitch); int remain = message.number_of_bytes % pitch; if (remain) { int width = remain / image::ComputePitch(texture_object->format(), 1); texture_object->SetRect( message.level, 0, rows, width, 1, AddPointerOffset(target_address, rows * pitch), pitch); } SendBooleanResponse(client->client_handle(), true); return true; } bool MessageQueue::ProcessMessageUpdateTexture2DRect( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageUpdateTexture2DRect::Msg& message) { // Check the length of the message to make sure it contains the size of // the requested buffer. if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for UPDATE_TEXTURE2D_RECT"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that this client did actually allocate the shared memory // corresponding to this handle. const SharedMemoryInfo* info = client->GetSharedMemoryInfo(message.shared_memory_id); if (info == NULL) { O3D_ERROR(service_locator_) << "shared memory id " << message.shared_memory_id << " not found"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that the Id passed in actually corresponds to a texture. Texture2D* texture_object = object_manager_->GetById(message.texture_id); if (texture_object == NULL) { O3D_ERROR(service_locator_) << "Texture with id " << message.texture_id << " not found"; SendBooleanResponse(client->client_handle(), false); return false; } // Check that we will not be reading past the end of the allocated shared // memory. int32 number_of_bytes = (message.height - 1) * message.pitch + image::ComputePitch(texture_object->format(), message.width); if (message.offset + number_of_bytes > info->size || message.offset + number_of_bytes < message.offset) { O3D_ERROR(service_locator_) << "Offset + size as computed by width, height and pitch" << " exceeds allocated shared memory size (" << message.offset << " + " << number_of_bytes << " > " << info->size; SendBooleanResponse(client->client_handle(), false); return false; } int mip_width = image::ComputeMipDimension(message.level, texture_object->width()); int mip_height = image::ComputeMipDimension(message.level, texture_object->height()); if (message.x < 0 || message.width < 0 || message.y < 0 || message.height < 0 || message.x + message.width > mip_width || message.y + message.height > mip_height) { O3D_ERROR(service_locator_) << "rect out of range (" << message.x << ", " << message.y << ", " << message.width << message.height << ")"; SendBooleanResponse(client->client_handle(), false); return false; } void *target_address = PointerFromVoidPointer(info->mapped_address, message.offset); texture_object->SetRect( message.level, message.x, message.y, message.width, message.height, target_address, message.pitch); SendBooleanResponse(client->client_handle(), true); return true; } // Processes a request to register a client-allocated shared memory // buffer on behalf of a connected client. Parses the arguments of // the message to determine how much space is being passed. It maps // the shared memory buffer into the local address space and sends a // message back to the client with the newly allocated shared memory // ID. bool MessageQueue::ProcessMessageRegisterSharedMemory( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageRegisterSharedMemory::Msg& message) { if (header->iov_length != 1 || header->handle_count != 1) { LOG(ERROR) << "Malformed message for REGISTER_SHARED_MEMORY"; return false; } int32 mem_size = message.mem_size; if (mem_size <= 0 || mem_size > MessageRegisterSharedMemory::Msg::kMaxSharedMemSize) { LOG(ERROR) << "Invalid mem size sent: " << mem_size << "(max size = " << MessageRegisterSharedMemory::Msg::kMaxSharedMemSize << ")"; return false; } // Fetch the handle to the preexisting shared memory object. nacl::Handle shared_memory = header->handles[0]; if (shared_memory == nacl::kInvalidHandle) { LOG_IMC_ERROR("Invalid shared memory object registered"); return false; } // Map it in local address space. void* shared_region = nacl::Map(0, mem_size, nacl::kProtRead | nacl::kProtWrite, nacl::kMapShared, shared_memory, 0); if (shared_region == nacl::kMapFailed) { LOG_IMC_ERROR("Failed to map shared memory"); nacl::Close(shared_memory); return false; } // Create a unique id for the shared memory buffer. int32 buffer_id = next_shared_memory_id_++; // Send the buffer id back to the client. nacl::MessageHeader response_header; nacl::IOVec id_vec; id_vec.base = &buffer_id; id_vec.length = sizeof(buffer_id); response_header.iov = &id_vec; response_header.iov_length = 1; response_header.handles = NULL; response_header.handle_count = 0; int result = nacl::SendDatagram(client->client_handle(), &response_header, 0); if (result != sizeof(buffer_id)) { LOG_IMC_ERROR("Failed to send shared memory ID back to the client"); nacl::Unmap(shared_region, mem_size); nacl::Close(shared_memory); return false; } // Register the newly mapped shared memory with the connected client. client->RegisterSharedMemory(buffer_id, shared_memory, shared_region, mem_size); return true; } // Processes a request to unregister a client-allocated shared memory // buffer, referenced by ID. bool MessageQueue::ProcessMessageUnregisterSharedMemory( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageUnregisterSharedMemory::Msg& message) { if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for UNREGISTER_SHARED_MEMORY"; return false; } bool res = client->UnregisterSharedMemory(message.buffer_id); SendBooleanResponse(client->client_handle(), res); return res; } // Processes a request to Render. bool MessageQueue::ProcessMessageRender( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageRender::Msg& message) { if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for RENDER"; return false; } Renderer* renderer(service_locator_->GetService()); if (renderer) { renderer->set_need_to_render(true); } return true; } // Processes a request to get the O3D version. bool MessageQueue::ProcessMessageGetVersion( ConnectedClient* client, int message_length, nacl::MessageHeader* header, nacl::Handle* handles, const MessageGetVersion::Msg& message) { if (header->iov_length != 1 || header->handle_count != 0) { LOG(ERROR) << "Malformed message for GET_VERSION"; return false; } ClientInfoManager* manager(service_locator_->GetService()); static const char* const kNullString = ""; const char* version = kNullString; if (manager) { version = manager->client_info().version().c_str(); } // Send the version string. DCHECK_LT(strlen(version), static_cast(MAX_VERSION_STRING_LENGTH)); MessageGetVersion::Response response(version); nacl::MessageHeader response_header; nacl::IOVec id_vec; id_vec.base = &response.data; id_vec.length = sizeof(response.data); response_header.iov = &id_vec; response_header.iov_length = 1; response_header.handles = NULL; response_header.handle_count = 0; nacl::SendDatagram(client->client_handle(), &response_header, 0); return true; } } // namespace o3d