// Copyright (c) 2017-2020, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // #ifndef MONERO_TRANSPORT_H #define MONERO_TRANSPORT_H #include #include #include #include #include #include #include "net/http_client.h" #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include "exceptions.hpp" #include "trezor_defs.hpp" #include "messages_map.hpp" #include "messages/messages.pb.h" #include "messages/messages-common.pb.h" #include "messages/messages-management.pb.h" #include "messages/messages-monero.pb.h" namespace hw { namespace trezor { using json = rapidjson::Document; using json_val = rapidjson::Value; namespace http = epee::net_utils::http; const std::string DEFAULT_BRIDGE = "127.0.0.1:21325"; uint64_t pack_version(uint32_t major, uint32_t minor=0, uint32_t patch=0); // Base HTTP comm serialization. bool t_serialize(const std::string & in, std::string & out); bool t_serialize(const epee::wipeable_string & in, std::string & out); bool t_serialize(const json_val & in, std::string & out); std::string t_serialize(const json_val & in); bool t_deserialize(const std::string & in, std::string & out); bool t_deserialize(std::string & in, epee::wipeable_string & out); bool t_deserialize(const std::string & in, json & out); // Flexible json serialization. HTTP client tailored for bridge API template bool invoke_bridge_http(const boost::string_ref uri, const t_req & out_struct, t_res & result_struct, t_transport& transport, const boost::string_ref method = "POST", std::chrono::milliseconds timeout = std::chrono::seconds(180)) { std::string req_param; t_serialize(out_struct, req_param); http::fields_list additional_params; additional_params.push_back(std::make_pair("Origin","https://monero.trezor.io")); additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8")); const http::http_response_info* pri = nullptr; const auto data_cleaner = epee::misc_utils::create_scope_leave_handler([&]() { if (!req_param.empty()) { memwipe(&req_param[0], req_param.size()); } transport.wipe_response(); }); if(!transport.invoke(uri, method, req_param, timeout, &pri, std::move(additional_params))) { MERROR("Failed to invoke http request to " << uri); return false; } if(!pri) { MERROR("Failed to invoke http request to " << uri << ", internal error (null response ptr)"); return false; } if(pri->m_response_code != 200) { MERROR("Failed to invoke http request to " << uri << ", wrong response code: " << pri->m_response_code << " Response Body: " << pri->m_body); return false; } return t_deserialize(const_cast(pri)->m_body, result_struct); } // Forward decl class Transport; class Protocol; // Communication protocol class Protocol { public: Protocol() = default; virtual ~Protocol() = default; virtual void session_begin(Transport & transport){ }; virtual void session_end(Transport & transport){ }; virtual void write(Transport & transport, const google::protobuf::Message & req)= 0; virtual void read(Transport & transport, std::shared_ptr & msg, messages::MessageType * msg_type=nullptr)= 0; }; class ProtocolV1 : public Protocol { public: ProtocolV1() = default; virtual ~ProtocolV1() = default; void write(Transport & transport, const google::protobuf::Message & req) override; void read(Transport & transport, std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) override; }; // Base transport typedef std::vector> t_transport_vect; class Transport { public: Transport(); virtual ~Transport() = default; virtual bool ping() { return false; }; virtual std::string get_path() const { return ""; }; virtual void enumerate(t_transport_vect & res){}; virtual void open(){}; virtual void close(){}; virtual void write(const google::protobuf::Message & req) =0; virtual void read(std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) =0; virtual std::shared_ptr find_debug() { return nullptr; }; virtual void write_chunk(const void * buff, size_t size) { }; virtual size_t read_chunk(void * buff, size_t size) { return 0; }; virtual std::ostream& dump(std::ostream& o) const { return o << "Transport<>"; } protected: long m_open_counter; virtual bool pre_open(); virtual bool pre_close(); }; // Bridge transport class BridgeTransport : public Transport { public: BridgeTransport( boost::optional device_path = boost::none, boost::optional bridge_host = boost::none); virtual ~BridgeTransport() = default; static const char * PATH_PREFIX; std::string get_path() const override; void enumerate(t_transport_vect & res) override; void open() override; void close() override; void write(const google::protobuf::Message &req) override; void read(std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) override; const boost::optional & device_info() const; std::ostream& dump(std::ostream& o) const override; private: epee::net_utils::http::http_simple_client m_http_client; std::string m_bridge_host; boost::optional m_device_path; boost::optional m_session; boost::optional m_response; boost::optional m_device_info; }; // UdpTransport transport using boost::asio::ip::udp; class UdpTransport : public Transport { public: explicit UdpTransport( boost::optional device_path=boost::none, boost::optional> proto=boost::none); virtual ~UdpTransport() = default; static const char * PATH_PREFIX; static const char * DEFAULT_HOST; static const int DEFAULT_PORT; bool ping() override; std::string get_path() const override; void enumerate(t_transport_vect & res) override; void open() override; void close() override; std::shared_ptr find_debug() override; void write(const google::protobuf::Message &req) override; void read(std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) override; void write_chunk(const void * buff, size_t size) override; size_t read_chunk(void * buff, size_t size) override; std::ostream& dump(std::ostream& o) const override; private: void require_socket(); ssize_t receive(void * buff, size_t size, boost::system::error_code * error_code=nullptr, bool no_throw=false, boost::posix_time::time_duration timeout=boost::posix_time::seconds(10)); void check_deadline(); static void handle_receive(const boost::system::error_code& ec, std::size_t length, boost::system::error_code* out_ec, std::size_t* out_length); bool ping_int(boost::posix_time::time_duration timeout=boost::posix_time::milliseconds(1500)); std::shared_ptr m_proto; std::string m_device_host; int m_device_port; std::unique_ptr m_socket; boost::asio::io_service m_io_service; boost::asio::deadline_timer m_deadline; udp::endpoint m_endpoint; }; #ifdef WITH_DEVICE_TREZOR_WEBUSB #include class WebUsbTransport : public Transport { public: explicit WebUsbTransport( boost::optional descriptor = boost::none, boost::optional> proto = boost::none ); virtual ~WebUsbTransport(); static const char * PATH_PREFIX; std::string get_path() const override; void enumerate(t_transport_vect & res) override; void open() override; void close() override; std::shared_ptr find_debug() override; void write(const google::protobuf::Message &req) override; void read(std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) override; void write_chunk(const void * buff, size_t size) override; size_t read_chunk(void * buff, size_t size) override; std::ostream& dump(std::ostream& o) const override; private: void require_device() const; void require_connected() const; int get_interface() const; unsigned char get_endpoint() const; std::shared_ptr m_proto; libusb_context *m_usb_session; libusb_device *m_usb_device; libusb_device_handle *m_usb_device_handle; std::unique_ptr m_usb_device_desc; std::vector m_port_numbers; int m_bus_id; int m_device_addr; #ifdef WITH_TREZOR_DEBUGGING bool m_debug_mode; #endif }; #endif // // General helpers // /** * Enumerates all transports */ void enumerate(t_transport_vect & res); /** * Sorts found transports by TREZOR_PATH environment variable. */ void sort_transports_by_env(t_transport_vect & res); /** * Transforms path to the transport */ std::shared_ptr transport(const std::string & path); /** * Transforms path to the particular transport */ template std::shared_ptr transport_typed(const std::string & path){ auto t = transport(path); if (!t){ return nullptr; } return std::dynamic_pointer_cast(t); } // Exception carries unexpected message being received namespace exc { class UnexpectedMessageException: public ProtocolException { protected: hw::trezor::messages::MessageType recvType; std::shared_ptr recvMsg; public: using ProtocolException::ProtocolException; UnexpectedMessageException(): ProtocolException("Trezor returned unexpected message") {}; UnexpectedMessageException(hw::trezor::messages::MessageType recvType, const std::shared_ptr & recvMsg) : recvType(recvType), recvMsg(recvMsg) { reason = std::string("Trezor returned unexpected message: ") + std::to_string(recvType); } }; } /** * Throws corresponding failure exception. */ [[ noreturn ]] void throw_failure_exception(const messages::common::Failure * failure); /** * Generic message holder, type + obj */ class GenericMessage { public: GenericMessage(): m_empty(true) {} GenericMessage(messages::MessageType m_type, const std::shared_ptr &m_msg); bool empty() const { return m_empty; } hw::trezor::messages::MessageType m_type; std::shared_ptr m_msg; bool m_empty; }; /** * Simple wrapper for write-read message exchange with expected message response type. * * @throws UnexpectedMessageException if the response message type is different than expected. * Exception contains message type and the message itself. */ template std::shared_ptr exchange_message(Transport & transport, const google::protobuf::Message & req, boost::optional resp_type = boost::none) { // Require strictly protocol buffers response in the template. BOOST_STATIC_ASSERT(boost::is_base_of::value); // Write the request transport.write(req); // Read the response std::shared_ptr msg_resp; hw::trezor::messages::MessageType msg_resp_type; transport.read(msg_resp, &msg_resp_type); // Determine type of expected message response messages::MessageType required_type = resp_type ? resp_type.get() : MessageMapper::get_message_wire_number(); if (msg_resp_type == required_type) { return message_ptr_retype(msg_resp); } else if (msg_resp_type == messages::MessageType_Failure){ throw_failure_exception(dynamic_cast(msg_resp.get())); } else { throw exc::UnexpectedMessageException(msg_resp_type, msg_resp); } } std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t); std::ostream& operator<<(std::ostream& o, std::shared_ptr const& t); }} #endif //MONERO_TRANSPORT_H