| // Copyright 2018 The Fuchsia Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "xdc-server.h" | 
 |  | 
 | #include <errno.h> | 
 | #include <fcntl.h> | 
 | #include <lib/fit/defer.h> | 
 | #include <poll.h> | 
 | #include <signal.h> | 
 | #include <stdio.h> | 
 | #include <sys/file.h> | 
 | #include <sys/socket.h> | 
 | #include <sys/types.h> | 
 | #include <sys/un.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <cassert> | 
 |  | 
 | #include <xdc-host-utils/conn.h> | 
 | #include <xdc-server-utils/msg.h> | 
 | #include <xdc-server-utils/packet.h> | 
 | #include <xdc-server-utils/stream.h> | 
 |  | 
 | #include "usb-handler.h" | 
 |  | 
 | namespace xdc { | 
 |  | 
 | static constexpr uint32_t MAX_PENDING_CONN_BACKLOG = 128; | 
 | static const char* XDC_LOCK_PATH = "/tmp/xdc.lock"; | 
 |  | 
 | void Client::SetStreamId(uint32_t stream_id) { | 
 |   registered_ = true; | 
 |   stream_id_ = stream_id; | 
 | } | 
 |  | 
 | void Client::SetConnected(bool connected) { | 
 |   if (connected == connected_) { | 
 |     fprintf(stderr, "tried to set client with stream id %u as %s again.\n", stream_id(), | 
 |             connected ? "connected" : "disconnected"); | 
 |     return; | 
 |   } | 
 |   printf("client with stream id %u is now %s to the xdc device stream.\n", stream_id(), | 
 |          connected ? "connected" : "disconnected"); | 
 |   connected_ = connected; | 
 | } | 
 |  | 
 | bool Client::UpdatePollState(bool usb_writable) { | 
 |   short updated_events = events_; | 
 |   // We want to poll for the client readable signal if: | 
 |   //  - The client has not yet registered a stream id, or, | 
 |   //  - The xdc stream of the client's id is ready to be written to. | 
 |   if (!stream_id() || (usb_writable && connected())) { | 
 |     updated_events |= POLLIN; | 
 |   } else { | 
 |     updated_events &= ~(POLLIN); | 
 |   } | 
 |   // We need to poll for the client writable signal if we have data to send to the client. | 
 |   if (completed_reads_.size() > 0) { | 
 |     updated_events |= POLLOUT; | 
 |   } else { | 
 |     updated_events &= ~POLLOUT; | 
 |   } | 
 |   if (updated_events != events_) { | 
 |     events_ = updated_events; | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | void Client::AddCompletedRead(std::unique_ptr<UsbHandler::Transfer> transfer) { | 
 |   completed_reads_.push_back(std::move(transfer)); | 
 | } | 
 |  | 
 | void Client::ProcessCompletedReads(const std::unique_ptr<UsbHandler>& usb_handler) { | 
 |   for (auto iter = completed_reads_.begin(); iter != completed_reads_.end();) { | 
 |     std::unique_ptr<UsbHandler::Transfer>& transfer = *iter; | 
 |  | 
 |     unsigned char* data = transfer->data() + transfer->offset(); | 
 |     ssize_t len_to_write = transfer->actual_length() - transfer->offset(); | 
 |  | 
 |     ssize_t total_written = 0; | 
 |     while (total_written < len_to_write) { | 
 |       ssize_t res = send(fd(), data + total_written, len_to_write - total_written, 0); | 
 |       if (res < 0) { | 
 |         if (errno == EAGAIN) { | 
 |           fprintf(stderr, "can't send completed read to client currently\n"); | 
 |           // Need to wait for client to be writable again. | 
 |           return; | 
 |         } else { | 
 |           fprintf(stderr, "can't write to client, err: %s\n", strerror(errno)); | 
 |           return; | 
 |         } | 
 |       } | 
 |       total_written += res; | 
 |       ssize_t offset = transfer->offset() + res; | 
 |       if (offset < std::numeric_limits<int>::min() || offset > std::numeric_limits<int>::max()) { | 
 |         fprintf(stderr, "offset out of range\n"); | 
 |         exit(-1); | 
 |       } | 
 |       __UNUSED bool set_offset_result = transfer->SetOffset(static_cast<int>(offset)); | 
 |       assert(set_offset_result); | 
 |     } | 
 |     usb_handler->RequeueRead(std::move(transfer)); | 
 |     iter = completed_reads_.erase(iter); | 
 |   } | 
 | } | 
 |  | 
 | zx_status_t Client::ProcessWrites(const std::unique_ptr<UsbHandler>& usb_handler) { | 
 |   if (!connected()) { | 
 |     return ZX_ERR_SHOULD_WAIT; | 
 |   } | 
 |   while (true) { | 
 |     if (!pending_write_) { | 
 |       pending_write_ = usb_handler->GetWriteTransfer(); | 
 |       if (!pending_write_) { | 
 |         return ZX_ERR_SHOULD_WAIT;  // No transfers currently available. | 
 |       } | 
 |     } | 
 |     // If there is no pending data to transfer, read more from the client. | 
 |     if (!has_write_data()) { | 
 |       // Read from the client into the usb transfer buffer. Leave space for the header. | 
 |       unsigned char* buf = pending_write_->write_data_buffer(); | 
 |  | 
 |       ssize_t n = recv(fd(), buf, UsbHandler::Transfer::MAX_WRITE_DATA_SIZE, 0); | 
 |       if (n == 0) { | 
 |         return ZX_ERR_PEER_CLOSED; | 
 |       } else if (n == EAGAIN) { | 
 |         return ZX_ERR_SHOULD_WAIT; | 
 |       } else if (n < 0) { | 
 |         fprintf(stderr, "recv got unhandled err: %s\n", strerror(errno)); | 
 |         return ZX_ERR_IO; | 
 |       } | 
 |       pending_write_->FillHeader(stream_id(), n); | 
 |     } | 
 |     if (usb_handler->writable()) { | 
 |       pending_write_ = usb_handler->QueueWriteTransfer(std::move(pending_write_)); | 
 |       if (pending_write_) { | 
 |         // Usb handler was busy and returned the write. | 
 |         return ZX_ERR_SHOULD_WAIT; | 
 |       } | 
 |     } else { | 
 |       break;  // Usb handler is busy, need to wait for some writes to complete. | 
 |     } | 
 |   } | 
 |   return ZX_ERR_SHOULD_WAIT; | 
 | } | 
 |  | 
 | void Client::ReturnTransfers(const std::unique_ptr<UsbHandler>& usb_handler) { | 
 |   for (auto& transfer : completed_reads_) { | 
 |     usb_handler->RequeueRead(std::move(transfer)); | 
 |   } | 
 |   completed_reads_.clear(); | 
 |  | 
 |   if (pending_write_) { | 
 |     usb_handler->ReturnWriteTransfer(std::move(pending_write_)); | 
 |   } | 
 | } | 
 |  | 
 | // static | 
 | std::unique_ptr<XdcServer> XdcServer::Create() { | 
 |   auto conn = std::make_unique<XdcServer>(ConstructorTag{}); | 
 |   if (!conn->Init()) { | 
 |     return nullptr; | 
 |   } | 
 |   return conn; | 
 | } | 
 |  | 
 | bool XdcServer::Init() { | 
 |   usb_handler_ = UsbHandler::Create(); | 
 |   if (!usb_handler_) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   socket_fd_.reset(socket(AF_UNIX, SOCK_STREAM, 0)); | 
 |   if (!socket_fd_) { | 
 |     fprintf(stderr, "failed to create socket, err: %s\n", strerror(errno)); | 
 |     return false; | 
 |   } | 
 |   sockaddr_un addr = {}; | 
 |   addr.sun_family = AF_UNIX; | 
 |   strncpy(addr.sun_path, XDC_SOCKET_PATH, sizeof(addr.sun_path)); | 
 |  | 
 |   // Check if another instance of the xdc server is running. | 
 |   socket_lock_fd_.reset(open(XDC_LOCK_PATH, O_CREAT | O_RDONLY, 0666)); | 
 |   if (!socket_lock_fd_) { | 
 |     return false; | 
 |   } | 
 |   int res = flock(socket_lock_fd_.get(), LOCK_EX | LOCK_NB); | 
 |   if (res != 0) { | 
 |     fprintf(stderr, "Failed to acquire socket lock, err: %s.\n", strerror(errno)); | 
 |     return false; | 
 |   } | 
 |   // Remove the socket file if it exists. | 
 |   unlink(XDC_SOCKET_PATH); | 
 |   if (bind(socket_fd_.get(), (sockaddr*)&addr, sizeof(addr)) != 0) { | 
 |     fprintf(stderr, "Could not bind socket with pathname: %s, err: %s\n", XDC_SOCKET_PATH, | 
 |             strerror(errno)); | 
 |     return false; | 
 |   } | 
 |   if (listen(socket_fd_.get(), MAX_PENDING_CONN_BACKLOG) < 0) { | 
 |     fprintf(stderr, "Could not listen on socket fd: %d, err: %s\n", socket_fd_.get(), | 
 |             strerror(errno)); | 
 |     return false; | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void XdcServer::UpdateClientPollEvents() { | 
 |   for (auto iter : clients_) { | 
 |     std::shared_ptr<Client> client = iter.second; | 
 |     bool changed = client->UpdatePollState(usb_handler_->writable()); | 
 |     if (changed) { | 
 |       // We need to update the corresponding file descriptor in the poll_fds_ array | 
 |       // passed to poll. | 
 |       int fd = client->fd(); | 
 |       auto is_fd = [fd](auto& elem) { return elem.fd == fd; }; | 
 |       auto fd_iter = std::find_if(poll_fds_.begin(), poll_fds_.end(), is_fd); | 
 |       if (fd_iter == poll_fds_.end()) { | 
 |         fprintf(stderr, "could not find pollfd for client with fd %d\n", fd); | 
 |         continue; | 
 |       } | 
 |       fd_iter->events = client->events(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void XdcServer::UpdateUsbHandlerFds() { | 
 |   std::map<int, short> added_fds; | 
 |   std::set<int> removed_fds; | 
 |   usb_handler_->GetFdUpdates(added_fds, removed_fds); | 
 |  | 
 |   for (auto iter : added_fds) { | 
 |     int fd = iter.first; | 
 |     short events = iter.second; | 
 |  | 
 |     auto match = std::find_if(poll_fds_.begin(), poll_fds_.end(), | 
 |                               [&fd](auto& pollfd) { return pollfd.fd == fd; }); | 
 |     if (match != poll_fds_.end()) { | 
 |       fprintf(stderr, "already have usb handler fd: %d\n", fd); | 
 |       continue; | 
 |     } | 
 |     poll_fds_.push_back(pollfd{fd, events, 0}); | 
 |     printf("usb handler added fd: %d\n", fd); | 
 |   } | 
 |   for (auto fd : removed_fds) { | 
 |     auto match = std::remove_if(poll_fds_.begin(), poll_fds_.end(), | 
 |                                 [&fd](auto& pollfd) { return pollfd.fd == fd; }); | 
 |     if (match == poll_fds_.end()) { | 
 |       fprintf(stderr, "could not find usb handler fd: %d to delete\n", fd); | 
 |       continue; | 
 |     } | 
 |     poll_fds_.erase(match, poll_fds_.end()); | 
 |     printf("usb handler removed fd: %d\n", fd); | 
 |   } | 
 | } | 
 |  | 
 | void XdcServer::Run() { | 
 |   signal(SIGPIPE, SIG_IGN);  // Prevent clients from causing SIGPIPE. | 
 |  | 
 |   printf("Waiting for connections on: %s\n", XDC_SOCKET_PATH); | 
 |  | 
 |   // Listen for new client connections. | 
 |   poll_fds_.push_back(pollfd{socket_fd_.get(), POLLIN, 0}); | 
 |  | 
 |   // Initialize to true as we want to get the initial usb handler fds. | 
 |   bool update_usb_handler_fds = true; | 
 |  | 
 |   for (;;) { | 
 |     if (update_usb_handler_fds) { | 
 |       UpdateUsbHandlerFds(); | 
 |       update_usb_handler_fds = false; | 
 |     } | 
 |  | 
 |     // poll expects an array of pollfds. | 
 |     int num = poll(&poll_fds_[0], static_cast<nfds_t>(poll_fds_.size()), -1 /* timeout */); | 
 |     if (num < 0) { | 
 |       fprintf(stderr, "poll failed, err: %s\n", strerror(errno)); | 
 |       break; | 
 |     } | 
 |     // Not using an iterator for poll_fds_ as we might add/remove elements. | 
 |     size_t num_sockets = poll_fds_.size(); | 
 |     size_t i = 0; | 
 |     while (i < num_sockets) { | 
 |       if (poll_fds_[i].fd == socket_fd_.get()) { | 
 |         // A new client is trying to connect. | 
 |         if (poll_fds_[i].revents & POLLIN) { | 
 |           ClientConnect(); | 
 |           // Don't need to increment num_sockets as there aren't poll events for it yet. | 
 |         } | 
 |       } else if (usb_handler_->IsValidFd(poll_fds_[i].fd)) { | 
 |         if (poll_fds_[i].revents) { | 
 |           std::vector<std::unique_ptr<UsbHandler::Transfer>> completed_reads; | 
 |           update_usb_handler_fds = usb_handler_->HandleEvents(completed_reads); | 
 |  | 
 |           SendQueuedCtrlMsgs(); | 
 |  | 
 |           for (auto& usb_transfer : completed_reads) { | 
 |             UsbReadComplete(std::move(usb_transfer)); | 
 |           } | 
 |         } | 
 |       } else { | 
 |         auto iter = clients_.find(poll_fds_[i].fd); | 
 |         if (iter == clients_.end()) { | 
 |           fprintf(stderr, "poll returned an unknown fd: %d\n", poll_fds_[i].fd); | 
 |           poll_fds_.erase(poll_fds_.begin() + i); | 
 |           --num_sockets; | 
 |           continue; | 
 |         } | 
 |  | 
 |         std::shared_ptr<Client> client = iter->second; | 
 |  | 
 |         // Received client disconnect signal. | 
 |         // Only remove the client if the corresponding xdc device stream is offline. | 
 |         // Otherwise the client may still have data buffered to send to the usb handler, | 
 |         // and we will wait until reading from the client returns zero (disconnect). | 
 |         bool delete_client = (poll_fds_[i].revents & POLLHUP) && !client->connected(); | 
 |  | 
 |         // Check if the client had pending data to write, or signalled new data available. | 
 |         bool do_write = client->has_write_data() && usb_handler_->writable() && client->connected(); | 
 |         bool new_data_available = poll_fds_[i].revents & POLLIN; | 
 |         if (!delete_client && (do_write || new_data_available)) { | 
 |           if (!client->registered()) { | 
 |             // Delete the client if registering the stream failed. | 
 |             delete_client = !RegisterStream(client); | 
 |           } | 
 |           if (!delete_client) { | 
 |             zx_status_t status = client->ProcessWrites(usb_handler_); | 
 |             if (status == ZX_ERR_PEER_CLOSED) { | 
 |               delete_client = true; | 
 |             } | 
 |           } | 
 |         } | 
 |  | 
 |         if (delete_client) { | 
 |           client->ReturnTransfers(usb_handler_); | 
 |           // Notify the host server that the stream is now offline. | 
 |           if (client->stream_id()) { | 
 |             NotifyStreamState(client->stream_id(), false /* online */); | 
 |           } | 
 |           poll_fds_.erase(poll_fds_.begin() + i); | 
 |           --num_sockets; | 
 |           printf("fd %d stream %u disconnected\n", client->fd(), client->stream_id()); | 
 |           clients_.erase(iter); | 
 |           continue; | 
 |         } | 
 |         client->ProcessCompletedReads(usb_handler_); | 
 |       } | 
 |       ++i; | 
 |     } | 
 |     UpdateClientPollEvents(); | 
 |   } | 
 | } | 
 |  | 
 | void XdcServer::ClientConnect() { | 
 |   struct sockaddr_un addr; | 
 |   socklen_t len = sizeof(addr); | 
 |   // Most of the time we want non-blocking transfers, so we can handle other clients / libusb. | 
 |   int client_fd = accept(socket_fd_.get(), (struct sockaddr*)&addr, &len); | 
 |   if (client_fd < 0) { | 
 |     fprintf(stderr, "Socket accept failed, err: %s\n", strerror(errno)); | 
 |     return; | 
 |   } | 
 |   if (clients_.count(client_fd) > 0) { | 
 |     fprintf(stderr, "Client already connected, socket fd: %d\n", client_fd); | 
 |     return; | 
 |   } | 
 |   int flags = fcntl(client_fd, F_GETFL, 0); | 
 |   if (flags < 0) { | 
 |     fprintf(stderr, "Could not get socket flags, err: %s\n", strerror(errno)); | 
 |     close(client_fd); | 
 |     return; | 
 |   } | 
 |   int res = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); | 
 |   if (res != 0) { | 
 |     fprintf(stderr, "Could not set socket as nonblocking, err: %s\n", strerror(errno)); | 
 |     close(client_fd); | 
 |     return; | 
 |   } | 
 |   printf("Client connected, socket fd: %d\n", client_fd); | 
 |   clients_[client_fd] = std::make_shared<Client>(client_fd); | 
 |   poll_fds_.push_back(pollfd{client_fd, POLLIN, 0}); | 
 | } | 
 |  | 
 | bool XdcServer::RegisterStream(std::shared_ptr<Client> client) { | 
 |   RegisterStreamRequest stream_id; | 
 |   ssize_t n = recv(client->fd(), &stream_id, sizeof(stream_id), MSG_WAITALL); | 
 |   if (n != sizeof(stream_id)) { | 
 |     fprintf(stderr, "failed to read stream id from client fd: %d, got len: %ld, got err: %s\n", | 
 |             client->fd(), n, strerror(errno)); | 
 |     return false; | 
 |   } | 
 |   // Client has disconnected. This will be handled in the main poll thread. | 
 |   if (n == 0) { | 
 |     return false; | 
 |   } | 
 |   RegisterStreamResponse resp = false; | 
 |   if (stream_id == DEBUG_STREAM_ID_RESERVED) { | 
 |     fprintf(stderr, "cannot register stream id %u\n", DEBUG_STREAM_ID_RESERVED); | 
 |   } else if (GetClient(stream_id)) { | 
 |     fprintf(stderr, "stream id %u was already registered\n", stream_id); | 
 |   } else { | 
 |     client->SetStreamId(stream_id); | 
 |     printf("registered stream id %u\n", stream_id); | 
 |     NotifyStreamState(stream_id, true /* online */); | 
 |     if (dev_stream_ids_.count(stream_id)) { | 
 |       client->SetConnected(true); | 
 |     } | 
 |     resp = true; | 
 |   } | 
 |  | 
 |   ssize_t res = send(client->fd(), &resp, sizeof(resp), MSG_WAITALL); | 
 |   if (res != sizeof(resp)) { | 
 |     // Failed to send reply, disconnect the client. | 
 |     return false; | 
 |   } | 
 |   return resp; | 
 | } | 
 |  | 
 | std::shared_ptr<Client> XdcServer::GetClient(uint32_t stream_id) { | 
 |   auto is_client = [stream_id](auto& pair) -> bool { | 
 |     return pair.second->stream_id() == stream_id; | 
 |   }; | 
 |   auto iter = std::find_if(clients_.begin(), clients_.end(), is_client); | 
 |   return iter == clients_.end() ? nullptr : iter->second; | 
 | } | 
 |  | 
 | void XdcServer::UsbReadComplete(std::unique_ptr<UsbHandler::Transfer> transfer) { | 
 |   auto requeue = fit::defer([&]() { usb_handler_->RequeueRead(std::move(transfer)); }); | 
 |  | 
 |   bool is_new_packet; | 
 |   uint32_t stream_id; | 
 |  | 
 |   zx_status_t status = xdc_update_packet_state(&read_packet_state_, transfer->data(), | 
 |                                                transfer->actual_length(), &is_new_packet); | 
 |   if (status != ZX_OK) { | 
 |     fprintf(stderr, "error processing transfer: %d, dropping read of size %d\n", status, | 
 |             transfer->actual_length()); | 
 |   } | 
 |   stream_id = read_packet_state_.header.stream_id; | 
 |   if (is_new_packet && stream_id == XDC_MSG_STREAM) { | 
 |     HandleCtrlMsg(transfer->data(), transfer->actual_length()); | 
 |     return; | 
 |   } | 
 |   // Pass the completed transfer to the registered client, if any. | 
 |   auto client = GetClient(stream_id); | 
 |   if (!client) { | 
 |     fprintf(stderr, "No client registered for stream %u, dropping read of size %d\n", stream_id, | 
 |             transfer->actual_length()); | 
 |     return; | 
 |   } | 
 |   // If it is the start of a new packet, the client should begin reading after the header. | 
 |   int offset = is_new_packet ? sizeof(xdc_packet_header_t) : 0; | 
 |   __UNUSED bool set_offset_result = transfer->SetOffset(offset); | 
 |   assert(set_offset_result); | 
 |   client->AddCompletedRead(std::move(transfer)); | 
 |   requeue.cancel(); | 
 | } | 
 |  | 
 | void XdcServer::HandleCtrlMsg(unsigned char* transfer_buf, int transfer_len) { | 
 |   int data_len = transfer_len - (int)sizeof(xdc_packet_header_t); | 
 |   if (data_len < (int)sizeof(xdc_msg_t)) { | 
 |     fprintf(stderr, "malformed msg, got %d bytes, need %lu\n", data_len, sizeof(xdc_msg_t)); | 
 |     return; | 
 |   } | 
 |   xdc_msg_t* msg = reinterpret_cast<xdc_msg_t*>(transfer_buf + sizeof(xdc_packet_header_t)); | 
 |   switch (msg->opcode) { | 
 |     case XDC_NOTIFY_STREAM_STATE: { | 
 |       uint32_t stream_id = msg->notify_stream_state.stream_id; | 
 |       bool online = msg->notify_stream_state.online; | 
 |  | 
 |       auto dev_stream = dev_stream_ids_.find(stream_id); | 
 |       bool saved_online_state = dev_stream != dev_stream_ids_.end(); | 
 |       if (online == saved_online_state) { | 
 |         fprintf(stderr, "tried to set stream %u to %s again\n", stream_id, | 
 |                 online ? "online" : "offline"); | 
 |         return; | 
 |       } | 
 |       if (online) { | 
 |         dev_stream_ids_.insert(stream_id); | 
 |       } else { | 
 |         dev_stream_ids_.erase(dev_stream); | 
 |       } | 
 |       printf("xdc device stream id %u is now %s\n", stream_id, online ? "online" : "offline"); | 
 |  | 
 |       // Update the host client's connected status. | 
 |       auto client = GetClient(stream_id); | 
 |       if (!client) { | 
 |         break; | 
 |       } | 
 |       client->SetConnected(online); | 
 |       break; | 
 |     } | 
 |     default: | 
 |       fprintf(stderr, "unknown msg opcode: %u\n", msg->opcode); | 
 |   } | 
 | } | 
 |  | 
 | void XdcServer::NotifyStreamState(uint32_t stream_id, bool online) { | 
 |   xdc_msg_t msg = {.opcode = XDC_NOTIFY_STREAM_STATE, | 
 |                    .notify_stream_state.stream_id = stream_id, | 
 |                    .notify_stream_state.online = online}; | 
 |   queued_ctrl_msgs_.push_back(msg); | 
 |   SendQueuedCtrlMsgs(); | 
 | } | 
 |  | 
 | bool XdcServer::SendCtrlMsg(xdc_msg_t& msg) { | 
 |   std::unique_ptr<UsbHandler::Transfer> transfer = usb_handler_->GetWriteTransfer(); | 
 |   if (!transfer) { | 
 |     return false; | 
 |   } | 
 |   __UNUSED zx_status_t res = transfer->FillData( | 
 |       DEBUG_STREAM_ID_RESERVED, reinterpret_cast<unsigned char*>(&msg), sizeof(msg)); | 
 |   assert(res == ZX_OK);  // Should not fail. | 
 |   transfer = usb_handler_->QueueWriteTransfer(std::move(transfer)); | 
 |   bool queued = !transfer; | 
 |   if (!queued) { | 
 |     usb_handler_->ReturnWriteTransfer(std::move(transfer)); | 
 |   } | 
 |   return queued; | 
 | } | 
 |  | 
 | void XdcServer::SendQueuedCtrlMsgs() { | 
 |   auto msgs_iter = queued_ctrl_msgs_.begin(); | 
 |   while (msgs_iter != queued_ctrl_msgs_.end()) { | 
 |     bool sent = SendCtrlMsg(*msgs_iter); | 
 |     if (sent) { | 
 |       msgs_iter = queued_ctrl_msgs_.erase(msgs_iter); | 
 |     } else { | 
 |       // Need to wait. | 
 |       return; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace xdc | 
 |  | 
 | int main(int argc, char** argv) { | 
 |   printf("Starting XHCI Debug Capability server...\n"); | 
 |   std::unique_ptr<xdc::XdcServer> xdc_server = xdc::XdcServer::Create(); | 
 |   if (!xdc_server) { | 
 |     return -1; | 
 |   } | 
 |   xdc_server->Run(); | 
 |   return 0; | 
 | } |