diff --git a/include/fc/rpc/binary_api_connection.hpp b/include/fc/rpc/binary_api_connection.hpp new file mode 100644 index 0000000..9c9ce2f --- /dev/null +++ b/include/fc/rpc/binary_api_connection.hpp @@ -0,0 +1,532 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +namespace fc { + class binary_api_connection; + + namespace detail { + template + class callback_functor + { + public: + typedef typename std::function::result_type result_type; + + callback_functor( std::weak_ptr< fc::binary_api_connection > con, uint64_t id ) + :_callback_id(id),_binary_api_connection(con){} + + template + result_type operator()( Args... args )const; + + private: + uint64_t _callback_id; + std::weak_ptr< fc::binary_api_connection > _binary_api_connection; + }; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 ) + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + return f(); + } + + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e ); + return call_generic( bind_first_arg( f, a0->as< typename std::decay::type >() ), a0+1, e ); + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + return variant( call_generic( f, args.begin(), args.end() ) ); + }; + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + call_generic( f, args.begin(), args.end() ); + return variant(); + }; + } + + /** + * If api is returned from a remote method, the API is eagerly bound to api of + * the correct type in api_visitor::from_variant(). This binding [1] needs a reference + * to the binary_api_connection, which is made available to from_variant() as a parameter. + * + * However, in the case of a remote method which returns api_base which can subsequently + * be cast by the caller with as, we need to keep track of the connection because + * the binding is done later (when the client code actually calls as). + * + * [1] The binding actually happens in get_remote_api(). + */ + class any_api : public api_base + { + public: + any_api( api_id_type api_id, const std::shared_ptr& con ) + : _api_id(api_id), _binary_api_connection(con) {} + + virtual uint64_t get_handle()const override + { return _api_id; } + + virtual api_id_type register_api( binary_api_connection& conn )const override + { FC_ASSERT( false ); return api_id_type(); } + + api_id_type _api_id; + std::weak_ptr _binary_api_connection; + }; + + } // namespace detail + + class generic_api + { + public: + template + generic_api( const Api& a, const std::shared_ptr& c ); + + generic_api( const generic_api& cpy ) = delete; + + vector call( const string& name, const vector& args ) + { + auto itr = _by_name.find(name); + FC_ASSERT( itr != _by_name.end(), "no method with name '${name}'", ("name",name)("api",_by_name) ); + return call( itr->second, args ); + } + + vector call( uint32_t method_id, const vector& args ) + { + FC_ASSERT( method_id < _methods.size() ); + return _methods[method_id](args); + } + + std::weak_ptr< fc::binary_api_connection > get_connection() + { + return _binary_api_connection; + } + + std::vector get_method_names()const + { + std::vector result; + result.reserve( _by_name.size() ); + for( auto& m : _by_name ) result.push_back(m.first); + return result; + } + + private: + friend struct api_visitor; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 )const + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + + template + R call_generic( const std::function& f, datastream& ds )const + { + return f(); + } + + template + R call_generic( const std::function,Args...)>& f, datastream& ds ) + { + uint64_t callback_id = 0; + fc::raw::unpack( ds, callback_id ); + detail::callback_functor arg0( get_connection(), callback_id ); + return call_generic( this->bind_first_arg,Args...>( f, std::function(arg0) ), ds ); + } + template + R call_generic( const std::function&,Args...)>& f, fc::datastream& ds ) + { + uint64_t callback_id = 0; + fc::raw::unpack( ds, callback_id ); + detail::callback_functor arg0( get_connection(), callback_id ); + return call_generic( this->bind_first_arg&,Args...>( f, arg0 ), ds ); + } + + template + R call_generic( const std::function& f, fc::datastream& ds ) + { + std::decay::type a0; + fc::raw::unpack( ds, a0 ); + return call_generic( this->bind_first_arg( f, a0 ), ds ); + } + + struct api_visitor + { + api_visitor( generic_api& a, const std::weak_ptr& s ):api(a),_api_con(s){ } + + template + std::function to_generic( const std::function(Args...)>& f )const; + + template + std::function to_generic( const std::function>(Args...)>& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + void operator()( const char* name, std::function& memb )const { + api._methods.emplace_back( to_generic( memb ) ); + api._by_name[name] = api._methods.size() - 1; + } + + generic_api& api; + const std::weak_ptr& _api_con; + }; + + + std::weak_ptr _binary_api_connection; + fc::any _api; + std::map< std::string, uint32_t > _by_name; + std::vector< std::function(const vector&)> > _methods; + }; // class generic_api + + + + class binary_api_connection : public std::enable_shared_from_this + { + public: + typedef std::vector params_type; + typedef std::vector result_type; + + binary_api_connection(){} + virtual ~binary_api_connection(){}; + + + template + api get_remote_api( api_id_type api_id = 0 ) + { + api result; + result->visit( api_visitor( api_id, this->shared_from_this() ) ); + return result; + } + + /** makes calls to the remote server */ + virtual result_type send_call( api_id_type api_id, string method_name, params_type args = params_type() ) = 0; + virtual result_type send_callback( uint64_t callback_id, params_type args = params_type() ) = 0; + virtual void send_notice( uint64_t callback_id, params_type args = params_type() ) = 0; + + result_type receive_call( api_id_type api_id, const string& method_name, const params_type& args = params_type() )const + { + FC_ASSERT( _local_apis.size() > api_id ); + return _local_apis[api_id]->call( method_name, args ); + } + result_type receive_callback( uint64_t callback_id, const params_type& args = params_type() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + return _local_callbacks[callback_id]( args ); + } + void receive_notice( uint64_t callback_id, const params_type& args = params_type() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + _local_callbacks[callback_id]( args ); + } + + template + api_id_type register_api( const Interface& a ) + { + auto handle = a.get_handle(); + auto itr = _handle_to_id.find(handle); + if( itr != _handle_to_id.end() ) return itr->second; + + _local_apis.push_back( std::unique_ptr( new generic_api(a, shared_from_this() ) ) ); + _handle_to_id[handle] = _local_apis.size() - 1; + return _local_apis.size() - 1; + } + + template + uint64_t register_callback( const std::function& cb ) + { + _local_callbacks.push_back( detail::to_generic( cb ) ); + return _local_callbacks.size() - 1; + } + + std::vector get_method_names( api_id_type local_api_id = 0 )const { return _local_apis[local_api_id]->get_method_names(); } + + fc::signal closed; + private: + std::vector< std::unique_ptr > _local_apis; + std::map< uint64_t, api_id_type > _handle_to_id; + std::vector< std::function > _local_callbacks; + + + struct api_visitor + { + uint32_t _api_id; + std::shared_ptr _connection; + + api_visitor( uint32_t api_id, std::shared_ptr con ) + :_api_id(api_id),_connection(std::move(con)) + { + } + + api_visitor() = delete; + + template + static Result from_vector( const vector& v, Result*, const std::shared_ptr& ) + { + return fc::raw::unpack( v ); + } + + template + static fc::api from_vector( const vector& v, + fc::api* /*used for template deduction*/, + const std::shared_ptr& con + ) + { + return con->get_remote_api( fc::raw::unpack( v ) ); + } + + static fc::api_ptr from_vector( + const vector& v, + fc::api_ptr* /* used for template deduction */, + const std::shared_ptr& con + ) + { + return fc::api_ptr( new detail::any_api( fc::raw::unpack(v), con ) ); + } + + template + static result_type convert_callbacks( const std::shared_ptr&, const T& v ) + { + return fc::raw::pack(v); + } + + template + static result_type convert_callbacks( const std::shared_ptr& con, const std::function& v ) + { + return con->register_callback( v ); + } + + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + auto var_result = con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + return from_vector( var_result, (Result*)nullptr, con ); + }; + } + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + }; + } + }; + }; + + class local_binary_api_connection : public binary_api_connection + { + public: + /** makes calls to the remote server */ + virtual result_type send_call( api_id_type api_id, string method_name, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_call( api_id, method_name, std::move(args) ); + } + virtual result_type send_callback( uint64_t callback_id, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_callback( callback_id, args ); + } + virtual void send_notice( uint64_t callback_id, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + _remote_connection->receive_notice( callback_id, args ); + } + + + void set_remote_connection( const std::shared_ptr& rc ) + { + FC_ASSERT( !_remote_connection ); + FC_ASSERT( rc != this->shared_from_this() ); + _remote_connection = rc; + } + const std::shared_ptr& remote_connection()const { return _remote_connection; } + + std::shared_ptr _remote_connection; + }; + + template + generic_api::generic_api( const Api& a, const std::shared_ptr& c ) + :_binary_api_connection(c),_api(a) + { + boost::any_cast(a)->visit( api_visitor( *this, c ) ); + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const params_type& args ) { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, args ); + return con->register_api( api_result ); + }; + } + template + std::function generic_api::api_visitor::to_generic( + const std::function>(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const params_type& args )-> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, ds ); + if( api_result ) + return con->register_api( *api_result ); + return result_type(); + }; + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const variants& args ) -> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, ds ); + if( !api_result ) + return result_type(); + return api_result->register_api( *con ); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &api; + return [f,gapi]( const params_type& args ) { + fc::raw::datastream ds( args.data(), args.size() ); + return fc::raw::pack(gapi->call_generic( f, ds )); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &api; + return [f,gapi]( const params_type& args ) { + fc::raw::datastream ds( args.data(), args.size() ); + gapi->call_generic( f, ds ); + return result_type(); + }; + } + + /** + * It is slightly unclean tight coupling to have this method in the api class. + * It breaks encapsulation by requiring an api class method to have a pointer + * to an binary_api_connection. The reason this is necessary is we have a goal of being + * able to call register_api() on an api through its base class api_base. But + * register_api() must know the template parameters! + * + * The only reasonable way to achieve the goal is to implement register_api() + * as a method in api (which obviously knows the template parameter T), + * then make the implementation accessible through the base class (by making + * it a pure virtual method in the base class which is overridden by the subclass's + * implementation). + */ + template< typename Interface, typename Transform > + api_id_type api< Interface, Transform >::register_api( binary_api_connection& conn )const + { + return conn.register_api( *this ); + } + + template< typename T > + api api_base::as() + { + // TODO: this method should probably be const (if it is not too hard) + api* maybe_requested_type = dynamic_cast< api* >(this); + if( maybe_requested_type != nullptr ) + return *maybe_requested_type; + + detail::any_api* maybe_any = dynamic_cast< detail::any_api* >(this); + FC_ASSERT( maybe_any != nullptr ); + std::shared_ptr< binary_api_connection > api_conn = maybe_any->_binary_api_connection.lock(); + FC_ASSERT( api_conn ); + return api_conn->get_remote_api( maybe_any->_api_id ); + } + + namespace detail { + template + template + typename callback_functor::result_type callback_functor::operator()( Args... args )const + { + std::shared_ptr< fc::binary_api_connection > locked = _binary_api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + + /// TODO------------->>> pack args... + locked->send_callback( _callback_id, fc::raw::pack( args... ) ).template as< result_type >(); + } + + + template + class callback_functor + { + public: + typedef void result_type; + + callback_functor( std::weak_ptr< fc::binary_api_connection > con, uint64_t id ) + :_callback_id(id),_binary_api_connection(con){} + + void operator()( Args... args )const + { + std::shared_ptr< fc::binary_api_connection > locked = _binary_api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + locked->send_notice( _callback_id, fc::variants{ args... } ); + } + + private: + uint64_t _callback_id; + std::weak_ptr< fc::binary_api_connection > _binary_api_connection; + }; + } // namespace detail + +} // fc