mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
2183 lines
64 KiB
C++
2183 lines
64 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
Copyright (c) 2011-2013, OpenCoin, Inc.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#ifndef BOOST_ASIO_INITFN_RESULT_TYPE
|
|
#define BOOST_ASIO_INITFN_RESULT_TYPE(expr,val) void
|
|
#endif
|
|
|
|
namespace AsioUnitTestsNamespace
|
|
{
|
|
|
|
using namespace boost;
|
|
|
|
class SslContext : public Uncopyable
|
|
{
|
|
public:
|
|
SslContext ()
|
|
{
|
|
}
|
|
|
|
virtual ~SslContext () { }
|
|
|
|
virtual asio::ssl::context& get_object () = 0;
|
|
|
|
protected:
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
class RippleSslContext : public SslContext
|
|
{
|
|
public:
|
|
RippleSslContext ()
|
|
: m_context (asio::ssl::context::sslv23)
|
|
{
|
|
init_ssl_context (m_context);
|
|
}
|
|
|
|
asio::ssl::context& get_object ()
|
|
{
|
|
return m_context;
|
|
}
|
|
|
|
public:
|
|
static char const* get_ciphers ()
|
|
{
|
|
static char const* ciphers = "ALL:!LOW:!EXP:!MD5:@STRENGTH";
|
|
|
|
return ciphers;
|
|
}
|
|
|
|
static DH* get_dh_params (int /*key_length*/)
|
|
{
|
|
static const unsigned char raw512DHParams [] =
|
|
{
|
|
0x30, 0x46, 0x02, 0x41, 0x00, 0x98, 0x15, 0xd2, 0xd0, 0x08, 0x32, 0xda,
|
|
0xaa, 0xac, 0xc4, 0x71, 0xa3, 0x1b, 0x11, 0xf0, 0x6c, 0x62, 0xb2, 0x35,
|
|
0x8a, 0x10, 0x92, 0xc6, 0x0a, 0xa3, 0x84, 0x7e, 0xaf, 0x17, 0x29, 0x0b,
|
|
0x70, 0xef, 0x07, 0x4f, 0xfc, 0x9d, 0x6d, 0x87, 0x99, 0x19, 0x09, 0x5b,
|
|
0x6e, 0xdb, 0x57, 0x72, 0x4a, 0x7e, 0xcd, 0xaf, 0xbd, 0x3a, 0x97, 0x55,
|
|
0x51, 0x77, 0x5a, 0x34, 0x7c, 0xe8, 0xc5, 0x71, 0x63, 0x02, 0x01, 0x02
|
|
};
|
|
|
|
struct ScopedDH
|
|
{
|
|
explicit ScopedDH (DH* dh)
|
|
: m_dh (dh)
|
|
{
|
|
}
|
|
|
|
explicit ScopedDH (unsigned char const* rawData, std::size_t bytes)
|
|
: m_dh (d2i_DHparams (nullptr, &rawData, bytes))
|
|
{
|
|
}
|
|
|
|
~ScopedDH ()
|
|
{
|
|
if (m_dh != nullptr)
|
|
DH_free (m_dh);
|
|
}
|
|
|
|
DH* get () const
|
|
{
|
|
return m_dh;
|
|
}
|
|
|
|
operator DH* () const
|
|
{
|
|
return get ();
|
|
}
|
|
|
|
private:
|
|
DH* m_dh;
|
|
};
|
|
|
|
static ScopedDH dh512 (raw512DHParams, sizeof (raw512DHParams));
|
|
|
|
return dh512;
|
|
}
|
|
|
|
static DH* tmp_dh_handler (SSL* /*ssl*/, int /*is_export*/, int key_length)
|
|
{
|
|
return DHparams_dup (get_dh_params (key_length));
|
|
}
|
|
|
|
static void init_ssl_context (asio::ssl::context& context)
|
|
{
|
|
context.set_options (
|
|
asio::ssl::context::default_workarounds |
|
|
asio::ssl::context::no_sslv2 |
|
|
asio::ssl::context::single_dh_use);
|
|
|
|
context.set_verify_mode (asio::ssl::verify_none);
|
|
|
|
SSL_CTX_set_tmp_dh_callback (context.native_handle (), tmp_dh_handler);
|
|
|
|
int const result = SSL_CTX_set_cipher_list (context.native_handle (), get_ciphers ());
|
|
|
|
if (result != 1)
|
|
FatalError ("invalid cipher list", __FILE__, __LINE__);
|
|
}
|
|
|
|
private:
|
|
asio::ssl::context m_context;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// A handshaking stream that can distinguish multiple protocols
|
|
//
|
|
// SSL note:
|
|
//
|
|
// http://stackoverflow.com/questions/8467277/boostasio-and-async-ssl-stream-how-to-detect-end-of-data-connection-close/18024071#18024071
|
|
//
|
|
class RippleHandshakeStream
|
|
: public asio::socket_base
|
|
, public asio::ssl::stream_base
|
|
{
|
|
public:
|
|
struct SocketInterfaces
|
|
: SocketInterface::Socket
|
|
, SocketInterface::Stream
|
|
, SocketInterface::Handshake
|
|
{};
|
|
|
|
enum Status
|
|
{
|
|
needMore,
|
|
proxy,
|
|
plain,
|
|
ssl
|
|
};
|
|
|
|
struct Options
|
|
{
|
|
Options ()
|
|
: useClientSsl (false)
|
|
, enableServerSsl (false)
|
|
, requireServerSsl (false)
|
|
, requireServerProxy (false)
|
|
{
|
|
}
|
|
|
|
// Always perform SSL handshake as client role
|
|
bool useClientSsl;
|
|
|
|
// Enable optional SSL capability as server role
|
|
bool enableServerSsl;
|
|
|
|
// Require SSL as server role.
|
|
// Does not require that enableServerSsl is set
|
|
bool requireServerSsl;
|
|
|
|
// Require PROXY protocol handshake as server role
|
|
bool requireServerProxy;
|
|
};
|
|
};
|
|
|
|
template <class Stream>
|
|
class RippleHandshakeStreamType : public RippleHandshakeStream
|
|
{
|
|
public:
|
|
typedef typename remove_reference <Stream>::type next_layer_type;
|
|
typedef typename next_layer_type::lowest_layer_type lowest_layer_type;
|
|
|
|
typedef typename add_reference <next_layer_type>::type next_layer_type_ref;
|
|
typedef typename asio::ssl::stream <next_layer_type_ref> SslStreamType;
|
|
|
|
typedef RippleHandshakeStreamType <Stream> ThisType;
|
|
|
|
template <class Arg>
|
|
explicit RippleHandshakeStreamType (Arg& arg, Options options = Options ())
|
|
: m_options (options)
|
|
, m_next_layer (arg)
|
|
, m_io_service (m_next_layer.get_io_service ())
|
|
, m_strand (m_io_service)
|
|
, m_status (needMore)
|
|
, m_role (Socket::client)
|
|
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
asio::io_service& get_io_service () noexcept
|
|
{
|
|
return m_io_service;
|
|
}
|
|
|
|
next_layer_type& next_layer () noexcept
|
|
{
|
|
return m_next_layer;
|
|
}
|
|
|
|
next_layer_type const& next_layer () const noexcept
|
|
{
|
|
return m_next_layer;
|
|
}
|
|
|
|
lowest_layer_type& lowest_layer () noexcept
|
|
{
|
|
return m_next_layer.lowest_layer ();
|
|
}
|
|
|
|
lowest_layer_type const& lowest_layer () const noexcept
|
|
{
|
|
return m_next_layer.lowest_layer ();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
Socket& stream () const noexcept
|
|
{
|
|
fatal_assert (m_stream != nullptr);
|
|
return *m_stream;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// SocketInterface
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
system::error_code cancel (boost::system::error_code& ec)
|
|
{
|
|
return lowest_layer ().cancel (ec);
|
|
}
|
|
|
|
system::error_code close (boost::system::error_code& ec)
|
|
{
|
|
return lowest_layer ().close (ec);
|
|
}
|
|
|
|
system::error_code shutdown (Socket::shutdown_type what, boost::system::error_code& ec)
|
|
{
|
|
return lowest_layer ().shutdown (what, ec);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
//
|
|
// StreamInterface
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
template <typename MutableBufferSequence>
|
|
std::size_t read_some (MutableBufferSequence const& buffers, system::error_code& ec)
|
|
{
|
|
if (m_buffer.size () > 0)
|
|
{
|
|
ec = system::error_code ();
|
|
std::size_t const amount = asio::buffer_copy (buffers, m_buffer.data ());
|
|
m_buffer.consume (amount);
|
|
return amount;
|
|
}
|
|
return stream ().read_some (buffers, ec);
|
|
}
|
|
|
|
template <typename ConstBufferSequence>
|
|
std::size_t write_some (ConstBufferSequence const& buffers, system::error_code& ec)
|
|
{
|
|
return stream ().write_some (buffers, ec);
|
|
}
|
|
|
|
template <typename MutableBufferSequence, typename ReadHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, void (boost::system::error_code, std::size_t))
|
|
async_read_some (MutableBufferSequence const& buffers, BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
|
|
{
|
|
if (m_buffer.size () > 0)
|
|
{
|
|
// Return the leftover bytes from the handshake
|
|
std::size_t const amount = asio::buffer_copy (buffers, m_buffer.data ());
|
|
m_buffer.consume (amount);
|
|
return m_io_service.post (m_strand.wrap (boost::bind (
|
|
handler, system::error_code (), amount)));
|
|
}
|
|
return stream ().async_read_some (buffers, m_strand.wrap (handler));
|
|
}
|
|
|
|
template <typename ConstBufferSequence, typename WriteHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, void (boost::system::error_code, std::size_t))
|
|
async_write_some (ConstBufferSequence const& buffers, BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
|
|
{
|
|
return stream ().async_write_some (buffers, handler);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
system::error_code handshake (Socket::handshake_type role, system::error_code& ec)
|
|
{
|
|
Action action = calcAction (role);
|
|
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
handshakePlain (ec);
|
|
break;
|
|
|
|
case actionSsl:
|
|
handshakeSsl (ec);
|
|
break;
|
|
|
|
case actionDetect:
|
|
detectHandshake (ec);
|
|
if (! ec)
|
|
{
|
|
action = calcDetectAction (ec);
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
handshakePlain (ec);
|
|
break;
|
|
case actionSsl:
|
|
handshakeSsl (ec);
|
|
break;
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ec;
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence>
|
|
boost::system::error_code handshake (handshake_type type,
|
|
ConstBufferSequence const& buffers, boost::system::error_code& ec)
|
|
{
|
|
Action action = calcAction (type);
|
|
ec = system::error_code ();
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
handshakePlain (buffers, ec);
|
|
break;
|
|
case actionSsl:
|
|
handshakeSsl (buffers, ec);
|
|
break;
|
|
case actionDetect:
|
|
detectHandshake (buffers, ec);
|
|
if (! ec)
|
|
{
|
|
action = calcDetectAction (ec);
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
handshakePlain (buffers, ec);
|
|
break;
|
|
case actionSsl:
|
|
handshakeSsl (buffers, ec);
|
|
break;
|
|
};
|
|
}
|
|
break;
|
|
}
|
|
return ec;
|
|
}
|
|
#endif
|
|
|
|
template <typename HandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void (boost::system::error_code))
|
|
async_handshake (handshake_type type, BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
|
|
{
|
|
Action const action = calcAction (type);
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
return handshakePlainAsync (handler);
|
|
break;
|
|
|
|
case actionSsl:
|
|
return handshakeSslAsync (handler);
|
|
break;
|
|
|
|
case actionDetect:
|
|
return detectHandshakeAsync (handler);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void (boost::system::error_code, std::size_t))
|
|
async_handshake (handshake_type type, const ConstBufferSequence& buffers,
|
|
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
|
|
{
|
|
Action const action = calcAction (type);
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
return handshakePlainAsync (buffers, handler);
|
|
break;
|
|
|
|
case actionSsl:
|
|
return handshakeSslAsync (buffers, handler);
|
|
break;
|
|
|
|
case actionDetect:
|
|
return detectHandshakeAsync (buffers, handler);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
system::error_code shutdown (system::error_code& ec)
|
|
{
|
|
if (m_status == ssl)
|
|
{
|
|
return m_ssl_stream->shutdown (ec);
|
|
}
|
|
else
|
|
{
|
|
// we need to close the lwest layer
|
|
return m_next_layer.shutdown (next_layer_type::shutdown_both, ec);
|
|
}
|
|
}
|
|
|
|
template <typename ShutdownHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler, void (boost::system::error_code))
|
|
async_shutdown (BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler)
|
|
{
|
|
if (m_status == ssl)
|
|
{
|
|
m_ssl_stream->async_shutdown (m_strand.wrap (handler));
|
|
}
|
|
else
|
|
{
|
|
system::error_code ec;
|
|
m_next_layer.shutdown (next_layer_type::shutdown_both, ec);
|
|
m_io_service.post (m_strand.wrap (boost::bind (handler, ec)));
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
enum Action
|
|
{
|
|
actionDetect,
|
|
actionPlain,
|
|
actionSsl,
|
|
actionFail
|
|
};
|
|
|
|
// Determines what action to take based on
|
|
// the stream options and the desired role.
|
|
//
|
|
Action calcAction (Socket::handshake_type role)
|
|
{
|
|
m_role = role;
|
|
|
|
if (role == Socket::server)
|
|
{
|
|
if (! m_options.enableServerSsl &&
|
|
! m_options.requireServerSsl &&
|
|
! m_options.requireServerProxy)
|
|
{
|
|
return actionPlain;
|
|
}
|
|
else if (m_options.requireServerSsl && ! m_options.requireServerProxy)
|
|
{
|
|
return actionSsl;
|
|
}
|
|
else
|
|
{
|
|
return actionDetect;
|
|
}
|
|
}
|
|
else if (m_role == Socket::client)
|
|
{
|
|
if (m_options.useClientSsl)
|
|
{
|
|
return actionSsl;
|
|
}
|
|
else
|
|
{
|
|
return actionPlain;
|
|
}
|
|
}
|
|
|
|
return actionPlain;
|
|
}
|
|
|
|
// Determines what action to take based on the auto-detected
|
|
// handshake, the stream options, and desired role.
|
|
//
|
|
Action calcDetectAction (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
if (m_status == plain)
|
|
{
|
|
if (! m_options.requireServerProxy && ! m_options.requireServerSsl)
|
|
{
|
|
return actionPlain;
|
|
}
|
|
else
|
|
{
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
}
|
|
else if (m_status == ssl)
|
|
{
|
|
if (! m_options.requireServerProxy)
|
|
{
|
|
if (m_options.enableServerSsl || m_options.requireServerSsl)
|
|
{
|
|
return actionSsl;
|
|
}
|
|
else
|
|
{
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
}
|
|
else if (m_status == proxy)
|
|
{
|
|
if (m_options.requireServerProxy)
|
|
{
|
|
// read the rest of the proxy string
|
|
// then transition to SSL handshake mode
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
else
|
|
{
|
|
// Can we make PROXY optional?
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
}
|
|
|
|
failedHandshake (ec);
|
|
return actionFail;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// called when options disallow handshake
|
|
void failedHandshake (system::error_code& ec)
|
|
{
|
|
// VFALCO TODO maybe use a ripple error category?
|
|
// set this to something custom that we can recognize later?
|
|
ec = asio::error::invalid_argument;
|
|
}
|
|
|
|
void createPlainStream ()
|
|
{
|
|
m_status = plain;
|
|
m_stream = new SocketWrapper <next_layer_type> (m_next_layer);
|
|
}
|
|
|
|
void handshakePlain (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
createPlainStream ();
|
|
}
|
|
|
|
template <typename ConstBufferSequence>
|
|
void handshakePlain (ConstBufferSequence const& buffers, system::error_code& ec)
|
|
{
|
|
fatal_assert (asio::buffer_size (buffers) == 0 );
|
|
ec = system::error_code ();
|
|
createPlainStream ();
|
|
}
|
|
|
|
template <typename HandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void (boost::system::error_code))
|
|
handshakePlainAsync (BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
|
|
{
|
|
createPlainStream ();
|
|
return m_io_service.post (m_strand.wrap (boost::bind (handler, system::error_code())));
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void (boost::system::error_code, std::size_t))
|
|
handshakePlainAsync (ConstBufferSequence const& buffers,
|
|
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
|
|
{
|
|
fatal_assert (asio::buffer_size (buffers) == 0);
|
|
createPlainStream ();
|
|
return m_io_service.post (m_strand.wrap (boost::bind (handler, system::error_code(), 0)));
|
|
}
|
|
#endif
|
|
|
|
void createSslStream ()
|
|
{
|
|
m_status = ssl;
|
|
m_ssl_stream = new SslStreamType (m_next_layer, m_context.get_object ());
|
|
m_stream = new SocketWrapper <SslStreamType> (*m_ssl_stream);
|
|
}
|
|
|
|
void handshakeSsl (system::error_code& ec)
|
|
{
|
|
createSslStream ();
|
|
m_ssl_stream->handshake (m_role, ec);
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence>
|
|
void handshakeSsl (ConstBufferSequence const& buffers, system::error_code& ec)
|
|
{
|
|
createSslStream ();
|
|
m_ssl_stream->handshake (m_role, buffers, ec);
|
|
}
|
|
#endif
|
|
|
|
template <typename HandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void (boost::system::error_code))
|
|
handshakeSslAsync (BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
|
|
{
|
|
createSslStream ();
|
|
return m_ssl_stream->async_handshake (m_role, handler);
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE (BufferedHandshakeHandler, void (boost::system::error_code, std::size_t))
|
|
handshakeSslAsync (ConstBufferSequence const& buffers,
|
|
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
|
|
{
|
|
createSslStream ();
|
|
return m_ssl_stream->async_handshake (m_role, buffers, handler);
|
|
}
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
enum
|
|
{
|
|
autoDetectBytes = 5
|
|
};
|
|
|
|
void detectHandshake (system::error_code& ec)
|
|
{
|
|
// Top up our buffer
|
|
bassert (m_buffer.size () == 0);
|
|
std::size_t const needed = autoDetectBytes;
|
|
std::size_t const amount = m_next_layer.receive (
|
|
m_buffer.prepare (needed), asio::socket_base::message_peek, ec);
|
|
m_buffer.commit (amount);
|
|
if (! ec)
|
|
{
|
|
analyzeHandshake (m_buffer.data ());
|
|
m_buffer.consume (amount);
|
|
if (m_status == needMore)
|
|
ec = asio::error::invalid_argument; // should never happen
|
|
}
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <class ConstBufferSequence>
|
|
void detectHandshake (ConstBufferSequence const& buffers, system::error_code& ec)
|
|
{
|
|
m_buffer.commit (asio::buffer_copy (
|
|
m_buffer.prepare (asio::buffer_size (buffers)), buffers));
|
|
detectHandshake (ec);
|
|
}
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
template <typename HandshakeHandler>
|
|
void onDetectRead (HandshakeHandler handler,
|
|
system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
m_buffer.commit (bytes_transferred);
|
|
|
|
if (! ec)
|
|
{
|
|
analyzeHandshake (m_buffer.data ());
|
|
|
|
system::error_code ec;
|
|
|
|
if (m_status != needMore)
|
|
{
|
|
m_buffer.consume (bytes_transferred);
|
|
|
|
Action action = calcDetectAction (ec);
|
|
if (! ec)
|
|
{
|
|
switch (action)
|
|
{
|
|
default:
|
|
case actionPlain:
|
|
handshakePlainAsync (handler);
|
|
break;
|
|
case actionSsl:
|
|
handshakeSslAsync (handler);
|
|
break;
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ec = asio::error::invalid_argument;
|
|
}
|
|
|
|
if (ec)
|
|
{
|
|
m_io_service.post (m_strand.wrap (boost::bind (handler, ec)));
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename HandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler, void (boost::system::error_code))
|
|
detectHandshakeAsync (BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
|
|
{
|
|
bassert (m_buffer.size () == 0);
|
|
return m_next_layer.async_receive (
|
|
m_buffer.prepare (autoDetectBytes), asio::socket_base::message_peek,
|
|
m_strand.wrap (boost::bind (&ThisType::onDetectRead <HandshakeHandler>, this, handler,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred)));
|
|
}
|
|
|
|
#if (BOOST_VERSION / 100) >= 1054
|
|
template <typename ConstBufferSequence, typename BufferedHandshakeHandler>
|
|
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler, void (boost::system::error_code, std::size_t))
|
|
detectHandshakeAsync (ConstBufferSequence const& buffers, BufferedHandshakeHandler handler)
|
|
{
|
|
fatal_error ("unimplemented");
|
|
}
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
static inline bool isPrintable (unsigned char c)
|
|
{
|
|
return (c < 127) && (c > 31);
|
|
}
|
|
|
|
template <class ConstBufferSequence>
|
|
void analyzeHandshake (ConstBufferSequence const& buffers)
|
|
{
|
|
m_status = needMore;
|
|
|
|
unsigned char data [5];
|
|
|
|
std::size_t const bytes = asio::buffer_copy (asio::buffer (data), buffers);
|
|
|
|
if (bytes > 0)
|
|
{
|
|
if ( isPrintable (data [0]) &&
|
|
((bytes < 2) || isPrintable (data [1])) &&
|
|
((bytes < 3) || isPrintable (data [2])) &&
|
|
((bytes < 4) || isPrintable (data [3])) &&
|
|
((bytes < 5) || isPrintable (data [4])))
|
|
{
|
|
if (bytes < 5 || memcmp (data, "PROXY", 5) != 0)
|
|
{
|
|
m_status = plain;
|
|
}
|
|
else
|
|
{
|
|
m_status = proxy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_status = ssl;
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
Options m_options;
|
|
RippleSslContext m_context;
|
|
Stream m_next_layer;
|
|
asio::io_service& m_io_service;
|
|
asio::io_service::strand m_strand;
|
|
Status m_status;
|
|
Socket::handshake_type m_role;
|
|
ScopedPointer <Socket> m_stream;
|
|
ScopedPointer <SslStreamType> m_ssl_stream;
|
|
asio::streambuf m_buffer;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
class AsioUnitTests : public UnitTest
|
|
{
|
|
public:
|
|
AsioUnitTests () : UnitTest ("Asio", "ripple", runManual)
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// These are passed as template arguments and package up
|
|
// the parameters needed to establish the connection.
|
|
|
|
// ip::tcp with v4 addresses
|
|
//
|
|
struct TcpV6
|
|
{
|
|
typedef asio::ip::tcp Protocol;
|
|
|
|
static asio::ip::tcp::endpoint server_endpoint ()
|
|
{
|
|
return asio::ip::tcp::endpoint (asio::ip::tcp::v6 (), 1052);
|
|
}
|
|
|
|
static asio::ip::tcp::endpoint client_endpoint ()
|
|
{
|
|
return asio::ip::tcp::endpoint (asio::ip::address_v6 ().from_string ("::1"), 1052);
|
|
}
|
|
};
|
|
|
|
// ip::tcp with v6 addresses
|
|
//
|
|
struct TcpV4
|
|
{
|
|
typedef asio::ip::tcp Protocol;
|
|
|
|
static asio::ip::tcp::endpoint server_endpoint ()
|
|
{
|
|
return asio::ip::tcp::endpoint (asio::ip::address_v4::any (), 1053);
|
|
}
|
|
|
|
static asio::ip::tcp::endpoint client_endpoint ()
|
|
{
|
|
return asio::ip::tcp::endpoint (asio::ip::address_v4::loopback (), 1053);
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// We create our own error category to distinguish unexpected
|
|
// errors like connection failures, versus intended errors like
|
|
// a planned mismatch in handshakes.
|
|
|
|
enum
|
|
{
|
|
timeout = 1,
|
|
unexpected // a unexpected test result was encountered
|
|
};
|
|
|
|
struct unit_test_category_t : system::error_category
|
|
{
|
|
char const* name () const noexcept
|
|
{
|
|
return "unit_test";
|
|
}
|
|
|
|
std::string message (int ev) const
|
|
{
|
|
switch (ev)
|
|
{
|
|
case timeout: return "The timeout expired before the test could complete";
|
|
case unexpected: return "An unexpected test result was encountered";
|
|
default:
|
|
break;
|
|
};
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
system::error_condition default_error_condition (int ev) const noexcept
|
|
{
|
|
return system::error_condition (ev, *this);
|
|
}
|
|
|
|
bool equivalent (int code, system::error_condition const& condition) const noexcept
|
|
{
|
|
return default_error_condition (code) == condition;
|
|
}
|
|
|
|
bool equivalent (system::error_code const& code, int condition) const noexcept
|
|
{
|
|
return *this == code.category() && code.value() == condition;
|
|
}
|
|
};
|
|
|
|
static unit_test_category_t& unit_test_category ()
|
|
{
|
|
static unit_test_category_t category;
|
|
return category;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// These flags get combined to determine the handshaking attributes
|
|
//
|
|
enum
|
|
{
|
|
none = 0,
|
|
client_ssl = 1,
|
|
server_ssl = 2,
|
|
server_ssl_required = 4,
|
|
server_proxy = 8
|
|
};
|
|
|
|
// The scenario object provides inputs to construct children with
|
|
// the test information. It also holds the outputs of the client and
|
|
// server threads.
|
|
//
|
|
class Scenario
|
|
{
|
|
public:
|
|
// Implicit construction from flags
|
|
Scenario (int options = 0)
|
|
{
|
|
handshakeOptions.useClientSsl = (options & client_ssl) != 0;
|
|
handshakeOptions.enableServerSsl = (options & (server_ssl | server_ssl_required)) != 0;
|
|
handshakeOptions.requireServerSsl = (options & server_ssl_required) != 0;
|
|
handshakeOptions.requireServerProxy = (options & server_proxy) !=0;
|
|
}
|
|
|
|
// inputs
|
|
RippleHandshakeStream::Options handshakeOptions;
|
|
|
|
// outputs
|
|
system::error_code client_error;
|
|
system::error_code server_error;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for client and server tests
|
|
//
|
|
class BasicTest : public Thread
|
|
{
|
|
public:
|
|
enum
|
|
{
|
|
// how long to wait until we give up
|
|
//milliSecondsToWait = 1000
|
|
//milliSecondsToWait = 20000
|
|
milliSecondsToWait = -1
|
|
};
|
|
|
|
BasicTest (UnitTest& test,
|
|
Scenario& scenario,
|
|
Socket::handshake_type role)
|
|
: Thread ((role == Socket::client) ? "client" : "server")
|
|
, m_test (test)
|
|
, m_scenario (scenario)
|
|
, m_role (role)
|
|
{
|
|
}
|
|
|
|
~BasicTest ()
|
|
{
|
|
}
|
|
|
|
// Called from the unit test thread, reports the
|
|
// error to the unit test if it indicates a failure.
|
|
//
|
|
bool check_success (system::error_code const& ec, bool eofIsOkay = false)
|
|
{
|
|
if (eofIsOkay && ec == asio::error::eof)
|
|
return true;
|
|
return m_test.expect (
|
|
(! ec) || (eofIsOkay && ec == asio::error::eof), ec.message ());
|
|
}
|
|
|
|
// Called from the thread to check the error code.
|
|
// This sets the error code in the scenario appropriately.
|
|
//
|
|
bool thread_success (system::error_code const& ec, bool eofIsOkay = false)
|
|
{
|
|
if (! check_success (ec, eofIsOkay))
|
|
{
|
|
if (m_role == Socket::server)
|
|
m_scenario.server_error = ec;
|
|
else
|
|
m_scenario.client_error = ec;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Called from the thread to check a condition
|
|
// This just calls thread_success with a special code if the condition is false
|
|
//
|
|
bool thread_expect (bool condition)
|
|
{
|
|
if (! condition)
|
|
return thread_success (system::error_code (unexpected, unit_test_category ()));
|
|
return true;
|
|
}
|
|
|
|
virtual system::error_code start (system::error_code& ec) = 0;
|
|
|
|
virtual void finish () = 0;
|
|
|
|
protected:
|
|
UnitTest& m_test;
|
|
Scenario& m_scenario;
|
|
Socket::handshake_type const m_role;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for synchronous operations
|
|
//
|
|
class BasicSync : public BasicTest
|
|
{
|
|
public:
|
|
BasicSync (UnitTest& test,
|
|
Scenario& scenario,
|
|
Socket::handshake_type role)
|
|
: BasicTest (test, scenario, role)
|
|
{
|
|
}
|
|
|
|
void finish ()
|
|
{
|
|
// This is dangerous
|
|
if (! stopThread (milliSecondsToWait))
|
|
{
|
|
check_success (system::error_code (timeout, unit_test_category ()));
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for synchronous servers
|
|
//
|
|
class BasicSyncServer : public BasicSync
|
|
{
|
|
public:
|
|
BasicSyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicSync (test, scenario, Socket::server)
|
|
{
|
|
}
|
|
|
|
void process (Socket& socket, system::error_code& ec)
|
|
{
|
|
{
|
|
asio::streambuf buf (5);
|
|
std::size_t const amount = asio::read_until (socket, buf, "hello", ec);
|
|
|
|
if (! thread_success (ec))
|
|
return;
|
|
|
|
if (! thread_expect (amount == 5))
|
|
return;
|
|
|
|
if (! thread_expect (buf.size () == 5))
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::size_t const amount = asio::write (socket, asio::buffer ("goodbye", 7), ec);
|
|
|
|
if (! thread_success (ec))
|
|
return;
|
|
|
|
if (! thread_expect (amount == 7))
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for synchronous clients
|
|
//
|
|
class BasicSyncClient : public BasicSync
|
|
{
|
|
public:
|
|
BasicSyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicSync (test, scenario, Socket::client)
|
|
{
|
|
}
|
|
|
|
void process (Socket& socket, system::error_code& ec)
|
|
{
|
|
{
|
|
std::size_t const amount = asio::write (socket, asio::buffer ("hello", 5), ec);
|
|
|
|
if (! thread_success (ec))
|
|
return;
|
|
|
|
if (! thread_expect (amount == 5))
|
|
return;
|
|
}
|
|
|
|
{
|
|
char data [7];
|
|
|
|
size_t const amount = asio::read (socket, asio::buffer (data, 7), ec);
|
|
|
|
if (! thread_success (ec, true))
|
|
return;
|
|
|
|
if (! thread_expect (amount == 7))
|
|
return;
|
|
|
|
thread_expect (memcmp (&data, "goodbye", 7) == 0);
|
|
}
|
|
|
|
// Wait for 1 byte which should never come. Instead,
|
|
// the server should close its end and we will get eof
|
|
{
|
|
char data [1];
|
|
asio::read (socket, asio::buffer (data, 1), ec);
|
|
if (ec == asio::error::eof)
|
|
{
|
|
ec = system::error_code ();
|
|
}
|
|
else if (thread_success (ec))
|
|
{
|
|
thread_expect (false);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// A synchronous server
|
|
//
|
|
template <class Transport>
|
|
class SyncServer : public BasicSyncServer
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
SyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicSyncServer (test, scenario)
|
|
, m_acceptor (m_io_service)
|
|
, m_socket (m_io_service)
|
|
{
|
|
}
|
|
|
|
system::error_code start (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
if (! check_success (m_acceptor.open (Transport::server_endpoint ().protocol (), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.set_option (typename SocketType::reuse_address (true), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.bind (Transport::server_endpoint (), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.listen (asio::socket_base::max_connections, ec)))
|
|
return ec;
|
|
|
|
startThread ();
|
|
|
|
return ec;
|
|
}
|
|
|
|
private:
|
|
void run ()
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (m_acceptor.accept (m_socket, ec)))
|
|
return;
|
|
|
|
if (! thread_success (m_acceptor.close (ec)))
|
|
return;
|
|
|
|
SocketWrapper <SocketType> socket (m_socket);
|
|
|
|
process (socket, ec);
|
|
|
|
if (! ec)
|
|
{
|
|
if (! thread_success (m_socket.shutdown (SocketType::shutdown_both, ec)))
|
|
return;
|
|
|
|
if (! thread_success (m_socket.close (ec)))
|
|
return;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
asio::io_service m_io_service;
|
|
Acceptor m_acceptor;
|
|
SocketType m_socket;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// A synchronous client
|
|
//
|
|
template <class Transport>
|
|
class SyncClient : public BasicSyncClient
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
SyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicSyncClient (test, scenario)
|
|
, m_socket (m_io_service)
|
|
{
|
|
}
|
|
|
|
system::error_code start (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
startThread ();
|
|
|
|
return ec;
|
|
}
|
|
|
|
private:
|
|
void run ()
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (m_socket.connect (Transport::client_endpoint (), ec)))
|
|
return;
|
|
|
|
SocketWrapper <SocketType> socket (m_socket);
|
|
|
|
process (socket, ec);
|
|
|
|
if (! ec)
|
|
{
|
|
if (! thread_success (m_socket.shutdown (SocketType::shutdown_both, ec)))
|
|
return;
|
|
|
|
if (! thread_success (m_socket.close (ec)))
|
|
return;
|
|
}
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
SocketType m_socket;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// A synchronous server that supports a handshake
|
|
//
|
|
template <class Transport, template <class Stream = typename Transport::Protocol::socket&> class HandshakeType>
|
|
class HandshakeSyncServer : public BasicSyncServer
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
HandshakeSyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicSyncServer (test, scenario)
|
|
, m_socket (m_io_service)
|
|
, m_acceptor (m_io_service)
|
|
, m_handshake (m_socket, m_scenario.handshakeOptions)
|
|
{
|
|
}
|
|
|
|
system::error_code start (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
if (! check_success (m_acceptor.open (Transport::server_endpoint ().protocol (), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.set_option (typename SocketType::reuse_address (true), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.bind (Transport::server_endpoint (), ec)))
|
|
return ec;
|
|
|
|
if (! check_success (m_acceptor.listen (asio::socket_base::max_connections, ec)))
|
|
return ec;
|
|
|
|
startThread ();
|
|
|
|
return ec;
|
|
}
|
|
|
|
private:
|
|
void run ()
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (m_acceptor.accept (m_socket, ec)))
|
|
return;
|
|
|
|
if (! thread_success (m_acceptor.close (ec)))
|
|
return;
|
|
|
|
SocketWrapper <HandshakeType <SocketType&> > socket (m_handshake);
|
|
|
|
if (! thread_success (socket.handshake (m_role, ec)))
|
|
return;
|
|
|
|
process (socket, ec);
|
|
|
|
if (! ec)
|
|
{
|
|
// closing the stream also shuts down the socket
|
|
if (! thread_success (socket.shutdown (ec), true))
|
|
return;
|
|
|
|
if (! thread_success (m_socket.close (ec)))
|
|
return;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
asio::io_service m_io_service;
|
|
SocketType m_socket;
|
|
Acceptor m_acceptor;
|
|
HandshakeType <SocketType&> m_handshake;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// A synchronous client that supports a handshake
|
|
//
|
|
template <class Transport, template <class Stream> class HandshakeType>
|
|
class HandshakeSyncClient : public BasicSyncClient
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
HandshakeSyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicSyncClient (test, scenario)
|
|
, m_socket (m_io_service)
|
|
, m_handshake (m_socket, m_scenario.handshakeOptions)
|
|
{
|
|
}
|
|
|
|
system::error_code start (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
startThread ();
|
|
|
|
return ec;
|
|
}
|
|
|
|
private:
|
|
void run ()
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (m_socket.connect (Transport::client_endpoint (), ec)))
|
|
return;
|
|
|
|
SocketWrapper <HandshakeType <SocketType&> > socket (m_handshake);
|
|
|
|
if (! thread_success (socket.handshake (m_role, ec)))
|
|
return;
|
|
|
|
process (socket, ec);
|
|
|
|
if (! ec)
|
|
{
|
|
// Without doing a shutdown on the handshake stream in the
|
|
// client, the call to close the socket will return "short read".
|
|
if (! thread_success (socket.shutdown (ec), true))
|
|
return;
|
|
|
|
if (! thread_success (m_socket.close (ec)))
|
|
return;
|
|
}
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
SocketType m_socket;
|
|
HandshakeType <SocketType&> m_handshake;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for asynchronous operations
|
|
//
|
|
class BasicAsync : public BasicTest
|
|
{
|
|
public:
|
|
BasicAsync (UnitTest& test,
|
|
Scenario& scenario,
|
|
Socket::handshake_type role)
|
|
: BasicTest (test, scenario, role)
|
|
{
|
|
}
|
|
|
|
system::error_code start (system::error_code& ec)
|
|
{
|
|
ec = system::error_code ();
|
|
|
|
// put the deadline timer here
|
|
|
|
on_start (ec);
|
|
|
|
if (! ec)
|
|
startThread ();
|
|
|
|
return ec;
|
|
}
|
|
|
|
void finish ()
|
|
{
|
|
// wait for io_service::run to return
|
|
m_done.wait ();
|
|
}
|
|
|
|
protected:
|
|
virtual asio::io_service& get_io_service () = 0;
|
|
virtual Socket& socket () = 0;
|
|
|
|
void run ()
|
|
{
|
|
get_io_service ().run ();
|
|
m_done.signal ();
|
|
}
|
|
|
|
virtual void on_start (system::error_code& ec) = 0;
|
|
|
|
virtual void on_shutdown (system::error_code const& ec) = 0;
|
|
|
|
virtual void closed () = 0;
|
|
|
|
protected:
|
|
asio::streambuf m_buf;
|
|
|
|
private:
|
|
WaitableEvent m_done;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for asynchronous servers
|
|
//
|
|
class BasicAsyncServer : public BasicAsync
|
|
{
|
|
public:
|
|
BasicAsyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicAsync (test, scenario, Socket::server)
|
|
{
|
|
}
|
|
|
|
void on_accept (system::error_code const& ec)
|
|
{
|
|
asio::async_read_until (socket (), m_buf, std::string ("hello"),
|
|
boost::bind (&BasicAsyncServer::on_read, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
|
|
void on_read (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 5))
|
|
return;
|
|
|
|
asio::async_write (socket (), asio::buffer ("goodbye", 7),
|
|
boost::bind (&BasicAsyncServer::on_write, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_write (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 7))
|
|
return;
|
|
|
|
{
|
|
system::error_code ec;
|
|
if (! thread_success (socket ().shutdown (Socket::shutdown_both, ec)))
|
|
return;
|
|
}
|
|
|
|
on_shutdown (ec);
|
|
}
|
|
}
|
|
|
|
void on_shutdown (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (socket ().shutdown (Socket::shutdown_both, ec)))
|
|
return;
|
|
|
|
if (! thread_success (socket ().close (ec)))
|
|
return;
|
|
|
|
closed ();
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Common code for asynchronous clients
|
|
//
|
|
class BasicAsyncClient : public BasicAsync
|
|
{
|
|
public:
|
|
BasicAsyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicAsync (test, scenario, Socket::client)
|
|
{
|
|
}
|
|
|
|
void on_connect (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
asio::async_write (socket (), asio::buffer ("hello", 5),
|
|
boost::bind (&BasicAsyncClient::on_write, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_write (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 5))
|
|
return;
|
|
|
|
asio::async_read_until (socket (), m_buf, std::string ("goodbye"),
|
|
boost::bind (&BasicAsyncClient::on_read, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_read (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 7))
|
|
return;
|
|
|
|
// should check the data here?
|
|
m_buf.consume (bytes_transferred);
|
|
|
|
asio::async_read (socket (), m_buf.prepare (1),
|
|
boost::bind (&BasicAsyncClient::on_read_final, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_read_final (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (ec == asio::error::eof)
|
|
{
|
|
system::error_code ec; // to hide the eof
|
|
|
|
if (! thread_success (socket ().shutdown (Socket::shutdown_both, ec)))
|
|
return;
|
|
|
|
on_shutdown (ec);
|
|
}
|
|
else if (thread_success (ec))
|
|
{
|
|
thread_expect (false);
|
|
}
|
|
}
|
|
|
|
void on_shutdown (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (socket ().shutdown (Socket::shutdown_both, ec)))
|
|
return;
|
|
|
|
if (! thread_success (socket ().close (ec)))
|
|
return;
|
|
|
|
closed ();
|
|
}
|
|
}
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
template <class Transport>
|
|
class AsyncServer : public BasicAsyncServer
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
AsyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicAsyncServer (test, scenario)
|
|
, m_acceptor (m_io_service)
|
|
, m_socket (m_io_service)
|
|
, m_socketWrapper (m_socket)
|
|
{
|
|
}
|
|
|
|
asio::io_service& get_io_service ()
|
|
{
|
|
return m_io_service;
|
|
}
|
|
|
|
Socket& socket ()
|
|
{
|
|
return m_socketWrapper;
|
|
}
|
|
|
|
void on_start (system::error_code& ec)
|
|
{
|
|
if (! check_success (m_acceptor.open (Transport::server_endpoint ().protocol (), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.set_option (typename SocketType::reuse_address (true), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.bind (Transport::server_endpoint (), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.listen (asio::socket_base::max_connections, ec)))
|
|
return;
|
|
|
|
m_acceptor.async_accept (m_socket, boost::bind (
|
|
&BasicAsyncServer::on_accept, this, asio::placeholders::error));
|
|
}
|
|
|
|
void closed ()
|
|
{
|
|
system::error_code ec;
|
|
if (! thread_success (m_acceptor.close (ec)))
|
|
return;
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
Acceptor m_acceptor;
|
|
SocketType m_socket;
|
|
asio::streambuf m_buf;
|
|
SocketWrapper <SocketType> m_socketWrapper;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// An asynchronous client
|
|
//
|
|
template <class Transport>
|
|
class AsyncClient : public BasicAsyncClient
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
|
|
AsyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicAsyncClient (test, scenario)
|
|
, m_socket (m_io_service)
|
|
, m_socketWrapper (m_socket)
|
|
{
|
|
}
|
|
|
|
asio::io_service& get_io_service ()
|
|
{
|
|
return m_io_service;
|
|
}
|
|
|
|
Socket& socket ()
|
|
{
|
|
return m_socketWrapper;
|
|
}
|
|
|
|
void on_start (system::error_code& ec)
|
|
{
|
|
m_socket.async_connect (Transport::client_endpoint (),
|
|
boost::bind (&BasicAsyncClient::on_connect, this, asio::placeholders::error));
|
|
}
|
|
|
|
void closed ()
|
|
{
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
SocketType m_socket;
|
|
asio::streambuf m_buf;
|
|
SocketWrapper <SocketType> m_socketWrapper;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// An asynchronous handshaking server
|
|
//
|
|
template <class Transport, template <class Stream> class HandshakeType>
|
|
class HandshakeAsyncServer : public BasicAsyncServer
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
typedef HandshakeType <SocketType&> StreamType;
|
|
typedef HandshakeAsyncServer <Transport, HandshakeType> ThisType;
|
|
|
|
HandshakeAsyncServer (UnitTest& test, Scenario& scenario)
|
|
: BasicAsyncServer (test, scenario)
|
|
, m_acceptor (m_io_service)
|
|
, m_socket (m_acceptor.get_io_service ())
|
|
, m_stream (m_socket, m_scenario.handshakeOptions)
|
|
, m_socketWrapper (m_stream)
|
|
{
|
|
}
|
|
|
|
asio::io_service& get_io_service ()
|
|
{
|
|
return m_io_service;
|
|
}
|
|
|
|
Socket& socket ()
|
|
{
|
|
return m_socketWrapper;
|
|
}
|
|
|
|
void on_start (system::error_code& ec)
|
|
{
|
|
if (! check_success (m_acceptor.open (Transport::server_endpoint ().protocol (), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.set_option (typename SocketType::reuse_address (true), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.bind (Transport::server_endpoint (), ec)))
|
|
return;
|
|
|
|
if (! check_success (m_acceptor.listen (asio::socket_base::max_connections, ec)))
|
|
return;
|
|
|
|
m_acceptor.async_accept (m_socket, boost::bind (
|
|
&ThisType::on_accept, this, asio::placeholders::error));
|
|
}
|
|
|
|
void on_accept (system::error_code const& ec)
|
|
{
|
|
{
|
|
system::error_code ec;
|
|
if (! thread_success (m_acceptor.close (ec)))
|
|
return;
|
|
}
|
|
|
|
if (thread_success (ec))
|
|
{
|
|
socket ().async_handshake (Socket::server,
|
|
boost::bind (&ThisType::on_handshake, this, asio::placeholders::error));
|
|
}
|
|
}
|
|
|
|
void on_handshake (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
asio::async_read_until (socket (), m_buf, std::string ("hello"),
|
|
boost::bind (&ThisType::on_read, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_read (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 5))
|
|
return;
|
|
|
|
asio::async_write (socket (), asio::buffer ("goodbye", 7),
|
|
boost::bind (&ThisType::on_write, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_write (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 7))
|
|
return;
|
|
|
|
socket ().async_shutdown (boost::bind (&ThisType::on_shutdown, this,
|
|
asio::placeholders::error));
|
|
}
|
|
}
|
|
|
|
void on_shutdown (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec, true))
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (socket ().close (ec)))
|
|
return;
|
|
|
|
closed ();
|
|
}
|
|
}
|
|
|
|
void closed ()
|
|
{
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
Acceptor m_acceptor;
|
|
SocketType m_socket;
|
|
asio::streambuf m_buf;
|
|
StreamType m_stream;
|
|
SocketWrapper <StreamType> m_socketWrapper;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// An asynchronous handshaking client
|
|
//
|
|
template <class Transport, template <class Stream> class HandshakeType>
|
|
class HandshakeAsyncClient : public BasicAsyncClient
|
|
{
|
|
public:
|
|
typedef typename Transport::Protocol Protocol;
|
|
typedef typename Protocol::socket SocketType;
|
|
typedef typename Protocol::acceptor Acceptor;
|
|
typedef typename Protocol::endpoint Endpoint;
|
|
typedef HandshakeType <SocketType&> StreamType;
|
|
typedef HandshakeAsyncClient <Transport, HandshakeType> ThisType;
|
|
|
|
HandshakeAsyncClient (UnitTest& test, Scenario& scenario)
|
|
: BasicAsyncClient (test, scenario)
|
|
, m_socket (m_io_service)
|
|
, m_stream (m_socket, m_scenario.handshakeOptions)
|
|
, m_socketWrapper (m_stream)
|
|
{
|
|
}
|
|
|
|
asio::io_service& get_io_service ()
|
|
{
|
|
return m_io_service;
|
|
}
|
|
|
|
Socket& socket ()
|
|
{
|
|
return m_socketWrapper;
|
|
}
|
|
|
|
void on_start (system::error_code& ec)
|
|
{
|
|
m_socket.async_connect (Transport::client_endpoint (), boost::bind (
|
|
&ThisType::on_connect, this, asio::placeholders::error));
|
|
}
|
|
|
|
void on_connect (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
socket ().async_handshake (Socket::client,
|
|
boost::bind (&ThisType::on_handshake, this, asio::placeholders::error));
|
|
}
|
|
}
|
|
|
|
void on_handshake (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
asio::async_write (socket (), asio::buffer ("hello", 5),
|
|
boost::bind (&ThisType::on_write, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_write (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 5))
|
|
return;
|
|
|
|
asio::async_read_until (socket (), m_buf, std::string ("goodbye"),
|
|
boost::bind (&ThisType::on_read, this,
|
|
asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_read (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (thread_success (ec))
|
|
{
|
|
if (! thread_expect (bytes_transferred == 7))
|
|
return;
|
|
|
|
m_buf.consume (bytes_transferred);
|
|
|
|
asio::async_read (socket (), m_buf.prepare (1), boost::bind (&ThisType::on_read_final,
|
|
this, asio::placeholders::error, asio::placeholders::bytes_transferred));
|
|
}
|
|
}
|
|
|
|
void on_read_final (system::error_code const& ec, std::size_t bytes_transferred)
|
|
{
|
|
if (ec == asio::error::eof)
|
|
{
|
|
socket ().async_shutdown (boost::bind (&ThisType::on_shutdown, this,
|
|
asio::placeholders::error));
|
|
}
|
|
else if (thread_success (ec))
|
|
{
|
|
thread_expect (false);
|
|
}
|
|
}
|
|
|
|
void on_shutdown (system::error_code const& ec)
|
|
{
|
|
if (thread_success (ec, true))
|
|
{
|
|
system::error_code ec;
|
|
|
|
if (! thread_success (socket ().close (ec)))
|
|
return;
|
|
|
|
closed ();
|
|
}
|
|
}
|
|
|
|
void closed ()
|
|
{
|
|
}
|
|
|
|
private:
|
|
asio::io_service m_io_service;
|
|
SocketType m_socket;
|
|
asio::streambuf m_buf;
|
|
StreamType m_stream;
|
|
SocketWrapper <StreamType> m_socketWrapper;
|
|
};
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Analyzes the client and server settings to
|
|
// determine if the correct test case outcome was achieved.
|
|
//
|
|
// This relies on distinguishing abnormal errors (like a socket connect
|
|
// failing, which should never happen) from errors that arise naturally
|
|
// because of the test parameters.
|
|
//
|
|
// For example, a non-ssl client attempting a
|
|
// connection to a server that has ssl required.
|
|
//
|
|
void checkScenario (Scenario const& s)
|
|
{
|
|
if (s.handshakeOptions.useClientSsl)
|
|
{
|
|
if (s.handshakeOptions.enableServerSsl)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
// client ssl on, but server ssl disabled
|
|
expect (s.client_error.value () != 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Test any generic synchronous client/server pair
|
|
//
|
|
template <class ServerType, class ClientType>
|
|
void testScenario (Scenario scenario = Scenario ())
|
|
{
|
|
String s;
|
|
s << "scenario <"
|
|
<< typeid (ServerType).name () << ", "
|
|
<< typeid (ClientType).name () << ">";
|
|
beginTestCase (s);
|
|
|
|
system::error_code ec;
|
|
|
|
try
|
|
{
|
|
ServerType server (*this, scenario);
|
|
|
|
try
|
|
{
|
|
ClientType client (*this, scenario);
|
|
|
|
server.start (ec);
|
|
|
|
if (expect (! ec, ec.message ()))
|
|
{
|
|
client.start (ec);
|
|
|
|
if (expect (! ec, ec.message ()))
|
|
{
|
|
// at this point the threads for the client and
|
|
// server should be doing their thing. so we will
|
|
// just try to stop them within some reasonable
|
|
// amount of time. by then they should have finished
|
|
// what they were doing and set the error codes in
|
|
// the scenario, or they will have gotten hung and
|
|
// will need to be killed. If they hang, we will
|
|
// record a timeout in the corresponding scenario
|
|
// error code and deal with it.
|
|
}
|
|
|
|
client.finish ();
|
|
}
|
|
|
|
server.finish ();
|
|
|
|
// only check scenario results if we
|
|
// didn't get an unexpected error
|
|
if (! ec)
|
|
{
|
|
checkScenario (scenario);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
failException ();
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
failException ();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
// Test wrapper and facade assignment and lifetime management
|
|
//
|
|
void testFacade ()
|
|
{
|
|
typedef asio::ip::tcp Protocol;
|
|
typedef Protocol::socket SocketType;
|
|
|
|
#if 0
|
|
beginTestCase ("facade");
|
|
|
|
asio::io_service ios;
|
|
|
|
{
|
|
SharedWrapper <SocketType> f1 (new SocketType (ios));
|
|
SharedWrapper <SocketType> f2 (f1);
|
|
|
|
expect (f1 == f2);
|
|
}
|
|
|
|
{
|
|
SharedWrapper <SocketType> f1 (new SocketType (ios));
|
|
SharedWrapper <SocketType> f2 (new SocketType (ios));
|
|
|
|
expect (f1 != f2);
|
|
f2 = f1;
|
|
expect (f1 == f2);
|
|
}
|
|
#endif
|
|
// test typedef inheritance
|
|
{
|
|
typedef SocketWrapper <SocketType> SocketWrapper;
|
|
//typedef SocketWrapper::lowest_layer_type lowest_layer_type;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
template <class ServerType, class ClientType>
|
|
void testHandshakes ()
|
|
{
|
|
testScenario <ServerType, ClientType> (client_ssl | server_ssl);
|
|
testScenario <ServerType, ClientType> (client_ssl | server_ssl_required);
|
|
#if 0
|
|
testScenario <ServerType, ClientType> (client_ssl);
|
|
testScenario <ServerType, ClientType> (server_ssl);
|
|
testScenario <ServerType, ClientType> (server_ssl_required);
|
|
#endif
|
|
}
|
|
|
|
|
|
template <class Transport>
|
|
void testTransport ()
|
|
{
|
|
#if 1
|
|
// Synchronous
|
|
testScenario <SyncServer <Transport>, SyncClient <Transport> > ();
|
|
testScenario <HandshakeSyncServer <Transport, RippleHandshakeStreamType>,
|
|
SyncClient <Transport> > ();
|
|
testScenario <SyncServer <Transport>,
|
|
HandshakeSyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testScenario <HandshakeSyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeSyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
#endif
|
|
|
|
// Asynchronous
|
|
testScenario <AsyncServer <Transport>, SyncClient <Transport> > ();
|
|
#if 1
|
|
testScenario <SyncServer <Transport>, AsyncClient <Transport> > ();
|
|
testScenario <AsyncServer <Transport>, AsyncClient <Transport> > ();
|
|
|
|
// Asynchronous
|
|
testScenario <HandshakeSyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeAsyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testScenario <HandshakeAsyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeSyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testScenario <HandshakeAsyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeAsyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
|
|
// Handshaking
|
|
testHandshakes <HandshakeSyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeSyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testHandshakes <HandshakeSyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeAsyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testHandshakes <HandshakeAsyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeSyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
testHandshakes <HandshakeAsyncServer <Transport, RippleHandshakeStreamType>,
|
|
HandshakeAsyncClient <Transport, RippleHandshakeStreamType> > ();
|
|
#endif
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
void runTest ()
|
|
{
|
|
testFacade ();
|
|
testTransport <TcpV4> ();
|
|
testTransport <TcpV6> ();
|
|
}
|
|
};
|
|
|
|
static AsioUnitTests asioUnitTests;
|
|
|
|
}
|