diff --git a/Readme.md b/Readme.md index 23442ec..823ba66 100644 --- a/Readme.md +++ b/Readme.md @@ -12,6 +12,7 @@ C++ object-oriented library containing implementation of core AndroidAuto(tm) fu ### Supported functionalities - AOAP (Android Open Accessory Protocol) - USB transport + - TCP transport - USB hotplug - AndroidAuto(tm) protocol - SSL encryption diff --git a/include/f1x/aasdk/Common/Data.hpp b/include/f1x/aasdk/Common/Data.hpp index 70d3917..cfe76c4 100644 --- a/include/f1x/aasdk/Common/Data.hpp +++ b/include/f1x/aasdk/Common/Data.hpp @@ -41,6 +41,7 @@ struct DataBuffer DataBuffer(void* _data, Data::size_type _size, Data::size_type offset = 0); explicit DataBuffer(Data& _data, Data::size_type offset = 0); bool operator==(const std::nullptr_t&) const; + bool operator==(const DataBuffer& buffer) const; Data::value_type* data; Data::size_type size; @@ -54,6 +55,7 @@ struct DataConstBuffer DataConstBuffer(const void* _data, Data::size_type _size, Data::size_type offset = 0); explicit DataConstBuffer(const Data& _data, Data::size_type offset = 0); bool operator==(const std::nullptr_t&) const; + bool operator==(const DataConstBuffer& buffer) const; const Data::value_type* cdata; Data::size_type size; diff --git a/include/f1x/aasdk/Error/Error.hpp b/include/f1x/aasdk/Error/Error.hpp index f9537b8..22275a6 100644 --- a/include/f1x/aasdk/Error/Error.hpp +++ b/include/f1x/aasdk/Error/Error.hpp @@ -41,6 +41,8 @@ public: bool operator!() const; bool operator==(const Error& other) const; + bool operator==(const ErrorCode& code) const; + bool operator!=(const ErrorCode& code) const; private: ErrorCode code_; diff --git a/include/f1x/aasdk/Error/ErrorCode.hpp b/include/f1x/aasdk/Error/ErrorCode.hpp index 7f3be33..046118c 100644 --- a/include/f1x/aasdk/Error/ErrorCode.hpp +++ b/include/f1x/aasdk/Error/ErrorCode.hpp @@ -29,40 +29,40 @@ namespace error enum class ErrorCode { - NONE, - USB_CLAIM_INTERFACE, - USB_INVALID_CONFIG_DESCRIPTOR, - USB_OBTAIN_INTERFACE_DESCRIPTOR, - USB_EMPTY_INTERFACES, - USB_INVALID_DEVICE_ENDPOINTS, - USB_INVALID_TRANSFER_METHOD, - USB_TRANSFER_ALLOCATION, - USB_LIST_DEVICES, - USB_OBTAIN_CONFIG_DESCRIPTOR, - USB_TRANSFER, - USB_SINK_COMMIT_OVERFLOW, - USB_SINK_CONSUME_UNDERFLOW, - USB_AOAP_PROTOCOL_VERSION, - USB_EMPTY_DEVICE_LIST, - USB_AOAP_DEVICE_NOT_FOUND, - SSL_READ_CERTIFICATE, - SSL_READ_PRIVATE_KEY, - SSL_METHOD, - SSL_CONTEXT_CREATION, - SSL_USE_CERTIFICATE, - SSL_USE_PRIVATE_KEY, - SSL_HANDLER_CREATION, - SSL_READ_BIO_CREATION, - SSL_WRITE_BIO_CREATION, - SSL_HANDSHAKE, - SSL_WRITE, - SSL_READ, - SSL_BIO_READ, - SSL_BIO_WRITE, - MESSENGER_INTERTWINED_CHANNELS, - OPERATION_ABORTED, - OPERATION_IN_PROGRESS, - PARSE_PAYLOAD + NONE = 0, + USB_CLAIM_INTERFACE = 1, + USB_INVALID_CONFIG_DESCRIPTOR = 2, + USB_OBTAIN_INTERFACE_DESCRIPTOR = 3, + USB_EMPTY_INTERFACES = 4, + USB_INVALID_DEVICE_ENDPOINTS = 5, + USB_INVALID_TRANSFER_METHOD = 6, + USB_TRANSFER_ALLOCATION = 7, + USB_LIST_DEVICES = 8, + USB_OBTAIN_CONFIG_DESCRIPTOR = 9, + USB_TRANSFER = 10, + DATA_SINK_COMMIT_OVERFLOW = 11, + DATA_SINK_CONSUME_UNDERFLOW = 12, + USB_AOAP_PROTOCOL_VERSION = 13, + USB_AOAP_DEVICE_NOT_FOUND = 14, + SSL_READ_CERTIFICATE = 15, + SSL_READ_PRIVATE_KEY = 16, + SSL_METHOD = 17, + SSL_CONTEXT_CREATION = 18, + SSL_USE_CERTIFICATE = 19, + SSL_USE_PRIVATE_KEY = 20, + SSL_HANDLER_CREATION = 21, + SSL_READ_BIO_CREATION = 22, + SSL_WRITE_BIO_CREATION = 23, + SSL_HANDSHAKE = 24, + SSL_WRITE = 25, + SSL_READ = 26, + SSL_BIO_READ = 27, + SSL_BIO_WRITE = 28, + MESSENGER_INTERTWINED_CHANNELS = 29, + OPERATION_ABORTED = 30, + OPERATION_IN_PROGRESS = 31, + PARSE_PAYLOAD = 32, + TCP_TRANSFER = 33 }; } diff --git a/include/f1x/aasdk/IO/IOContextWrapper.hpp b/include/f1x/aasdk/IO/IOContextWrapper.hpp index 2478f90..60febfa 100644 --- a/include/f1x/aasdk/IO/IOContextWrapper.hpp +++ b/include/f1x/aasdk/IO/IOContextWrapper.hpp @@ -28,11 +28,9 @@ namespace aasdk namespace io { -class IOContextWrapper: boost::noncopyable +class IOContextWrapper { public: - typedef std::shared_ptr Pointer; - IOContextWrapper(); explicit IOContextWrapper(boost::asio::io_service& ioService); explicit IOContextWrapper(boost::asio::io_service::strand& strand); diff --git a/include/f1x/aasdk/IO/Promise.hpp b/include/f1x/aasdk/IO/Promise.hpp index 0775641..9f5e222 100644 --- a/include/f1x/aasdk/IO/Promise.hpp +++ b/include/f1x/aasdk/IO/Promise.hpp @@ -51,13 +51,13 @@ public: } Promise(boost::asio::io_service& ioService) - : ioContextWrapper_(std::make_shared(ioService)) + : ioContextWrapper_(ioService) { } Promise(boost::asio::io_service::strand& strand) - : ioContextWrapper_(std::make_shared(strand)) + : ioContextWrapper_(strand) { } @@ -66,8 +66,8 @@ public: { std::lock_guard lock(mutex_); - resolveHandler_ = std::make_shared(std::move(resolveHandler)); - rejectHandler_ = rejectHandler == nullptr ? nullptr : std::make_shared(std::move(rejectHandler)); + resolveHandler_ = std::move(resolveHandler); + rejectHandler_ = std::move(rejectHandler); } void resolve(ResolveArgumentType argument) @@ -76,14 +76,13 @@ public: if(resolveHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([argument = std::move(argument), resolveHandler = std::move(resolveHandler_)]() mutable { - (*resolveHandler)(std::move(argument)); + ioContextWrapper_.post([argument = std::move(argument), resolveHandler = std::move(resolveHandler_)]() mutable { + resolveHandler(std::move(argument)); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - rejectHandler_.reset(); + rejectHandler_ = RejectHandler(); } void reject(ErrorArgumentType error) @@ -92,25 +91,24 @@ public: if(rejectHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([error = std::move(error), rejectHandler = std::move(rejectHandler_)]() mutable { - (*rejectHandler)(std::move(error)); + ioContextWrapper_.post([error = std::move(error), rejectHandler = std::move(rejectHandler_)]() mutable { + rejectHandler(std::move(error)); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - resolveHandler_.reset(); + resolveHandler_ = ResolveHandler(); } private: bool isPending() const { - return ioContextWrapper_ != nullptr && ioContextWrapper_->isActive(); + return ioContextWrapper_.isActive(); } - std::shared_ptr resolveHandler_; - std::shared_ptr rejectHandler_; - IOContextWrapper::Pointer ioContextWrapper_; + ResolveHandler resolveHandler_; + RejectHandler rejectHandler_; + IOContextWrapper ioContextWrapper_; std::mutex mutex_; }; @@ -134,13 +132,13 @@ public: } Promise(boost::asio::io_service& ioService) - : ioContextWrapper_(std::make_shared(ioService)) + : ioContextWrapper_(ioService) { } Promise(boost::asio::io_service::strand& strand) - : ioContextWrapper_(std::make_shared(strand)) + : ioContextWrapper_(strand) { } @@ -149,8 +147,8 @@ public: { std::lock_guard lock(mutex_); - resolveHandler_ = std::make_shared(std::move(resolveHandler)); - rejectHandler_ = rejectHandler == nullptr ? nullptr : std::make_shared(std::move(rejectHandler)); + resolveHandler_ = std::move(resolveHandler); + rejectHandler_ = std::move(rejectHandler); } void resolve() @@ -159,14 +157,13 @@ public: if(resolveHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([resolveHandler = std::move(resolveHandler_)]() mutable { - (*resolveHandler)(); + ioContextWrapper_.post([resolveHandler = std::move(resolveHandler_)]() mutable { + resolveHandler(); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - rejectHandler_.reset(); + rejectHandler_ = RejectHandler(); } void reject(ErrorArgumentType error) @@ -175,25 +172,24 @@ public: if(rejectHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([error = std::move(error), rejectHandler = std::move(rejectHandler_)]() mutable { - (*rejectHandler)(std::move(error)); + ioContextWrapper_.post([error = std::move(error), rejectHandler = std::move(rejectHandler_)]() mutable { + rejectHandler(std::move(error)); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - resolveHandler_.reset(); + resolveHandler_ = ResolveHandler(); } private: bool isPending() const { - return ioContextWrapper_ != nullptr && ioContextWrapper_->isActive(); + return ioContextWrapper_.isActive(); } - std::shared_ptr resolveHandler_; - std::shared_ptr rejectHandler_; - IOContextWrapper::Pointer ioContextWrapper_; + ResolveHandler resolveHandler_; + RejectHandler rejectHandler_; + IOContextWrapper ioContextWrapper_; std::mutex mutex_; }; @@ -216,13 +212,13 @@ public: } Promise(boost::asio::io_service& ioService) - : ioContextWrapper_(std::make_shared(ioService)) + : ioContextWrapper_(ioService) { } Promise(boost::asio::io_service::strand& strand) - : ioContextWrapper_(std::make_shared(strand)) + : ioContextWrapper_(strand) { } @@ -231,8 +227,8 @@ public: { std::lock_guard lock(mutex_); - resolveHandler_ = std::make_shared(std::move(resolveHandler)); - rejectHandler_ = rejectHandler == nullptr ? nullptr : std::make_shared(std::move(rejectHandler)); + resolveHandler_ = std::move(resolveHandler); + rejectHandler_ = std::move(rejectHandler); } void resolve() @@ -241,14 +237,13 @@ public: if(resolveHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([resolveHandler = std::move(resolveHandler_)]() mutable { - (*resolveHandler)(); + ioContextWrapper_.post([resolveHandler = std::move(resolveHandler_)]() mutable { + resolveHandler(); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - rejectHandler_.reset(); + rejectHandler_ = RejectHandler(); } void reject() @@ -257,25 +252,24 @@ public: if(rejectHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([rejectHandler = std::move(rejectHandler_)]() mutable { - (*rejectHandler)(); + ioContextWrapper_.post([rejectHandler = std::move(rejectHandler_)]() mutable { + rejectHandler(); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - resolveHandler_.reset(); + resolveHandler_ = ResolveHandler(); } private: bool isPending() const { - return ioContextWrapper_ != nullptr && ioContextWrapper_->isActive(); + return ioContextWrapper_.isActive(); } - std::shared_ptr resolveHandler_; - std::shared_ptr rejectHandler_; - IOContextWrapper::Pointer ioContextWrapper_; + ResolveHandler resolveHandler_; + RejectHandler rejectHandler_; + IOContextWrapper ioContextWrapper_; std::mutex mutex_; }; @@ -299,13 +293,13 @@ public: } Promise(boost::asio::io_service& ioService) - : ioContextWrapper_(std::make_shared(ioService)) + : ioContextWrapper_(ioService) { } Promise(boost::asio::io_service::strand& strand) - : ioContextWrapper_(std::make_shared(strand)) + : ioContextWrapper_(strand) { } @@ -314,8 +308,8 @@ public: { std::lock_guard lock(mutex_); - resolveHandler_ = std::make_shared(std::move(resolveHandler)); - rejectHandler_ = rejectHandler == nullptr ? nullptr : std::make_shared(std::move(rejectHandler)); + resolveHandler_ = std::move(resolveHandler); + rejectHandler_ = std::move(rejectHandler); } void resolve(ResolveArgumentType argument) @@ -324,14 +318,13 @@ public: if(resolveHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([argument = std::move(argument), resolveHandler = std::move(resolveHandler_)]() mutable { - (*resolveHandler)(std::move(argument)); + ioContextWrapper_.post([argument = std::move(argument), resolveHandler = std::move(resolveHandler_)]() mutable { + resolveHandler(std::move(argument)); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - rejectHandler_.reset(); + rejectHandler_ = RejectHandler(); } void reject() @@ -340,25 +333,24 @@ public: if(rejectHandler_ != nullptr && this->isPending()) { - ioContextWrapper_->post([rejectHandler = std::move(rejectHandler_)]() mutable { - (*rejectHandler)(); + ioContextWrapper_.post([rejectHandler = std::move(rejectHandler_)]() mutable { + rejectHandler(); }); } - ioContextWrapper_->reset(); ioContextWrapper_.reset(); - resolveHandler_.reset(); + resolveHandler_ = ResolveHandler(); } private: bool isPending() const { - return ioContextWrapper_ != nullptr && ioContextWrapper_->isActive(); + return ioContextWrapper_.isActive(); } - std::shared_ptr resolveHandler_; - std::shared_ptr rejectHandler_; - IOContextWrapper::Pointer ioContextWrapper_; + ResolveHandler resolveHandler_; + RejectHandler rejectHandler_; + IOContextWrapper ioContextWrapper_; std::mutex mutex_; }; diff --git a/include/f1x/aasdk/TCP/ITCPEndpoint.hpp b/include/f1x/aasdk/TCP/ITCPEndpoint.hpp new file mode 100644 index 0000000..61b47b1 --- /dev/null +++ b/include/f1x/aasdk/TCP/ITCPEndpoint.hpp @@ -0,0 +1,48 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +class ITCPEndpoint +{ +public: + typedef std::shared_ptr Pointer; + typedef io::Promise Promise; + typedef std::shared_ptr SocketPointer; + + virtual ~ITCPEndpoint() = default; + + virtual void send(common::DataConstBuffer buffer, Promise::Pointer promise) = 0; + virtual void receive(common::DataBuffer buffer, Promise::Pointer promise) = 0; + virtual void stop() = 0; +}; + +} +} +} diff --git a/include/f1x/aasdk/TCP/ITCPWrapper.hpp b/include/f1x/aasdk/TCP/ITCPWrapper.hpp new file mode 100644 index 0000000..1accba4 --- /dev/null +++ b/include/f1x/aasdk/TCP/ITCPWrapper.hpp @@ -0,0 +1,49 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +class ITCPWrapper +{ +public: + typedef std::function Handler; + typedef std::function ConnectHandler; + + virtual ~ITCPWrapper() = default; + + virtual void asyncWrite(boost::asio::ip::tcp::socket& socket, common::DataConstBuffer buffer, Handler handler) = 0; + virtual void asyncRead(boost::asio::ip::tcp::socket& socket, common::DataBuffer buffer, Handler handler) = 0; + virtual void close(boost::asio::ip::tcp::socket& socket) = 0; + virtual void asyncConnect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port, ConnectHandler handler) = 0; + virtual boost::system::error_code connect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port) = 0; +}; + +} +} +} diff --git a/include/f1x/aasdk/TCP/TCPEndpoint.hpp b/include/f1x/aasdk/TCP/TCPEndpoint.hpp new file mode 100644 index 0000000..36f8237 --- /dev/null +++ b/include/f1x/aasdk/TCP/TCPEndpoint.hpp @@ -0,0 +1,52 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +class TCPEndpoint: public ITCPEndpoint, public std::enable_shared_from_this +{ +public: + TCPEndpoint(ITCPWrapper& tcpWrapper, SocketPointer socket); + + void send(common::DataConstBuffer buffer, Promise::Pointer promise) override; + void receive(common::DataBuffer buffer, Promise::Pointer promise) override; + void stop() override; + +private: + using std::enable_shared_from_this::shared_from_this; + + void asyncOperationHandler(const boost::system::error_code& ec, size_t bytesTransferred, Promise::Pointer promise); + + ITCPWrapper& tcpWrapper_; + SocketPointer socket_; +}; + +} +} +} diff --git a/include/f1x/aasdk/TCP/TCPWrapper.hpp b/include/f1x/aasdk/TCP/TCPWrapper.hpp new file mode 100644 index 0000000..1940d51 --- /dev/null +++ b/include/f1x/aasdk/TCP/TCPWrapper.hpp @@ -0,0 +1,42 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +class TCPWrapper: public ITCPWrapper +{ +public: + void asyncWrite(boost::asio::ip::tcp::socket& socket, common::DataConstBuffer buffer, Handler handler) override; + void asyncRead(boost::asio::ip::tcp::socket& socket, common::DataBuffer buffer, Handler handler) override; + void close(boost::asio::ip::tcp::socket& socket) override; + void asyncConnect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port, ConnectHandler handler) override; + boost::system::error_code connect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port) override; +}; + +} +} +} diff --git a/include/f1x/aasdk/Transport/USBDataSink.hpp b/include/f1x/aasdk/Transport/DataSink.hpp similarity index 97% rename from include/f1x/aasdk/Transport/USBDataSink.hpp rename to include/f1x/aasdk/Transport/DataSink.hpp index 2c7d3e6..4bc764f 100644 --- a/include/f1x/aasdk/Transport/USBDataSink.hpp +++ b/include/f1x/aasdk/Transport/DataSink.hpp @@ -30,10 +30,10 @@ namespace aasdk namespace transport { -class USBDataSink +class DataSink { public: - USBDataSink(); + DataSink(); common::DataBuffer fill(); void commit(common::Data::size_type size); diff --git a/include/f1x/aasdk/Transport/TCPTransport.hpp b/include/f1x/aasdk/Transport/TCPTransport.hpp new file mode 100644 index 0000000..0159176 --- /dev/null +++ b/include/f1x/aasdk/Transport/TCPTransport.hpp @@ -0,0 +1,48 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace transport +{ + +class TCPTransport: public Transport +{ +public: + TCPTransport(boost::asio::io_service& ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint); + + void stop() override; + +private: + void enqueueReceive(common::DataBuffer buffer) override; + void enqueueSend(SendQueue::iterator queueElement) override; + void sendHandler(SendQueue::iterator queueElement, const error::Error& e); + + tcp::ITCPEndpoint::Pointer tcpEndpoint_; +}; + +} +} +} diff --git a/include/f1x/aasdk/Transport/Transport.hpp b/include/f1x/aasdk/Transport/Transport.hpp new file mode 100644 index 0000000..04ccc27 --- /dev/null +++ b/include/f1x/aasdk/Transport/Transport.hpp @@ -0,0 +1,65 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace transport +{ + +class Transport: public ITransport, public std::enable_shared_from_this, boost::noncopyable +{ +public: + Transport(boost::asio::io_service& ioService); + + void receive(size_t size, ReceivePromise::Pointer promise) override; + void send(common::Data data, SendPromise::Pointer promise) override; + +protected: + typedef std::list> ReceiveQueue; + typedef std::list> SendQueue; + + using std::enable_shared_from_this::shared_from_this; + void receiveHandler(size_t bytesTransferred); + void distributeReceivedData(); + void rejectReceivePromises(const error::Error& e); + + virtual void enqueueReceive(common::DataBuffer buffer) = 0; + virtual void enqueueSend(SendQueue::iterator queueElement) = 0; + + DataSink receivedDataSink_; + + boost::asio::io_service::strand receiveStrand_; + ReceiveQueue receiveQueue_; + + boost::asio::io_service::strand sendStrand_; + SendQueue sendQueue_; +}; + +} +} +} diff --git a/include/f1x/aasdk/Transport/USBTransport.hpp b/include/f1x/aasdk/Transport/USBTransport.hpp index 9d3ed71..3464130 100644 --- a/include/f1x/aasdk/Transport/USBTransport.hpp +++ b/include/f1x/aasdk/Transport/USBTransport.hpp @@ -18,11 +18,8 @@ #pragma once -#include -#include #include -#include -#include +#include #include namespace f1x @@ -32,35 +29,20 @@ namespace aasdk namespace transport { -class USBTransport: public ITransport, public std::enable_shared_from_this, boost::noncopyable +class USBTransport: public Transport { public: USBTransport(boost::asio::io_service& ioService, usb::IAOAPDevice::Pointer aoapDevice); - void receive(size_t size, ReceivePromise::Pointer promise) override; - void send(common::Data data, SendPromise::Pointer promise) override; void stop() override; private: - typedef std::list> InTransferQueue; - typedef std::list> OutTransferQueue; - - using std::enable_shared_from_this::shared_from_this; - void receiveHandler(size_t bytesTransferred); - void distributeReceivedData(); - void rejectReceivePromises(const error::Error& e); - - void doSend(OutTransferQueue::iterator queueElementIter, common::Data::size_type offset); - void sendHandler(OutTransferQueue::iterator queueElementIter, common::Data::size_type offset, size_t bytesTransferred); + void enqueueReceive(common::DataBuffer buffer) override; + void enqueueSend(SendQueue::iterator queueElement) override; + void doSend(SendQueue::iterator queueElement, common::Data::size_type offset); + void sendHandler(SendQueue::iterator queueElement, common::Data::size_type offset, size_t bytesTransferred); usb::IAOAPDevice::Pointer aoapDevice_; - USBDataSink usbReceivedDataSink_; - - boost::asio::io_service::strand receiveStrand_; - InTransferQueue receiveQueue_; - - boost::asio::io_service::strand sendStrand_; - OutTransferQueue sendQueue_; static constexpr uint32_t cSendTimeoutMs = 10000; static constexpr uint32_t cReceiveTimeoutMs = 0; diff --git a/include/f1x/aasdk/USB/ConnectedAccessoriesEnumerator.hpp b/include/f1x/aasdk/USB/ConnectedAccessoriesEnumerator.hpp new file mode 100644 index 0000000..1a93f1c --- /dev/null +++ b/include/f1x/aasdk/USB/ConnectedAccessoriesEnumerator.hpp @@ -0,0 +1,58 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace usb +{ + +class ConnectedAccessoriesEnumerator: public IConnectedAccessoriesEnumerator, public std::enable_shared_from_this +{ +public: + ConnectedAccessoriesEnumerator(IUSBWrapper& usbWrapper, boost::asio::io_service& ioService, IAccessoryModeQueryChainFactory& queryChainFactory); + + void enumerate(Promise::Pointer promise) override; + void cancel() override; + +private: + using std::enable_shared_from_this::shared_from_this; + void queryNextDevice(); + DeviceHandle getNextDeviceHandle(); + void reset(); + + IUSBWrapper& usbWrapper_; + boost::asio::io_service::strand strand_; + IAccessoryModeQueryChainFactory& queryChainFactory_; + IAccessoryModeQueryChain::Pointer queryChain_; + Promise::Pointer promise_; + DeviceListHandle deviceListHandle_; + DeviceList::iterator actualDeviceIter_; +}; + +} +} +} diff --git a/include/f1x/aasdk/USB/IConnectedAccessoriesEnumerator.hpp b/include/f1x/aasdk/USB/IConnectedAccessoriesEnumerator.hpp new file mode 100644 index 0000000..f0a5fc9 --- /dev/null +++ b/include/f1x/aasdk/USB/IConnectedAccessoriesEnumerator.hpp @@ -0,0 +1,44 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace usb +{ + +class IConnectedAccessoriesEnumerator +{ +public: + typedef std::shared_ptr Pointer; + typedef io::Promise Promise; + + virtual ~IConnectedAccessoriesEnumerator() = default; + virtual void enumerate(Promise::Pointer promise) = 0; + virtual void cancel() = 0; +}; + +} +} +} diff --git a/include/f1x/aasdk/USB/USBHub.hpp b/include/f1x/aasdk/USB/USBHub.hpp index 1eaafed..ca05e84 100644 --- a/include/f1x/aasdk/USB/USBHub.hpp +++ b/include/f1x/aasdk/USB/USBHub.hpp @@ -19,7 +19,6 @@ #pragma once #include -#include #include #include #include diff --git a/include_ut/f1x/aasdk/TCP/UT/TCPEndpoint.mock.hpp b/include_ut/f1x/aasdk/TCP/UT/TCPEndpoint.mock.hpp new file mode 100644 index 0000000..5972406 --- /dev/null +++ b/include_ut/f1x/aasdk/TCP/UT/TCPEndpoint.mock.hpp @@ -0,0 +1,44 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ +namespace ut +{ + +class TCPEndpointMock: public ITCPEndpoint +{ +public: + MOCK_METHOD2(send, void(common::DataConstBuffer buffer, Promise::Pointer promise)); + MOCK_METHOD2(receive, void(common::DataBuffer buffer, Promise::Pointer promise)); + MOCK_METHOD0(stop, void()); +}; + +} +} +} +} diff --git a/include_ut/f1x/aasdk/TCP/UT/TCPEndpointPromiseHandler.mock.hpp b/include_ut/f1x/aasdk/TCP/UT/TCPEndpointPromiseHandler.mock.hpp new file mode 100644 index 0000000..4d7c182 --- /dev/null +++ b/include_ut/f1x/aasdk/TCP/UT/TCPEndpointPromiseHandler.mock.hpp @@ -0,0 +1,43 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ +namespace ut +{ + +class TCPEndpointPromiseHandlerMock +{ +public: + MOCK_METHOD1(onResolve, void(size_t transferredBytes)); + MOCK_METHOD1(onReject, void(const error::Error& e)); +}; + +} +} +} +} diff --git a/include_ut/f1x/aasdk/TCP/UT/TCPWrapper.mock.hpp b/include_ut/f1x/aasdk/TCP/UT/TCPWrapper.mock.hpp new file mode 100644 index 0000000..b0f1881 --- /dev/null +++ b/include_ut/f1x/aasdk/TCP/UT/TCPWrapper.mock.hpp @@ -0,0 +1,46 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ +namespace ut +{ + +class TCPWrapperMock: public ITCPWrapper +{ +public: + MOCK_METHOD3(asyncWrite, void(boost::asio::ip::tcp::socket& socket, common::DataConstBuffer buffer, Handler handler)); + MOCK_METHOD3(asyncRead, void(boost::asio::ip::tcp::socket& socket, common::DataBuffer buffer, Handler handler)); + MOCK_METHOD1(close, void(boost::asio::ip::tcp::socket& socket)); + MOCK_METHOD4(asyncConnect, void(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port, ConnectHandler handler)); + MOCK_METHOD3(connect, boost::system::error_code(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port)); +}; + +} +} +} +} diff --git a/include_ut/f1x/aasdk/USB/UT/ConnectedAccessoriesEnumeratorPromiseHandler.mock.hpp b/include_ut/f1x/aasdk/USB/UT/ConnectedAccessoriesEnumeratorPromiseHandler.mock.hpp new file mode 100644 index 0000000..23c45ec --- /dev/null +++ b/include_ut/f1x/aasdk/USB/UT/ConnectedAccessoriesEnumeratorPromiseHandler.mock.hpp @@ -0,0 +1,44 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace usb +{ +namespace ut +{ + +class ConnectedAccessoriesEnumeratorPromiseHandlerMock +{ +public: + MOCK_METHOD1(onResolve, void(bool result)); + MOCK_METHOD1(onReject, void(const error::Error& e)); +}; + +} +} +} +} diff --git a/src/Common/Data.cpp b/src/Common/Data.cpp index 989a4ef..2227a6b 100644 --- a/src/Common/Data.cpp +++ b/src/Common/Data.cpp @@ -65,6 +65,11 @@ bool DataBuffer::operator==(const std::nullptr_t&) const return data == nullptr || size == 0; } +bool DataBuffer::operator==(const DataBuffer& buffer) const +{ + return data == buffer.data && size == buffer.size; +} + DataConstBuffer::DataConstBuffer() : cdata(nullptr) , size(0) @@ -109,6 +114,11 @@ bool DataConstBuffer::operator==(const std::nullptr_t&) const return cdata == nullptr || size == 0; } +bool DataConstBuffer::operator==(const DataConstBuffer& buffer) const +{ + return cdata == buffer.cdata && size == buffer.size; +} + common::Data createData(const DataConstBuffer& buffer) { common::Data data; diff --git a/src/Error/Error.cpp b/src/Error/Error.cpp index b8cdbb6..b28c1db 100644 --- a/src/Error/Error.cpp +++ b/src/Error/Error.cpp @@ -57,7 +57,7 @@ const char* Error::what() const noexcept bool Error::operator!() const { - return code_ != ErrorCode::NONE || nativeCode_ != 0; + return code_ == ErrorCode::NONE; } bool Error::operator==(const Error& other) const @@ -65,6 +65,16 @@ bool Error::operator==(const Error& other) const return code_ == other.code_ && nativeCode_ == other.nativeCode_; } +bool Error::operator==(const ErrorCode& code) const +{ + return code_ == code; +} + +bool Error::operator!=(const ErrorCode& code) const +{ + return !operator==(code); +} + } } } diff --git a/src/Messenger/Messenger.cpp b/src/Messenger/Messenger.cpp index 17b267b..3266f21 100644 --- a/src/Messenger/Messenger.cpp +++ b/src/Messenger/Messenger.cpp @@ -133,9 +133,9 @@ void Messenger::rejectSendPromiseQueue(const error::Error& e) void Messenger::stop() { - this->rejectReceivePromiseQueue(error::Error(error::ErrorCode::OPERATION_ABORTED)); - this->rejectSendPromiseQueue(error::Error(error::ErrorCode::OPERATION_ABORTED)); - channelReceiveMessageQueue_.clear(); + receiveStrand_.dispatch([this, self = this->shared_from_this()]() { + channelReceiveMessageQueue_.clear(); + }); } } diff --git a/src/TCP/TCPEndpoint.cpp b/src/TCP/TCPEndpoint.cpp new file mode 100644 index 0000000..0ffa375 --- /dev/null +++ b/src/TCP/TCPEndpoint.cpp @@ -0,0 +1,75 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +TCPEndpoint::TCPEndpoint(ITCPWrapper& tcpWrapper, SocketPointer socket) + : tcpWrapper_(tcpWrapper) + , socket_(std::move(socket)) +{ + +} + +void TCPEndpoint::send(common::DataConstBuffer buffer, Promise::Pointer promise) +{ + tcpWrapper_.asyncWrite(*socket_, std::move(buffer), + std::bind(&TCPEndpoint::asyncOperationHandler, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2, + std::move(promise))); +} + +void TCPEndpoint::receive(common::DataBuffer buffer, Promise::Pointer promise) +{ + tcpWrapper_.asyncRead(*socket_, std::move(buffer), + std::bind(&TCPEndpoint::asyncOperationHandler, + this->shared_from_this(), + std::placeholders::_1, + std::placeholders::_2, + std::move(promise))); +} + +void TCPEndpoint::stop() +{ + tcpWrapper_.close(*socket_); +} + +void TCPEndpoint::asyncOperationHandler(const boost::system::error_code& ec, size_t bytesTransferred, Promise::Pointer promise) +{ + if(!ec) + { + promise->resolve(bytesTransferred); + } + else + { + auto error = ec == boost::asio::error::operation_aborted ? error::Error(error::ErrorCode::OPERATION_ABORTED) : error::Error(error::ErrorCode::TCP_TRANSFER, static_cast(ec.value())); + promise->reject(error); + } +} + +} +} +} diff --git a/src/TCP/TCPEndpoint.ut.cpp b/src/TCP/TCPEndpoint.ut.cpp new file mode 100644 index 0000000..0222eda --- /dev/null +++ b/src/TCP/TCPEndpoint.ut.cpp @@ -0,0 +1,132 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ +namespace ut +{ + +using ::testing::_; +using ::testing::SaveArg; + +class TCPEndpointUnitTest +{ +protected: + TCPEndpointUnitTest() + : socket_(std::make_shared(ioService_)) + , promise_(ITCPEndpoint::Promise::defer(ioService_)) + { + promise_->then(std::bind(&TCPEndpointPromiseHandlerMock::onResolve, &promiseHandlerMock_, std::placeholders::_1), + std::bind(&TCPEndpointPromiseHandlerMock::onReject, &promiseHandlerMock_, std::placeholders::_1)); + } + + TCPWrapperMock tcpWrapperMock_; + TCPEndpointPromiseHandlerMock promiseHandlerMock_; + boost::asio::io_service ioService_; + ITCPEndpoint::SocketPointer socket_; + ITCPEndpoint::Promise::Pointer promise_; +}; + +BOOST_FIXTURE_TEST_CASE(TCPEndpoint_Receive, TCPEndpointUnitTest) +{ + auto tcpEndpoint = std::make_shared(tcpWrapperMock_, std::move(socket_)); + + common::DataBuffer buffer; + ITCPWrapper::Handler handler; + EXPECT_CALL(tcpWrapperMock_, asyncRead(_, _, _)).WillOnce(DoAll(SaveArg<1>(&buffer), SaveArg<2>(&handler))); + + common::Data actualData(100, 0); + tcpEndpoint->receive(common::DataBuffer(actualData), std::move(promise_)); + + const common::Data expectedData(actualData.size(), 0x5F); + std::copy(expectedData.begin(), expectedData.end(), buffer.data); + + EXPECT_CALL(promiseHandlerMock_, onResolve(expectedData.size())); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + handler(boost::system::error_code(), expectedData.size()); + + ioService_.run(); + + BOOST_CHECK_EQUAL_COLLECTIONS(actualData.begin(), actualData.end(), expectedData.begin(), expectedData.end()); +} + +BOOST_FIXTURE_TEST_CASE(TCPEndpoint_ReceiveError, TCPEndpointUnitTest) +{ + auto tcpEndpoint = std::make_shared(tcpWrapperMock_, std::move(socket_)); + + common::DataBuffer buffer; + ITCPWrapper::Handler handler; + EXPECT_CALL(tcpWrapperMock_, asyncRead(_, _, _)).WillOnce(DoAll(SaveArg<1>(&buffer), SaveArg<2>(&handler))); + + common::Data actualData(100, 0); + tcpEndpoint->receive(common::DataBuffer(actualData), std::move(promise_)); + + EXPECT_CALL(promiseHandlerMock_, onResolve(_)).Times(0); + EXPECT_CALL(promiseHandlerMock_, onReject(error::Error(error::ErrorCode::TCP_TRANSFER, boost::asio::error::bad_descriptor))); + handler(boost::asio::error::bad_descriptor, 0); + + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPEndpoint_Send, TCPEndpointUnitTest) +{ + auto tcpEndpoint = std::make_shared(tcpWrapperMock_, std::move(socket_)); + + common::Data actualData(100, 0); + common::DataConstBuffer buffer(actualData); + ITCPWrapper::Handler handler; + EXPECT_CALL(tcpWrapperMock_, asyncWrite(_, buffer, _)).WillOnce(SaveArg<2>(&handler)); + tcpEndpoint->send(common::DataConstBuffer(actualData), std::move(promise_)); + + EXPECT_CALL(promiseHandlerMock_, onResolve(actualData.size())); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + handler(boost::system::error_code(), actualData.size()); + + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPEndpoint_SendError, TCPEndpointUnitTest) +{ + auto tcpEndpoint = std::make_shared(tcpWrapperMock_, std::move(socket_)); + + common::Data actualData(100, 0); + common::DataConstBuffer buffer(actualData); + ITCPWrapper::Handler handler; + EXPECT_CALL(tcpWrapperMock_, asyncWrite(_, buffer, _)).WillOnce(SaveArg<2>(&handler)); + tcpEndpoint->send(common::DataConstBuffer(actualData), std::move(promise_)); + + EXPECT_CALL(promiseHandlerMock_, onResolve(_)).Times(0); + EXPECT_CALL(promiseHandlerMock_, onReject(error::Error(error::ErrorCode::OPERATION_ABORTED))); + handler(boost::asio::error::operation_aborted, 0); + + ioService_.run(); +} + +} +} +} +} diff --git a/src/TCP/TCPWrapper.cpp b/src/TCP/TCPWrapper.cpp new file mode 100644 index 0000000..70048cf --- /dev/null +++ b/src/TCP/TCPWrapper.cpp @@ -0,0 +1,60 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace tcp +{ + +void TCPWrapper::asyncWrite(boost::asio::ip::tcp::socket& socket, common::DataConstBuffer buffer, Handler handler) +{ + boost::asio::async_write(socket, boost::asio::buffer(buffer.cdata, buffer.size), std::move(handler)); +} + +void TCPWrapper::asyncRead(boost::asio::ip::tcp::socket& socket, common::DataBuffer buffer, Handler handler) +{ + socket.async_receive(boost::asio::buffer(buffer.data, buffer.size), std::move(handler)); +} + +void TCPWrapper::close(boost::asio::ip::tcp::socket& socket) +{ + boost::system::error_code ec; + socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + socket.close(ec); +} + +void TCPWrapper::asyncConnect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port, ConnectHandler handler) +{ + socket.async_connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(hostname), port), std::move(handler)); +} + +boost::system::error_code TCPWrapper::connect(boost::asio::ip::tcp::socket& socket, const std::string& hostname, uint16_t port) +{ + boost::system::error_code ec; + socket.connect(boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(hostname), port), ec); + return ec; +} + +} +} +} diff --git a/src/Transport/USBDataSink.cpp b/src/Transport/DataSink.cpp similarity index 60% rename from src/Transport/USBDataSink.cpp rename to src/Transport/DataSink.cpp index 772054d..023f0b7 100644 --- a/src/Transport/USBDataSink.cpp +++ b/src/Transport/DataSink.cpp @@ -17,7 +17,7 @@ */ #include -#include +#include #include namespace f1x @@ -27,44 +27,45 @@ namespace aasdk namespace transport { -USBDataSink::USBDataSink() +DataSink::DataSink() : data_(common::cStaticDataSize) { } -common::DataBuffer USBDataSink::fill() +common::DataBuffer DataSink::fill() { const auto offset = data_.size(); data_.resize(data_.size() + cChunkSize); - return common::DataBuffer(&data_[offset], cChunkSize); + auto ptr = data_.is_linearized() ? &data_[offset] : data_.linearize() + offset; + return common::DataBuffer(ptr, cChunkSize); } -void USBDataSink::commit(common::Data::size_type size) +void DataSink::commit(common::Data::size_type size) { if(size > cChunkSize) { - throw error::Error(error::ErrorCode::USB_SINK_COMMIT_OVERFLOW); + throw error::Error(error::ErrorCode::DATA_SINK_COMMIT_OVERFLOW); } - data_.resize(data_.size() - (cChunkSize - size)); + data_.erase_end((cChunkSize - size)); } -common::Data::size_type USBDataSink::getAvailableSize() +common::Data::size_type DataSink::getAvailableSize() { return data_.size(); } -common::Data USBDataSink::consume(common::Data::size_type size) +common::Data DataSink::consume(common::Data::size_type size) { if(size > data_.size()) { - throw error::Error(error::ErrorCode::USB_SINK_CONSUME_UNDERFLOW); + throw error::Error(error::ErrorCode::DATA_SINK_CONSUME_UNDERFLOW); } - common::Data data; - common::copy(data, common::DataConstBuffer(&data_[0], size)); - data_.erase(data_.begin(), data_.begin() + size); + common::Data data(size, 0); + std::copy(data_.begin(), data_.begin() + size, data.begin()); + data_.erase_begin(size); return data; } diff --git a/src/Transport/TCPTransport.cpp b/src/Transport/TCPTransport.cpp new file mode 100644 index 0000000..c38cc5c --- /dev/null +++ b/src/Transport/TCPTransport.cpp @@ -0,0 +1,88 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include + +namespace f1x +{ +namespace aasdk +{ +namespace transport +{ + +TCPTransport::TCPTransport(boost::asio::io_service& ioService, tcp::ITCPEndpoint::Pointer tcpEndpoint) + : Transport(ioService) + , tcpEndpoint_(std::move(tcpEndpoint)) +{ + +} + +void TCPTransport::enqueueReceive(common::DataBuffer buffer) +{ + auto receivePromise = tcp::ITCPEndpoint::Promise::defer(receiveStrand_); + receivePromise->then([this, self = this->shared_from_this()](auto bytesTransferred) { + this->receiveHandler(bytesTransferred); + }, + [this, self = this->shared_from_this()](auto e) { + this->rejectReceivePromises(e); + }); + + tcpEndpoint_->receive(buffer, std::move(receivePromise)); +} + +void TCPTransport::enqueueSend(SendQueue::iterator queueElement) +{ + auto sendPromise = tcp::ITCPEndpoint::Promise::defer(sendStrand_); + + sendPromise->then([this, self = this->shared_from_this(), queueElement](auto) { + this->sendHandler(queueElement, error::Error()); + }, + [this, self = this->shared_from_this(), queueElement](auto e) { + this->sendHandler(queueElement, e); + }); + + tcpEndpoint_->send(common::DataConstBuffer(queueElement->first), std::move(sendPromise)); +} + +void TCPTransport::stop() +{ + tcpEndpoint_->stop(); +} + +void TCPTransport::sendHandler(SendQueue::iterator queueElement, const error::Error& e) +{ + if(!e) + { + queueElement->second->resolve(); + } + else + { + queueElement->second->reject(e); + } + + sendQueue_.erase(queueElement); + + if(!sendQueue_.empty()) + { + this->enqueueSend(sendQueue_.begin()); + } +} + +} +} +} diff --git a/src/Transport/TCPTransport.ut.cpp b/src/Transport/TCPTransport.ut.cpp new file mode 100644 index 0000000..58d00e4 --- /dev/null +++ b/src/Transport/TCPTransport.ut.cpp @@ -0,0 +1,279 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include +#include +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace transport +{ +namespace ut +{ + +using ::testing::ReturnRef; +using ::testing::SaveArg; +using ::testing::_; +using ::testing::AtLeast; + +class TCPTransportUnitTest +{ +protected: + TCPTransportUnitTest() + : receivePromise_(ITransport::ReceivePromise::defer(ioService_)) + , sendPromise_(ITransport::SendPromise::defer(ioService_)) + , tcpEndpoint_(&tcpEndpointMock_, [](auto*) {}) + { + receivePromise_->then(std::bind(&TransportReceivePromiseHandlerMock::onResolve, &receivePromiseHandlerMock_, std::placeholders::_1), + std::bind(&TransportReceivePromiseHandlerMock::onReject, &receivePromiseHandlerMock_, std::placeholders::_1)); + + sendPromise_->then(std::bind(&TransportSendPromiseHandlerMock::onResolve, &sendPromiseHandlerMock_), + std::bind(&TransportSendPromiseHandlerMock::onReject, &sendPromiseHandlerMock_, std::placeholders::_1)); + } + + boost::asio::io_service ioService_; + tcp::ut::TCPEndpointMock tcpEndpointMock_; + TransportReceivePromiseHandlerMock receivePromiseHandlerMock_; + ITransport::ReceivePromise::Pointer receivePromise_; + TransportSendPromiseHandlerMock sendPromiseHandlerMock_; + ITransport::SendPromise::Pointer sendPromise_; + tcp::ITCPEndpoint::Pointer tcpEndpoint_; +}; + +BOOST_FIXTURE_TEST_CASE(TCPTransport_ReceiveAtOnce, TCPTransportUnitTest) +{ + const size_t receiveSize = 100; + + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + common::DataBuffer dataBuffer; + EXPECT_CALL(tcpEndpointMock_, receive(_, _)).WillOnce(DoAll(SaveArg<0>(&dataBuffer), SaveArg<1>(&tcpEndpointPromise))); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + transport->receive(receiveSize, std::move(receivePromise_)); + ioService_.run(); + ioService_.reset(); + + BOOST_TEST(dataBuffer.size >= receiveSize); + common::Data expectedData(receiveSize, 0x5E); + std::copy(expectedData.begin(), expectedData.end(), dataBuffer.data); + + EXPECT_CALL(receivePromiseHandlerMock_, onResolve(expectedData)).Times(1); + EXPECT_CALL(receivePromiseHandlerMock_, onReject(_)).Times(0); + tcpEndpointPromise->resolve(receiveSize); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_ReceiveInPieces, TCPTransportUnitTest) +{ + const size_t stepsCount = 100; + const size_t receiveSize = 1000 * stepsCount; + const size_t stepSize = receiveSize / stepsCount; + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + transport->receive(receiveSize, std::move(receivePromise_)); + + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + common::DataBuffer dataBuffer; + EXPECT_CALL(tcpEndpointMock_, receive(_, _)).Times(AtLeast(stepsCount)) + .WillRepeatedly(DoAll(SaveArg<0>(&dataBuffer), SaveArg<1>(&tcpEndpointPromise))); + + common::Data expectedData(receiveSize, 0x5E); + EXPECT_CALL(receivePromiseHandlerMock_, onResolve(expectedData)).Times(1); + EXPECT_CALL(receivePromiseHandlerMock_, onReject(_)).Times(0); + + for(size_t i = 0; i < stepsCount; ++i) + { + ioService_.run(); + ioService_.reset(); + + BOOST_TEST(dataBuffer.size >= stepSize); + + std::fill(dataBuffer.data, dataBuffer.data + stepSize, 0x5E); + tcpEndpointPromise->resolve(stepSize); + ioService_.run(); + } +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_OnlyOneReceiveAtATime, TCPTransportUnitTest) +{ + const size_t receiveSize = 200; + const size_t stepSize = receiveSize / 2; + + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + common::DataBuffer dataBuffer; + EXPECT_CALL(tcpEndpointMock_, receive(_, _)).WillOnce(DoAll(SaveArg<0>(&dataBuffer), SaveArg<1>(&tcpEndpointPromise))); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + transport->receive(stepSize, std::move(receivePromise_)); + ioService_.run(); + ioService_.reset(); + + BOOST_TEST(dataBuffer.size >= receiveSize); + std::fill(dataBuffer.data, dataBuffer.data + stepSize, 0x5E); + std::fill(dataBuffer.data + stepSize, dataBuffer.data + receiveSize, 0x5F); + + auto secondPromise = ITransport::ReceivePromise::defer(ioService_); + TransportReceivePromiseHandlerMock secondPromiseHandlerMock; + secondPromise->then(std::bind(&TransportReceivePromiseHandlerMock::onResolve, &secondPromiseHandlerMock, std::placeholders::_1), + std::bind(&TransportReceivePromiseHandlerMock::onReject, &secondPromiseHandlerMock, std::placeholders::_1)); + + transport->receive(stepSize, std::move(secondPromise)); + ioService_.run(); + ioService_.reset(); + + common::Data expectedData(stepSize, 0x5E); + EXPECT_CALL(receivePromiseHandlerMock_, onResolve(expectedData)).Times(1); + EXPECT_CALL(receivePromiseHandlerMock_, onReject(_)).Times(0); + + common::Data secondExpectedData(stepSize, 0x5F); + EXPECT_CALL(secondPromiseHandlerMock, onResolve(secondExpectedData)).Times(1); + EXPECT_CALL(secondPromiseHandlerMock, onReject(_)).Times(0); + + tcpEndpointPromise->resolve(receiveSize); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_ReceiveError, TCPTransportUnitTest) +{ + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + EXPECT_CALL(tcpEndpointMock_, receive(_, _)).WillOnce(SaveArg<1>(&tcpEndpointPromise)); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + transport->receive(1000, std::move(receivePromise_)); + + auto secondPromise = ITransport::ReceivePromise::defer(ioService_); + secondPromise->then(std::bind(&TransportReceivePromiseHandlerMock::onResolve, &receivePromiseHandlerMock_, std::placeholders::_1), + std::bind(&TransportReceivePromiseHandlerMock::onReject, &receivePromiseHandlerMock_, std::placeholders::_1)); + + transport->receive(1000, std::move(secondPromise)); + ioService_.run(); + ioService_.reset(); + + const error::Error e(error::ErrorCode::TCP_TRANSFER, 11); + EXPECT_CALL(receivePromiseHandlerMock_, onResolve(_)).Times(0); + EXPECT_CALL(receivePromiseHandlerMock_, onReject(e)).Times(2); + + tcpEndpointPromise->reject(e); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_Send, TCPTransportUnitTest) +{ + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + common::DataConstBuffer buffer; + EXPECT_CALL(tcpEndpointMock_, send(_, _)).WillOnce(DoAll(SaveArg<0>(&buffer), SaveArg<1>(&tcpEndpointPromise))); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + const common::Data expectedData(1000, 0x5E); + transport->send(expectedData, std::move(sendPromise_)); + ioService_.run(); + ioService_.reset(); + + common::Data actualData(buffer.cdata, buffer.cdata + buffer.size); + BOOST_CHECK_EQUAL_COLLECTIONS(actualData.begin(), actualData.end(), expectedData.begin(), expectedData.end()); + + EXPECT_CALL(sendPromiseHandlerMock_, onReject(_)).Times(0); + EXPECT_CALL(sendPromiseHandlerMock_, onResolve()); + tcpEndpointPromise->resolve(expectedData.size()); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_OnlyOneSendAtATime, TCPTransportUnitTest) +{ + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + common::DataConstBuffer buffer; + EXPECT_CALL(tcpEndpointMock_, send(_, _)).Times(2).WillRepeatedly(DoAll(SaveArg<0>(&buffer), SaveArg<1>(&tcpEndpointPromise))); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + const common::Data expectedData1(1000, 0x5E); + transport->send(expectedData1, std::move(sendPromise_)); + ioService_.run(); + ioService_.reset(); + + const common::Data expectedData2(3000, 0x5F); + + auto secondSendPromise = ITransport::SendPromise::defer(ioService_); + TransportSendPromiseHandlerMock secondSendPromiseHandlerMock; + secondSendPromise->then(std::bind(&TransportSendPromiseHandlerMock::onResolve, &secondSendPromiseHandlerMock), + std::bind(&TransportSendPromiseHandlerMock::onReject, &secondSendPromiseHandlerMock, std::placeholders::_1)); + + transport->send(expectedData2, std::move(secondSendPromise)); + ioService_.run(); + ioService_.reset(); + + common::Data actualData1(buffer.cdata, buffer.cdata + buffer.size); + BOOST_CHECK_EQUAL_COLLECTIONS(actualData1.begin(), actualData1.end(), expectedData1.begin(), expectedData1.end()); + + EXPECT_CALL(sendPromiseHandlerMock_, onReject(_)).Times(0); + EXPECT_CALL(sendPromiseHandlerMock_, onResolve()); + tcpEndpointPromise->resolve(expectedData1.size()); + ioService_.run(); + ioService_.reset(); + + common::Data actualData2(buffer.cdata, buffer.cdata + buffer.size); + BOOST_CHECK_EQUAL_COLLECTIONS(actualData2.begin(), actualData2.end(), expectedData2.begin(), expectedData2.end()); + + EXPECT_CALL(secondSendPromiseHandlerMock, onReject(_)).Times(0); + EXPECT_CALL(secondSendPromiseHandlerMock, onResolve()); + tcpEndpointPromise->resolve(expectedData2.size()); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(TCPTransport_SendError, TCPTransportUnitTest) +{ + tcp::ITCPEndpoint::Promise::Pointer tcpEndpointPromise; + EXPECT_CALL(tcpEndpointMock_, send(_, _)).Times(2).WillRepeatedly(SaveArg<1>(&tcpEndpointPromise)); + + auto transport(std::make_shared(ioService_, tcpEndpoint_)); + const common::Data expectedData1(1000, 0x5E); + transport->send(expectedData1, std::move(sendPromise_)); + ioService_.run(); + ioService_.reset(); + + auto secondSendPromise = ITransport::SendPromise::defer(ioService_); + TransportSendPromiseHandlerMock secondSendPromiseHandlerMock; + secondSendPromise->then(std::bind(&TransportSendPromiseHandlerMock::onResolve, &secondSendPromiseHandlerMock), + std::bind(&TransportSendPromiseHandlerMock::onReject, &secondSendPromiseHandlerMock, std::placeholders::_1)); + + const common::Data expectedData2(3000, 0x5F); + transport->send(expectedData2, std::move(secondSendPromise)); + ioService_.run(); + ioService_.reset(); + + const error::Error e(error::ErrorCode::USB_TRANSFER, 15); + EXPECT_CALL(sendPromiseHandlerMock_, onReject(e)); + EXPECT_CALL(sendPromiseHandlerMock_, onResolve()).Times(0); + tcpEndpointPromise->reject(e); + ioService_.run(); + ioService_.reset(); + + EXPECT_CALL(secondSendPromiseHandlerMock, onReject(_)).Times(0); + EXPECT_CALL(secondSendPromiseHandlerMock, onResolve()); + tcpEndpointPromise->resolve(expectedData2.size()); + ioService_.run(); +} + +} +} +} +} diff --git a/src/Transport/Transport.cpp b/src/Transport/Transport.cpp new file mode 100644 index 0000000..9f64242 --- /dev/null +++ b/src/Transport/Transport.cpp @@ -0,0 +1,109 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include + +namespace f1x +{ +namespace aasdk +{ +namespace transport +{ + +Transport::Transport(boost::asio::io_service& ioService) + : receiveStrand_(ioService) + , sendStrand_(ioService) +{} + +void Transport::receive(size_t size, ReceivePromise::Pointer promise) +{ + receiveStrand_.dispatch([this, self = this->shared_from_this(), size, promise = std::move(promise)]() mutable { + receiveQueue_.emplace_back(std::make_pair(size, std::move(promise))); + + if(receiveQueue_.size() == 1) + { + try + { + this->distributeReceivedData(); + } + catch(const error::Error& e) + { + this->rejectReceivePromises(e); + } + } + }); +} + +void Transport::receiveHandler(size_t bytesTransferred) +{ + try + { + receivedDataSink_.commit(bytesTransferred); + this->distributeReceivedData(); + } + catch(const error::Error& e) + { + this->rejectReceivePromises(e); + } +} + +void Transport::distributeReceivedData() +{ + for(auto queueElement = receiveQueue_.begin(); queueElement != receiveQueue_.end();) + { + if(receivedDataSink_.getAvailableSize() < queueElement->first) + { + auto buffer = receivedDataSink_.fill(); + this->enqueueReceive(std::move(buffer)); + + break; + } + else + { + auto data(receivedDataSink_.consume(queueElement->first)); + queueElement->second->resolve(std::move(data)); + queueElement = receiveQueue_.erase(queueElement); + } + } +} + +void Transport::rejectReceivePromises(const error::Error& e) +{ + for(auto& queueElement : receiveQueue_) + { + queueElement.second->reject(e); + } + + receiveQueue_.clear(); +} + +void Transport::send(common::Data data, SendPromise::Pointer promise) +{ + sendStrand_.dispatch([this, self = this->shared_from_this(), data = std::move(data), promise = std::move(promise)]() mutable { + sendQueue_.emplace_back(std::make_pair(std::move(data), std::move(promise))); + + if(sendQueue_.size() == 1) + { + this->enqueueSend(sendQueue_.begin()); + } + }); +} + +} +} +} diff --git a/src/Transport/USBTransport.cpp b/src/Transport/USBTransport.cpp index 89505d5..7a8a1cc 100644 --- a/src/Transport/USBTransport.cpp +++ b/src/Transport/USBTransport.cpp @@ -26,116 +26,57 @@ namespace transport { USBTransport::USBTransport(boost::asio::io_service& ioService, usb::IAOAPDevice::Pointer aoapDevice) - : aoapDevice_(std::move(aoapDevice)) - , receiveStrand_(ioService) - , sendStrand_(ioService) + : Transport(ioService) + , aoapDevice_(std::move(aoapDevice)) {} -void USBTransport::receive(size_t size, ReceivePromise::Pointer promise) +void USBTransport::enqueueReceive(common::DataBuffer buffer) { - receiveStrand_.dispatch([this, self = this->shared_from_this(), size, promise = std::move(promise)]() mutable { - receiveQueue_.emplace_back(std::make_pair(size, std::move(promise))); + auto usbEndpointPromise = usb::IUSBEndpoint::Promise::defer(receiveStrand_); + usbEndpointPromise->then([this, self = this->shared_from_this()](auto bytesTransferred) { + this->receiveHandler(bytesTransferred); + }, + [this, self = this->shared_from_this()](auto e) { + this->rejectReceivePromises(e); + }); - if(receiveQueue_.size() == 1) - { - try - { - this->distributeReceivedData(); - } - catch(const error::Error& e) - { - this->rejectReceivePromises(e); - } - } - }); + aoapDevice_->getInEndpoint().bulkTransfer(buffer, cReceiveTimeoutMs, std::move(usbEndpointPromise)); } -void USBTransport::receiveHandler(size_t bytesTransferred) +void USBTransport::enqueueSend(SendQueue::iterator queueElement) { - try - { - usbReceivedDataSink_.commit(bytesTransferred); - this->distributeReceivedData(); - } - catch(const error::Error& e) - { - this->rejectReceivePromises(e); - } + this->doSend(queueElement, 0); } -void USBTransport::distributeReceivedData() -{ - for(auto queueElement = receiveQueue_.begin(); queueElement != receiveQueue_.end();) - { - if(usbReceivedDataSink_.getAvailableSize() < queueElement->first) - { - auto buffer = usbReceivedDataSink_.fill(); - - auto usbEndpointPromise = usb::IUSBEndpoint::Promise::defer(receiveStrand_); - usbEndpointPromise->then(std::bind(&USBTransport::receiveHandler, this->shared_from_this(), std::placeholders::_1), - std::bind(&USBTransport::rejectReceivePromises, this->shared_from_this(), std::placeholders::_1)); - aoapDevice_->getInEndpoint().bulkTransfer(buffer, cReceiveTimeoutMs, std::move(usbEndpointPromise)); - break; - } - else - { - auto data(usbReceivedDataSink_.consume(queueElement->first)); - queueElement->second->resolve(std::move(data)); - queueElement = receiveQueue_.erase(queueElement); - } - } -} - -void USBTransport::rejectReceivePromises(const error::Error& e) -{ - for(auto& queueElement : receiveQueue_) - { - queueElement.second->reject(e); - } - - receiveQueue_.clear(); -} - -void USBTransport::send(common::Data data, SendPromise::Pointer promise) -{ - sendStrand_.dispatch([this, self = this->shared_from_this(), data = std::move(data), promise = std::move(promise)]() mutable { - sendQueue_.emplace_back(std::make_pair(std::move(data), std::move(promise))); - - if(sendQueue_.size() == 1) - { - this->doSend(sendQueue_.begin(), 0); - } - }); -} - -void USBTransport::doSend(OutTransferQueue::iterator queueElementIter, common::Data::size_type offset) +void USBTransport::doSend(SendQueue::iterator queueElement, common::Data::size_type offset) { auto usbEndpointPromise = usb::IUSBEndpoint::Promise::defer(sendStrand_); - usbEndpointPromise->then([this, self = this->shared_from_this(), queueElementIter, offset](size_t bytesTransferred) mutable { - this->sendHandler(queueElementIter, offset, bytesTransferred); + usbEndpointPromise->then([this, self = this->shared_from_this(), queueElement, offset](size_t bytesTransferred) mutable { + this->sendHandler(queueElement, offset, bytesTransferred); }, - [this, self = this->shared_from_this(), queueElementIter](const error::Error& e) mutable { - queueElementIter->second->reject(e); - sendQueue_.erase(queueElementIter); + [this, self = this->shared_from_this(), queueElement](const error::Error& e) mutable { + queueElement->second->reject(e); + sendQueue_.erase(queueElement); if(!sendQueue_.empty()) { this->doSend(sendQueue_.begin(), 0); } }); - aoapDevice_->getOutEndpoint().bulkTransfer(common::DataBuffer(queueElementIter->first, offset), cSendTimeoutMs, std::move(usbEndpointPromise)); + + aoapDevice_->getOutEndpoint().bulkTransfer(common::DataBuffer(queueElement->first, offset), cSendTimeoutMs, std::move(usbEndpointPromise)); } -void USBTransport::sendHandler(OutTransferQueue::iterator queueElementIter, common::Data::size_type offset, size_t bytesTransferred) +void USBTransport::sendHandler(SendQueue::iterator queueElement, common::Data::size_type offset, size_t bytesTransferred) { - if(offset + bytesTransferred < queueElementIter->first.size()) + if(offset + bytesTransferred < queueElement->first.size()) { - this->doSend(queueElementIter, offset + bytesTransferred); + this->doSend(queueElement, offset + bytesTransferred); } else { - queueElementIter->second->resolve(); - sendQueue_.erase(queueElementIter); + queueElement->second->resolve(); + sendQueue_.erase(queueElement); if(!sendQueue_.empty()) { diff --git a/src/Transport/USBTransport.ut.cpp b/src/Transport/USBTransport.ut.cpp index 7205271..a6d41fa 100644 --- a/src/Transport/USBTransport.ut.cpp +++ b/src/Transport/USBTransport.ut.cpp @@ -279,8 +279,7 @@ BOOST_FIXTURE_TEST_CASE(USBTransport_OnlyOneSendAtATime, USBTransportUnitTest) BOOST_FIXTURE_TEST_CASE(USBTransport_SendError, USBTransportUnitTest) { usb::IUSBEndpoint::Promise::Pointer usbEndpointPromise; - common::DataBuffer buffer; - EXPECT_CALL(outEndpointMock_, bulkTransfer(_, _, _)).Times(2).WillRepeatedly(DoAll(SaveArg<0>(&buffer), SaveArg<2>(&usbEndpointPromise))); + EXPECT_CALL(outEndpointMock_, bulkTransfer(_, _, _)).Times(2).WillRepeatedly(SaveArg<2>(&usbEndpointPromise)); USBTransport::Pointer transport(std::make_shared(ioService_, aoapDevice_)); const common::Data expectedData1(1000, 0x5E); @@ -288,13 +287,12 @@ BOOST_FIXTURE_TEST_CASE(USBTransport_SendError, USBTransportUnitTest) ioService_.run(); ioService_.reset(); - const common::Data expectedData2(3000, 0x5F); - auto secondSendPromise = ITransport::SendPromise::defer(ioService_); TransportSendPromiseHandlerMock secondSendPromiseHandlerMock; secondSendPromise->then(std::bind(&TransportSendPromiseHandlerMock::onResolve, &secondSendPromiseHandlerMock), std::bind(&TransportSendPromiseHandlerMock::onReject, &secondSendPromiseHandlerMock, std::placeholders::_1)); + const common::Data expectedData2(3000, 0x5F); transport->send(expectedData2, std::move(secondSendPromise)); ioService_.run(); ioService_.reset(); diff --git a/src/USB/AccessoryModeQueryChain.cpp b/src/USB/AccessoryModeQueryChain.cpp index 6ca3ffe..5b4b4fe 100644 --- a/src/USB/AccessoryModeQueryChain.cpp +++ b/src/USB/AccessoryModeQueryChain.cpp @@ -66,11 +66,13 @@ void AccessoryModeQueryChain::start(DeviceHandle handle, Promise::Pointer promis void AccessoryModeQueryChain::cancel() { - if(activeQuery_ != nullptr) - { - activeQuery_->cancel(); - activeQuery_.reset(); - } + strand_.dispatch([this, self = this->shared_from_this()]() { + if(activeQuery_ != nullptr) + { + activeQuery_->cancel(); + activeQuery_.reset(); + } + }); } void AccessoryModeQueryChain::startQuery(AccessoryModeQueryType queryType, IUSBEndpoint::Pointer usbEndpoint, IAccessoryModeQuery::Promise::Pointer queryPromise) diff --git a/src/USB/ConnectedAccessoriesEnumerator.cpp b/src/USB/ConnectedAccessoriesEnumerator.cpp new file mode 100644 index 0000000..75758fa --- /dev/null +++ b/src/USB/ConnectedAccessoriesEnumerator.cpp @@ -0,0 +1,134 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include + +namespace f1x +{ +namespace aasdk +{ +namespace usb +{ + +ConnectedAccessoriesEnumerator::ConnectedAccessoriesEnumerator(IUSBWrapper& usbWrapper, boost::asio::io_service& ioService, IAccessoryModeQueryChainFactory& queryChainFactory) + : usbWrapper_(usbWrapper) + , strand_(ioService) + , queryChainFactory_(queryChainFactory) +{ + +} + +void ConnectedAccessoriesEnumerator::enumerate(Promise::Pointer promise) +{ + strand_.dispatch([this, self = this->shared_from_this(), promise = std::move(promise)]() mutable { + if(promise_ != nullptr) + { + promise->reject(error::Error(error::ErrorCode::OPERATION_IN_PROGRESS)); + } + else + { + promise_ = std::move(promise); + + auto result = usbWrapper_.getDeviceList(deviceListHandle_); + + if(result < 0) + { + promise_->reject(error::Error(error::ErrorCode::USB_LIST_DEVICES)); + } + else if(deviceListHandle_->empty()) + { + promise_->resolve(false); + } + else + { + actualDeviceIter_ = deviceListHandle_->begin(); + this->queryNextDevice(); + } + } + }); +} + +void ConnectedAccessoriesEnumerator::cancel() +{ + strand_.dispatch([this, self = this->shared_from_this()]() mutable { + if(queryChain_ != nullptr) + { + queryChain_->cancel(); + queryChain_.reset(); + } + }); +} + +void ConnectedAccessoriesEnumerator::queryNextDevice() +{ + auto deviceHandle = this->getNextDeviceHandle(); + + if(deviceHandle != nullptr) + { + queryChain_ = queryChainFactory_.create(); + auto queryChainPromise = IAccessoryModeQueryChain::Promise::defer(strand_); + + queryChainPromise->then([this, self = this->shared_from_this()](DeviceHandle) mutable { + promise_->resolve(true); + this->reset(); + }, + [this, self = this->shared_from_this()](const error::Error& e) mutable { + if(e != error::ErrorCode::OPERATION_ABORTED) + { + this->queryNextDevice(); + } + }); + + queryChain_->start(std::move(deviceHandle), std::move(queryChainPromise)); + } + else if(actualDeviceIter_ == deviceListHandle_->end()) + { + promise_->resolve(false); + this->reset(); + } +} + +DeviceHandle ConnectedAccessoriesEnumerator::getNextDeviceHandle() +{ + DeviceHandle handle; + + while(actualDeviceIter_ != deviceListHandle_->end()) + { + auto openResult = usbWrapper_.open(*actualDeviceIter_, handle); + ++actualDeviceIter_; + + if(openResult == 0) + { + break; + } + } + + return handle; +} + +void ConnectedAccessoriesEnumerator::reset() +{ + queryChain_.reset(); + deviceListHandle_.reset(); + actualDeviceIter_ = DeviceList::iterator(); + promise_.reset(); +} + +} +} +} diff --git a/src/USB/ConnectedAccessoriesEnumerator.ut.cpp b/src/USB/ConnectedAccessoriesEnumerator.ut.cpp new file mode 100644 index 0000000..297b4d4 --- /dev/null +++ b/src/USB/ConnectedAccessoriesEnumerator.ut.cpp @@ -0,0 +1,211 @@ +/* +* This file is part of aasdk library project. +* Copyright (C) 2018 f1x.studio (Michal Szwaj) +* +* aasdk is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. + +* aasdk is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with aasdk. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +namespace f1x +{ +namespace aasdk +{ +namespace usb +{ +namespace ut +{ + +using ::testing::_; +using ::testing::SetArgReferee; +using ::testing::Return; +using ::testing::SaveArg; + +class ConnectedAccessoriesEnumeratorUnitTest +{ +protected: + ConnectedAccessoriesEnumeratorUnitTest() + : queryChain_(&queryChainMock_, [](auto*) {}) + , deviceListHandle_(&deviceList_, [](auto*) {}) + , device_(reinterpret_cast(-1)) + , deviceHandle_(reinterpret_cast(&dummyDeviceHandle_), [](auto*) {}) + , promise_(IConnectedAccessoriesEnumerator::Promise::defer(ioService_)) + { + promise_->then(std::bind(&ConnectedAccessoriesEnumeratorPromiseHandlerMock::onResolve, &promiseHandlerMock_, std::placeholders::_1), + std::bind(&ConnectedAccessoriesEnumeratorPromiseHandlerMock::onReject, &promiseHandlerMock_, std::placeholders::_1)); + } + + boost::asio::io_service ioService_; + USBWrapperMock usbWrapperMock_; + AccessoryModeQueryChainFactoryMock queryChainFactoryMock_; + AccessoryModeQueryChainMock queryChainMock_; + IAccessoryModeQueryChain::Pointer queryChain_; + DeviceList deviceList_; + DeviceListHandle deviceListHandle_; + libusb_device* device_; + USBWrapperMock::DummyDeviceHandle dummyDeviceHandle_; + DeviceHandle deviceHandle_; + ConnectedAccessoriesEnumeratorPromiseHandlerMock promiseHandlerMock_; + IConnectedAccessoriesEnumerator::Promise::Pointer promise_; +}; + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_FirstDeviceIsAOAPCapables, ConnectedAccessoriesEnumeratorUnitTest) +{ + deviceList_.push_back(reinterpret_cast(1)); + EXPECT_CALL(queryChainFactoryMock_, create()).WillOnce(Return(queryChain_)); + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(0))); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + EXPECT_CALL(usbWrapperMock_, open(*deviceList_.begin(), _)).WillOnce(DoAll(SetArgReferee<1>(deviceHandle_), Return(0))); + + IAccessoryModeQueryChain::Promise::Pointer queryChainPromise; + EXPECT_CALL(queryChainMock_, start(deviceHandle_, _)).WillOnce(SaveArg<1>(&queryChainPromise)); + ioService_.run(); + ioService_.reset(); + + EXPECT_CALL(promiseHandlerMock_, onResolve(true)); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + queryChainPromise->resolve(deviceHandle_); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_SecondDeviceIsAOAPCapable, ConnectedAccessoriesEnumeratorUnitTest) +{ + deviceList_.push_back(reinterpret_cast(1)); + deviceList_.push_back(reinterpret_cast(2)); + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + + EXPECT_CALL(queryChainFactoryMock_, create()).Times(deviceList_.size()).WillRepeatedly(Return(queryChain_)); + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(0))); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + EXPECT_CALL(usbWrapperMock_, open(*deviceList_.begin(), _)).WillOnce(DoAll(SetArgReferee<1>(deviceHandle_), Return(0))); + IAccessoryModeQueryChain::Promise::Pointer queryChainPromise; + EXPECT_CALL(queryChainMock_, start(deviceHandle_, _)).WillRepeatedly(SaveArg<1>(&queryChainPromise)); + ioService_.run(); + ioService_.reset(); + + EXPECT_CALL(promiseHandlerMock_, onResolve(_)).Times(0); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + + // open second device + USBWrapperMock::DummyDeviceHandle dummyDeviceHandle2; + DeviceHandle deviceHandle2(reinterpret_cast(&dummyDeviceHandle2), [](auto*) {}); + EXPECT_CALL(usbWrapperMock_, open(*(++deviceList_.begin()), _)).WillOnce(DoAll(SetArgReferee<1>(deviceHandle2), Return(0))); + + IAccessoryModeQueryChain::Promise::Pointer queryChainPromise2; + EXPECT_CALL(queryChainMock_, start(deviceHandle2, _)).WillRepeatedly(SaveArg<1>(&queryChainPromise2)); + + queryChainPromise->reject(error::Error(error::ErrorCode::USB_AOAP_PROTOCOL_VERSION)); + ioService_.run(); + ioService_.reset(); + + EXPECT_CALL(promiseHandlerMock_, onResolve(true)); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + queryChainPromise2->resolve(deviceHandle2); + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_NoAOAPCapableDevice, ConnectedAccessoriesEnumeratorUnitTest) +{ + for(size_t i = 1; i < 1000; ++i) + { + deviceList_.push_back(reinterpret_cast(i)); + } + + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(0))); + EXPECT_CALL(queryChainFactoryMock_, create()).Times(deviceList_.size()).WillRepeatedly(Return(queryChain_)); + + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + EXPECT_CALL(promiseHandlerMock_, onResolve(false)); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + + for(const auto& device : deviceList_) + { + USBWrapperMock::DummyDeviceHandle dummyDeviceHandle; + DeviceHandle deviceHandle(reinterpret_cast(&dummyDeviceHandle), [](auto*) {}); + EXPECT_CALL(usbWrapperMock_, open(device, _)).WillOnce(DoAll(SetArgReferee<1>(deviceHandle), Return(0))); + + IAccessoryModeQueryChain::Promise::Pointer queryChainPromise; + EXPECT_CALL(queryChainMock_, start(deviceHandle, _)).WillRepeatedly(SaveArg<1>(&queryChainPromise)); + + ioService_.run(); + ioService_.reset(); + + queryChainPromise->reject(error::Error(error::ErrorCode::USB_AOAP_PROTOCOL_VERSION)); + } + + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_GetDeviceListFailed, ConnectedAccessoriesEnumeratorUnitTest) +{ + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(-1))); + EXPECT_CALL(promiseHandlerMock_, onResolve(_)).Times(0); + EXPECT_CALL(promiseHandlerMock_, onReject(error::Error(error::ErrorCode::USB_LIST_DEVICES))); + + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_EmptyDevicesList, ConnectedAccessoriesEnumeratorUnitTest) +{ + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(0))); + EXPECT_CALL(promiseHandlerMock_, onResolve(false)); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + ioService_.run(); +} + +BOOST_FIXTURE_TEST_CASE(ConnectedAccessoriesEnumerator_OpenDeviceFailed, ConnectedAccessoriesEnumeratorUnitTest) +{ + for(size_t i = 1; i < 1000; ++i) + { + deviceList_.push_back(reinterpret_cast(i)); + } + + EXPECT_CALL(usbWrapperMock_, getDeviceList(_)).WillOnce(DoAll(SetArgReferee<0>(deviceListHandle_), Return(0))); + + auto connectedAccessoriesEnumerator(std::make_shared(usbWrapperMock_, ioService_, queryChainFactoryMock_)); + connectedAccessoriesEnumerator->enumerate(std::move(promise_)); + + EXPECT_CALL(promiseHandlerMock_, onResolve(false)); + EXPECT_CALL(promiseHandlerMock_, onReject(_)).Times(0); + + for(const auto& device : deviceList_) + { + EXPECT_CALL(usbWrapperMock_, open(device, _)).WillOnce(DoAll(SetArgReferee<1>(nullptr), Return(0xFFF))); + } + + ioService_.run(); +} + +} +} +} +} diff --git a/src/USB/USBEndpoint.cpp b/src/USB/USBEndpoint.cpp index 72cf1a3..128d78c 100644 --- a/src/USB/USBEndpoint.cpp +++ b/src/USB/USBEndpoint.cpp @@ -159,7 +159,8 @@ void USBEndpoint::transferHandler(libusb_transfer *transfer) } else { - promise->reject(error::Error(error::ErrorCode::USB_TRANSFER, transfer->status)); + auto error = transfer->status == LIBUSB_TRANSFER_CANCELLED ? error::Error(error::ErrorCode::OPERATION_ABORTED) : error::Error(error::ErrorCode::USB_TRANSFER, transfer->status); + promise->reject(error); } self->usbWrapper_.freeTransfer(transfer); diff --git a/src/USB/USBEndpoint.ut.cpp b/src/USB/USBEndpoint.ut.cpp index e0cf3b3..a171ba6 100644 --- a/src/USB/USBEndpoint.ut.cpp +++ b/src/USB/USBEndpoint.ut.cpp @@ -283,7 +283,7 @@ BOOST_FIXTURE_TEST_CASE(USBEndpoint_BulkTransferFailed, USBEndpointUnitTest) transferCallback(&transfer); EXPECT_CALL(usbWrapperMock_, freeTransfer(&transfer)); - EXPECT_CALL(promiseHandlerMock_, onReject(error::Error(error::ErrorCode::USB_TRANSFER, transfer.status))).Times(1); + EXPECT_CALL(promiseHandlerMock_, onReject(error::Error(error::ErrorCode::OPERATION_ABORTED))).Times(1); EXPECT_CALL(promiseHandlerMock_, onResolve(_)).Times(0); ioService_.run(); }